From b219204255c110294c25fbf4031fb1892e4d0ba1 Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Mon, 15 Apr 2013 20:21:47 -0600 Subject: [PATCH] Polish TPROXY support for OpenBSD and FreeBSD Current OpenBSD implementation of PF divert-to works similarly to TPROXY and only requires a getsockname() lookup to locate the TCP packet original destination. We can use the same PF configuration to preform "intercept" option but the old PF transparent code does lookups on /dev/pf which fails badly on the new PF versions. getsockname() is what is really required and already performed by TcpAcceptor on all incoming connections, so there is no need for a special PF lookup code now. Add a new ./configure option --with-nat-devpf to enable the old /dev/pf NAT lookup code in a backward-compatible way for older OS versions and OpenBSD based distros which have not yet ported the new PF code. The option is disabled by default since the systems requiring it are fairly old now. Also remove the getsockname() lookup in the IPFW lookup implementation which is redundant behind TcpAcceptor. --- configure.ac | 30 ++++-- doc/release-notes/release-3.4.sgml | 46 +++++++++- src/comm.cc | 49 ++++++---- src/ip/Intercept.cc | 141 ++++++++++++----------------- src/ip/Intercept.h | 18 +--- 5 files changed, 158 insertions(+), 126 deletions(-) diff --git a/configure.ac b/configure.ac index a72e1d70a6..7e90292049 100644 --- a/configure.ac +++ b/configure.ac @@ -1469,7 +1469,17 @@ AC_ARG_ENABLE(pf-transparent, [unrecognized argument to --enable-pf-transparent: $enableval]) ]) #will be AC_DEFINEd later, after checking for appropriate infrastructure -AC_MSG_NOTICE([PF-based transparent proxying requested: ${enable_pf_transparent:=auto}]) +AC_MSG_NOTICE([PF-based transparent proxying requested: ${enable_pf_transparent:=no}]) + +dnl Enable /dev/pf support for older PF Transparent Proxy systems (OpenBSD 4.x and older) +AC_ARG_WITH(nat-devpf, + AS_HELP_STRING([--with-nat-devpf], + [Enable /dev/pf support for NAT on older OpenBSD and FreeBSD kernels.]), [ + SQUID_YESNO([$enableval], + [unrecognized argument to --with-nat-devpf: $enableval]) +]) +#will be AC_DEFINEd later, after checking for appropriate infrastructure +AC_MSG_NOTICE([NAT lookups via /dev/pf: ${with_nat_devpf:=no}]) # Linux Netfilter Transparent Proxy AC_ARG_ENABLE(linux-netfilter, @@ -3359,22 +3369,24 @@ dnl Solaris minor version (8, 9, 10, ...) CXXFLAGS="-DSOLARIS2=$solrev $CXXFLAGS" fi -dnl PF support requires a header file. -if test "x$enable_pf_transparent" != "xno" ; then +dnl PF /dev/pf support requires a header file. +if test "x$with_nat_devpf" != "xno" ; then if test "x$ac_cv_header_net_pfvar_h" = "xyes" -o \ "x$ac_cv_header_net_pf_pfvar_h" = "xyes"; then - if test "x$enable_pf_transparent" = "xauto" ; then - enable_pf_transparent="yes" + if test "x$with_nat_devpf" = "xauto" ; then + with_nat_devpf="no" fi else - if test "x$enable_pf_transparent" = "xyes" ; then - AC_MSG_ERROR([PF-based transparent proxy requested but needed header not found]) + if test "x$with_nat_devpf" = "xyes" ; then + AC_MSG_ERROR([PF /dev/pf based NAT requested but needed header not found]) fi - enable_pf_transparent="no" + with_nat_devpf="no" fi fi -SQUID_DEFINE_BOOL(PF_TRANSPARENT,$enable_pf_transparent, +SQUID_DEFINE_BOOL(PF_TRANSPARENT,${enable_pf_transparent:=no}, [Enable support for PF-style transparent proxying]) +SQUID_DEFINE_BOOL(USE_NAT_DEVPF,${with_nat_devpf:=no}, + [Enable support for /dev/pf NAT lookups]) if test "x$enable_linux_netfilter" != "xno" ; then if test "x$ac_cv_header_linux_netfilter_ipv4_h" = "xyes"; then diff --git a/doc/release-notes/release-3.4.sgml b/doc/release-notes/release-3.4.sgml index 2dc0472a86..d1a7565ad4 100644 --- a/doc/release-notes/release-3.4.sgml +++ b/doc/release-notes/release-3.4.sgml @@ -40,6 +40,7 @@ The 3.4 change history can be . + +

