]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Added clientside_mark ACL for checking CONNMARK (#111)
authorAndrey <rybakovandrey85@gmail.com>
Tue, 16 Jan 2018 23:28:59 +0000 (02:28 +0300)
committerAlex Rousskov <rousskov@measurement-factory.com>
Tue, 16 Jan 2018 23:28:59 +0000 (16:28 -0700)
Matches CONNMARK of accepted connections. Takes into account
clientside_mark and qos_flows mark changes (because Squid-set marks are
cached by Squid in conn->nfmark). Ignores 3rd-party marks set after
Squid has accepted the connection from a client (because Squid never
re-queries the connection to update/sync conn->nfmark).

Also added a debugs()-friendly API to print hex values.

src/AclRegs.cc
src/Debug.h
src/FwdState.cc
src/acl/ConnMark.cc [new file with mode: 0644]
src/acl/ConnMark.h [new file with mode: 0644]
src/acl/Makefile.am
src/cf.data.pre
src/comm/TcpAcceptor.cc
src/ip/QosConfig.cc
src/ip/QosConfig.h
src/tools.cc

index 8479d27ed4a07e0b7daf9b90d784bb2c9b346678..c9ca9711686e21489120fde18e0404f7999fe964 100644 (file)
@@ -33,6 +33,9 @@
 #include "acl/DestinationDomain.h"
 #include "acl/DestinationIp.h"
 #include "acl/DomainData.h"
+#if USE_LIBNETFILTERCONNTRACK
+#include "acl/ConnMark.h"
+#endif
 #if USE_AUTH
 #include "acl/ExtUser.h"
 #endif
@@ -158,6 +161,10 @@ Acl::Init()
     RegisterMaker("has", [](TypeName name)->ACL* {return new ACLStrategised<ACLChecklist *>(new ACLHasComponentData, new ACLHasComponentStrategy, name); });
     RegisterMaker("transaction_initiator", [](TypeName name)->ACL* {return new TransactionInitiator(name);});
 
+#if USE_LIBNETFILTERCONNTRACK
+    RegisterMaker("clientside_mark", [](TypeName name)->ACL* { return new Acl::ConnMark; });
+#endif
+
 #if USE_OPENSSL
     RegisterMaker("ssl_error", [](TypeName name)->ACL* { return new ACLStrategised<const Security::CertErrors *>(new ACLSslErrorData, new ACLSslErrorStrategy, name); });
     RegisterMaker("user_cert", [](TypeName name)->ACL* { return new ACLStrategised<X509*>(new ACLCertificateData(Ssl::GetX509UserAttribute, "*"), new ACLCertificateStrategy, name); });
index e1186f3f55b38122c6036134ce0e7fdd42b8e0e8..3fb9003253243d037083245f28b2d7dbf272ae6b 100644 (file)
@@ -244,5 +244,28 @@ operator <<(std::ostream &os, const RawPointerT<Pointer> &pd)
         return os << "[nil]";
 }
 
+/// std::ostream manipulator to print integers as hex numbers prefixed by 0x
+template <class Integer>
+class AsHex
+{
+public:
+    explicit AsHex(const Integer n): raw(n) {}
+    Integer raw; ///< the integer to print
+};
+
+template <class Integer>
+inline std::ostream &
+operator <<(std::ostream &os, const AsHex<Integer> number)
+{
+    const auto oldFlags = os.flags();
+    os << std::hex << std::showbase << number.raw;
+    os.setf(oldFlags);
+    return os;
+}
+
+/// a helper to ease AsHex object creation
+template <class Integer>
+inline AsHex<Integer> asHex(const Integer n) { return AsHex<Integer>(n); }
+
 #endif /* SQUID_DEBUG_H */
 
