diff src/lib/net.c @ 20823:06fd5e4f123e

lib: If connect() fails with EADDRNOTAVAIL, retry it 4 more times. This is needed on busy systems. Nicely explained in: https://idea.popcount.org/2014-04-03-bind-before-connect/
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Tue, 27 Sep 2016 16:15:42 +0300
parents f3e249261abd
children 13a159cfd232
line wrap: on
line diff
--- a/src/lib/net.c	Wed Oct 05 15:03:05 2016 +0300
+++ b/src/lib/net.c	Tue Sep 27 16:15:42 2016 +0300
@@ -41,6 +41,19 @@
 #  define NEEDS_LOCAL_CREDS 1
 #endif
 
+/* If connect() fails with EADDRNOTAVAIL (or some others on FreeBSD), retry it
+   this many times.
+
+   This is needed on busy systems kernel may assign the same source port to two
+   sockets at bind() stage, which is what we generally want to allow more than
+   64k outgoing connections to different destinations. However, at bind() stage
+   the kernel doesn't know the destination yet. So it's possible that it
+   assigns the same source port to two (or more) sockets that have the same
+   destination IP+port as well. In this case connect() will fail with
+   EADDRNOTAVAIL. We'll need to retry this and hope that the next attempt won't
+   conflict. */
+#define MAX_CONNECT_RETRIES 4
+
 bool net_ip_compare(const struct ip_addr *ip1, const struct ip_addr *ip2)
 {
 	return net_ip_cmp(ip1, ip2) == 0;
@@ -151,38 +164,7 @@
 	return 0;
 }
 
-#ifdef __FreeBSD__
-static int
-net_connect_ip_full_freebsd(const struct ip_addr *ip, in_port_t port,
-			    const struct ip_addr *my_ip, int sock_type,
-			    bool blocking);
-
-static int net_connect_ip_full(const struct ip_addr *ip, in_port_t port,
-			       const struct ip_addr *my_ip, int sock_type,
-			       bool blocking)
-{
-	int fd, try;
-
-	for (try = 0;;) {
-		fd = net_connect_ip_full_freebsd(ip, port, my_ip, sock_type, blocking);
-		if (fd != -1 || ++try == 5 ||
-		    (errno != EADDRINUSE && errno != EACCES))
-			break;
-		/*
-		   This may be just a temporary problem:
-
-		   EADDRINUSE: busy
-		   EACCES: pf may cause this if another connection used
-		           the same port recently
-		*/
-	}
-	return fd;
-}
-/* then some kludging: */
-#define net_connect_ip_full net_connect_ip_full_freebsd
-#endif
-
-static int net_connect_ip_full(const struct ip_addr *ip, in_port_t port,
+static int net_connect_ip_once(const struct ip_addr *ip, in_port_t port,
 			       const struct ip_addr *my_ip, int sock_type, bool blocking)
 {
 	union sockaddr_union so;
@@ -237,9 +219,29 @@
 
 	return fd;
 }
+
+static int net_connect_ip_full(const struct ip_addr *ip, in_port_t port,
+			       const struct ip_addr *my_ip, int sock_type,
+			       bool blocking)
+{
+	int fd, try;
+
+	for (try = 0;;) {
+		fd = net_connect_ip_once(ip, port, my_ip, sock_type, blocking);
+		if (fd != -1 || try++ >= MAX_CONNECT_RETRIES ||
+		    (errno != EADDRNOTAVAIL
 #ifdef __FreeBSD__
-#  undef net_connect_ip_full
+		     /* busy */
+		     && errno != EADDRINUSE
+		     /* pf may cause this if another connection used
+			the same port recently */
+		     && errno != EACCES
 #endif
+		    ))
+			break;
+	}
+	return fd;
+}
 
 int net_connect_ip(const struct ip_addr *ip, in_port_t port,
 		   const struct ip_addr *my_ip)