You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
SSLproxy/src/nat.c

599 lines
14 KiB
C

/*-
* SSLsplit - transparent SSL/TLS interception
* https://www.roe.ch/SSLsplit
*
* Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
* 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
#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 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: */