index 51bfc168a95753e0c35dc9c4f81ad2ada567527f..6cd0a8bc5f7e629448ada24393657fb04c173667 100644 (file)
@@ -994,8 +994,8 @@ FwdState::dispatch()
     if (Ip::Qos::TheConfig.isHitNfmarkActive()) {
         if (Comm::IsConnOpen(clientConn) && Comm::IsConnOpen(serverConnection())) {
             fde * clientFde = &fd_table[clientConn->fd]; // XXX: move the fd_table access into Ip::Qos
-            /* Get the netfilter mark for the connection */
-            Ip::Qos::getNfmarkFromServer(serverConnection(), clientFde);
+            /* Get the netfilter CONNMARK */
+            clientFde->nfmarkFromServer = Ip::Qos::getNfmarkFromConnection(serverConnection(), Ip::Qos::dirOpened);
         }
     }
 
diff --git a/src/acl/ConnMark.cc b/src/acl/ConnMark.cc
new file mode 100644 (file)
index 0000000..5a62e6f
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+/* DEBUG: section 28    Access Control */
+
+#include "squid.h"
+#include "acl/ConnMark.h"
+#include "acl/FilledChecklist.h"
+#include "client_side.h"
+#include "Debug.h"
+#include "http/Stream.h"
+#include "sbuf/Stream.h"
+
+bool
+Acl::ConnMark::empty() const
+{
+    return false;
+}
+
+static std::ostream &
+operator <<(std::ostream &os, const Acl::ConnMark::ConnMarkQuery connmark)
+{
+    os << asHex(connmark.first);
+    if (connmark.second != 0xffffffff) {
+        os << '/' << asHex(connmark.second);
+    }
+    return os;
+}
+
+nfmark_t
+Acl::ConnMark::getNumber(Parser::Tokenizer &tokenizer, const SBuf &token) const
+{
+    int64_t number;
+    if (!tokenizer.int64(number, 0, false)) {
+        throw TexcHere(ToSBuf("acl ", typeString(), ": invalid value '", tokenizer.buf(), "' in ", token));
+    }
+
+    if (number > std::numeric_limits<nfmark_t>::max()) {
+        throw TexcHere(ToSBuf("acl ", typeString(), ": number ", number, " in ", token, " is too big"));
+    }
+    return static_cast<nfmark_t>(number);
+}
+
+void
+Acl::ConnMark::parse()
+{
+    while (const char *t = ConfigParser::strtokFile()) {
+        SBuf token(t);
+        Parser::Tokenizer tokenizer(token);
+
+        const auto mark = getNumber(tokenizer, token);
+        const auto mask = tokenizer.skip('/') ? getNumber(tokenizer, token) : 0xffffffff;
+
+        if (!tokenizer.atEnd()) {
+            throw TexcHere(ToSBuf("acl ", typeString(), ": trailing garbage in ", token));
+        }
+
+        const ConnMarkQuery connmark(mark, mask);
+        marks.push_back(connmark);
+        debugs(28, 7, "added " << connmark);
+    }
+
+    if (marks.empty()) {
+        throw TexcHere(ToSBuf("acl ", typeString(), " requires at least one mark"));
+    }
+}
+
+int
+Acl::ConnMark::match(ACLChecklist *cl)
+{
+    const auto *checklist = Filled(cl);
+    const auto connmark = checklist->conn()->clientConnection->nfmark;
+
+    for (const auto &m : marks) {
+        if ((connmark & m.second) == m.first) {
+            debugs(28, 5, "found " << m << " matching " << asHex(connmark));
+            return 1;
+        }
+        debugs(28, 7, "skipped " << m << " mismatching " << asHex(connmark));
+    }
+    return 0;
+}
+
+SBufList
+Acl::ConnMark::dump() const
+{
+    SBufList sl;
+    for (const auto &m : marks) {
+        sl.push_back(ToSBuf(m));
+    }
+    return sl;
+}
+
+char const *
+Acl::ConnMark::typeString() const
+{
+    return "clientside_mark";
+}
+
diff --git a/src/acl/ConnMark.h b/src/acl/ConnMark.h
new file mode 100644 (file)
index 0000000..29ffbf8
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 1996-2018 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_ACLCONNMARK_H
+#define SQUID_ACLCONNMARK_H
+
+#include "acl/Acl.h"
+#include "ip/forward.h"
+#include "parser/Tokenizer.h"
+
+#include <vector>
+
+namespace Acl {
+
+class ConnMark : public ACL
+{
+    MEMPROXY_CLASS(ConnMark);
+
+public:
+    /* ACL API */
+    virtual char const *typeString() const override;
+    virtual void parse() override;
+    virtual int match(ACLChecklist *checklist) override;
+    virtual SBufList dump() const override;
+    virtual bool empty() const override;
+
+    /// a mark/mask pair for matching CONNMARKs
+    typedef std::pair<nfmark_t, nfmark_t> ConnMarkQuery;
+
+private:
+    nfmark_t getNumber(Parser::Tokenizer &tokenizer, const SBuf &token) const;
+    std::vector<ConnMarkQuery> marks; ///< mark/mask pairs in configured order
+};
+
+} // namespace Acl
+
+#endif /* SQUID_ACLCONNMARK_H */
+
index cbbad58cd109806b19af313d2241972ee4c62a3f..4c3c480203c75a195ed58b8e7df624dacfab79ef 100644 (file)
@@ -67,6 +67,8 @@ libacls_la_SOURCES = \
        Asn.h \
        ConnectionsEncrypted.cc \
        ConnectionsEncrypted.h \
