]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ip/Intercept.cc
Polish TPROXY support for OpenBSD and FreeBSD
[thirdparty/squid.git] / src / ip / Intercept.cc
index 4412fe90cf92913a7e8be51e92441d1db82a833e..ae2e890564cd3866932cab0be5d11bd6d2d6def0 100644 (file)
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
-#include "config.h"
+#include "squid.h"
+#include "comm/Connection.h"
 #include "ip/Intercept.h"
 #include "fde.h"
+#include "src/tools.h"
 
 #if IPF_TRANSPARENT
 
@@ -69,6 +71,9 @@
 #elif HAVE_NETINET_IP_NAT_H
 #include <netinet/ip_nat.h>
 #endif
+#if HAVE_ERRNO_H
+#include <errno.h>
+#endif
 
 #endif /* IPF_TRANSPARENT required headers */
 
 #endif /* PF_TRANSPARENT required headers */
 
 #if LINUX_NETFILTER
-#include <linux/netfilter_ipv4.h>
+#if HAVE_LIMITS_H
+/* must be before including netfilter_ipv4.h */
+#include <limits.h>
 #endif
+#include <linux/netfilter_ipv4.h>
+#endif /* LINUX_NETFILTER required headers */
 
 // single global instance for access by other components.
 Ip::Intercept Ip::Interceptor;
