]> git.ipfire.org Git - thirdparty/squid.git/blob - src/ip/Intercept.cc
Add missing includes
[thirdparty/squid.git] / src / ip / Intercept.cc
1 /*
2 * DEBUG: section 89 NAT / IP Interception
3 * AUTHOR: Robert Collins
4 * AUTHOR: Amos Jeffries
5 *
6 * SQUID Web Proxy Cache http://www.squid-cache.org/
7 * ----------------------------------------------------------
8 *
9 * Squid is the result of efforts by numerous individuals from
10 * the Internet community; see the CONTRIBUTORS file for full
11 * details. Many organizations have provided support for Squid's
12 * development; see the SPONSORS file for full details. Squid is
13 * Copyrighted (C) 2001 by the Regents of the University of
14 * California; see the COPYRIGHT file for full details. Squid
15 * incorporates software developed and/or copyrighted by other
16 * sources; see the CREDITS file for full details.
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
27 *
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
31 *
32 */
33 #include "squid.h"
34 #include "comm/Connection.h"
35 #include "ip/Intercept.h"
36 #include "fde.h"
37
38 #if IPF_TRANSPARENT
39
40 #if HAVE_SYS_IOCTL_H
41 #include <sys/ioctl.h>
42 #endif
43 #if HAVE_NETINET_TCP_H
44 #include <netinet/tcp.h>
45 #endif
46 #if HAVE_NET_IF_H
47 #include <net/if.h>
48 #endif
49 #if HAVE_IPL_H
50 #include <ipl.h>
51 #elif HAVE_NETINET_IPL_H
52 #include <netinet/ipl.h>
53 #endif
54 #if HAVE_IP_FIL_COMPAT_H
55 #include <ip_fil_compat.h>
56 #elif HAVE_NETINET_IP_FIL_COMPAT_H
57 #include <netinet/ip_fil_compat.h>
58 #elif HAVE_IP_COMPAT_H
59 #include <ip_compat.h>
60 #elif HAVE_NETINET_IP_COMPAT_H
61 #include <netinet/ip_compat.h>
62 #endif
63 #if HAVE_IP_FIL_H
64 #include <ip_fil.h>
65 #elif HAVE_NETINET_IP_FIL_H
66 #include <netinet/ip_fil.h>
67 #endif
68 #if HAVE_IP_NAT_H
69 #include <ip_nat.h>
70 #elif HAVE_NETINET_IP_NAT_H
71 #include <netinet/ip_nat.h>
72 #endif
73 #if HAVE_ERRNO_H
74 #include <errno.h>
75 #endif
76
77 #endif /* IPF_TRANSPARENT required headers */
78
79 #if PF_TRANSPARENT
80 #include <sys/socket.h>
81 #include <sys/ioctl.h>
82 #include <sys/fcntl.h>
83 #include <net/if.h>
84 #include <netinet/in.h>
85 #if HAVE_NET_PF_PFVAR_H
86 #include <net/pf/pfvar.h>
87 #endif /* HAVE_NET_PF_PFVAR_H */
88 #if HAVE_NET_PFVAR_H
89 #include <net/pfvar.h>
90 #endif /* HAVE_NET_PFVAR_H */
91 #endif /* PF_TRANSPARENT required headers */
92
93 #if LINUX_NETFILTER
94 #include <linux/netfilter_ipv4.h>
95 #endif
96
97 // single global instance for access by other components.
98 Ip::Intercept Ip::Interceptor;
99
100 void
101 Ip::Intercept::StopTransparency(const char *str)
102 {
103 if (transparentActive_) {
104 debugs(89, DBG_IMPORTANT, "Stopping full transparency: " << str);
105 transparentActive_ = 0;
106 }
107 }
108
109 void
110 Ip::Intercept::StopInterception(const char *str)
111 {
112 if (interceptActive_) {
113 debugs(89, DBG_IMPORTANT, "Stopping IP interception: " << str);
114 interceptActive_ = 0;
115 }
116 }
117
118 bool
119 Ip::Intercept::NetfilterInterception(const Comm::ConnectionPointer &newConn, int silent)
120 {
121 #if LINUX_NETFILTER
122 struct sockaddr_in lookup;
123 socklen_t len = sizeof(struct sockaddr_in);
124 newConn->local.GetSockAddr(lookup);
125
126 /** \par
127 * Try NAT lookup for REDIRECT or DNAT targets. */
128 if ( getsockopt(newConn->fd, IPPROTO_IP, SO_ORIGINAL_DST, &lookup, &len) != 0) {
129 if (!silent) {
130 debugs(89, DBG_IMPORTANT, HERE << " NF getsockopt(SO_ORIGINAL_DST) failed on " << newConn << ": " << xstrerror());
131 lastReported_ = squid_curtime;
132 }
133 debugs(89, 9, HERE << "address: " << newConn);
134 return false;
135 } else {
136 newConn->local = lookup;
137 debugs(89, 5, HERE << "address NAT: " << newConn);
138 return true;
139 }
140 #endif
141 return false;
142 }
143
144 bool
145 Ip::Intercept::NetfilterTransparent(const Comm::ConnectionPointer &newConn, int silent)
146 {
147 #if LINUX_NETFILTER
148 /* Trust the user configured properly. If not no harm done.
149 * We will simply attempt a bind outgoing on our own IP.
150 */
151 newConn->remote.SetPort(0); // allow random outgoing port to prevent address clashes
152 debugs(89, 5, HERE << "address TPROXY: " << newConn);
153 return true;
154 #else
155 return false;
156 #endif
157 }
158
159 bool
160 Ip::Intercept::IpfwInterception(const Comm::ConnectionPointer &newConn, int silent)
161 {
162 #if IPFW_TRANSPARENT
163 struct sockaddr_storage lookup;
164 socklen_t len = sizeof(struct sockaddr_storage);
165 newConn->local.GetSockAddr(lookup, AF_INET);
166
167 /** \par
168 * Try lookup for IPFW interception. */
169 if ( getsockname(newConn->fd, (struct sockaddr*)&lookup, &len) != 0 ) {
170 if ( !silent ) {
171 debugs(89, DBG_IMPORTANT, HERE << " IPFW getsockname(...) failed: " << xstrerror());
172 lastReported_ = squid_curtime;
173 }
174 debugs(89, 9, HERE << "address: " << newConn);
175 return false;
176 } else {
177 newConn->local = lookup;
178 debugs(89, 5, HERE << "address NAT: " << newConn);
179 return true;
180 }
181 #endif
182 return false;
183 }
184
185 bool
186 Ip::Intercept::IpfInterception(const Comm::ConnectionPointer &newConn, int silent)
187 {
188 #if IPF_TRANSPARENT /* --enable-ipf-transparent */
189
190 struct natlookup natLookup;
191 static int natfd = -1;
192 int x;
193
194 // all fields must be set to 0
195 memset(&natLookup, 0, sizeof(natLookup));
196 // for NAT lookup set local and remote IP:port's
197 natLookup.nl_inport = htons(newConn->local.GetPort());
198 newConn->local.GetInAddr(natLookup.nl_inip);
199 natLookup.nl_outport = htons(newConn->remote.GetPort());
200 newConn->remote.GetInAddr(natLookup.nl_outip);
201 // ... and the TCP flag
202 natLookup.nl_flags = IPN_TCP;
203
204 if (natfd < 0) {
205 int save_errno;
206 enter_suid();
207 #ifdef IPNAT_NAME
208 natfd = open(IPNAT_NAME, O_RDONLY, 0);
209 #else
210 natfd = open(IPL_NAT, O_RDONLY, 0);
211 #endif
212 save_errno = errno;
213 leave_suid();
214 errno = save_errno;
215 }
216
217 if (natfd < 0) {
218 if (!silent) {
219 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT open failed: " << xstrerror());
220 lastReported_ = squid_curtime;
221 return false;
222 }
223 }
224
225 #if defined(IPFILTER_VERSION) && (IPFILTER_VERSION >= 4000027)
226 struct ipfobj obj;
227 memset(&obj, 0, sizeof(obj));
228 obj.ipfo_rev = IPFILTER_VERSION;
229 obj.ipfo_size = sizeof(natLookup);
230 obj.ipfo_ptr = &natLookup;
231 obj.ipfo_type = IPFOBJ_NATLOOKUP;
232
233 x = ioctl(natfd, SIOCGNATL, &obj);
234 #else
235 /*
236 * IP-Filter changed the type for SIOCGNATL between
237 * 3.3 and 3.4. It also changed the cmd value for
238 * SIOCGNATL, so at least we can detect it. We could
239 * put something in configure and use ifdefs here, but
240 * this seems simpler.
241 */
242 static int siocgnatl_cmd = SIOCGNATL & 0xff;
243 if (63 == siocgnatl_cmd) {
244 struct natlookup *nlp = &natLookup;
245 x = ioctl(natfd, SIOCGNATL, &nlp);
246 } else {
247 x = ioctl(natfd, SIOCGNATL, &natLookup);
248 }
249
250 #endif
251 if (x < 0) {
252 if (errno != ESRCH) {
253 if (!silent) {
254 debugs(89, DBG_IMPORTANT, "IPF (IPFilter) NAT lookup failed: ioctl(SIOCGNATL) (v=" << IPFILTER_VERSION << "): " << xstrerror());
255 lastReported_ = squid_curtime;
256 }
257
258 close(natfd);
259 natfd = -1;
260 }
261
262 debugs(89, 9, HERE << "address: " << newConn);
263 return false;
264 } else {
265 newConn->local = natLookup.nl_realip;
266 newConn->local.SetPort(ntohs(natLookup.nl_realport));
267 debugs(89, 5, HERE << "address NAT: " << newConn);
268 return true;
269 }
270
271 #endif /* --enable-ipf-transparent */
272 return false;
273 }
274
275 bool
276 Ip::Intercept::PfInterception(const Comm::ConnectionPointer &newConn, int silent)
277 {
278 #if PF_TRANSPARENT /* --enable-pf-transparent */
279
280 struct pfioc_natlook nl;
281 static int pffd = -1;
282
283 if (pffd < 0)
284 pffd = open("/dev/pf", O_RDONLY);
285
286 if (pffd < 0) {
287 if (!silent) {
288 debugs(89, DBG_IMPORTANT, HERE << "PF open failed: " << xstrerror());
289 lastReported_ = squid_curtime;
290 }
291 return false;
292 }
293
294 memset(&nl, 0, sizeof(struct pfioc_natlook));
295 newConn->remote.GetInAddr(nl.saddr.v4);
296 nl.sport = htons(newConn->remote.GetPort());
297
298 newConn->local.GetInAddr(nl.daddr.v4);
299 nl.dport = htons(newConn->local.GetPort());
300
301 nl.af = AF_INET;
302 nl.proto = IPPROTO_TCP;
303 nl.direction = PF_OUT;
304
305 if (ioctl(pffd, DIOCNATLOOK, &nl)) {
306 if (errno != ENOENT) {
307 if (!silent) {
308 debugs(89, DBG_IMPORTANT, HERE << "PF lookup failed: ioctl(DIOCNATLOOK)");
309 lastReported_ = squid_curtime;
310 }
311 close(pffd);
312 pffd = -1;
313 }
314 debugs(89, 9, HERE << "address: " << newConn);
315 return false;
316 } else {
317 newConn->local = nl.rdaddr.v4;
318 newConn->local.SetPort(ntohs(nl.rdport));
319 debugs(89, 5, HERE << "address NAT: " << newConn);
320 return true;
321 }
322
323 #endif /* --enable-pf-transparent */
324 return false;
325 }
326
327 bool
328 Ip::Intercept::Lookup(const Comm::ConnectionPointer &newConn, const Comm::ConnectionPointer &listenConn)
329 {
330 /* --enable-linux-netfilter */
331 /* --enable-ipfw-transparent */
332 /* --enable-ipf-transparent */
333 /* --enable-pf-transparent */
334 #if IPF_TRANSPARENT || LINUX_NETFILTER || IPFW_TRANSPARENT || PF_TRANSPARENT
335
336 #if 0
337 // Crop interception errors down to one per minute.
338 int silent = (squid_curtime - lastReported_ > 60 ? 0 : 1);
339 #else
340 // Show all interception errors.
341 int silent = 0;
342 #endif
343
344 debugs(89, 5, HERE << "address BEGIN: me/client= " << newConn->local << ", destination/me= " << newConn->remote);
345
346 newConn->flags |= (listenConn->flags & (COMM_TRANSPARENT|COMM_INTERCEPTION));
347
348 /* NP: try TPROXY first, its much quieter than NAT when non-matching */
349 if (transparentActive_ && listenConn->flags&COMM_TRANSPARENT) {
350 if (NetfilterTransparent(newConn, silent)) return true;
351 }
352
353 /* NAT is only available in IPv4 */
354 if ( !newConn->local.IsIPv4() ) return false;
355 if ( !newConn->remote.IsIPv4() ) return false;
356
357 if (interceptActive_ && listenConn->flags&COMM_INTERCEPTION) {
358 /* NAT methods that use sock-opts to return client address */
359 if (NetfilterInterception(newConn, silent)) return true;
360 if (IpfwInterception(newConn, silent)) return true;
361
362 /* NAT methods that use ioctl to return client address AND destination address */
363 if (PfInterception(newConn, silent)) return true;
364 if (IpfInterception(newConn, silent)) return true;
365 }
366
367 #else /* none of the transparent options configured */
368 debugs(89, DBG_IMPORTANT, "WARNING: transparent proxying not supported");
369 #endif
370
371 return false;
372 }
373
374 bool
375 Ip::Intercept::ProbeForTproxy(Ip::Address &test)
376 {
377 debugs(3, 3, "Detect TPROXY support on port " << test);
378
379 #if defined(IP_TRANSPARENT)
380
381 int tos = 1;
382 int tmp_sock = -1;
383
384 /* Probe to see if the Kernel TPROXY support is IPv6-enabled */
385 if (test.IsIPv6()) {
386 debugs(3, 3, "...Probing for IPv6 TPROXY support.");
387
388 struct sockaddr_in6 tmp_ip6;
389 Ip::Address tmp = "::2";
390 tmp.SetPort(0);
391 tmp.GetSockAddr(tmp_ip6);
392
393 if ( (tmp_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
394 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
395 bind(tmp_sock, (struct sockaddr*)&tmp_ip6, sizeof(struct sockaddr_in6)) == 0 ) {
396
397 debugs(3, 3, "IPv6 TPROXY support detected. Using.");
398 close(tmp_sock);
399 return true;
400 }
401 if (tmp_sock >= 0) {
402 close(tmp_sock);
403 tmp_sock = -1;
404 }
405 }
406
407 if ( test.IsIPv6() && !test.SetIPv4() ) {
408 debugs(3, DBG_CRITICAL, "TPROXY lacks IPv6 support for " << test );
409 return false;
410 }
411
412 /* Probe to see if the Kernel TPROXY support is IPv4-enabled (aka present) */
413 if (test.IsIPv4()) {
414 debugs(3, 3, "...Probing for IPv4 TPROXY support.");
415
416 struct sockaddr_in tmp_ip4;
417 Ip::Address tmp = "127.0.0.2";
418 tmp.SetPort(0);
419 tmp.GetSockAddr(tmp_ip4);
420
421 if ( (tmp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) >= 0 &&
422 setsockopt(tmp_sock, SOL_IP, IP_TRANSPARENT, (char *)&tos, sizeof(int)) == 0 &&
423 bind(tmp_sock, (struct sockaddr*)&tmp_ip4, sizeof(struct sockaddr_in)) == 0 ) {
424
425 debugs(3, 3, "IPv4 TPROXY support detected. Using.");
426 close(tmp_sock);
427 return true;
428 }
429 if (tmp_sock >= 0) {
430 close(tmp_sock);
431 }
432 }
433
434 #else /* undefined IP_TRANSPARENT */
435 debugs(3, 3, "setsockopt(IP_TRANSPARENT) not supported on this platform. Disabling TPROXYv4.");
436 #endif
437 return false;
438 }