/*- * SSLsplit - transparent SSL/TLS interception * https://www.roe.ch/SSLsplit * * Copyright (c) 2009-2019, Daniel Roethlisberger . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "nat.h" #include "log.h" #include "attrib.h" #include #include #include #include #ifdef HAVE_PF #include #include #include #include #include #include #ifdef __APPLE__ #define PRIVATE #endif /* __APPLE__ */ #include #ifdef __APPLE__ #undef PRIVATE #endif /* __APPLE__ */ #include #endif /* HAVE_PF */ #ifdef HAVE_IPFILTER #include #include #include #include #include #include #include #endif /* HAVE_IPFILTER */ #ifdef HAVE_NETFILTER #include #include #include #include #include #endif /* HAVE_NETFILTER */ /* * Access NAT state tables in a NAT engine independent way. * Adding support for additional NAT engines should require only * changes in this file. */ /* * pf */ #ifdef HAVE_PF static int nat_pf_fd = -1; static int nat_pf_preinit(void) { nat_pf_fd = open("/dev/pf", O_RDONLY); if (nat_pf_fd < 0) { log_err_level_printf(LOG_CRIT, "Error opening '/dev/pf': %s\n", strerror(errno)); return -1; } return 0; } static int nat_pf_init(void) { int rv; rv = fcntl(nat_pf_fd, F_SETFD, fcntl(nat_pf_fd, F_GETFD) | FD_CLOEXEC); if (rv == -1) { log_err_level_printf(LOG_CRIT, "Error setting FD_CLOEXEC on '/dev/pf': %s\n", strerror(errno)); return -1; } return 0; } static void nat_pf_fini(void) { close(nat_pf_fd); } static int nat_pf_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, evutil_socket_t s, UNUSED struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) { if (getsockname(s, dst_addr, dst_addrlen) == -1) { log_err_level_printf(LOG_CRIT, "Error from getsockname(): %s\n", strerror(errno)); return -1; } return 0; } #endif /* HAVE_PF */ /* * ipfilter */ #ifdef HAVE_IPFILTER static int nat_ipfilter_fd = -1; static int nat_ipfilter_preinit(void) { nat_ipfilter_fd = open(IPNAT_NAME, O_RDONLY); if (nat_ipfilter_fd < 0) { log_err_printf("Error opening '%s': %s\n", IPNAT_NAME, strerror(errno)); return -1; } return 0; } static int nat_ipfilter_init(void) { int rv; rv = fcntl(nat_ipfilter_fd, F_SETFD, fcntl(nat_ipfilter_fd, F_GETFD) | FD_CLOEXEC); if (rv == -1) { log_err_printf("Error setting FD_CLOEXEC on '%s': %s\n", IPNAT_NAME, strerror(errno)); return -1; } return 0; } static void nat_ipfilter_fini(void) { close(nat_ipfilter_fd); } static int nat_ipfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, evutil_socket_t s, struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) { struct sockaddr_storage our_addr; socklen_t our_addrlen; struct natlookup nl; struct ipfobj ipfo; our_addrlen = sizeof(struct sockaddr_storage); if (getsockname(s, (struct sockaddr *)&our_addr, &our_addrlen) == -1) { log_err_printf("Error from getsockname(): %s\n", strerror(errno)); return -1; } memset(&nl, 0, sizeof(struct natlookup)); if (src_addr->sa_family == AF_INET) { struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr; struct sockaddr_in *our_sai = (struct sockaddr_in *)&our_addr; nl.nl_outip.s_addr = src_sai->sin_addr.s_addr; nl.nl_outport = src_sai->sin_port; nl.nl_inip.s_addr = our_sai->sin_addr.s_addr; nl.nl_inport = our_sai->sin_port; } else { log_err_printf("The ipfilter NAT engine does not " "support IPv6 state lookups\n"); return -1; } nl.nl_flags = IPN_TCP; /* assuming IPv4 from here */ memset(&ipfo, 0, sizeof(struct ipfobj)); ipfo.ipfo_rev = IPFILTER_VERSION; ipfo.ipfo_size = sizeof(struct natlookup); ipfo.ipfo_ptr = &nl; ipfo.ipfo_type = IPFOBJ_NATLOOKUP; if (ioctl(nat_ipfilter_fd, SIOCGNATL, &ipfo) == -1) { if (errno != ESRCH) { log_err_printf("Error from ioctl(SIOCGNATL): %s\n", strerror(errno)); } return -1; } if ((nl.nl_inport == nl.nl_realport) && (nl.nl_inip.s_addr == nl.nl_realip.s_addr)) { /* no destination address/port translation in place */ return -1; } /* copy original destination address */ struct sockaddr_in *dst_sai = (struct sockaddr_in *)dst_addr; memset(dst_sai, 0, sizeof(struct sockaddr_in)); dst_sai->sin_addr.s_addr = nl.nl_realip.s_addr; dst_sai->sin_port = nl.nl_realport; dst_sai->sin_family = AF_INET; *dst_addrlen = sizeof(struct sockaddr_in); return 0; } #endif /* HAVE_IPFILTER */ /* * netfilter, tproxy */ #ifdef HAVE_NETFILTER /* * Linux commit 121d1e0941e05c64ee4223064dd83eb24e871739 adding * IP6T_SO_ORIGINAL_DST was first released as part of Linux v3.8-rc1 in 2012. * Before that, this interface only supported IPv4. */ static int nat_netfilter_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, evutil_socket_t s, struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) { int rv; if (src_addr->sa_family == AF_INET) { rv = getsockopt(s, SOL_IP, SO_ORIGINAL_DST, dst_addr, dst_addrlen); if (rv == -1) { log_err_printf("Error from getsockopt(" "SO_ORIGINAL_DST): %s\n", strerror(errno)); } } else { #ifdef IP6T_SO_ORIGINAL_DST rv = getsockopt(s, SOL_IPV6, IP6T_SO_ORIGINAL_DST, dst_addr, dst_addrlen); if (rv == -1) { log_err_printf("Error from getsockopt(" "IP6T_SO_ORIGINAL_DST): %s\n", strerror(errno)); } #else /* !IP6T_SO_ORIGINAL_DST */ log_err_printf("The netfilter NAT engine only " "supports IPv4 state lookups on " "this version of Linux\n"); return -1; #endif /* !IP6T_SO_ORIGINAL_DST */ } return rv; } #ifdef IP_TRANSPARENT /* * Set the listening socket IP_TRANSPARENT. This makes the Linux IP routing * stack omit the source address checks on output, which is needed for * Linux TPROXY transparent proxying support. */ static int nat_iptransparent_socket_cb(evutil_socket_t s) { int on = 1; int rv; rv = setsockopt(s, SOL_IP, IP_TRANSPARENT, (void*)&on, sizeof(on)); if (rv == -1) { log_err_printf("Error from setsockopt(IP_TRANSPARENT): %s\n", strerror(errno)); } return rv; } #endif /* IP_TRANSPARENT */ #endif /* HAVE_NETFILTER */ /* * generic */ #if defined(HAVE_IPFW) || (defined(HAVE_NETFILTER) && defined(IP_TRANSPARENT)) /* * Generic getsockname based implementation. This assumes that getsockname, * by kernel magic, gives us the original destination. */ static int nat_getsockname_lookup_cb(struct sockaddr *dst_addr, socklen_t *dst_addrlen, evutil_socket_t s, UNUSED struct sockaddr *src_addr, UNUSED socklen_t src_addrlen) { if (getsockname(s, dst_addr, dst_addrlen) == -1) { log_err_level_printf(LOG_CRIT, "Error from getsockname(): %s\n", strerror(errno)); return -1; } return 0; } #endif /* * NAT engine glue code and API. */ typedef int (*nat_init_cb_t)(void); typedef void (*nat_fini_cb_t)(void); struct engine { const char *name; unsigned int ipv6 : 1; unsigned int used : 1; nat_init_cb_t preinitcb; nat_init_cb_t initcb; nat_fini_cb_t finicb; nat_lookup_cb_t lookupcb; nat_socket_cb_t socketcb; }; struct engine engines[] = { #ifdef HAVE_PF { "pf", 1, 0, nat_pf_preinit, nat_pf_init, nat_pf_fini, nat_pf_lookup_cb, NULL }, #endif /* HAVE_PF */ #ifdef HAVE_IPFW { "ipfw", 1, 0, NULL, NULL, NULL, nat_getsockname_lookup_cb, NULL }, #endif /* HAVE_IPFW */ #ifdef HAVE_IPFILTER { "ipfilter", 0, 0, nat_ipfilter_preinit, nat_ipfilter_init, nat_ipfilter_fini, nat_ipfilter_lookup_cb, NULL }, #endif /* HAVE_IPFILTER */ #ifdef HAVE_NETFILTER { #ifdef IP6T_SO_ORIGINAL_DST "netfilter", 1, 0, #else /* !IP6T_SO_ORIGINAL_DST */ "netfilter", 0, 0, #endif /* !IP6T_SO_ORIGINAL_DST */ NULL, NULL, NULL, nat_netfilter_lookup_cb, NULL }, #ifdef IP_TRANSPARENT { "tproxy", 1, 0, NULL, NULL, NULL, nat_getsockname_lookup_cb, nat_iptransparent_socket_cb }, #endif /* IP_TRANSPARENT */ #endif /* HAVE_NETFILTER */ { NULL, 0, 0, NULL, NULL, NULL, NULL, NULL } }; /* * Return the name of the default NAT engine. */ const char * nat_getdefaultname(void) { return engines[0].name; } /* * Look for a NAT engine in the table and return the index if found. * If there is no NAT engine with the given name, then the index of the * sentinel table entry is returned. */ static int nat_index(const char *name) { if (name) for (int i = 0; engines[i].name; i++) if (!strcmp(name, engines[i].name)) return i; return ((sizeof(engines) / sizeof(struct engine)) - 1); } /* * Returns !=0 if the named NAT engine exists, 0 if it does not exist. * NULL refers to the default NAT engine. */ int nat_exist(const char *name) { if (!name) name = engines[0].name; return !!engines[nat_index(name)].name; } /* * Returns !=0 if the named NAT engine has been marked as used, 0 if not. * NULL refers to the default NAT engine. */ int nat_used(const char *name) { if (!name) name = engines[0].name; return !!engines[nat_index(name)].used; } /* * Returns the lookup callback of the named NAT engine and marks the NAT * engine as used. * NULL refers to the default NAT engine. */ nat_lookup_cb_t nat_getlookupcb(const char *name) { int i; if (!name) name = engines[0].name; i = nat_index(name); engines[i].used = 1; return engines[i].lookupcb; } /* * Returns the socket callback of the named NAT engine. * NULL refers to the default NAT engine. */ nat_socket_cb_t nat_getsocketcb(const char *name) { if (!name) name = engines[0].name; return engines[nat_index(name)].socketcb; } /* * Returns 1 if name is a NAT engine which supports IPv6. * NULL refers to the default NAT engine. */ int nat_ipv6ready(const char *name) { if (!name) name = engines[0].name; return engines[nat_index(name)].ipv6; } /* * List all available NAT engines to standard output and flush. */ void nat_list_engines(void) { for (int i = 0; engines[i].name; i++) { fprintf(stdout, "%s%s\n", engines[i].name, i ? "" : " (default)"); } fflush(stdout); } /* * Pre-initialize all NAT engines which were marked as used by previous calls * to nat_getlookupcb(). * * Privileged initialization under root privs, before dropping privs, * before calling daemon(). Here should be initialization which needs * to provide the user feedback on errors. This includes opening * special device files, for which the user may not have sufficient privs. * * Returns -1 on failure, 0 on success. */ int nat_preinit(void) { for (int i = 0; engines[i].preinitcb && engines[i].used; i++) { log_dbg_printf("NAT engine preinit '%s'\n", engines[i].name); if (engines[i].preinitcb() == -1) return -1; } return 0; } /* * Undo nat_preinit - close all file descriptors, for use in privsep parent. */ void nat_preinit_undo(void) { nat_fini(); } /* * Initialize all NAT engines which were marked as used by previous calls to * nat_getlookupcb(). * * Unprivileged initialization, possibly root, possibly nobody or service user. * * Returns -1 on failure, 0 on success. */ int nat_init(void) { for (int i = 0; engines[i].initcb && engines[i].used; i++) { log_dbg_printf("NAT engine init '%s'\n", engines[i].name); if (engines[i].initcb() == -1) return -1; } return 0; } /* * Cleanup all NAT engines which were marked as used by previous calls to * nat_getlookupcb(). */ void nat_fini(void) { for (int i = 0; engines[i].finicb && engines[i].used; i++) { log_dbg_printf("NAT engine fini '%s'\n", engines[i].name); engines[i].finicb(); } } /* * Print version and option availability to standard error. */ void nat_version(void) { fprintf(stderr, "NAT engines:"); for (int i = 0; engines[i].name; i++) { fprintf(stderr, " %s%s", engines[i].name, i ? "" : "*"); } if (!engines[0].name) fprintf(stderr, " -"); fprintf(stderr, "\n"); #ifdef HAVE_IPFILTER fprintf(stderr, "ipfilter: version %d\n", IPFILTER_VERSION); #endif /* HAVE_IPFILTER */ #ifdef HAVE_NETFILTER fprintf(stderr, "netfilter:"); #ifdef IP_TRANSPARENT fprintf(stderr, " IP_TRANSPARENT"); #else /* !IP_TRANSPARENT */ fprintf(stderr, " !IP_TRANSPARENT"); #endif /* !IP_TRANSPARENT */ #ifdef IP6T_SO_ORIGINAL_DST fprintf(stderr, " IP6T_SO_ORIGINAL_DST"); #else /* !IP6T_SO_ORIGINAL_DST */ fprintf(stderr, " !IP6T_SO_ORIGINAL_DST"); #endif /* !IP6T_SO_ORIGINAL_DST */ fprintf(stderr, "\n"); #endif /* HAVE_NETFILTER */ } /* vim: set noet ft=c: */