Mercurial > dovecot > core-2.2
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)