+       ConnMark.cc \
+       ConnMark.h \
        DestinationAsn.h \
        DestinationDomain.cc \
        DestinationDomain.h \
index 1706dbec07a7132d83099702837592b75fd9d4c2..fcb06b09a97bba69064cbe79c0e3aa187262672d 100644 (file)
@@ -1043,6 +1043,23 @@ DOC_START
          # NOTE 2: IPv6 protocol does not contain ARP. MAC/EUI is either
          # encoded directly in the IPv6 address or not available.
 
+       acl aclname clientside_mark mark[/mask] ...
+         # matches CONNMARK of an accepted connection [fast]
+         #
+         # mark and mask are unsigned integers (hex, octal, or decimal).
+         # If multiple marks are given, then the ACL matches if at least
+         # one mark matches.
+         #
+         # Uses netfilter-conntrack library.
+         # Requires building Squid with --enable-linux-netfilter.
+         #
+         # The client, various intermediaries, and Squid itself may set
+         # CONNMARK at various times. The last CONNMARK set wins. This ACL
+         # checks the mark present on an accepted connection or set by
+         # Squid afterwards, depending on the ACL check timing. This ACL
+         # effectively ignores any mark set by other agents after Squid has
+         # accepted the connection.
+
        acl aclname srcdomain   .foo.com ...
          # reverse lookup, from client IP [slow]
        acl aclname dstdomain [-n] .foo.com ...
index 8e18d1d5eb5d912023f7218ff858239da579a9ad..bfef5cb153031b6181410feaf9c3ab26785df624 100644 (file)
@@ -302,6 +302,8 @@ Comm::TcpAcceptor::acceptOne()
         return;
     }
 
+    newConnDetails->nfmark = Ip::Qos::getNfmarkFromConnection(newConnDetails, Ip::Qos::dirAccepted);
+
     debugs(5, 5, HERE << "Listener: " << conn <<
            " accepted new connection " << newConnDetails <<
            " handler Subscription: " << theCallSub);
index 390b65827083f697fd61764006d7df282e2c1976..b0d1c07f4489a65d3fdfb097eda3c507ac7eec9a 100644 (file)
@@ -77,71 +77,74 @@ Ip::Qos::getTosFromServer(const Comm::ConnectionPointer &server, fde *clientFde)
 #endif
 }
 
