diff options
-rw-r--r-- | src/proto_udp.c | 47 |
1 files changed, 45 insertions, 2 deletions
diff --git a/src/proto_udp.c b/src/proto_udp.c index 33a4dca46..1dc731778 100644 --- a/src/proto_udp.c +++ b/src/proto_udp.c @@ -42,6 +42,7 @@ static int udp_bind_listener(struct listener *listener, char *errmsg, int errlen); static int udp_suspend_receiver(struct receiver *rx); +static int udp_resume_receiver(struct receiver *rx); static void udp_enable_listener(struct listener *listener); static void udp_disable_listener(struct listener *listener); static void udp4_add_listener(struct listener *listener, int port); @@ -62,6 +63,7 @@ static struct protocol proto_udp4 = { .rx_enable = sock_enable, .rx_disable = sock_disable, .rx_suspend = udp_suspend_receiver, + .rx_resume = udp_resume_receiver, .receivers = LIST_HEAD_INIT(proto_udp4.receivers), .nb_receivers = 0, }; @@ -83,6 +85,7 @@ static struct protocol proto_udp6 = { .rx_enable = sock_enable, .rx_disable = sock_disable, .rx_suspend = udp_suspend_receiver, + .rx_resume = udp_resume_receiver, .receivers = LIST_HEAD_INIT(proto_udp6.receivers), .nb_receivers = 0, }; @@ -181,11 +184,51 @@ static void udp_disable_listener(struct listener *l) /* Suspend a receiver. Returns < 0 in case of failure, 0 if the receiver * was totally stopped, or > 0 if correctly suspended. + * The principle is a bit ugly but works well, at least on Linux: in order to + * suspend the receiver, we want it to stop receiving traffic, which means that + * the socket must be unhashed from the kernel's socket table. The simple way + * to do this is to connect to any address that is reachable and will not be + * used by regular traffic, and a great one is reconnecting to self. */ static int udp_suspend_receiver(struct receiver *rx) { - /* we don't support suspend on UDP */ - return -1; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + + if (rx->fd < 0) + return 0; + + if (getsockname(rx->fd, (struct sockaddr *)&ss, &len) < 0) + return -1; + + if (connect(rx->fd, (struct sockaddr *)&ss, len) < 0) + return -1; + + /* not necessary but may make debugging clearer */ + fd_stop_recv(rx->fd); + return 1; +} + +/* Resume a receiver. Returns < 0 in case of failure, 0 if the receiver + * was totally stopped, or > 0 if correctly suspended. + * The principle is to reverse the change above, we'll break the connection by + * connecting to AF_UNSPEC. The association breaks and the socket starts to + * receive from everywhere again. + */ +static int udp_resume_receiver(struct receiver *rx) +{ + struct sockaddr sa; + socklen_t len = sizeof(sa); + + if (rx->fd < 0) + return 0; + + sa.sa_family = AF_UNSPEC; + if (connect(rx->fd, &sa, len) < 0) + return -1; + + fd_want_recv(rx->fd); + return 1; } /* |