1 /*
2  * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
3  *
4  * Squid software is distributed under GPLv2+ license and includes
5  * contributions from numerous individuals and organizations.
6  * Please see the COPYING and CONTRIBUTORS files for details.
7  */
8 
9 #include "squid.h"
10 
11 #include "acl/Gadgets.h"
12 #include "cache_cf.h"
13 #include "comm/Connection.h"
14 #include "compat/cmsg.h"
15 #include "ConfigParser.h"
16 #include "fde.h"
17 #include "globals.h"
18 #include "hier_code.h"
19 #include "ip/QosConfig.h"
20 #include "ip/tools.h"
21 #include "Parsing.h"
22 
23 #include <cerrno>
24 
25 CBDATA_CLASS_INIT(acl_tos);
26 
~acl_tos()27 acl_tos::~acl_tos()
28 {
29     aclDestroyAclList(&aclList);
30     delete next;
31 }
32 
33 CBDATA_CLASS_INIT(acl_nfmark);
34 
~acl_nfmark()35 acl_nfmark::~acl_nfmark()
36 {
37     aclDestroyAclList(&aclList);
38     delete next;
39 }
40 
41 void
getTosFromServer(const Comm::ConnectionPointer & server,fde * clientFde)42 Ip::Qos::getTosFromServer(const Comm::ConnectionPointer &server, fde *clientFde)
43 {
44 #if USE_QOS_TOS && _SQUID_LINUX_
45     /* Bug 2537: This part of ZPH only applies to patched Linux kernels. */
46     tos_t tos = 1;
47     int tos_len = sizeof(tos);
48     clientFde->tosFromServer = 0;
49     if (setsockopt(server->fd,SOL_IP,IP_RECVTOS,&tos,tos_len)==0) {
50         unsigned char buf[512];
51         int len = 512;
52         if (getsockopt(server->fd,SOL_IP,IP_PKTOPTIONS,buf,(socklen_t*)&len) == 0) {
53             /* Parse the PKTOPTIONS structure to locate the TOS data message
54              * prepared in the kernel by the ZPH incoming TCP TOS preserving
55              * patch.
56              */
57             unsigned char * pbuf = buf;
58             while (pbuf-buf < len) {
59                 struct cmsghdr *o = (struct cmsghdr*)pbuf;
60                 if (o->cmsg_len<=0)
61                     break;
62 
63                 if (o->cmsg_level == SOL_IP && o->cmsg_type == IP_TOS) {
64                     int *tmp = (int*)SQUID_CMSG_DATA(o);
65                     clientFde->tosFromServer = (tos_t)*tmp;
66                     break;
67                 }
68                 pbuf += CMSG_LEN(o->cmsg_len);
69             }
70         } else {
71             int xerrno = errno;
72             debugs(33, DBG_IMPORTANT, "QOS: error in getsockopt(IP_PKTOPTIONS) on " << server << " " << xstrerr(xerrno));
73         }
74     } else {
75         int xerrno = errno;
76         debugs(33, DBG_IMPORTANT, "QOS: error in setsockopt(IP_RECVTOS) on " << server << " " << xstrerr(xerrno));
77     }
78 #endif
79 }
80 
81 nfmark_t
getNfmarkFromConnection(const Comm::ConnectionPointer & conn,const Ip::Qos::ConnectionDirection connDir)82 Ip::Qos::getNfmarkFromConnection(const Comm::ConnectionPointer &conn, const Ip::Qos::ConnectionDirection connDir)
83 {
84     nfmark_t mark = 0;
85 #if USE_LIBNETFILTERCONNTRACK
86     /* Allocate a new conntrack */
87     if (struct nf_conntrack *ct = nfct_new()) {
88         /* Prepare data needed to find the connection in the conntrack table.
89          * We need the local and remote IP address, and the local and remote
90          * port numbers.
91          */
92         const auto src = (connDir == Ip::Qos::dirAccepted) ? conn->remote : conn->local;
93         const auto dst = (connDir == Ip::Qos::dirAccepted) ? conn->local : conn->remote;
94 
95         if (Ip::EnableIpv6 && src.isIPv6()) {
96             nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET6);
97             struct in6_addr conn_fde_dst_ip6;
98             dst.getInAddr(conn_fde_dst_ip6);
99             nfct_set_attr(ct, ATTR_ORIG_IPV6_DST, conn_fde_dst_ip6.s6_addr);
100             struct in6_addr conn_fde_src_ip6;
101             src.getInAddr(conn_fde_src_ip6);
102             nfct_set_attr(ct, ATTR_ORIG_IPV6_SRC, conn_fde_src_ip6.s6_addr);
103         } else {
104             nfct_set_attr_u8(ct, ATTR_L3PROTO, AF_INET);
105             struct in_addr conn_fde_dst_ip;
106             dst.getInAddr(conn_fde_dst_ip);
107             nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_DST, conn_fde_dst_ip.s_addr);
108             struct in_addr conn_fde_src_ip;
109             src.getInAddr(conn_fde_src_ip);
110             nfct_set_attr_u32(ct, ATTR_ORIG_IPV4_SRC, conn_fde_src_ip.s_addr);
111         }
112 
113         nfct_set_attr_u8(ct, ATTR_L4PROTO, IPPROTO_TCP);
114         nfct_set_attr_u16(ct, ATTR_ORIG_PORT_DST, htons(dst.port()));
115         nfct_set_attr_u16(ct, ATTR_ORIG_PORT_SRC, htons(src.port()));
116 
117         /* Open a handle to the conntrack */
118         if (struct nfct_handle *h = nfct_open(CONNTRACK, 0)) {
119             /* Register the callback. The callback function will record the mark value. */
120             nfct_callback_register(h, NFCT_T_ALL, getNfmarkCallback, static_cast<void *>(&mark));
121             /* Query the conntrack table using the data previously set */
122             int x = nfct_query(h, NFCT_Q_GET, ct);
123             if (x == -1) {
124                 const int xerrno = errno;
125                 debugs(17, 2, "QOS: Failed to retrieve connection mark: (" << x << ") " << xstrerr(xerrno)
126                        << " (Destination " << dst << ", source " << src << ")" );
127             }
128             nfct_close(h);
129         } else {
130             debugs(17, 2, "QOS: Failed to open conntrack handle for netfilter mark retrieval.");
131         }
132         nfct_destroy(ct);
133     } else {
134         debugs(17, 2, "QOS: Failed to allocate new conntrack for netfilter mark retrieval.");
135     }
136 #endif
137     return mark;
138 }
139 
140 #if USE_LIBNETFILTERCONNTRACK
141 int
getNfmarkCallback(enum nf_conntrack_msg_type,struct nf_conntrack * ct,void * connmark)142 Ip::Qos::getNfmarkCallback(enum nf_conntrack_msg_type,
143                            struct nf_conntrack *ct,
144                            void *connmark)
145 {
146     auto *mark = static_cast<nfmark_t *>(connmark);
147     *mark = nfct_get_attr_u32(ct, ATTR_MARK);
148     debugs(17, 3, asHex(*mark));
149     return NFCT_CB_CONTINUE;
150 }
151 #endif
152 
153 int
doTosLocalMiss(const Comm::ConnectionPointer & conn,const hier_code hierCode)154 Ip::Qos::doTosLocalMiss(const Comm::ConnectionPointer &conn, const hier_code hierCode)
155 {
156     tos_t tos = 0;
157     if (Ip::Qos::TheConfig.tosSiblingHit && hierCode==SIBLING_HIT) {
158         tos = Ip::Qos::TheConfig.tosSiblingHit;
159         debugs(33, 2, "QOS: Sibling Peer hit with hier code=" << hierCode << ", TOS=" << int(tos));
160     } else if (Ip::Qos::TheConfig.tosParentHit && hierCode==PARENT_HIT) {
161         tos = Ip::Qos::TheConfig.tosParentHit;
162         debugs(33, 2, "QOS: Parent Peer hit with hier code=" << hierCode << ", TOS=" << int(tos));
163     } else if (Ip::Qos::TheConfig.preserveMissTos) {
164         tos = fd_table[conn->fd].tosFromServer & Ip::Qos::TheConfig.preserveMissTosMask;
165         tos = (tos & ~Ip::Qos::TheConfig.tosMissMask) | (Ip::Qos::TheConfig.tosMiss & Ip::Qos::TheConfig.tosMissMask);
166         debugs(33, 2, "QOS: Preserving TOS on miss, TOS=" << int(tos));
167     } else if (Ip::Qos::TheConfig.tosMiss) {
168         tos = Ip::Qos::TheConfig.tosMiss & Ip::Qos::TheConfig.tosMissMask;
169         debugs(33, 2, "QOS: Cache miss, setting TOS=" << int(tos));
170     }
171     return setSockTos(conn, tos);
172 }
173 
174 int
doNfmarkLocalMiss(const Comm::ConnectionPointer & conn,const hier_code hierCode)175 Ip::Qos::doNfmarkLocalMiss(const Comm::ConnectionPointer &conn, const hier_code hierCode)
176 {
177     nfmark_t mark = 0;
178     if (Ip::Qos::TheConfig.markSiblingHit && hierCode==SIBLING_HIT) {
179         mark = Ip::Qos::TheConfig.markSiblingHit;
180         debugs(33, 2, "QOS: Sibling Peer hit with hier code=" << hierCode << ", Mark=" << mark);
181     } else if (Ip::Qos::TheConfig.markParentHit && hierCode==PARENT_HIT) {
182         mark = Ip::Qos::TheConfig.markParentHit;
183         debugs(33, 2, "QOS: Parent Peer hit with hier code=" << hierCode << ", Mark=" << mark);
184     } else if (Ip::Qos::TheConfig.preserveMissMark) {
185         mark = fd_table[conn->fd].nfmarkFromServer & Ip::Qos::TheConfig.preserveMissMarkMask;
186         mark = (mark & ~Ip::Qos::TheConfig.markMissMask) | (Ip::Qos::TheConfig.markMiss & Ip::Qos::TheConfig.markMissMask);
187         debugs(33, 2, "QOS: Preserving mark on miss, Mark=" << mark);
188     } else if (Ip::Qos::TheConfig.markMiss) {
189         mark = Ip::Qos::TheConfig.markMiss & Ip::Qos::TheConfig.markMissMask;
190         debugs(33, 2, "QOS: Cache miss, setting Mark=" << mark);
191     }
192     return setSockNfmark(conn, mark);
193 }
194 
195 int
doTosLocalHit(const Comm::ConnectionPointer & conn)196 Ip::Qos::doTosLocalHit(const Comm::ConnectionPointer &conn)
197 {
198     debugs(33, 2, "QOS: Setting TOS for local hit, TOS=" << int(Ip::Qos::TheConfig.tosLocalHit));
199     return setSockTos(conn, Ip::Qos::TheConfig.tosLocalHit);
200 }
201 
202 int
doNfmarkLocalHit(const Comm::ConnectionPointer & conn)203 Ip::Qos::doNfmarkLocalHit(const Comm::ConnectionPointer &conn)
204 {
205     debugs(33, 2, "QOS: Setting netfilter mark for local hit, mark=" << Ip::Qos::TheConfig.markLocalHit);
206     return setSockNfmark(conn, Ip::Qos::TheConfig.markLocalHit);
207 }
208 
209 /* Qos::Config class */
210 
211 Ip::Qos::Config Ip::Qos::TheConfig;
212 
Config()213 Ip::Qos::Config::Config() : tosLocalHit(0), tosSiblingHit(0), tosParentHit(0),
214     tosMiss(0), tosMissMask(0), preserveMissTos(false),
215     preserveMissTosMask(0xFF), markLocalHit(0), markSiblingHit(0),
216     markParentHit(0), markMiss(0), markMissMask(0),
217     preserveMissMark(false), preserveMissMarkMask(0xFFFFFFFF),
218     tosToServer(NULL), tosToClient(NULL), nfmarkToServer(NULL),
219     nfmarkToClient(NULL)
220 {
221 }
222 
223 void
parseConfigLine()224 Ip::Qos::Config::parseConfigLine()
225 {
226     /* parse options ... */
227     char *token;
228     /* These are set as appropriate and then used to check whether the initial loop has been done */
229     bool mark = false;
230     bool tos = false;
231     /* Assume preserve is true. We don't set at initialisation as this affects isHitTosActive().
232        We have to do this now, as we may never match the 'tos' parameter below */
233 #if !USE_QOS_TOS
234     debugs(3, DBG_CRITICAL, "ERROR: Invalid option 'qos_flows'. QOS features not enabled in this build");
235     self_destruct();
236 #endif
237 
238     while ( (token = ConfigParser::NextToken()) ) {
239 
240         // Work out TOS or mark. Default to TOS for backwards compatibility
241         if (!(mark || tos)) {
242             if (strncmp(token, "mark",4) == 0) {
243 #if SO_MARK && USE_LIBCAP
244                 mark = true;
245                 // Assume preserve is true. We don't set at initialisation as this affects isHitNfmarkActive()
246 #if USE_LIBNETFILTERCONNTRACK
247                 preserveMissMark = true;
248 # else // USE_LIBNETFILTERCONNTRACK
249                 preserveMissMark = false;
250                 debugs(3, DBG_IMPORTANT, "WARNING: Squid not compiled with Netfilter conntrack library. "
251                        << "Netfilter mark preservation not available.");
252 #endif // USE_LIBNETFILTERCONNTRACK
253 #elif SO_MARK // SO_MARK && USE_LIBCAP
254                 debugs(3, DBG_CRITICAL, "ERROR: Invalid parameter 'mark' in qos_flows option. "
255                        << "Linux Netfilter marking not available without LIBCAP support.");
256                 self_destruct();
257 #else // SO_MARK && USE_LIBCAP
258                 debugs(3, DBG_CRITICAL, "ERROR: Invalid parameter 'mark' in qos_flows option. "
259                        << "Linux Netfilter marking not available on this platform.");
260                 self_destruct();
261 #endif // SO_MARK && USE_LIBCAP
262             } else if (strncmp(token, "tos",3) == 0) {
263                 preserveMissTos = true;
264                 tos = true;
265             } else {
266                 preserveMissTos = true;
267                 tos = true;
268             }
269         }
270 
271         if (strncmp(token, "local-hit=",10) == 0) {
272 
273             if (mark) {
274                 if (!xstrtoui(&token[10], NULL, &markLocalHit, 0, std::numeric_limits<nfmark_t>::max())) {
275                     debugs(3, DBG_CRITICAL, "ERROR: Bad mark local-hit value " << &token[10]);
276                     self_destruct();
277                 }
278             } else {
279                 unsigned int v = 0;
280                 if (!xstrtoui(&token[10], NULL, &v, 0, std::numeric_limits<tos_t>::max())) {
281                     debugs(3, DBG_CRITICAL, "ERROR: Bad TOS local-hit value " << &token[10]);
282                     self_destruct();
283                 }
284                 tosLocalHit = (tos_t)v;
285             }
286 
287         } else if (strncmp(token, "sibling-hit=",12) == 0) {
288 
289             if (mark) {
290                 if (!xstrtoui(&token[12], NULL, &markSiblingHit, 0, std::numeric_limits<nfmark_t>::max())) {
291                     debugs(3, DBG_CRITICAL, "ERROR: Bad mark sibling-hit value " << &token[12]);
292                     self_destruct();
293                 }
294             } else {
295                 unsigned int v = 0;
296                 if (!xstrtoui(&token[12], NULL, &v, 0, std::numeric_limits<tos_t>::max())) {
297                     debugs(3, DBG_CRITICAL, "ERROR: Bad TOS sibling-hit value " << &token[12]);
298                     self_destruct();
299                 }
300                 tosSiblingHit = (tos_t)v;
301             }
302 
303         } else if (strncmp(token, "parent-hit=",11) == 0) {
304 
305             if (mark) {
306                 if (!xstrtoui(&token[11], NULL, &markParentHit, 0, std::numeric_limits<nfmark_t>::max())) {
307                     debugs(3, DBG_CRITICAL, "ERROR: Bad mark parent-hit value " << &token[11]);
308                     self_destruct();
309                 }
310             } else {
311                 unsigned int v = 0;
312                 if (!xstrtoui(&token[11], NULL, &v, 0, std::numeric_limits<tos_t>::max())) {
313                     debugs(3, DBG_CRITICAL, "ERROR: Bad TOS parent-hit value " << &token[11]);
314                     self_destruct();
315                 }
316                 tosParentHit = (tos_t)v;
317             }
318 
319         } else if (strncmp(token, "miss=",5) == 0) {
320 
321             char *end;
322             if (mark) {
323                 if (!xstrtoui(&token[5], &end, &markMiss, 0, std::numeric_limits<nfmark_t>::max())) {
324                     debugs(3, DBG_CRITICAL, "ERROR: Bad mark miss value " << &token[5]);
325                     self_destruct();
326                 }
327                 if (*end == '/') {
328                     if (!xstrtoui(end + 1, NULL, &markMissMask, 0, std::numeric_limits<nfmark_t>::max())) {
329                         debugs(3, DBG_CRITICAL, "ERROR: Bad mark miss mask value " << (end + 1) << ". Using 0xFFFFFFFF instead.");
330                         markMissMask = 0xFFFFFFFF;
331                     }
332                 } else {
333                     markMissMask = 0xFFFFFFFF;
334                 }
335             } else {
336                 unsigned int v = 0;
337                 if (!xstrtoui(&token[5], &end, &v, 0, std::numeric_limits<tos_t>::max())) {
338                     debugs(3, DBG_CRITICAL, "ERROR: Bad TOS miss value " << &token[5]);
339                     self_destruct();
340                 }
341                 tosMiss = (tos_t)v;
342                 if (*end == '/') {
343                     if (!xstrtoui(end + 1, NULL, &v, 0, std::numeric_limits<tos_t>::max())) {
344                         debugs(3, DBG_CRITICAL, "ERROR: Bad TOS miss mask value " << (end + 1) << ". Using 0xFF instead.");
345                         tosMissMask = 0xFF;
346                     } else
347                         tosMissMask = (tos_t)v;
348                 } else {
349                     tosMissMask = 0xFF;
350                 }
351             }
352 
353         } else if (strcmp(token, "disable-preserve-miss") == 0) {
354 
355             if (preserveMissTosMask!=0xFFU || preserveMissMarkMask!=0xFFFFFFFFU) {
356                 debugs(3, DBG_CRITICAL, "ERROR: miss-mask feature cannot be set with disable-preserve-miss");
357                 self_destruct();
358             }
359             if (mark) {
360                 preserveMissMark = false;
361                 preserveMissMarkMask = 0;
362             } else {
363                 preserveMissTos = false;
364                 preserveMissTosMask = 0;
365             }
366 
367         } else if (strncmp(token, "miss-mask=",10) == 0) {
368 
369             if (mark && preserveMissMark) {
370                 if (!xstrtoui(&token[10], NULL, &preserveMissMarkMask, 0, std::numeric_limits<nfmark_t>::max())) {
371                     debugs(3, DBG_CRITICAL, "ERROR: Bad mark miss-mark value " << &token[10]);
372                     self_destruct();
373                 }
374             } else if (preserveMissTos) {
375                 unsigned int v = 0;
376                 if (!xstrtoui(&token[10], NULL, &v, 0, std::numeric_limits<tos_t>::max())) {
377                     debugs(3, DBG_CRITICAL, "ERROR: Bad TOS miss-mark value " << &token[10]);
378                     self_destruct();
379                 }
380                 preserveMissTosMask = (tos_t)v;
381             } else {
382                 debugs(3, DBG_CRITICAL, "ERROR: miss-mask feature cannot be set without miss-preservation enabled");
383                 self_destruct();
384             }
385 
386         }
387     }
388 }
389 
390 /**
391  * NOTE: Due to the low-level nature of the library these
392  * objects are part of the dump function must be self-contained.
393  * which means no StoreEntry refrences. Just a basic char* buffer.
394 */
395 void
dumpConfigLine(char * entry,const char * name) const396 Ip::Qos::Config::dumpConfigLine(char *entry, const char *name) const
397 {
398     char *p = entry;
399     if (isHitTosActive()) {
400 
401         p += snprintf(p, 11, "%s", name); // strlen("qos_flows ");
402         p += snprintf(p, 4, "%s", "tos");
403 
404         if (tosLocalHit > 0) {
405             p += snprintf(p, 16, " local-hit=0x%02X", tosLocalHit);
406         }
407         if (tosSiblingHit > 0) {
408             p += snprintf(p, 18, " sibling-hit=0x%02X", tosSiblingHit);
409         }
410         if (tosParentHit > 0) {
411             p += snprintf(p, 17, " parent-hit=0x%02X", tosParentHit);
412         }
413         if (tosMiss > 0) {
414             p += snprintf(p, 11, " miss=0x%02X", tosMiss);
415             if (tosMissMask!=0xFFU) {
416                 p += snprintf(p, 6, "/0x%02X", tosMissMask);
417             }
418         }
419         if (preserveMissTos == 0) {
420             p += snprintf(p, 23, " disable-preserve-miss");
421         }
422         if (preserveMissTos && preserveMissTosMask != 0) {
423             p += snprintf(p, 16, " miss-mask=0x%02X", preserveMissTosMask);
424         }
425         p += snprintf(p, 2, "\n");
426     }
427 
428     if (isHitNfmarkActive()) {
429         p += snprintf(p, 11, "%s", name); // strlen("qos_flows ");
430         p += snprintf(p, 5, "%s", "mark");
431 
432         if (markLocalHit > 0) {
433             p += snprintf(p, 22, " local-hit=0x%02X", markLocalHit);
434         }
435         if (markSiblingHit > 0) {
436             p += snprintf(p, 24, " sibling-hit=0x%02X", markSiblingHit);
437         }
438         if (markParentHit > 0) {
439             p += snprintf(p, 23, " parent-hit=0x%02X", markParentHit);
440         }
441         if (markMiss > 0) {
442             p += snprintf(p, 17, " miss=0x%02X", markMiss);
443             if (markMissMask!=0xFFFFFFFFU) {
444                 p += snprintf(p, 12, "/0x%02X", markMissMask);
445             }
446         }
447         if (preserveMissMark == false) {
448             p += snprintf(p, 23, " disable-preserve-miss");
449         }
450         if (preserveMissMark && preserveMissMarkMask != 0) {
451             p += snprintf(p, 22, " miss-mask=0x%02X", preserveMissMarkMask);
452         }
453         p += snprintf(p, 2, "\n");
454     }
455 }
456 
457 #if !_USE_INLINE_
458 #include "Qos.cci"
459 #endif
460 
461