summaryrefslogtreecommitdiff
path: root/src/nat.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/nat.cc')
-rw-r--r--src/nat.cc680
1 files changed, 680 insertions, 0 deletions
diff --git a/src/nat.cc b/src/nat.cc
new file mode 100644
index 0000000..59fe153
--- /dev/null
+++ b/src/nat.cc
@@ -0,0 +1,680 @@
+/*-
+ * SSLsplit - transparent SSL/TLS interception
+ * https://www.roe.ch/SSLsplit
+ *
+ * Copyright (c) 2009-2018, Daniel Roethlisberger <[email protected]>.
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#ifdef HAVE_PF
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/fcntl.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#ifdef __APPLE__
+#define PRIVATE
+#endif /* __APPLE__ */
+#include <net/pfvar.h>
+#ifdef __APPLE__
+#undef PRIVATE
+#endif /* __APPLE__ */
+#include <unistd.h>
+#endif /* HAVE_PF */
+
+#ifdef HAVE_IPFILTER
+#include <sys/ioctl.h>
+#include <netinet/tcp.h>
+#include <net/if.h>
+#include <netinet/ipl.h>
+#include <netinet/ip_compat.h>
+#include <netinet/ip_fil.h>
+#include <netinet/ip_nat.h>
+#endif /* HAVE_IPFILTER */
+
+#ifdef HAVE_NETFILTER
+extern "C"
+{
+#include <limits.h>
+#include <linux/netfilter_ipv4.h>
+#include <linux/netfilter_ipv6.h>
+#include <linux/if.h>
+#include <linux/netfilter_ipv6/ip6_tables.h>
+}
+#endif /* HAVE_NETFILTER */
+
+
+/*
+ * Access NAT state tables in a NAT engine independant 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_printf("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_printf("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,
+ struct sockaddr *src_addr, UNUSED socklen_t src_addrlen)
+{
+#ifdef __APPLE__
+#define sport sxport.port
+#define dport dxport.port
+#define rdport rdxport.port
+#ifdef v4addr /* XNU 4570.1.46 and newer */
+#define v4 v4addr
+#define v6 v6addr
+#endif /* XNU 4570.1.46 and newer */
+#endif /* __APPLE__ */
+ struct sockaddr_storage our_addr;
+ socklen_t our_addrlen;
+ struct pfioc_natlook nl;
+
+ 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 pfioc_natlook));
+ nl.af = src_addr->sa_family;
+ if (nl.af == AF_INET) {
+ struct sockaddr_in *src_sai = (struct sockaddr_in *)src_addr;
+ struct sockaddr_in *our_sai = (struct sockaddr_in *)&our_addr;
+ nl.saddr.v4.s_addr = src_sai->sin_addr.s_addr;
+ nl.sport = src_sai->sin_port;
+ nl.daddr.v4.s_addr = our_sai->sin_addr.s_addr;
+ nl.dport = our_sai->sin_port;
+ }
+ if (nl.af == AF_INET6) {
+ struct sockaddr_in6 *src_sai = (struct sockaddr_in6 *)src_addr;
+ struct sockaddr_in6 *our_sai = (struct sockaddr_in6 *)&our_addr;
+ memcpy(&nl.saddr.v6.s6_addr, &src_sai->sin6_addr.s6_addr, 16);
+ nl.sport = src_sai->sin6_port;
+ memcpy(&nl.daddr.v6.s6_addr, &our_sai->sin6_addr.s6_addr, 16);
+ nl.dport = our_sai->sin6_port;
+ }
+ nl.proto = IPPROTO_TCP;
+ nl.direction = PF_OUT;
+
+ if (ioctl(nat_pf_fd, DIOCNATLOOK, &nl)) {
+ if (errno != ENOENT) {
+ log_err_printf("Error from ioctl(DIOCNATLOOK): %s\n",
+ strerror(errno));
+ }
+ return -1;
+ }
+
+ if ((nl.dport == nl.rdport) &&
+ ((nl.af == AF_INET && nl.daddr.v4.s_addr == nl.rdaddr.v4.s_addr) ||
+ (nl.af == AF_INET6 &&
+ !memcmp(nl.daddr.v6.s6_addr, nl.rdaddr.v6.s6_addr, 16)))) {
+ /* no destination address/port translation in place */
+ return -1;
+ }
+
+ /* copy original destination address */
+ if (nl.af == AF_INET) {
+ 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.rdaddr.v4.s_addr;
+ dst_sai->sin_port = nl.rdport;
+ dst_sai->sin_family = nl.af;
+ *dst_addrlen = sizeof(struct sockaddr_in);
+ }
+ if (nl.af == AF_INET6) {
+ struct sockaddr_in6 *dst_sai = (struct sockaddr_in6 *)dst_addr;
+ memset(dst_sai, 0, sizeof(struct sockaddr_in6));
+ memcpy(dst_sai->sin6_addr.s6_addr, nl.rdaddr.v6.s6_addr, 16);
+ dst_sai->sin6_port = nl.rdport;
+ dst_sai->sin6_family = nl.af;
+ *dst_addrlen = sizeof(struct sockaddr_in6);
+ }
+
+ return 0;
+#ifdef __APPLE__
+#undef sport
+#undef dport
+#undef rdport
+#ifdef v4addr /* XNU 4570.1.46 and newer */
+#undef v4
+#undef v6
+#endif /* XNU 4570.1.46 and newer */
+#endif /* __APPLE__ */
+}
+#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_printf("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: */