Hello all,

We have used haproxy for several months. And periodically see the next error messages in the log:
============================================================================
Sep 27 16:17:06 localhost haproxy[12874]: Connect() failed for backend https: no free ports.
============================================================================

I've investigated this issue and found that EADDRNOTAVAIL error is returned sometimes. Probably it is caused by the fact that we are using one port from ephemeral range for our internal needs. According to http://en.wikipedia.org/wiki/Ephemeral_port 'connect' function usually just uses Round-Robin algorithm to choice the next ephemeral port, and so when it encountered already used port -- it just produces the above error.

Solution for this issue is simple -- add a loop around connect. We have implemented it and tested on our environment. It works for us. May it will be good enough to include into the core haproxy...

Logic of the solution is simple -- try to connect 3 times in case of EAGAIN, EADDRINUSE or EADDRNOTAVAIL errors:
============================================================================
*# diff -b ../../haproxy-1.5-dev21-orig/src/proto_tcp.c proto_tcp.c*
277c277
<       int fd;
---
      int fd, try_count = 0;
437a438
      while (1) {
441a443
                              if (++try_count >= 3) {
453a456
                              }
467a471,473
              } else {
                      break;
              }
============================================================================

Full patch created with "diff -Naur ../../haproxy-1.5-dev21-orig/src/proto_tcp.c proto_tcp.c" is attached to this letter.

What do you think about it? Does it make sense for anybody else?


--
Best regards,
 Denis Malyshkin,
Senior C++ Developer
of ISS Art, Ltd., Omsk, Russia.
Mobile Phone: +7 913 669 2896
Office tel/fax +7 3812 396959
Yahoo Messenger: dmalyshkin
Web: http://www.issart.com
E-mail: [email protected]

--- ../../haproxy-1.5-dev21-orig/src/proto_tcp.c        2013-12-16 
23:45:49.000000000 +0000
+++ proto_tcp.c 2014-01-27 06:36:05.956975800 +0000
@@ -274,7 +274,7 @@
 
 int tcp_connect_server(struct connection *conn, int data, int delack)
 {
-       int fd;
+       int fd, try_count = 0; 
        struct server *srv;
        struct proxy *be;
        struct conn_src *src;
@@ -435,35 +435,41 @@
        if (global.tune.server_rcvbuf)
                 setsockopt(fd, SOL_SOCKET, SO_RCVBUF, 
&global.tune.server_rcvbuf, sizeof(global.tune.server_rcvbuf));
 
-       if ((connect(fd, (struct sockaddr *)&conn->addr.to, 
get_addr_len(&conn->addr.to)) == -1) &&
-           (errno != EINPROGRESS) && (errno != EALREADY) && (errno != 
EISCONN)) {
-
-               if (errno == EAGAIN || errno == EADDRINUSE || errno == 
EADDRNOTAVAIL) {
-                       char *msg;
-                       if (errno == EAGAIN || errno == EADDRNOTAVAIL)
-                               msg = "no free ports";
-                       else
-                               msg = "local address already in use";
-
-                       qfprintf(stderr,"Connect() failed for backend %s: 
%s.\n", be->id, msg);
-                       port_range_release_port(fdinfo[fd].port_range, 
fdinfo[fd].local_port);
-                       fdinfo[fd].port_range = NULL;
-                       close(fd);
-                       send_log(be, LOG_ERR, "Connect() failed for backend %s: 
%s.\n", be->id, msg);
-                       return SN_ERR_RESOURCE;
-               } else if (errno == ETIMEDOUT) {
-                       //qfprintf(stderr,"Connect(): ETIMEDOUT");
-                       port_range_release_port(fdinfo[fd].port_range, 
fdinfo[fd].local_port);
-                       fdinfo[fd].port_range = NULL;
-                       close(fd);
-                       return SN_ERR_SRVTO;
+       while (1) {
+               if ((connect(fd, (struct sockaddr *)&conn->addr.to, 
get_addr_len(&conn->addr.to)) == -1) &&
+                   (errno != EINPROGRESS) && (errno != EALREADY) && (errno != 
EISCONN)) {
+
+                       if (errno == EAGAIN || errno == EADDRINUSE || errno == 
EADDRNOTAVAIL) {
+                               if (++try_count >= 3) {
+                                       char *msg;
+                                       if (errno == EAGAIN || errno == 
EADDRNOTAVAIL)
+                                               msg = "no free ports";
+                                       else
+                                               msg = "local address already in 
use";
+
+                                       qfprintf(stderr,"Connect() failed for 
backend %s: %s.\n", be->id, msg);
+                                       
port_range_release_port(fdinfo[fd].port_range, fdinfo[fd].local_port);
+                                       fdinfo[fd].port_range = NULL;
+                                       close(fd);
+                                       send_log(be, LOG_ERR, "Connect() failed 
for backend %s: %s.\n", be->id, msg);
+                                       return SN_ERR_RESOURCE;
+                               }
+                       } else if (errno == ETIMEDOUT) {
+                               //qfprintf(stderr,"Connect(): ETIMEDOUT");
+                               port_range_release_port(fdinfo[fd].port_range, 
fdinfo[fd].local_port);
+                               fdinfo[fd].port_range = NULL;
+                               close(fd);
+                               return SN_ERR_SRVTO;
+                       } else {
+                               // (errno == ECONNREFUSED || errno == 
ENETUNREACH || errno == EACCES || errno == EPERM)
+                               //qfprintf(stderr,"Connect(): %d", errno);
+                               port_range_release_port(fdinfo[fd].port_range, 
fdinfo[fd].local_port);
+                               fdinfo[fd].port_range = NULL;
+                               close(fd);
+                               return SN_ERR_SRVCL;
+                       }
                } else {
-                       // (errno == ECONNREFUSED || errno == ENETUNREACH || 
errno == EACCES || errno == EPERM)
-                       //qfprintf(stderr,"Connect(): %d", errno);
-                       port_range_release_port(fdinfo[fd].port_range, 
fdinfo[fd].local_port);
-                       fdinfo[fd].port_range = NULL;
-                       close(fd);
-                       return SN_ERR_SRVCL;
+                       break;
                }
        }
 

Reply via email to