MEDIUM: udp: implement udp_suspend() and udp_resume()
In Linux kernel's net/ipv4/udp.c there's a udp_disconnect() function
which is called when connecting to AF_UNSPEC, and which unhashes a
"connection". This property, which is also documented in connect(2)
both in Linux and Open Group's man pages for datagrams, is interesting
because it allows to reverse a connect() which is in fact a filter on
the source. As such we can suspend a receiver by making it connect to
itself, which will cause it not to receive any traffic anymore, letting
a new one receive it all, then resume it by breaking this connection.
This was tested to work well on Linux, other operating systems should
also be tested. Before this, sending a SIGTTOU to a process having a
UDP syslog forwarder would cause this error:
[WARNING] 280/194249 (3268) : Paused frontend GLOBAL.
[WARNING] 280/194249 (3268) : Some proxies refused to pause, performing soft stop now.
[WARNING] 280/194249 (3268) : Proxy GLOBAL stopped (cumulated conns: FE: 0, BE: 0).
[WARNING] 280/194249 (3268) : Proxy sylog-loadb stopped (cumulated conns: FE: 0, BE: 0).
With this change, it now proceeds just like with TCP listeners:
[WARNING] 280/195503 (3885) : Paused frontend GLOBAL.
[WARNING] 280/195503 (3885) : Paused frontend sylog-loadb.
And SIGTTIN also works:
[WARNING] 280/195507 (3885) : Resumed frontend GLOBAL.
[WARNING] 280/195507 (3885) : Resumed frontend sylog-loadb.
On Linux this also works with TCP listeners (which can then be resumed
using listen()) and established TCP sockets (which we currently kill
using setsockopt(so_linger)), both not being portable on other OSes.
UNIX sockets and ABNS sockets do not support it however (connect
always fails). This needs to be further explored to see if other OSes
might benefit from this to perform portable and reliable resets
particularly on the backend side.
diff --git a/src/proto_udp.c b/src/proto_udp.c
index 33a4dca..1dc7317 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 @@
.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 @@
.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 @@
/* 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;
}
/*