#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
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); });
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 */
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);
}
}
--- /dev/null
+/*
+ * 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";
+}
+
--- /dev/null
+/*
+ * 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 */
+
Asn.h \
ConnectionsEncrypted.cc \
ConnectionsEncrypted.h \
+ ConnMark.cc \
+ ConnMark.h \
DestinationAsn.h \
DestinationDomain.cc \
DestinationDomain.h \
# 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 ...
return;
}
+ newConnDetails->nfmark = Ip::Qos::getNfmarkFromConnection(newConnDetails, Ip::Qos::dirAccepted);
+
debugs(5, 5, HERE << "Listener: " << conn <<
" accepted new connection " << newConnDetails <<
" handler Subscription: " << theCallSub);
#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
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.
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
/**
* 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
/**
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()) {