Please find attached the first version of the netfilter mark patch. I've not yet tested it extensively, but would welcome some initial feedback or comments.
My comments are: - The existing TOS patch cannot be disabled at runtime. As such, this mark patch cannot be either. Would it be preferable to only enable them both if the qos_flows config option is present? This would also have the advantage of being able to add CAP_NET_ADMIN as appropriate at runtime. - I have used sscanf instead of strtoul for both TOS and MARK in QosConfig.cc (sscanf doesn't auto-detect the format of unsigned long int). As a result, the tos variable could be changed to type char, which is what it should be in my opinion. Should I do this? - The code in forward.cc to obtain all the data needed to locate the connection information is messy. Is there a better way to achieve it? Conntrack needs to be passed local and remote port numbers and IP addresses. Is it too late to get this in v3.2? I will update the appropriate release-notes once I know. Thanks, Andy
# Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: a...@andybev.com-20100801233324-uoy4sp2e3uszw5qn # target_branch: file:///home/andrew/squid-repo/trunk/ # testament_sha1: 4bd1e0b517dc3322fa114f30fb26ff2483cb3410 # timestamp: 2010-08-02 00:34:42 +0100 # base_revision_id: squ...@treenet.co.nz-20100801134158-\ # vhc003yv3ikwao7e # # Begin patch === modified file 'configure.in' --- configure.in 2010-08-01 06:29:48 +0000 +++ configure.in 2010-08-01 20:09:13 +0000 @@ -1146,6 +1146,32 @@ fi AC_SUBST(SSLLIB) +dnl Allow user to specify libnetfilter_conntrack (needed for QOS netfilter marking) +AC_ARG_WITH(netfilter-conntrack, + AS_HELP_STRING([--with-netfilter-conntrack=PATH], + [Compile with the Netfilter conntrack libraries. The path to + the development libraries and headers + installation can be specified if outside of the + system standard directories]), [ +case "$with_netfilter_conntrack" in + no) + : # Nothing special to do here + ;; + yes) + AC_CHECK_LIB([netfilter_conntrack], [nfct_query],, + AC_MSG_ERROR([libnetfilter-conntrack library not found. Needed for netfilter-conntrack support]), + [-lnetfilter_conntrack]) + ;; + *) + if test ! -d $withval ; then + AC_MSG_ERROR([--with-netfilter-conntrack path does not point to a directory]) + fi + LDFLAGS="-L$with_netfilter_conntrack/lib $LDFLAGS" + CPPFLAGS="-I$with_netfilter_conntrack/include $CPPFLAGS" + with_netfilter_conntrack=yes + ;; + esac +]) AC_ARG_ENABLE(forw-via-db, AS_HELP_STRING([--enable-forw-via-db],[Enable Forw/Via database]), [ @@ -2061,6 +2087,15 @@ [Enable Zero Penalty Hit QOS. When set, Squid will alter the TOS field of HIT responses to help policing network traffic]) AC_MSG_NOTICE([ZPH QOS enabled: $enable_zph_qos]) +if test "$enable_zph_qos" = "yes" ; then + if test "$with_netfilter_conntrack" = "yes" ; then + SQUID_DEFINE_BOOL(USE_QOS_NFMARK,$with_netfilter_conntrack, + [Enable support for QOS netfilter packet marking]) + else + AC_MSG_WARN([--with-netfilter-conntrack not enabled. QOS features will not support Netfilter marking.]) + fi + AC_MSG_NOTICE([QOS netfilter marking enabled: $with_netfilter_conntrack]) +fi dnl --with-maxfd present for compatibility with Squid-2. dnl undocumented in ./configure --help to encourage using the Squid-3 directive. === modified file 'src/cf.data.pre' --- src/cf.data.pre 2010-07-29 13:04:44 +0000 +++ src/cf.data.pre 2010-08-01 20:09:13 +0000 @@ -1532,18 +1532,23 @@ LOC: Ip::Qos::TheConfig DOC_START Allows you to select a TOS/DSCP value to mark outgoing - connections with, based on where the reply was sourced. + connections with, based on where the reply was sourced. For + platforms using netfilter, allows you to set a netfilter mark + value instead of, or in addition to, a TOS value. TOS values really only have local significance - so you should know what you're specifying. For more information, see RFC2474, RFC2475, and RFC3260. The TOS/DSCP byte must be exactly that - octet value 0x00-0xFF. - Note that in practice often only values up to 0x3F are usable - as the two highest bits have been redefined for use by ECN - (RFC3168). - - This setting is configured by setting the source TOS values: + Note that in practice often only values up to 0x3F are usable as + the two highest bits have been redefined for use by ECN (RFC3168). + + Mark values can be any unsigned integer value (normally up to 0xFFFFFFFF) + + This setting is configured by setting the following values: + + tos|mark Whether to set TOS or netfilter mark values local-hit=0xFF Value to mark local cache hits. @@ -1551,23 +1556,26 @@ parent-hit=0xFF Value to mark hits from parent peers. - - NOTE: 'miss' preserve feature is only possible on Linux at this time. - - For the following to work correctly, you will need to patch your - linux kernel with the TOS preserving ZPH patch. - The kernel patch can be downloaded from http://zph.bratcheda.org + The following features are only possible on Linux at this time, and + in the case of TOS preservation require your kernel to be patch with + the TOS preserving ZPH patch, available from http://zph.bratcheda.org + No patch is needed to preserve the netfilter mark. disable-preserve-miss - By default, the existing TOS value of the response coming - from the remote server will be retained and masked with - miss-mark. This option disables that feature. + This option disables the preservation of the TOS or netfilter + mark. By default, the existing TOS or netfilter mark value of + the response coming from the remote server will be retained + and masked with miss-mark. + NOTE: in the case of a netfilter mark, the mark must be set on + the connection (using the CONNMARK target) not on the packet + (MARK target). miss-mask=0xFF - Allows you to mask certain bits in the TOS received from the - remote server, before copying the value to the TOS sent - towards clients. - Default: 0xFF (TOS from server is not changed). + Allows you to mask certain bits in the TOS or mark value + received from the remote server, before copying the value to + the TOS sent towards clients. + Default for tos: 0xFF (TOS from server is not changed). + Default for mark: 0xFFFFFFFF (mark from server is not changed). DOC_END === modified file 'src/client_side_reply.cc' --- src/client_side_reply.cc 2010-04-17 10:38:50 +0000 +++ src/client_side_reply.cc 2010-08-01 22:33:49 +0000 @@ -1672,6 +1672,12 @@ debugs(33, 2, "ZPH Local hit, TOS=" << Ip::Qos::TheConfig.tos_local_hit); comm_set_tos(http->getConn()->fd, Ip::Qos::TheConfig.tos_local_hit); } +#if USE_QOS_NFMARK + if (Ip::Qos::TheConfig.mark_local_hit) { + debugs(33, 2, "ZPH Local hit, Mark=" << Ip::Qos::TheConfig.mark_local_hit); + comm_set_mark(http->getConn()->fd, Ip::Qos::TheConfig.mark_local_hit); + } +#endif /* USE_QOS_NFMARK */ #endif /* USE_ZPH_QOS */ localTempBuffer.offset = reqofs; localTempBuffer.length = getNextNode()->readBuffer.length; @@ -1966,8 +1972,22 @@ debugs(33, 2, "ZPH: Preserving TOS on miss, TOS="<<tos); } comm_set_tos(fd,tos); +#if USE_QOS_NFMARK + unsigned int mark = 0; + if (Ip::Qos::TheConfig.mark_sibling_hit && http->request->hier.code==SIBLING_HIT ) { + mark = Ip::Qos::TheConfig.mark_sibling_hit; + debugs(33, 2, "ZPH: Sibling Peer hit with hier.code=" << http->request->hier.code << ", Mark=" << mark); + } else if (Ip::Qos::TheConfig.mark_parent_hit && http->request->hier.code==PARENT_HIT) { + mark = Ip::Qos::TheConfig.mark_parent_hit; + debugs(33, 2, "ZPH: Parent Peer hit with hier.code=" << http->request->hier.code << ", Mark=" << mark); + } else if (Ip::Qos::TheConfig.preserve_miss_tos) { + mark = fd_table[fd].upstreamMark & Ip::Qos::TheConfig.preserve_miss_mark_mask; + debugs(33, 2, "ZPH: Preserving mark on miss, Mark="<<mark); + } + comm_set_mark(fd,mark); +#endif /* USE_QOS_NFMARK */ } -#endif +#endif /* USE_ZPH_QOS */ /* We've got the final data to start pushing... */ flags.storelogiccomplete = 1; === modified file 'src/comm.cc' --- src/comm.cc 2010-07-28 12:39:35 +0000 +++ src/comm.cc 2010-08-01 08:41:08 +0000 @@ -646,6 +646,20 @@ #endif /* sockopt */ } +int +comm_set_mark(int fd, unsigned int mark) +{ +#ifdef SO_MARK + int x = setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(unsigned int)); + if (x < 0) + debugs(50, 1, "comm_set_mark: setsockopt(SO_MARK) on FD " << fd << ": " << xstrerror()); + return x; +#else + debugs(50, 0, "WARNING: setsockopt(SO_MARK) not supported on this platform"); + return -1; +#endif +} + /** * Set the socket IP_TRANSPARENT option for Linux TPROXY v4 support. */ === modified file 'src/comm.h' --- src/comm.h 2010-07-06 23:09:44 +0000 +++ src/comm.h 2010-08-01 08:41:08 +0000 @@ -77,6 +77,7 @@ SQUIDCEXTERN int comm_openex(int, int, Ip::Address &, int, unsigned char TOS, const char *); SQUIDCEXTERN u_short comm_local_port(int fd); SQUIDCEXTERN int comm_set_tos(int fd, int tos); +SQUIDCEXTERN int comm_set_mark(int fd, unsigned int tos); SQUIDCEXTERN void commSetSelect(int, unsigned int, PF *, void *, time_t); SQUIDCEXTERN void commResetSelect(int); === modified file 'src/fde.h' --- src/fde.h 2010-07-06 23:09:44 +0000 +++ src/fde.h 2010-08-01 08:41:08 +0000 @@ -111,6 +111,9 @@ #endif #if USE_ZPH_QOS unsigned char upstreamTOS; /* see FwdState::dispatch() */ +#if USE_QOS_NFMARK + unsigned int upstreamMark; /* see FwdState::dispatch() */ +#endif #endif private: === modified file 'src/forward.cc' --- src/forward.cc 2010-08-01 04:33:31 +0000 +++ src/forward.cc 2010-08-01 21:48:03 +0000 @@ -49,6 +49,10 @@ #include "icmp/net_db.h" #include "ip/Intercept.h" #include "ip/tools.h" +#if USE_QOS_NFMARK +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h> +#endif static PSC fwdStartCompleteWrapper; static PF fwdServerClosedWrapper; @@ -965,6 +969,24 @@ self = NULL; // refcounted } +#if USE_ZPH_QOS && USE_QOS_NFMARK && defined(_SQUID_LINUX_) +/* Callback function to mark connection once it's been found */ +int FwdState::getNfctMark(enum nf_conntrack_msg_type type, + struct nf_conntrack *ct, + void *data) +{ + unsigned int mark; + fde *clientFde; + + mark = nfct_get_attr_u32(ct, ATTR_MARK); + clientFde = (fde *)data; + clientFde->upstreamMark = mark; + debugs(17, 3, "ZPH: Retrieved connection mark value: " << mark); + + return NFCT_CB_CONTINUE; +} +#endif + void FwdState::dispatch() { @@ -991,14 +1013,90 @@ netdbPingSite(request->GetHost()); #if USE_ZPH_QOS && defined(_SQUID_LINUX_) - /* Bug 2537: This part of ZPH only applies to patched Linux kernels. */ + /* Bug 2537: The TOS forward part of ZPH only applies to patched Linux kernels. */ - /* Retrieves remote server TOS value, and stores it as part of the + /* Retrieves remote server TOS or MARK value, and stores it as part of the * original client request FD object. It is later used to forward - * remote server's TOS in the response to the client in case of a MISS. + * remote server's TOS/MARK in the response to the client in case of a MISS. */ fde * clientFde = &fd_table[client_fd]; +#if USE_QOS_NFMARK + fde * servFde = &fd_table[server_fd]; + if (clientFde && servFde) { + + /* Register nfct callback in order to retrieve connection mark */ + struct nfct_handle *h; + struct nf_conntrack *ct; + + ct = nfct_new(); + if (ct) { + + /* 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. + */ + + Ip::Address serv_fde_local_conn; + struct addrinfo *addr = NULL; + serv_fde_local_conn.InitAddrInfo(addr); + getsockname(server_fd, addr->ai_addr, &(addr->ai_addrlen)); + serv_fde_local_conn = *addr; + serv_fde_local_conn.FreeAddrInfo(addr); + serv_fde_local_conn.GetAddrInfo(addr); + + unsigned short serv_fde_local_port; + serv_fde_local_port = ((struct sockaddr_in*)addr->ai_addr)->sin_port; + struct in6_addr serv_fde_local_ip6; + struct in_addr serv_fde_local_ip; + + if (Ip::EnableIpv6 && serv_fde_local_conn.IsIPv6()) { + serv_fde_local_ip6 = ((struct sockaddr_in6*)addr->ai_addr)->sin6_addr; + nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6); + struct in6_addr serv_fde_remote_ip6; + inet_pton(AF_INET6,servFde->ipaddr,(struct in6_addr*)&serv_fde_remote_ip6); + nfct_set_attr(ct, ATTR_IPV6_DST, serv_fde_remote_ip6.s6_addr); + nfct_set_attr(ct, ATTR_IPV6_SRC, serv_fde_local_ip6.s6_addr); + } else { + serv_fde_local_ip = ((struct sockaddr_in*)addr->ai_addr)->sin_addr; + nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET); + nfct_set_attr_u32(ct, ATTR_IPV4_DST, inet_addr(servFde->ipaddr)); + nfct_set_attr_u32(ct, ATTR_IPV4_SRC, serv_fde_local_ip.s_addr); + } + + nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_TCP); + nfct_set_attr_u16(ct, ATTR_PORT_DST, htons(servFde->remote_port)); + nfct_set_attr_u16(ct, ATTR_PORT_SRC, serv_fde_local_port); + + h = nfct_open(CONNTRACK, 0); + + if (h) { + /* Register the callback. This will retrieve the mark value. */ + nfct_callback_register(h, NFCT_T_ALL, getNfctMark, (void *) clientFde); + int x = nfct_query(h, NFCT_Q_GET, ct); + if (x == -1) { + char str[INET_ADDRSTRLEN]; + if (Ip::EnableIpv6 && serv_fde_local_conn.IsIPv6()) { + inet_ntop(AF_INET6, &serv_fde_local_ip6, str, INET_ADDRSTRLEN); + } else { + inet_ntop(AF_INET, &serv_fde_local_ip, str, INET_ADDRSTRLEN); + } + debugs(17, 1, "Failed to retrieve connection mark: (" << x << ") " << strerror(errno) + << " (Dest IP:" << servFde->ipaddr << ", src IP:" << str << ", dest port:" + << servFde->remote_port << ", src port:" << ntohs(serv_fde_local_port) << ")" ); + } + + nfct_close(h); + + } else { + debugs(17, 1, "Failed to open handle on conntrack"); + } + + } else { + debugs(17, 1, "Failed to allocate new conntrack"); + } +#else if (clientFde) { +#endif int tos = 1; int tos_len = sizeof(tos); clientFde->upstreamTOS = 0; === modified file 'src/forward.h' --- src/forward.h 2010-05-02 19:32:42 +0000 +++ src/forward.h 2010-08-01 08:41:08 +0000 @@ -9,6 +9,10 @@ #include "comm.h" #include "hier_code.h" #include "ip/Address.h" +#if USE_QOS_NFMARK +#include <libnetfilter_conntrack/libnetfilter_conntrack.h> +#include <libnetfilter_conntrack/libnetfilter_conntrack_tcp.h> +#endif class FwdServer { @@ -33,6 +37,9 @@ void complete(); void handleUnregisteredServerEnd(); int reforward(); +#if USE_QOS_NFMARK + static int getNfctMark(enum nf_conntrack_msg_type type, struct nf_conntrack *ct, void *clientFde); +#endif bool reforwardableStatus(http_status s); void serverClosed(int fd); void connectStart(); === modified file 'src/ip/QosConfig.cc' --- src/ip/QosConfig.cc 2010-04-17 02:29:04 +0000 +++ src/ip/QosConfig.cc 2010-08-01 23:21:02 +0000 @@ -11,7 +11,12 @@ tos_sibling_hit(0), tos_parent_hit(0), preserve_miss_tos(1), - preserve_miss_tos_mask(255) + preserve_miss_tos_mask(255), + mark_local_hit(0), + mark_sibling_hit(0), + mark_parent_hit(0), + preserve_miss_mark(1), + preserve_miss_mark_mask(0xFFFFFFFF) { ; } @@ -19,22 +24,53 @@ void Ip::Qos::QosConfig::parseConfigLine() { - // %i honors 0 and 0x prefixes, which are important for things like umask + // strtoul honors 0 and 0x prefixes, which are important for things like umask /* parse options ... */ char *token; + /* Assume that the option is for 'tos' not 'mark' (for backwards compatability) */ + bool mark = false; + while ( (token = strtok(NULL, w_space)) ) { - - if (strncmp(token, "local-hit=",10) == 0) { - sscanf(&token[10], "%i", &tos_local_hit); + if (strncmp(token, "mark",4) == 0) { +#if USE_QOS_NFMARK + mark = true; +#else + debugs(3, 0, "WARNING: netfilter marking not enabled in this build"); +#endif + } else if (strncmp(token, "tos",3) == 0) { + mark = false; + } else if (strncmp(token, "local-hit=",10) == 0) { + if (mark) { + mark_local_hit = (unsigned int)strtoul(&token[10], NULL, 0); + } else { + tos_local_hit = (int)strtoul(&token[10], NULL, 0); + } } else if (strncmp(token, "sibling-hit=",12) == 0) { - sscanf(&token[12], "%i", &tos_sibling_hit); + if (mark) { + mark_sibling_hit = (unsigned int)strtoul(&token[10], NULL, 0); + } else { + tos_sibling_hit = (int)strtoul(&token[10], NULL, 0); + } } else if (strncmp(token, "parent-hit=",11) == 0) { - sscanf(&token[11], "%i", &tos_parent_hit); + if (mark) { + mark_parent_hit = (unsigned int)strtoul(&token[10], NULL, 0); + } else { + tos_parent_hit = (int)strtoul(&token[10], NULL, 0); + } } else if (strcmp(token, "disable-preserve-miss") == 0) { - preserve_miss_tos = 0; - preserve_miss_tos_mask = 0; - } else if (preserve_miss_tos && strncmp(token, "miss-mask=",10) == 0) { - sscanf(&token[10], "%i", &preserve_miss_tos_mask); + if (mark) { + preserve_miss_mark = 0; + preserve_miss_mark_mask = 0; + } else { + preserve_miss_tos = 0; + preserve_miss_tos_mask = 0; + } + } else if (strncmp(token, "miss-mask=",10) == 0) { + if (mark && preserve_miss_mark) { + preserve_miss_mark_mask = (unsigned int)strtoul(&token[10], NULL, 0); + } else if (preserve_miss_tos) { + preserve_miss_tos_mask = (int)strtoul(&token[10], NULL, 0); + } } } } @@ -52,24 +88,43 @@ p += strlen(name); if (tos_local_hit >0) { - snprintf(p, 15, " local-hit=%2x", tos_local_hit); + snprintf(p, 15, " (TOS)local-hit=%2x", tos_local_hit); p += 15; } - if (tos_sibling_hit >0) { - snprintf(p, 17, " sibling-hit=%2x", tos_sibling_hit); + snprintf(p, 17, " (TOS)sibling-hit=%2x", tos_sibling_hit); p += 17; } if (tos_parent_hit >0) { - snprintf(p, 16, " parent-hit=%2x", tos_parent_hit); + snprintf(p, 16, " (TOS)parent-hit=%2x", tos_parent_hit); p += 16; } - if (preserve_miss_tos != 0) { - snprintf(p, 22, " disable-preserve-miss"); + if (preserve_miss_tos == 0) { + snprintf(p, 22, " (TOS)disable-preserve-miss"); p += 22; } if (preserve_miss_tos && preserve_miss_tos_mask != 0) { - snprintf(p, 15, " miss-mask=%2x", preserve_miss_tos_mask); + snprintf(p, 15, " (TOS)miss-mask=%2x", preserve_miss_tos_mask); + p += 15; + } + if (mark_local_hit >0) { + snprintf(p, 15, " (mark)local-hit=%2x", tos_local_hit); + p += 15; + } + if (mark_sibling_hit >0) { + snprintf(p, 17, " (mark)sibling-hit=%2x", tos_sibling_hit); + p += 17; + } + if (mark_parent_hit >0) { + snprintf(p, 16, " (mark)parent-hit=%2x", tos_parent_hit); + p += 16; + } + if (preserve_miss_mark == 0) { + snprintf(p, 22, " (mark)disable-preserve-miss"); + p += 22; + } + if (preserve_miss_mark && preserve_miss_mark_mask != 0) { + snprintf(p, 15, " (mark)miss-mask=%2x", preserve_miss_mark_mask); p += 15; } snprintf(p, 1, "\n"); === modified file 'src/ip/QosConfig.h' --- src/ip/QosConfig.h 2010-04-18 00:13:00 +0000 +++ src/ip/QosConfig.h 2010-08-01 08:41:08 +0000 @@ -19,6 +19,11 @@ int tos_parent_hit; int preserve_miss_tos; int preserve_miss_tos_mask; + unsigned int mark_local_hit; + unsigned int mark_sibling_hit; + unsigned int mark_parent_hit; + int preserve_miss_mark; + unsigned int preserve_miss_mark_mask; public: QosConfig(); === modified file 'src/tools.cc' --- src/tools.cc 2010-07-25 08:10:12 +0000 +++ src/tools.cc 2010-08-01 08:41:08 +0000 @@ -1308,9 +1308,13 @@ cap_value_t cap_list[10]; cap_list[ncaps++] = CAP_NET_BIND_SERVICE; +#if USE_QOS_NFMARK && defined(_SQUID_LINUX_) + cap_list[ncaps++] = CAP_NET_ADMIN; +#else if (Ip::Interceptor.TransparentActive()) { cap_list[ncaps++] = CAP_NET_ADMIN; } +#endif cap_clear_flag(caps, CAP_EFFECTIVE); rc |= cap_set_flag(caps, CAP_EFFECTIVE, ncaps, cap_list, CAP_SET); # Begin bundle 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 KcKEhVYNszA=