From 390db4831094d3eb2350371f6ebd533efd6b8c90 Mon Sep 17 00:00:00 2001 From: Simon Arlott <sa.me.uk> Date: Sat, 24 Apr 2021 16:22:12 +0100 Subject: [PATCH] Don't use transparent proxying for clients on the listener itself It's not possible to bind to the same address and port when the client is the same as the listener because it will already be in use by the incoming connection. Detect this and use a new port on the same address instead. --- src/address.c | 25 +++++++++++++++++++++++++ src/address.h | 1 + src/connection.c | 46 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/address.c b/src/address.c index 9a544a5b..a2ac36f5 100644 --- a/src/address.c +++ b/src/address.c @@ -34,6 +34,7 @@ #include <arpa/inet.h> /* inet_pton */ #include <sys/un.h> #include <assert.h> +#include <string.h> #include "address.h" @@ -259,6 +260,30 @@ address_compare(const struct Address *addr_1, const struct Address *addr_2) { return result; } +int +address_addr_eq(const struct Address *addr, const struct sockaddr *sa) { + if (addr->type != SOCKADDR) + return 0; + + if (address_sa(addr)->sa_family != sa->sa_family) + return 0; + + switch (address_sa(addr)->sa_family) { + case AF_INET: + return !memcmp(&((struct sockaddr_in *)addr->data)->sin_addr, + &((struct sockaddr_in *)sa)->sin_addr, + sizeof(((struct sockaddr_in *)sa)->sin_addr)); + + case AF_INET6: + return !memcmp(&((struct sockaddr_in6 *)addr->data)->sin6_addr, + &((struct sockaddr_in6 *)sa)->sin6_addr, + sizeof(((struct sockaddr_in6 *)sa)->sin6_addr)); + + default: + return 0; + } +} + int address_is_hostname(const struct Address *addr) { return addr != NULL && addr->type == HOSTNAME; diff --git a/src/address.h b/src/address.h index 66125eb6..6d0f5f2f 100644 --- a/src/address.h +++ b/src/address.h @@ -44,6 +44,7 @@ struct Address *new_address_sa(const struct sockaddr *, socklen_t); struct Address *copy_address(const struct Address *); size_t address_len(const struct Address *); int address_compare(const struct Address *, const struct Address *); +int address_addr_eq(const struct Address *, const struct sockaddr *); int address_is_hostname(const struct Address *); int address_is_sockaddr(const struct Address *); int address_is_wildcard(const struct Address *); diff --git a/src/connection.c b/src/connection.c index c10a1090..1e3e7495 100644 --- a/src/connection.c +++ b/src/connection.c @@ -630,23 +630,45 @@ initiate_server_connect(struct Connection *con, struct ev_loop *loop) { if (con->listener->transparent_proxy && con->client.addr.ss_family == con->server.addr.ss_family) { + int result; + + if (address_addr_eq(con->listener->address, + (struct sockaddr *)&con->client.addr)) { + /* Client is the same host as the listener, bind to a new port. */ + struct sockaddr_storage self = con->client.addr; + + switch (con->client.addr.ss_family) { + case AF_INET: + ((struct sockaddr_in *)&self)->sin_port = 0; + break; + + case AF_INET6: + ((struct sockaddr_in6 *)&self)->sin6_port = 0; + break; + } + + result = bind(sockfd, (struct sockaddr *)&self, + con->client.addr_len); + } else { #ifdef IP_TRANSPARENT - int on = 1; - int result = setsockopt(sockfd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); + int on = 1; + result = setsockopt(sockfd, SOL_IP, IP_TRANSPARENT, &on, sizeof(on)); #else - int result = -EPERM; - /* XXX error: not implemented would be better, but this shouldn't be - * reached since it is prohibited in the configuration parser. */ + result = -EPERM; + /* XXX error: not implemented would be better, but this shouldn't be + * reached since it is prohibited in the configuration parser. */ #endif - if (result < 0) { - err("setsockopt IP_TRANSPARENT failed: %s", strerror(errno)); - close(sockfd); - abort_connection(con); - return; + if (result < 0) { + err("setsockopt IP_TRANSPARENT failed: %s", strerror(errno)); + close(sockfd); + abort_connection(con); + return; + } + + result = bind(sockfd, (struct sockaddr *)&con->client.addr, + con->client.addr_len); } - result = bind(sockfd, (struct sockaddr *)&con->client.addr, - con->client.addr_len); if (result < 0) { err("bind failed: %s", strerror(errno)); close(sockfd);