@@ -96,129 +105,97 @@ Ip::Intercept Ip::Interceptor;
 void
 Ip::Intercept::StopTransparency(const char *str)
 {
-    if (transparent_active) {
+    if (transparentActive_) {
         debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
-        transparent_active = 0;
+        transparentActive_ = 0;
     }
 }
 
 void
 Ip::Intercept::StopInterception(const char *str)
 {
-    if (intercept_active) {
+    if (interceptActive_) {
         debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
-        intercept_active = 0;
+        interceptActive_ = 0;
     }
 }
 
-int
-Ip::Intercept::NetfilterInterception(int fd, const Ip::Address &me, Ip::Address &dst, int silent)
+bool
+Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
 {
 #if LINUX_NETFILTER
-    struct addrinfo *lookup = NULL;
-
-    dst.GetAddrInfo(lookup,AF_INET);
+    struct sockaddr_in lookup;
+    socklen_t len = sizeof(struct sockaddr_in);
+    newConn->local.GetSockAddr(lookup);
 
     /** \par
      * Try NAT lookup for REDIRECT or DNAT targets. */
-    if ( getsockopt(fd, IPPROTO_IP, SO_ORIGINAL_DST, lookup->ai_addr, &lookup->ai_addrlen) != 0) {
+    if ( getsockopt(newConn->fd, IPPROTO_IP, SO_ORIGINAL_DST, &lookup, &len) != 0) {
         if (!silent) {
-            debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on FD " << fd << ": " << xstrerror());
-            last_reported = squid_curtime;
+            debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
+            lastReported_ = squid_curtime;
         }
+        debugs(89, 9, HERE << "address: " << newConn);
+        return false;
     } else {
-        dst = *lookup;
+        newConn->local = lookup;
+        debugs(89, 5, HERE << "address NAT: " << newConn);
+        return true;
     }
-
-    Address::FreeAddrInfo(lookup);
-
-    if (me != dst) {
-        debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
-        return 0;
-    }
-
-    debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
 #endif
-    return -1;
+    return false;
 }
 
-int
-Ip::Intercept::NetfilterTransparent(int fd, const Ip::Address &me, Ip::Address &client, int silent)
+bool
+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.
      */
-    if (fd_table[fd].flags.transparent) {
-        client.SetPort(0); // allow random outgoing port to prevent address clashes
-        debugs(89, 5, HERE << "address TPROXY: me= " << me << ", client= " << client);
-        return 0;
-    }
-
-    debugs(89, 9, HERE << "address: me= " << me << ", client= " << client);
+    newConn->remote.SetPort(0); // allow random outgoing port to prevent address clashes
+    debugs(89, 5, HERE << "address TPROXY: " << newConn);
+    return true;
+#else
+    return false;
 #endif
-    return -1;
 }
 
-int
-Ip::Intercept::IpfwInterception(int fd, const Ip::Address &me, Ip::Address &dst, int silent)
+bool
+Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
 {
 #if IPFW_TRANSPARENT
-    struct addrinfo *lookup = NULL;
-
-    dst.GetAddrInfo(lookup,AF_INET);
-
-    /** \par
-     * Try lookup for IPFW interception. */
-    if ( getsockname(fd, lookup->ai_addr, &lookup->ai_addrlen) != 0 ) {
-        if ( !silent ) {
-            debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror());
-            last_reported = squid_curtime;
-        }
-    } else {
-        dst = *lookup;
-    }
-
-    Address::FreeAddrInfo(lookup);
-
-    if (me != dst) {
-        debugs(89, 5, HERE << "address NAT: me= " << me << ", dst= " << dst);
-        return 0;
-    }
-
-    debugs(89, 9, HERE << "address: me= " << me << ", dst= " << dst);
+    /* 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
-    return -1;
 }
 
-int
-Ip::Intercept::IpfInterception(int fd, const Ip::Address &me, Ip::Address &client, Ip::Address &dst, int silent)
+bool
+Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
 {
 #if IPF_TRANSPARENT  /* --enable-ipf-transparent */
 
-#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
-    struct ipfobj obj;
-#else
-    static int siocgnatl_cmd = SIOCGNATL & 0xff;
-#endif
     struct natlookup natLookup;
     static int natfd = -1;
     int x;
 
-#if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
-
-    obj.ipfo_rev = IPFILTER_VERSION;
-    obj.ipfo_size = sizeof(natLookup);
-    obj.ipfo_ptr = &natLookup;
-    obj.ipfo_type = IPFOBJ_NATLOOKUP;
-    obj.ipfo_offset = 0;
-#endif
-
-    natLookup.nl_inport = htons(me.GetPort());
-    natLookup.nl_outport = htons(dst.GetPort());
-    me.GetInAddr(natLookup.nl_inip);
-    dst.GetInAddr(natLookup.nl_outip);
+    // all fields must be set to 0
+    memset(&natLookup, 0, sizeof(natLookup));
+    // for NAT lookup set local and remote IP:port's
+    natLookup.nl_inport = htons(newConn->local.GetPort());
+    newConn->local.GetInAddr(natLookup.nl_inip);
+    natLookup.nl_outport = htons(newConn->remote.GetPort());
+    newConn->remote.GetInAddr(natLookup.nl_outip);
+    // ... and the TCP flag
     natLookup.nl_flags = IPN_TCP;
 
     if (natfd < 0) {
@@ -236,13 +213,20 @@ Ip::Intercept::IpfInterception(int fd, const Ip::Address &me, Ip::Address &clien
 
     if (natfd < 0) {
         if (!silent) {
-            debugs(89, DBG_IMPORTANT, HERE << "NAT open failed: " << xstrerror());
-            last_reported = squid_curtime;
-            return -1;
+            debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
+            lastReported_ = squid_curtime;
+            return false;
         }
     }
 
 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
+    struct ipfobj obj;
+    memset(&obj, 0, sizeof(obj));
+    obj.ipfo_rev = IPFILTER_VERSION;
+    obj.ipfo_size = sizeof(natLookup);
+    obj.ipfo_ptr = &natLookup;
+    obj.ipfo_type = IPFOBJ_NATLOOKUP;
+
     x = ioctl(natfd, SIOCGNATL, &obj);
 #else
     /*
@@ -252,6 +236,7 @@ Ip::Intercept::IpfInterception(int fd, const Ip::Address &me, Ip::Address &clien
     * put something in configure and use ifdefs here, but
     * this seems simpler.
     */
+    static int siocgnatl_cmd = SIOCGNATL & 0xff;
     if (63 == siocgnatl_cmd) {
         struct natlookup *nlp = &natLookup;
         x = ioctl(natfd, SIOCGNATL, &nlp);
@@ -263,37 +248,44 @@ Ip::Intercept::IpfInterception(int fd, const Ip::Address &me, Ip::Address &clien
     if (x < 0) {
         if (errno != ESRCH) {
             if (!silent) {
-                debugs(89, DBG_IMPORTANT, HERE << "NAT lookup failed: ioctl(SIOCGNATL)");
-                last_reported = squid_curtime;
+                debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
+                lastReported_ = squid_curtime;
             }
 
             close(natfd);
             natfd = -1;
         }
 
-        return -1;
+        debugs(89, 9, HERE << "address: " << newConn);
+        return false;
     } else {
-        if (client != natLookup.nl_realip) {
-            client = natLookup.nl_realip;
-            client.SetPort(ntohs(natLookup.nl_realport));
-        }
-        // else. we already copied it.
-
-        debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
-        return 0;
+        newConn->local = natLookup.nl_realip;
+        newConn->local.SetPort(ntohs(natLookup.nl_realport));
+        debugs(89, 5, HERE << "address NAT: " << newConn);
+        return true;
     }
 
-    debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
-
 #endif /* --enable-ipf-transparent */
-    return -1;
+    return false;
 }
 
-int
-Ip::Intercept::PfInterception(int fd, const Ip::Address &me, Ip::Address &client, Ip::Address &dst, int silent)
+bool
+Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
 {
 #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.
+     */
+    debugs(89, 5, HERE << "address NAT divert-to: " << newConn);
+    return true;
+
+#else /* USE_NAT_DEVPF / --with-nat-devpf */
+
     struct pfioc_natlook nl;
     static int pffd = -1;
 
@@ -303,17 +295,17 @@ Ip::Intercept::PfInterception(int fd, const Ip::Address &me, Ip::Address &client
     if (pffd < 0) {
         if (!silent) {
             debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
-            last_reported = squid_curtime;
+            lastReported_ = squid_curtime;
         }
-        return -1;
+        return false;
     }
 
     memset(&nl, 0, sizeof(struct pfioc_natlook));
-    dst.GetInAddr(nl.saddr.v4);
-    nl.sport = htons(dst.GetPort());
+    newConn->remote.GetInAddr(nl.saddr.v4);
+    nl.sport = htons(newConn->remote.GetPort());
 
-    me.GetInAddr(nl.daddr.v4);
-    nl.dport = htons(me.GetPort());
+    newConn->local.GetInAddr(nl.daddr.v4);
+    nl.dport = htons(newConn->local.GetPort());
 
     nl.af = AF_INET;
     nl.proto = IPPROTO_TCP;
@@ -323,31 +315,26 @@ Ip::Intercept::PfInterception(int fd, const Ip::Address &me, Ip::Address &client
         if (errno != ENOENT) {
             if (!silent) {
                 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
-                last_reported = squid_curtime;
+                lastReported_ = squid_curtime;
             }
             close(pffd);
             pffd = -1;
         }
+        debugs(89, 9, HERE << "address: " << newConn);
+        return false;
     } else {
-        int natted = (client != nl.rdaddr.v4);
-        client = nl.rdaddr.v4;
-        client.SetPort(ntohs(nl.rdport));
-
-        if (natted) {
-            debugs(89, 5, HERE << "address NAT: me= " << me << ", client= " << client << ", dst= " << dst);
-            return 0;
-        }
+        newConn->local = nl.rdaddr.v4;
+        newConn->local.SetPort(ntohs(nl.rdport));
+        debugs(89, 5, HERE << "address NAT: " << newConn);
+        return true;
     }
-
-    debugs(89, 9, HERE << "address: me= " << me << ", client= " << client << ", dst= " << dst);
-
+#endif /* --with-nat-devpf */
 #endif /* --enable-pf-transparent */
-    return -1;
+    return false;
 }
 
-
-int
-Ip::Intercept::NatLookup(int fd, const Ip::Address &me, const Ip::Address &peer, Ip::Address &client, Ip::Address &dst)
+bool
+Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
 {
     /* --enable-linux-netfilter    */
     /* --enable-ipfw-transparent   */
@@ -355,57 +342,74 @@ Ip::Intercept::NatLookup(int fd, const Ip::Address &me, const Ip::Address &peer,
     /* --enable-pf-transparent     */
 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
 
-    client = me;
-    dst = peer;
-
 #if 0
     // Crop interception errors down to one per minute.
-    int silent = (squid_curtime - last_reported > 60 ? 0 : 1);
+    int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
 #else
     // Show all interception errors.
     int silent = 0;
 #endif
 
-    debugs(89, 5, HERE << "address BEGIN: me= " << me << ", client= " << client <<
-           ", dst= " << dst << ", peer= " << peer);
+    debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
+
+    newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
 
     /* NP: try TPROXY first, its much quieter than NAT when non-matching */
-    if (transparent_active) {
-        if ( NetfilterTransparent(fd, me, dst, silent) == 0) return 0;
+    if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
+        if (TproxyTransparent(newConn, silent)) return true;
     }
 
     /* NAT is only available in IPv4 */
-    if ( !me.IsIPv4()   ) return -1;
-    if ( !peer.IsIPv4() ) return -1;
+    if ( !newConn->local.IsIPv4()  ) return false;
+    if ( !newConn->remote.IsIPv4() ) return false;
 
-    if (intercept_active) {
+    if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
         /* NAT methods that use sock-opts to return client address */
-        if ( NetfilterInterception(fd, me, client, silent) == 0) return 0;
-        if ( IpfwInterception(fd, me, client, silent) == 0) return 0;
+        if (NetfilterInterception(newConn, silent)) return true;
+        if (IpfwInterception(newConn, silent)) return true;
 
         /* NAT methods that use ioctl to return client address AND destination address */
-        if ( PfInterception(fd, me, client, dst, silent) == 0) return 0;
-        if ( IpfInterception(fd, me, client, dst, silent) == 0) return 0;
+        if (PfInterception(newConn, silent)) return true;
+        if (IpfInterception(newConn, silent)) return true;
     }
 
 #else /* none of the transparent options configured */
     debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
 #endif
 
-    return -1;
+    return false;
 }
 
 bool
 Ip::Intercept::ProbeForTproxy(Ip::Address &test)
 {
-    debugs(3, 3, "Detect TPROXY support on port " << test);
+    bool doneSuid = false;
+
+#if _SQUID_LINUX_ && defined(IP_TRANSPARENT) // Linux
+# define soLevel SOL_IP
+# define soFlag  IP_TRANSPARENT
 
-#if defined(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;
     int tmp_sock = -1;
 
-#if USE_IPV6
     /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
     if (test.IsIPv6()) {
         debugs(3, 3, "...Probing for IPv6 TPROXY support.");
@@ -416,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) {
@@ -431,9 +437,10 @@ 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;
     }
-#endif
 
     /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
     if (test.IsIPv4()) {
@@ -445,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) {
@@ -457,8 +466,11 @@ Ip::Intercept::ProbeForTproxy(Ip::Address &test)
         }
     }
 
-#else /* undefined IP_TRANSPARENT */
-    debugs(3, 3, "setsockopt(IP_TRANSPARENT) not supported on this platform. Disabling TPROXYv4.");
+#else
+    debugs(3, 3, "TPROXY setsockopt() not supported on this platform. Disabling TPROXY.");
+
 #endif
+    if (doneSuid)
+        leave_suid();
     return false;
 }