From 7c28cee26b33ffc6820b27165740b957dfcda70d Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Sat, 27 Jul 2024 22:08:42 +0200 Subject: [PATCH 1/3] optimized setsockopt() options for better cooperation between different transparent-ip applications. See: https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections for an explanation for those changes. --- common.c | 48 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/common.c b/common.c index b04066a..c08c3da 100644 --- a/common.c +++ b/common.c @@ -243,12 +243,17 @@ int is_same_machine(struct addrinfo* from) /* Transparent proxying: bind the peer address of fd to the peer address of * fd_from */ -#define IP_TRANSPARENT 19 +#ifndef IP_TRANSPARENT + #define IP_TRANSPARENT 19 +#endif +#ifndef IP_BIND_ADDRESS_NO_PORT + #define IP_BIND_ADDRESS_NO_PORT 24 +#endif int bind_peer(int fd, int fd_from) { struct addrinfo from; struct sockaddr_storage ss; - int res, trans = 1; + int res, enable = 1, disable = 0; memset(&from, 0, sizeof(from)); from.ai_addr = (struct sockaddr*)&ss; @@ -264,15 +269,17 @@ int bind_peer(int fd, int fd_from) return 0; #ifndef IP_BINDANY /* use IP_TRANSPARENT */ - res = setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &trans, sizeof(trans)); + res = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &enable, sizeof(enable)); CHECK_RES_DIE(res, "setsockopt IP_TRANSPARENT"); + res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); #else if (from.ai_addr->sa_family==AF_INET) { /* IPv4 */ - res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &trans, sizeof(trans)); + res = setsockopt(fd, IPPROTO_IP, IP_BINDANY, &enable, sizeof(enable)); CHECK_RES_RETURN(res, "setsockopt IP_BINDANY", res); #ifdef IPV6_BINDANY } else { /* IPv6 */ - res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &trans, sizeof(trans)); + res = setsockopt(fd, IPPROTO_IPV6, IPV6_BINDANY, &enable, sizeof(enable)); CHECK_RES_RETURN(res, "setsockopt IPV6_BINDANY", res); #endif /* IPV6_BINDANY */ } @@ -284,19 +291,30 @@ int bind_peer(int fd, int fd_from) "bind", errno, strerror(errno)); return res; } - - /* - * If there is more than one transparent mode proxy going on, such as - * using sslh as the target of stunnel also in transparent mode, then - * the (ip,port) combination will already be bound for the previous application. - * In that case, the best we can do is bind with a different port. - * This does mean the local server can't use the ident protocol as the port will - * have changed, but most people won't care. - * Also note that stunnel uses the same logic for the same situation. - */ + res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable)); + CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); + res = setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable)); + CHECK_RES_DIE(res, "setsockopt IP_BIND_ADDRESS_NO_PORT"); ((struct sockaddr_in *)from.ai_addr)->sin_port = 0; res = bind(fd, from.ai_addr, from.ai_addrlen); CHECK_RES_RETURN(res, "bind", res); + /* + * There was a serious problem, when daisy-chaining programs using the same + * ip transparent mechanism, as sslh uses. stunnel was mentioned in a previous + * comment. This problem should now be solved through the two methods, getting + * a connection established: + * In the first try, SO_REUSEADDR is set to socket, which will allow the same + * IP-address:port tuple, as it is used by another application. The check for + * inconsistency with other connections (same 4-value-tupel) is done at the + * moment, when the connection gets established. + * If that fails, SO_REUSEADDR gets removed and IP_BIND_ADDRESS_NO_PORT get set. + * This will search for a free port, which will not collide with current + * connections. Read more in this excellent blog-post: + * https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections + * The problem will still appear, if the another application in the daisy-chain + * does not use similar mechanisms. In that case you must either pull this + * application at the beginning of the chain, or get it fixed. + */ } return 0; From c0d49d4c36055c31cec16cdb15057135eb6840a7 Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Sun, 28 Jul 2024 18:39:21 +0200 Subject: [PATCH 2/3] Changed a CHECK_RES_DIE to CHECK_RES_RETURN, as the real problem will occur at connect! --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index c08c3da..0a3b86a 100644 --- a/common.c +++ b/common.c @@ -294,7 +294,7 @@ int bind_peer(int fd, int fd_from) res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable)); CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); res = setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable)); - CHECK_RES_DIE(res, "setsockopt IP_BIND_ADDRESS_NO_PORT"); + CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT"); ((struct sockaddr_in *)from.ai_addr)->sin_port = 0; res = bind(fd, from.ai_addr, from.ai_addrlen); CHECK_RES_RETURN(res, "bind", res); From e80c8e192c66df75cb96028864331c58b2b6dae3 Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Sun, 28 Jul 2024 19:00:36 +0200 Subject: [PATCH 3/3] added missing 3rd argument to CHECK_RES_RESULT --- common.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common.c b/common.c index 0a3b86a..2eebda2 100644 --- a/common.c +++ b/common.c @@ -294,7 +294,7 @@ int bind_peer(int fd, int fd_from) res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &disable, sizeof(disable)); CHECK_RES_DIE(res, "setsockopt SO_REUSEADDR"); res = setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &enable, sizeof(enable)); - CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT"); + CHECK_RES_RETURN(res, "setsockopt IP_BIND_ADDRESS_NO_PORT", res); ((struct sockaddr_in *)from.ai_addr)->sin_port = 0; res = bind(fd, from.ai_addr, from.ai_addrlen); CHECK_RES_RETURN(res, "bind", res);