From 653d992747bfda4f6eb757354c1cc674492a21de Mon Sep 17 00:00:00 2001 From: Andrey Date: Wed, 17 Jan 2018 02:28:59 +0300 Subject: [PATCH] Added clientside_mark ACL for checking CONNMARK (#111) 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 | 7 +++ src/Debug.h | 23 +++++++++ src/FwdState.cc | 4 +- src/acl/ConnMark.cc | 104 ++++++++++++++++++++++++++++++++++++++++ src/acl/ConnMark.h | 43 +++++++++++++++++ src/acl/Makefile.am | 2 + src/cf.data.pre | 17 +++++++ src/comm/TcpAcceptor.cc | 2 + src/ip/QosConfig.cc | 61 ++++++++++++----------- src/ip/QosConfig.h | 23 +++++---- src/tools.cc | 4 ++ 11 files changed, 251 insertions(+), 39 deletions(-) create mode 100644 src/acl/ConnMark.cc create mode 100644 src/acl/ConnMark.h diff --git a/src/AclRegs.cc b/src/AclRegs.cc index 8479d27ed4..c9ca971168 100644 --- a/src/AclRegs.cc +++ b/src/AclRegs.cc @@ -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(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(new ACLSslErrorData, new ACLSslErrorStrategy, name); }); RegisterMaker("user_cert", [](TypeName name)->ACL* { return new ACLStrategised(new ACLCertificateData(Ssl::GetX509UserAttribute, "*"), new ACLCertificateStrategy, name); }); diff --git a/src/Debug.h b/src/Debug.h index e1186f3f55..3fb9003253 100644 --- a/src/Debug.h +++ b/src/Debug.h @@ -244,5 +244,28 @@ operator <<(std::ostream &os, const RawPointerT &pd) return os << "[nil]"; } +/// std::ostream manipulator to print integers as hex numbers prefixed by 0x +template +class AsHex +{ +public: + explicit AsHex(const Integer n): raw(n) {} + Integer raw; ///< the integer to print +}; + +template +inline std::ostream & +operator <<(std::ostream &os, const AsHex 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 +inline AsHex asHex(const Integer n) { return AsHex(n); } + #endif /* SQUID_DEBUG_H */ diff --git a/src/FwdState.cc b/src/FwdState.cc index 51bfc168a9..6cd0a8bc5f 100644 --- a/src/FwdState.cc +++ b/src/FwdState.cc @@ -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 index 0000000000..5a62e6f36a --- /dev/null +++ b/src/acl/ConnMark.cc @@ -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::max()) { + throw TexcHere(ToSBuf("acl ", typeString(), ": number ", number, " in ", token, " is too big")); + } + return static_cast(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 index 0000000000..29ffbf8fb1 --- /dev/null +++ b/src/acl/ConnMark.h @@ -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 + +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 ConnMarkQuery; + +private: + nfmark_t getNumber(Parser::Tokenizer &tokenizer, const SBuf &token) const; + std::vector marks; ///< mark/mask pairs in configured order +}; + +} // namespace Acl + +#endif /* SQUID_ACLCONNMARK_H */ + diff --git a/src/acl/Makefile.am b/src/acl/Makefile.am index cbbad58cd1..4c3c480203 100644 --- a/src/acl/Makefile.am +++ b/src/acl/Makefile.am @@ -67,6 +67,8 @@ libacls_la_SOURCES = \ Asn.h \ ConnectionsEncrypted.cc \ ConnectionsEncrypted.h \ + ConnMark.cc \ + ConnMark.h \ DestinationAsn.h \ DestinationDomain.cc \ DestinationDomain.h \ diff --git a/src/cf.data.pre b/src/cf.data.pre index 1706dbec07..fcb06b09a9 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -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 ... diff --git a/src/comm/TcpAcceptor.cc b/src/comm/TcpAcceptor.cc index 8e18d1d5eb..bfef5cb153 100644 --- a/src/comm/TcpAcceptor.cc +++ b/src/comm/TcpAcceptor.cc @@ -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); diff --git a/src/ip/QosConfig.cc b/src/ip/QosConfig.cc index 390b658270..b0d1c07f44 100644 --- a/src/ip/QosConfig.cc +++ b/src/ip/QosConfig.cc @@ -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(&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(connmark); + *mark = nfct_get_attr_u32(ct, ATTR_MARK); + debugs(17, 3, asHex(*mark)); return NFCT_CB_CONTINUE; } #endif diff --git a/src/ip/QosConfig.h b/src/ip/QosConfig.h index 6160c25a8e..cab364147b 100644 --- a/src/ip/QosConfig.h +++ b/src/ip/QosConfig.h @@ -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 /** diff --git a/src/tools.cc b/src/tools.cc index ce7bd1e1d6..daf057528b 100644 --- a/src/tools.cc +++ b/src/tools.cc @@ -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()) { -- 2.39.5