]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Polish TPROXY support for OpenBSD and FreeBSD
authorAmos Jeffries <squid3@treenet.co.nz>
Tue, 16 Apr 2013 02:21:47 +0000 (20:21 -0600)
committerAmos Jeffries <squid3@treenet.co.nz>
Tue, 16 Apr 2013 02:21:47 +0000 (20:21 -0600)
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
doc/release-notes/release-3.4.sgml
src/comm.cc
src/ip/Intercept.cc
src/ip/Intercept.h

index a72e1d70a677c0edb29e93af3f9a63df00e34cf7..7e90292049dc7a39c4313dae9e75f82238bc644f 100644 (file)
@@ -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
index 2dc0472a864631ba108662055e362b2b577e1b1d..d1a7565ad47f8de0fcaf8f8b4a1db86175378763 100644 (file)
@@ -40,6 +40,7 @@ The 3.4 change history can be <url url="http://www.squid-cache.org/Versions/v3/3
 <itemize>
        <item>Helper protocol extensions
        <item>SSL Server Certificate Validator
+       <item>TPROXY Support for OpenBSD 5.1+ and FreeBSD 9+
 </itemize>
 
 Most user-facing changes are reflected in squid.conf (see below).
@@ -93,6 +94,27 @@ Most user-facing changes are reflected in squid.conf (see below).
    <em>ssl_crtd</em> related options. 
 
 
+<sect1>TPROXY Support for OpenBSD 5.1+ and FreeBSD 9+
+<p>Details at <url url="http://wiki.squid-cache.org/ConfigExamples/Intercept/OpenBsdPf">.
+
+<p>The Packet Filter (PF) firewall in OpenBSD 4.4 and later offers traffic interception
+   using several very simple methods. One of which is the <em>divert-to</em> rule type
+   which acts as a simple routing diversion instead of performing NAT packet alterations.
+
+<p>The IP Firewall (IPFW) on FreeBSD 9+ contains a port of the Linux Netfilter TPROXY feature.
+
+<p>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
+   <em>http_port ... tproxy</em> configuration to work.
+
+<p>NOTE: To resolve NAT lookup issues on recent PF firewall versions the code behind
+   <em>./configure --enable-pf-transparent</em> 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 <em>--with-nat-devpf</em> to enable /dev/pf support when using PF firewall.
+
+
 <sect>Changes to squid.conf since Squid-3.3
 <p>
 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:
        <p>New result code <em>BH</em> to signal helper internal errors
        <p>Details at <url url="http://wiki.squid-cache.org/Features/AddonHelpers">.
 
+       <tag>http_port</tag>
+       <p>Support <em>tproxy</em> mode traffic on BSD systems with BINDANY support
+          (OpenBSD 5+, FreeBSD 9+ so far).
+       <p>Changed build options behind <em>intercept</em> traffic mode handling on BSD.
+          see <em>--enable-pf-transparent</em> for more details.
+
        <tag>logformat</tag>
        <p>New format code <em>%note</em> to log a transaction annotation linked to the
           transaction by ICAP, eCAP, a helper, or the <em>note</em> squid.conf directive.
@@ -186,14 +214,28 @@ This section gives an account of those changes in three categories:
 <sect1>New options<label id="newoptions">
 <p>
 <descrip>
-       <p><em>There are no new ./configure options in Squid-3.4.</em>
+       <tag>--with-nat-pf</tag>
+       <p>New option to alter the behaviour of <em>http_port ... intercept</em> option
+          in squid.conf.
+       <p>When this option is used Squid performs the /dev/pf lookups required to
+          support PF <em>rdr-to</em> rules. Otherwise Squid will perform perform the
+          getsockname() API calls to support PF <em>divert-to</em> rules.
+       <p>NOTE: systems such as NetBSD and FreeBSD which do not yet support
+          the getsockname() API in recent PF versions require this option.
 
 </descrip>
 
 <sect1>Changes to existing options<label id="modifiedoptions">
 <p>
 <descrip>
-       <p><em>There are no changed ./configure options in Squid-3.4.</em>
+       <tag>--enable-pf-transparent</tag>
+       <p>NAT table support updated to use the getsockname() API provided by the
+          latest PF versions <em>divert-to</em>. This allows <em>http_port</em>
+          in squid.conf to support both <em>intercept</em> and <em>tproxy</em> traffic
+          and to silence NAT lookup failure messages on recent BSD.
+       <p>NOTE: systems such as NetBSD and FreeBSD which do not yet support
+          the getsockname() API in recent PF versions require <em>--with-nat-devpf</em>
+          to re-enable /dev/pf support when using PF firewall.
 
 </descrip>
 </p>
index bc57cca228ba71fc7a9e52d15f3bc149c8885b67..caf6d5032ee7d11eb32d52f76ebf01f80968a7e4 100644 (file)
@@ -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
 }
 
 /**
index 0dd48456e818492d9b4f1cd30929417cbe61a14d..ae2e890564cd3866932cab0be5d11bd6d2d6def0 100644 (file)
@@ -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;
 }
index f65d8ba8da85554208c0f30293441eeef381e72c..08d05f70725913e901eed9078e4138cba281733b 100644 (file)
@@ -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 */