-void Ip::Qos::getNfmarkFromServer(const Comm::ConnectionPointer &server, const fde *clientFde)
+nfmark_t
+Ip::Qos::getNfmarkFromConnection(const Comm::ConnectionPointer &conn, const Ip::Qos::ConnectionDirection connDir)
 {
+    nfmark_t mark = 0;
 #if USE_LIBNETFILTERCONNTRACK
     /* Allocate a new conntrack */
     if (struct nf_conntrack *ct = nfct_new()) {
-
         /* Prepare data needed to find the connection in the conntrack table.
          * We need the local and remote IP address, and the local and remote
          * port numbers.
          */
+        const auto src = (connDir == Ip::Qos::dirAccepted) ? conn->remote : conn->local;
+        const auto dst = (connDir == Ip::Qos::dirAccepted) ? conn->local : conn->remote;
 
-        if (Ip::EnableIpv6 && server->local.isIPv6()) {
+        if (Ip::EnableIpv6 && src.isIPv6()) {
             nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6);
-            struct in6_addr serv_fde_remote_ip6;
-            server->remote.getInAddr(serv_fde_remote_ip6);
-            nfct_set_attr(ct, ATTR_IPV6_DST, serv_fde_remote_ip6.s6_addr);
-            struct in6_addr serv_fde_local_ip6;
-            server->local.getInAddr(serv_fde_local_ip6);
-            nfct_set_attr(ct, ATTR_IPV6_SRC, serv_fde_local_ip6.s6_addr);
+            struct in6_addr conn_fde_dst_ip6;
+            dst.getInAddr(conn_fde_dst_ip6);
+            nfct_set_attr(ct, ATTR_ORIG_IPV6_DST, conn_fde_dst_ip6.s6_addr);
+            struct in6_addr conn_fde_src_ip6;
+            src.getInAddr(conn_fde_src_ip6);
+            nfct_set_attr(ct, ATTR_ORIG_IPV6_SRC, conn_fde_src_ip6.s6_addr);
         } else {
             nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET);
-            struct in_addr serv_fde_remote_ip;
-            server->remote.getInAddr(serv_fde_remote_ip);
-            nfct_set_attr_u32(ct, ATTR_IPV4_DST, serv_fde_remote_ip.s_addr);
-            struct in_addr serv_fde_local_ip;
-            server->local.getInAddr(serv_fde_local_ip);
-            nfct_set_attr_u32(ct, ATTR_IPV4_SRC, serv_fde_local_ip.s_addr);
+            struct in_addr conn_fde_dst_ip;
+            dst.getInAddr(conn_fde_dst_ip);
+            nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_DST, conn_fde_dst_ip.s_addr);
+            struct in_addr conn_fde_src_ip;
+            src.getInAddr(conn_fde_src_ip);
+            nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_SRC, conn_fde_src_ip.s_addr);
         }
 
         nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_TCP);
-        nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(server->remote.port()));
-        nfct_set_attr_u16(ct, ATTR_PORT_SRC, htons(server->local.port()));
+        nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, htons(dst.port()));
+        nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, htons(src.port()));
 
         /* Open a handle to the conntrack */
         if (struct nfct_handle *h = nfct_open(CONNTRACK, 0)) {
             /* Register the callback. The callback function will record the mark value. */
-            nfct_callback_register(h, NFCT_T_ALL, getNfMarkCallback, (void *)clientFde);
+            nfct_callback_register(h, NFCT_T_ALL, getNfmarkCallback, static_cast<void *>(&mark));
             /* Query the conntrack table using the data previously set */
             int x = nfct_query(h, NFCT_Q_GET, ct);
             if (x == -1) {
-                debugs(17, 2, "QOS: Failed to retrieve connection mark: (" << x << ") " << strerror(errno)
-                       << " (Destination " << server->remote << ", source " << server->local << ")" );
+                const int xerrno = errno;
+                debugs(17, 2, "QOS: Failed to retrieve connection mark: (" << x << ") " << xstrerr(xerrno)
+                       << " (Destination " << dst << ", source " << src << ")" );
             }
             nfct_close(h);
         } else {
-            debugs(17, 2, "QOS: Failed to open conntrack handle for upstream netfilter mark retrieval.");
+            debugs(17, 2, "QOS: Failed to open conntrack handle for netfilter mark retrieval.");
         }
         nfct_destroy(ct);
-
     } else {
-        debugs(17, 2, "QOS: Failed to allocate new conntrack for upstream netfilter mark retrieval.");
+        debugs(17, 2, "QOS: Failed to allocate new conntrack for netfilter mark retrieval.");
     }
 #endif
