diff --git a/Makefile.am b/Makefile.am index 75631fa..9e16fe4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -39,10 +39,11 @@ dist_man_MANS = tnat64.1 tnat64.conf.5 tnat64-validateconf.1 dist_doc_DATA = tnat64.conf.complex.example tnat64.conf.simple.example -export DIG NSLOOKUP NETCAT +export DIG NSLOOKUP NETCAT PYTHON export objdir = .libs -TESTS = tests/01-correct-ipv6-address tests/02-local-connection +TESTS = tests/01-correct-ipv6-address tests/02-local-connection \ + tests/03-check-netlink-getsockname install-exec-hook: cd $(DESTDIR)$(pkglibdir) && \ diff --git a/configure.ac b/configure.ac index 6817e90..d7444ed 100644 --- a/configure.ac +++ b/configure.ac @@ -352,6 +352,9 @@ fi dnl See if we can run tests + +AC_CHECK_PROGS(PYTHON, [python3 python python2.7]) + AC_CHECK_PROG(DIG, dig, dig) if test -z "$DIG" then diff --git a/tests/01-correct-ipv6-address b/tests/01-correct-ipv6-address index 26f7223..79b98ef 100755 --- a/tests/01-correct-ipv6-address +++ b/tests/01-correct-ipv6-address @@ -1,5 +1,7 @@ #!/bin/sh -e +# Test for github bug #1. + if [ -z "$DIG" ] && [ -z "$NSLOOKUP" ] then exit 77 @@ -16,8 +18,8 @@ else fi TNAT64_DEBUG=10 LD_PRELOAD=$objdir/libtnat64.so $TOOL example.org ${P}0.0.0.0 2>&1 | awk ' -BEGIN { e = 1 } -/Checking if IPv6 address :: is behind the NAT64.../ { e = 0 } +BEGIN { e = 0 } +/Checking if IPv6 address :: is behind the NAT64.../ { e = 1 } // END { exit e } ' diff --git a/tests/03-check-netlink-getsockname b/tests/03-check-netlink-getsockname new file mode 100755 index 0000000..636d25f --- /dev/null +++ b/tests/03-check-netlink-getsockname @@ -0,0 +1,30 @@ +#!/bin/sh -e + +# Test for github bug #6 - ensure that NETLINK works. + +if [ -z "$PYTHON" ] +then + exit 77 +fi + +: ${objdir:=.} + +TNAT64_DEBUG=10 LD_PRELOAD=$objdir/libtnat64.so $PYTHON << EOF + +import sys, socket, traceback + +def code(): + sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW) + print("sock is " + str(sock)) + + try: + sockname = sock.getsockname() + print("Success: sockname=" + str(sockname)) + sock.close() + return + except: + traceback.print_exc() + sys.exit(42) + +code() +EOF diff --git a/tnat64.c b/tnat64.c index fe29a80..9b09377 100644 --- a/tnat64.c +++ b/tnat64.c @@ -39,6 +39,7 @@ static const char *progname = "libtnat64"; /* Name used in err msgs */ #include #include #include +#include #include #include #include @@ -70,6 +71,23 @@ static struct in6_addr ipv4mapped; static int current_af = AF_INET6; +static void * socket_fd_flags = 0; +static int socket_fd_max = 0; + +/* + * socket_fd_flags is an array with four bits for each possible socket. + * Upon first usage, getrlimit is executed to find out the maximum + * number of sockets, then the array is allocated for that size. + * + * This array is used to store if a particular socket always was an IPv6 + * socket or if it was "upgraded" from an IPv4 socket to an IPv6 socket. + * + * This is needed so that functions like getpeername know if they can return + * an IPv6 address to the IPv6-aware application (if the socket was opened) + * as an IPv6 socket, or if the application intended to open an IPv4 socket + * and thus expects an IPv4 peer address. + */ + /* Exported Function Prototypes */ void _init(void); int socket(SOCKET_SIGNATURE); @@ -81,6 +99,123 @@ int getsockname(GETSOCKNAME_SIGNATURE); static int get_config(); static int get_environment(); +// This flag is set if the socket was requested by the application +// as an IPv4 socket and we upgraded it to IPv6. +#define TNAT_FLAG_SOCK_UPGRADED_TO_IPV6 (1 << 0) +#define TNAT_FLAG_UNUSED_1 (1 << 1) +#define TNAT_FLAG_UNUSED_2 (1 << 2) +#define TNAT_FLAG_UNUSED_3 (1 << 3) + +/// returns flags (up to 4 bits) for the given socket. +int get_custom_fd_flags(int fd) { + + // Any socket that doesn't have an entry in this array + // can't have any flags. Same for if the array hasn't been + // allocated yet. + if (socket_fd_flags == 0 || fd >= socket_fd_max) return 0; + + int idx = fd/2; + char * arr = socket_fd_flags; + arr += idx; + + if (fd % 2 == 0) { + return (*arr & 0xF0) >> 4; + } + return (*arr & 0x0F); +} + +int allocate_or_resize_flag_array() { + /* We need that flag array (socket_fd_flags) to store some information + about each allocated socket. Check the socket limit for the process, + then allocate an array. */ + + // Moving this allocation to usage time (called by set_custom_fd_flags) + // instead of performing it at application start means that we don't + // waste time and memory allocating this for IPv6-native applications + // that won't use the NAT64. + // Also, it keeps allocation and reallocation/resizing code at the same place. + + struct rlimit limit; + getrlimit(RLIMIT_NOFILE, &limit); + show_msg(MSGDEBUG, "Checking file descriptor limits - current limit: %d, max limit: %d\n", limit.rlim_cur, limit.rlim_max); + + int needed_size = (limit.rlim_max / 2) + 1; + + + if (socket_fd_flags == 0) { + // Array hasn't been allocated yet (1st call since start), allocate it. + + show_msg(MSGDEBUG, "Perform initial flag array allocation (size %d bytes, %d entries)\n", needed_size, limit.rlim_max); + // Perform initial allocation. + socket_fd_flags = calloc(needed_size, 1); + if (socket_fd_flags == 0) { + show_msg(MSGERR, "Failed to allocate buffer for the flag array - calloc returned 0!\n"); + return (-1); + } + socket_fd_max = limit.rlim_max; + show_msg(MSGDEBUG, "Allocated the flag buffer to size of %d (%d entries)!\n", needed_size, limit.rlim_max); + } + else if (socket_fd_max < limit.rlim_max) { + show_msg(MSGDEBUG, "Perform reallocation of the flag array (old size %d entries, new size %d entries)\n", socket_fd_max, limit.rlim_max); + // Array is already allocated but too small, make it larger + void * new_allocation = realloc(socket_fd_flags, needed_size); + if (new_allocation == 0) { + show_msg(MSGERR, "Failed to re-allocate the flag buffer to new size - realloc returned 0!\n"); + return (-1); + } + int old_size = (socket_fd_max / 2) + 1; + + socket_fd_flags = new_allocation; + socket_fd_max = limit.rlim_max; + + // Now set the new space to 0: + if (needed_size > old_size) { + memset(new_allocation + old_size, 0, needed_size - old_size); + } + + show_msg(MSGDEBUG, "Re-allocated the flag buffer to new size of %d (%d entries)!\n", needed_size, limit.rlim_max); + } + else { + show_msg(MSGDEBUG, "Array re-allocation not needed.\n"); + } + + return 0; +} + +int set_custom_fd_flags(int fd, int flags) { + + if (socket_fd_flags == 0 || fd >= socket_fd_max) { + // If this happens, we need to allocate (or resize) the flag array. + allocate_or_resize_flag_array(); + } + + if (socket_fd_flags == 0 || fd >= socket_fd_max) { + // This should never happen, but if it does, let's not crash. + show_msg(MSGERR, "Allocation failed, or application used a file descriptor larger than the kernel allows - abort.\n"); + return (-1); + } + + if (flags > 15 || flags < 0) { + show_msg(MSGWARN, "Tried to set invalid flag %d for fd %d\n", flags, fd); + return (-2); + } + + int idx = fd/2; + char * arr = socket_fd_flags; + arr += idx; + + if (fd % 2 == 0) { + *arr = (flags << 4) | (*arr & 0x0F); + } + else { + *arr = (*arr & 0xF0) | flags; + } + + return flags; + + +} + void _init(void) { #ifdef USE_OLD_DLSYM @@ -181,6 +316,13 @@ int socket(SOCKET_SIGNATURE) return sock; } + // Store the socket in our custom array so we know that that's an IPv4 + // socket we forcibly upgraded to IPv6. + int flag_ret = set_custom_fd_flags(sock, TNAT_FLAG_SOCK_UPGRADED_TO_IPV6); + if (flag_ret < 0) { + show_msg(MSGWARN, "Setting a custom flag for the socket %d failed with error %d\n", sock, flag_ret); + } + // Now set this IPv6 socket to allow both IPv6 and IPv4 connections. // Most OSes do that by default, but not all. int no = 0; @@ -192,7 +334,15 @@ int socket(SOCKET_SIGNATURE) } else { - return realsocket(__domain, __type, __protocol); + int sock = realsocket(__domain, __type, __protocol); + if (sock >= 0 && socket_fd_flags != 0) { + // Only needed if we got a socket and the array is already allocated. + int flag_ret = set_custom_fd_flags(sock, 0); + if (flag_ret < 0) { + show_msg(MSGWARN, "Setting a custom flag for the socket %d failed with error %d\n", sock, flag_ret); + } + } + return sock; } } @@ -218,7 +368,10 @@ int connect(CONNECT_SIGNATURE) connaddr = (struct sockaddr_in *)__addr; /* Get the type of the socket */ - getsockopt(__fd, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &sock_type_len); + if (getsockopt(__fd, SOL_SOCKET, SO_TYPE, (void *)&sock_type, &sock_type_len) < 0) { + show_msg(MSGERR, "Can't figure out socket type! - error %d (%s)\n", errno, strerror(errno)); + return (-1); + } /* If this isn't an INET socket for a TCP stream we can't */ /* handle it, just call the real connect now */ @@ -233,23 +386,52 @@ int connect(CONNECT_SIGNATURE) show_msg(MSGDEBUG, "Got connection request for socket %d to " "%s:%d\n", __fd, inet_ntoa(connaddr->sin_addr), ntohs(connaddr->sin_port)); + /* If the address is local call realconnect */ if (!(is_local(config, &(connaddr->sin_addr)))) { show_msg(MSGDEBUG, "Connection for socket %d is local\n", __fd); /* Rewrite to an IPv6 socket connect */ - dest_address6.sin6_family = AF_INET6; - dest_address6.sin6_port = connaddr->sin_port; - dest_address6.sin6_flowinfo = 0; - dest_address6.sin6_scope_id = 0; - memcpy(&dest_address6.sin6_addr, &ipv4mapped, sizeof(struct in6_addr)); - memcpy(&dest_address6.sin6_addr.s6_addr[NAT64PREFIXLEN], &connaddr->sin_addr, sizeof(struct in_addr)); - if (inet_ntop(AF_INET6, &dest_address6.sin6_addr, addrbuffer, sizeof(addrbuffer))) - { - show_msg(MSGDEBUG, "Connecting to local IPv4-mapped IPv6 address %s...\n", addrbuffer); + + // Check if this socket can send data to IPv4 addresses or if that's disabled: + int sockopt = 0; + socklen_t len = sizeof(sockopt); + if (getsockopt(__fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&sockopt, &len) < 0) { + show_msg(MSGWARN, "Can't figure out if this IPv6 socket supports IPv4, assume it does - error %d (%s)\n", errno, strerror(errno)); + } + + if (sockopt == 0) { + // IPv6 socket supports IPv4 because the V6ONLY flag is not 1. + + dest_address6.sin6_family = AF_INET6; + dest_address6.sin6_port = connaddr->sin_port; + dest_address6.sin6_flowinfo = 0; + dest_address6.sin6_scope_id = 0; + memcpy(&dest_address6.sin6_addr, &ipv4mapped, sizeof(struct in6_addr)); + memcpy(&dest_address6.sin6_addr.s6_addr[NAT64PREFIXLEN], &connaddr->sin_addr, sizeof(struct in_addr)); + if (inet_ntop(AF_INET6, &dest_address6.sin6_addr, addrbuffer, sizeof(addrbuffer))) + { + show_msg(MSGDEBUG, "Connecting to local IPv4-mapped IPv6 address %s...\n", addrbuffer); + } + + return realconnect(__fd, (struct sockaddr *)&dest_address6, sizeof(struct sockaddr_in6)); } + else { + show_msg(MSGWARN, "Aborting local IPv4 connection because socket doesn't support it.\n"); + + if (connaddr->sin_addr.s_addr == htonl(0x7f000001)) { + // Application wants to connect to 127.0.0.1 but the socket doesn't support IPv4 connections. + // Why not try to connect to ::1 instead? Better than returning an error ... - return realconnect(__fd, (struct sockaddr *)&dest_address6, sizeof(struct sockaddr_in6)); + show_msg(MSGWARN, "Trying to connect to [::1] instead of 127.0.0.1 ...\n"); + dest_address6.sin6_family = AF_INET6; + dest_address6.sin6_port = connaddr->sin_port; + dest_address6.sin6_flowinfo = 0; + dest_address6.sin6_scope_id = 0; + inet_pton(AF_INET6, "::1", &dest_address6.sin6_addr); + return realconnect(__fd, (struct sockaddr *)&dest_address6, sizeof(struct sockaddr_in6)); + } + } } /* Don't retry more than once */ @@ -316,16 +498,39 @@ int connect(CONNECT_SIGNATURE) if (realconnect(__fd, (struct sockaddr *)&dest_address6, sizeof(struct sockaddr_in6)) == 0) { + show_msg(MSGDEBUG, "Connected successfully.\n"); return 0; } if (errno != ENETUNREACH) { + show_msg(MSGDEBUG, "Connect failed with errno=%d\n", errno); return -1; } else { - current_af = AF_INET; - failed++; + // Connection to the IPv6 address failed for some reason. + // Increment the error counter + failed++; + + // Now check if the IPv6 socket allows connecting to IPv4 targets - + // if so, try to connect over IPv4. + + // If the socket does NOT support IPv4 destinations, there's + // no need to try to connect to one - just return an error. + + int sockopt = 0; + socklen_t len = sizeof(sockopt); + if (getsockopt(__fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&sockopt, &len) < 0) { + show_msg(MSGWARN, "Can't figure out if this IPv6 socket supports IPv4, assuming it does (error %d: %s)\n", errno, strerror(errno)); + } + + if (sockopt == 0) { + // IPv6 socket supports IPv4 because the V6ONLY flag is not 1. + current_af = AF_INET; + } + else { + return -1; + } } } @@ -366,44 +571,82 @@ int getpeername(GETPEERNAME_SIGNATURE) get_config(); show_msg(MSGDEBUG, "Got getpeername call for socket %d\n", __fd); - socklen_t needlen = *__len; - int ret = realgetpeername(__fd, __addr, &needlen); + + int sock_flags = get_custom_fd_flags(__fd); + + if (sock_flags < 0) { + show_msg(MSGERR, "Failed to query socket flags for fd %d, err=%d\n", __fd, sock_flags); + // Assume it's a normal socket, though this should never happen. + sock_flags = 0; + } + if (sock_flags >= 0 && (!(sock_flags & TNAT_FLAG_SOCK_UPGRADED_TO_IPV6))) { + // TNAT_FLAG_SOCK_UPGRADED_TO_IPV6 is not set - this means that whatever + // that socket is, we don't care, it's not something we need to modify. + // So we can call the original getpeername. + show_msg(MSGDEBUG, "None of our modded sockets, call real getpeername\n"); + return realgetpeername(__fd, __addr, __len); + } + + struct sockaddr_storage sockaddr_st; + socklen_t size = sizeof(sockaddr_st); + + int ret = realgetpeername(__fd, (struct sockaddr *)&sockaddr_st, &size); if (ret < 0) { + // If we end up here, it's not because of a too-small buffer, + // because sockaddr_storage is the largest possible one. return ret; } - if (*__len < sizeof(struct sockaddr_in)) - { - *__len = sizeof(struct sockaddr_in); - errno = EINVAL; - return -1; - } /* TODO: AF_INET6 is not necessarily 10, this debug print is wrong */ - if (__addr->sa_family <= 10) { - show_msg(MSGDEBUG, "Address family is %s\n", afs[__addr->sa_family]); + if (sockaddr_st.ss_family <= 10) { + show_msg(MSGDEBUG, "Address family is %s\n", afs[sockaddr_st.ss_family]); } - if (__addr->sa_family == AF_INET6) + if (sockaddr_st.ss_family == AF_INET6) { struct sockaddr_in6 realpeer; socklen_t realpeerlen = sizeof(realpeer); - int ret = realgetpeername(__fd, (struct sockaddr *)&realpeer, &realpeerlen); - if (ret < 0) { - return ret; - } + memcpy(&realpeer, &sockaddr_st, realpeerlen); + if ((!memcmp(&realpeer.sin6_addr, &ipv4mapped, NAT64PREFIXLEN)) || (check_prefix(config, &realpeer.sin6_addr))) { - struct sockaddr_in * result; - result = (struct sockaddr_in *)__addr; - result->sin_family = AF_INET; - result->sin_port = realpeer.sin6_port; - memcpy(&result->sin_addr, &realpeer.sin6_addr.s6_addr[12], sizeof(struct in_addr)); + struct sockaddr_in result; + memset(&result, 0, sizeof(result)); + + result.sin_family = AF_INET; + result.sin_port = realpeer.sin6_port; + memcpy(&result.sin_addr, &realpeer.sin6_addr.s6_addr[12], sizeof(struct in_addr)); + + // Copy up to *__len bytes into the available space. + memcpy(__addr, &result, *__len); + *__len = sizeof(struct sockaddr_in); return ret; } + else { + // Not sure what the best data to return here would be. + // This should never happen in normal operation, though. + // This would only be executed if the socket was connected to an IPv6 address + // that's not part of a NAT64 prefix. + + // Might be a useful feature for the future - if an application is connecting + // to a given IPv4 address but you know the server also has an IPv6 without NAT64 + // maybe there can be config entries mapping a given IPv4 to a given IPv6 address. + // Unless that's out of scope for this project ... + + // For the meantime, maybe return some of the reserved IPv4's in 240/4? + + show_msg(MSGWARN, "How does this happen?\n"); + } } + + // Not IPv6, return original result + show_msg(MSGDEBUG, "Returning original values\n"); + memcpy(__addr, &sockaddr_st, *__len); + *__len = size; + return ret; } @@ -415,54 +658,109 @@ int getsockname(GETSOCKNAME_SIGNATURE) show_msg(MSGERR, "Unresolved symbol: getsockname\n"); return (-1); } - if (realgetpeername == NULL) - { - show_msg(MSGERR, "Unresolved symbol: getpeername\n"); - return (-1); - } /* If we haven't initialized yet, do it now */ get_config(); show_msg(MSGDEBUG, "Got getsockname call for socket %d\n", __fd); - socklen_t needlen = *__len; - int ret = realgetsockname(__fd, __addr, &needlen); - if (ret < 0) - { - return ret; + + int sock_flags = get_custom_fd_flags(__fd); + + if (sock_flags < 0) { + show_msg(MSGERR, "Failed to query socket flags for fd %d, err=%d\n", __fd, sock_flags); + // Assume it's a normal socket, though this should never happen. + sock_flags = 0; } - if (*__len < sizeof(struct sockaddr_in)) - { - *__len = sizeof(struct sockaddr_in); - errno = EINVAL; - return -1; + if (sock_flags >= 0 && (!(sock_flags & TNAT_FLAG_SOCK_UPGRADED_TO_IPV6))) { + // TNAT_FLAG_SOCK_UPGRADED_TO_IPV6 is not set - this means that whatever + // that socket is, we don't care, it's not something we need to modify. + // So we can call the original getsockname. + show_msg(MSGDEBUG, "None of our modded sockets, call real getsockname\n"); + return realgetsockname(__fd, __addr, __len); } + + /* The software calling getsockname is expecting an IPv4 response. + This means that the __addr pointer might only have space for a sockaddr_in, + not for a sockaddr_in6. It's probably unreasonable to expect the calling + application to provide a buffer large enough for an IPv6 sockaddr_in6 + if they're assuming they talk IPv4. So, better allocate our own buffers, temporarily. */ + + struct sockaddr_storage sockaddr_st; + socklen_t size = sizeof(sockaddr_st); + int ret = realgetsockname(__fd, (struct sockaddr *)&sockaddr_st, &size); + if (ret < 0) { + // If we end up here, it's not because of a too-small buffer, + // because sockaddr_storage is the largest possible one. + show_msg(MSGDEBUG, "realgetsockname(%d) returned %d\n", __fd, ret); + return ret; + } + /* TODO: AF_INET6 is not necessarily 10, this debug print is wrong */ - if (__addr->sa_family <= 10) { - show_msg(MSGDEBUG, "Address family is %s\n", afs[__addr->sa_family]); + if (sockaddr_st.ss_family <= 10) { + show_msg(MSGDEBUG, "Address family is %s\n", afs[sockaddr_st.ss_family]); } - if (__addr->sa_family == AF_INET6) + if (sockaddr_st.ss_family == AF_INET6) { + struct sockaddr_in6 realsock; socklen_t realsocklen = sizeof(realsock); - int ret = realgetsockname(__fd, (struct sockaddr *)&realsock, &realsocklen); - if (ret < 0) { - return ret; - } + memcpy(&realsock, &sockaddr_st, realsocklen); + if ((!memcmp(&realsock.sin6_addr, &ipv4mapped, NAT64PREFIXLEN)) || (check_prefix(config, &realsock.sin6_addr))) { - struct sockaddr_in * result; - result = (struct sockaddr_in *)__addr; - result->sin_family = AF_INET; - result->sin_port = realsock.sin6_port; - memcpy(&result->sin_addr, &realsock.sin6_addr.s6_addr[12], sizeof(struct in_addr)); + struct sockaddr_in result; + memset(&result, 0, sizeof(result)); + + result.sin_family = AF_INET; + result.sin_port = realsock.sin6_port; + memcpy(&result.sin_addr, &realsock.sin6_addr.s6_addr[12], sizeof(struct in_addr)); + + // Copy up to *__len bytes into the available space. + int memcpy_size = *__len; + if (memcpy_size > sizeof(struct sockaddr_in)) { + memcpy_size = sizeof(struct sockaddr_in); + } + + memcpy(__addr, &result, memcpy_size); + *__len = sizeof(struct sockaddr_in); + return ret; + } + else { + // Application called getsockname, but the socket is not listening on an IPv6-mapped address. + // The socket is listening on a "real" IPv6 address. + // Returning that address to the application as-is is going to cause issues. + // It's probably better to make the application believe it is bound to the unspecific address, + // i.e. return an IPv4 address of 0.0.0.0 in this case. + + struct sockaddr_in result; + memset(&result, 0, sizeof(result)); + + result.sin_family = AF_INET; + result.sin_port = realsock.sin6_port; + result.sin_addr.s_addr = 0; + + int memcpy_size = *__len; + if (memcpy_size > sizeof(struct sockaddr_in)) { + memcpy_size = sizeof(struct sockaddr_in); + } + + show_msg(MSGDEBUG, "Returning fake IPv4 0.0.0.0 as sockname\n"); + + memcpy(__addr, &result, memcpy_size); *__len = sizeof(struct sockaddr_in); return ret; } + } + + // Not IPv6, return original result + show_msg(MSGDEBUG, "Returning original values\n"); + memcpy(__addr, &sockaddr_st, *__len); + *__len = size; + return ret; }