The Packet Filter (PF) firewall in OpenBSD 4.4 and later offers traffic interception + using several very simple methods. One of which is the divert-to rule type + which acts as a simple routing diversion instead of performing NAT packet alterations. + +

The IP Firewall (IPFW) on FreeBSD 9+ contains a port of the Linux Netfilter TPROXY feature. + +

This version of Squid adds support for these features through the ./configure + options --enable-pf-transparent and --enable-ipfw-transparent when Squid is built on + systems with the required support. No special extras are required to enable + http_port ... tproxy configuration to work. + +

NOTE: To resolve NAT lookup issues on recent PF firewall versions the code behind + ./configure --enable-pf-transparent has been altered and is expected to + break on the version of PF firewall shipped with BSD systems such as NetBSD and FreeBSD + which do not yet support the getsockname() API. + These systems require --with-nat-devpf to enable /dev/pf support when using PF firewall. + + Changes to squid.conf since Squid-3.3

There have been changes to Squid's configuration file since Squid-3.3. @@ -144,6 +166,12 @@ This section gives a thorough account of those changes in three categories:

New result code BH to signal helper internal errors

Details at . + http_port +

Support tproxy mode traffic on BSD systems with BINDANY support + (OpenBSD 5+, FreeBSD 9+ so far). +

Changed build options behind intercept traffic mode handling on BSD. + see --enable-pf-transparent for more details. + logformat

New format code %note to log a transaction annotation linked to the transaction by ICAP, eCAP, a helper, or the note squid.conf directive. @@ -186,14 +214,28 @@ This section gives an account of those changes in three categories: New options

-

There are no new ./configure options in Squid-3.4. + --with-nat-pf +

New option to alter the behaviour of http_port ... intercept option + in squid.conf. +

When this option is used Squid performs the /dev/pf lookups required to + support PF rdr-to rules. Otherwise Squid will perform perform the + getsockname() API calls to support PF divert-to rules. +

NOTE: systems such as NetBSD and FreeBSD which do not yet support + the getsockname() API in recent PF versions require this option. Changes to existing options

-

There are no changed ./configure options in Squid-3.4. + --enable-pf-transparent +

NAT table support updated to use the getsockname() API provided by the + latest PF versions divert-to. This allows http_port + in squid.conf to support both intercept and tproxy traffic + and to silence NAT lookup failure messages on recent BSD. +

NOTE: systems such as NetBSD and FreeBSD which do not yet support + the getsockname() API in recent PF versions require --with-nat-devpf + to re-enable /dev/pf support when using PF firewall.