+    return mark;
 }
 
 #if USE_LIBNETFILTERCONNTRACK
 int
-Ip::Qos::getNfMarkCallback(enum nf_conntrack_msg_type,
+Ip::Qos::getNfmarkCallback(enum nf_conntrack_msg_type,
                            struct nf_conntrack *ct,
-                           void *data)
+                           void *connmark)
 {
-    fde *clientFde = (fde *)data;
-    clientFde->nfmarkFromServer = nfct_get_attr_u32(ct, ATTR_MARK);
-    debugs(17, 3, "QOS: Retrieved connection mark value: " << clientFde->nfmarkFromServer);
-
+    auto *mark = static_cast<nfmark_t *>(connmark);
+    *mark = nfct_get_attr_u32(ct, ATTR_MARK);
+    debugs(17, 3, asHex(*mark));
     return NFCT_CB_CONTINUE;
 }
 #endif
index 6160c25a8e43bb1e2460e73e5f67d4227412e353..cab364147b876b923c3b42e3410a99dfa5a9fd02 100644 (file)
@@ -61,6 +61,12 @@ namespace Ip
 namespace Qos
 {
 
+/// Possible Squid roles in connection handling
+enum ConnectionDirection {
+    dirAccepted, ///< accepted (from a client by Squid)
+    dirOpened ///< opened (by Squid to an origin server or peer)
+};
+
 /**
 * Function to retrieve the TOS value of the inbound packet.
 * Called by FwdState::dispatch if QOS options are enabled.
@@ -71,13 +77,14 @@ namespace Qos
 void getTosFromServer(const Comm::ConnectionPointer &server, fde *clientFde);
 
 /**
-* Function to retrieve the netfilter mark value of the connection
-* to the upstream server. Called by FwdState::dispatch if QOS
-* options are enabled.
-* @param server    Server side descriptor of connection to get mark for
-* @param clientFde Pointer to client side fde instance to set nfmarkFromServer in
+* Function to retrieve the netfilter mark value of the connection.
+* Called by FwdState::dispatch if QOS options are enabled or by
+* Comm::TcpAcceptor::acceptOne
+*
+* @param conn    Pointer to connection to get mark for
+* @param connDir Specifies connection type (incoming or outgoing)
 */
-void getNfmarkFromServer(const Comm::ConnectionPointer &server, const fde *clientFde);
+nfmark_t getNfmarkFromConnection(const Comm::ConnectionPointer &conn, const ConnectionDirection connDir);
 
 #if USE_LIBNETFILTERCONNTRACK
 /**
@@ -87,9 +94,9 @@ void getNfmarkFromServer(const Comm::ConnectionPointer &server, const fde *clien
 * nfct_callback_register is used to register this function.
 * @param nf_conntrack_msg_type Type of conntrack message
 * @param nf_conntrack Pointer to the conntrack structure
-* @param clientFde Pointer to client side fde instance to set nfmarkFromServer in
+* @param mark Pointer to nfmark_t mark
 */
-int getNfMarkCallback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *clientFde);
+int getNfmarkCallback(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *mark);
 #endif
 
 /**
index ce7bd1e1d68fa053bc19f825cf7e5f104cd20a40..daf057528b87e51c6da06e1a4c90b5e4d91cca95 100644 (file)
@@ -1110,6 +1110,10 @@ restoreCapabilities(bool keep)
         cap_list[ncaps] = CAP_NET_BIND_SERVICE;
         ++ncaps;
         if (Ip::Interceptor.TransparentActive() ||
+#if USE_LIBNETFILTERCONNTRACK
+                // netfilter_conntrack requires CAP_NET_ADMIN to get client's CONNMARK
+                Ip::Interceptor.InterceptActive() ||
+#endif
                 Ip::Qos::TheConfig.isHitNfmarkActive() ||
                 Ip::Qos::TheConfig.isAclNfmarkActive() ||
                 Ip::Qos::TheConfig.isAclTosActive()) {