diff --git a/src/comm.cc b/src/comm.cc index bc57cca228..caf6d5032e 100644 --- a/src/comm.cc +++ b/src/comm.cc @@ -495,35 +495,46 @@ comm_set_v6only(int fd, int tos) } /** - * Set the socket IP_TRANSPARENT option for Linux TPROXY v4 support, - * or set the socket SO_BINDANY option for BSD divert-to support. + * Set the socket option required for TPROXY spoofing for: + * - Linux TPROXY v4 support, + * - OpenBSD divert-to support, + * - FreeBSD IPFW TPROXY v4 support. */ void comm_set_transparent(int fd) { -#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) - int tos = 1; - if (setsockopt(fd, SOL_IP, IP_TRANSPARENT, (char *) &tos, sizeof(int)) < 0) { - debugs(50, DBG_IMPORTANT, "comm_open: setsockopt(IP_TRANSPARENT) on FD " << fd << ": " << xstrerror()); - } else { - /* mark the socket as having transparent options */ - fd_table[fd].flags.transparent = true; - } + bool doneSuid = false; +#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux +# define soLevel SOL_IP +# define soFlag IP_TRANSPARENT -#elif defined(SO_BINDANY) - int tos = 1; +#elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF +# define soLevel SOL_SOCKET +# define soFlag SO_BINDANY + enter_suid(); + doneSuid = true; + +#elif defined(IP_BINDANY) // FreeBSD with IPFW +# define soLevel IPPROTO_IP +# define soFlag IP_BINDANY enter_suid(); - if (setsockopt(fd, SOL_SOCKET, SO_BINDANY, (char *) &tos, sizeof(int)) < 0) { - debugs(50, DBG_IMPORTANT, "comm_open: setsockopt(SO_BINDANY) on FD " << fd << ": " << xstrerror()); + doneSuid = true; + +#else + debugs(50, DBG_CRITICAL, "WARNING: comm_open: setsockopt(TPROXY) not supported on this platform"); +#endif /* sockopt */ + +#if defined(soLevel) && defined(soFlag) + int tos = 1; + if (setsockopt(fd, soLevel, soFlag, (char *) &tos, sizeof(int)) < 0) { + debugs(50, DBG_IMPORTANT, "comm_open: setsockopt(TPROXY) on FD " << fd << ": " << xstrerror()); } else { /* mark the socket as having transparent options */ fd_table[fd].flags.transparent = true; } - leave_suid(); - -#else - debugs(50, DBG_CRITICAL, "WARNING: comm_open: setsockopt(IP_TRANSPARENT) not supported on this platform"); -#endif /* sockopt */ + if (doneSuid) + leave_suid(); +#endif } /** diff --git a/src/ip/Intercept.cc b/src/ip/Intercept.cc index 0dd48456e8..ae2e890564 100644 --- a/src/ip/Intercept.cc +++ b/src/ip/Intercept.cc @@ -147,9 +147,12 @@ Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int } bool -Ip::Intercept::NetfilterTransparent(const Comm::ConnectionPointer &newConn, int silent) +Ip::Intercept::TproxyTransparent(const Comm::ConnectionPointer &newConn, int silent) { -#if LINUX_NETFILTER +#if (LINUX_NETFILTER && defined(IP_TRANSPARENT)) || \ + (PF_TRANSPARENT && defined(SO_BINDANY)) || \ + (IPFW_TRANSPARENT && defined(IP_BINDANY)) + /* Trust the user configured properly. If not no harm done. * We will simply attempt a bind outgoing on our own IP. */ @@ -165,26 +168,15 @@ bool Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent) { #if IPFW_TRANSPARENT - struct sockaddr_storage lookup; - socklen_t len = sizeof(struct sockaddr_storage); - newConn->local.GetSockAddr(lookup, AF_INET); - - /** \par - * Try lookup for IPFW interception. */ - if ( getsockname(newConn->fd, (struct sockaddr*)&lookup, &len) != 0 ) { - if ( !silent ) { - debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror()); - lastReported_ = squid_curtime; - } - debugs(89, 9, HERE << "address: " << newConn); - return false; - } else { - newConn->local = lookup; - debugs(89, 5, HERE << "address NAT: " << newConn); - return true; - } -#endif + /* The getsockname() call performed already provided the TCP packet details. + * There is no way to identify whether they came from NAT or not. + * Trust the user configured properly. + */ + debugs(89, 5, HERE << "address NAT: " << newConn); + return true; +#else return false; +#endif } bool @@ -278,24 +270,21 @@ Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silen } bool -Ip::Intercept::PfTransparent(const Comm::ConnectionPointer &newConn, int silent) +Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent) { -#if PF_TRANSPARENT && defined(SO_BINDANY) - /* Trust the user configured properly. If not no harm done. - * We will simply attempt a bind outgoing on our own IP. +#if PF_TRANSPARENT /* --enable-pf-transparent */ + +#if !USE_NAT_DEVPF + /* On recent PF versions the getsockname() call performed already provided + * the required TCP packet details. + * There is no way to identify whether they came from NAT or not. + * + * Trust the user configured properly. */ - newConn->remote.SetPort(0); // allow random outgoing port to prevent address clashes - debugs(89, 5, HERE << "address DIVERT: " << newConn); + debugs(89, 5, HERE << "address NAT divert-to: " << newConn); return true; -#else - return false; -#endif -} -bool -Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent) -{ -#if PF_TRANSPARENT /* --enable-pf-transparent */ +#else /* USE_NAT_DEVPF / --with-nat-devpf */ struct pfioc_natlook nl; static int pffd = -1; @@ -339,7 +328,7 @@ Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent debugs(89, 5, HERE << "address NAT: " << newConn); return true; } - +#endif /* --with-nat-devpf */ #endif /* --enable-pf-transparent */ return false; } @@ -367,8 +356,7 @@ Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::Connec /* NP: try TPROXY first, its much quieter than NAT when non-matching */ if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) { - if (NetfilterTransparent(newConn, silent)) return true; - if (PfTransparent(newConn, silent)) return true; + if (TproxyTransparent(newConn, silent)) return true; } /* NAT is only available in IPv4 */ @@ -395,7 +383,28 @@ Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::Connec bool Ip::Intercept::ProbeForTproxy(Ip::Address &test) { -#if defined(IP_TRANSPARENT) + bool doneSuid = false; + +#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux +# define soLevel SOL_IP +# define soFlag IP_TRANSPARENT + +#elif defined(SO_BINDANY) // OpenBSD 4.7+ and NetBSD with PF +# define soLevel SOL_SOCKET +# define soFlag SO_BINDANY + enter_suid(); + doneSuid = true; + +#elif defined(IP_BINDANY) // FreeBSD with IPFW +# define soLevel IPPROTO_IP +# define soFlag IP_BINDANY + enter_suid(); + doneSuid = true; + +#endif + +#if defined(soLevel) && defined(soFlag) + debugs(3, 3, "Detect TPROXY support on port " << test); int tos = 1; @@ -411,11 +420,13 @@ Ip::Intercept::ProbeForTproxy(Ip::Address &test) tmp.GetSockAddr(tmp_ip6); if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 && - setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 && + setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 && bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) { debugs(3, 3, "IPv6 TPROXY support detected. Using."); close(tmp_sock); + if (doneSuid) + leave_suid(); return true; } if (tmp_sock >= 0) { @@ -426,6 +437,8 @@ Ip::Intercept::ProbeForTproxy(Ip::Address &test) if ( test.IsIPv6() && !test.SetIPv4() ) { debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test ); + if (doneSuid) + leave_suid(); return false; } @@ -439,11 +452,13 @@ Ip::Intercept::ProbeForTproxy(Ip::Address &test) tmp.GetSockAddr(tmp_ip4); if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 && - setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 && + setsockopt(tmp_sock, soLevel, soFlag, (char *)&tos, sizeof(int)) == 0 && bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) { debugs(3, 3, "IPv4 TPROXY support detected. Using."); close(tmp_sock); + if (doneSuid) + leave_suid(); return true; } if (tmp_sock >= 0) { @@ -451,51 +466,11 @@ Ip::Intercept::ProbeForTproxy(Ip::Address &test) } } -#elif defined(SO_BINDANY) - debugs(3, 3, "Detect BINDANY support on port " << test); - - int tos = 1; - int tmp_sock = -1; - - if (test.IsIPv6()) { - debugs(3, 3, "...Probing for IPv6 SO_BINDANY support."); - - struct sockaddr_in6 tmp_ip6; - Ip::Address tmp = "::2"; - tmp.SetPort(0); - tmp.GetSockAddr(tmp_ip6); - - if ((tmp_sock = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)) >=0 && - (setsockopt(tmp_sock, SOL_SOCKET, SO_BINDANY, (char *)&tos, - sizeof(tos)) == 0) && - (bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0)) { - debugs(3, 3, "IPv6 BINDANY support detected. Using."); - close(tmp_sock); - return true; - } - } - - if (test.IsIPv4()) { - debugs(3, 3, "...Probing for IPv4 SO_BINDANY support."); - - struct sockaddr_in tmp_ip4; - Ip::Address tmp = "127.0.0.2"; - tmp.SetPort(0); - tmp.GetSockAddr(tmp_ip4); - - if ((tmp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) >=0 && - (setsockopt(tmp_sock, SOL_SOCKET, SO_BINDANY, (char *)&tos, - sizeof(tos)) == 0) && - (bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0)) { - debugs(3, 3, "IPv4 BINDANY support detected. Using."); - close(tmp_sock); - return true; - } - } - #else debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY."); #endif + if (doneSuid) + leave_suid(); return false; } diff --git a/src/ip/Intercept.h b/src/ip/Intercept.h index f65d8ba8da..08d05f7072 100644 --- a/src/ip/Intercept.h +++ b/src/ip/Intercept.h @@ -88,22 +88,23 @@ public: private: /** - * perform Lookups on Netfilter interception targets (REDIRECT, DNAT). + * perform Lookups on fully-transparent interception targets (TPROXY). + * Supports Netfilter, PF and IPFW. * * \param silent 0 if errors are to be displayed. 1 if errors are to be hidden. * \param newConn Details known, to be updated where relevant. * \return Whether successfuly located the new address. */ - bool NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent); + bool TproxyTransparent(const Comm::ConnectionPointer &newConn, int silent); /** - * perform Lookups on Netfilter fully-transparent interception targets (TPROXY). + * perform Lookups on Netfilter interception targets (REDIRECT, DNAT). * * \param silent 0 if errors are to be displayed. 1 if errors are to be hidden. * \param newConn Details known, to be updated where relevant. * \return Whether successfuly located the new address. */ - bool NetfilterTransparent(const Comm::ConnectionPointer &newConn, int silent); + bool NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent); /** * perform Lookups on IPFW interception. @@ -132,15 +133,6 @@ private: */ bool PfInterception(const Comm::ConnectionPointer &newConn, int silent); - /** - * perform Lookups on PF fully-transparent interception target (DIVERT). - * - * \param silent 0 if errors are to be displayed. 1 if errors are to be hidden. - * \param newConn Details known, to be updated where relevant. - * \return Whether successfuly located the new address. - */ - bool PfTransparent(const Comm::ConnectionPointer &newConn, int silent); - int transparentActive_; int interceptActive_; time_t lastReported_; /**< Time of last error report. Throttles NAT error display to 1 per minute */ -- 2.47.2