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 /* DEBUG: section 42    ICMP Pinger program */
10 
11 #define SQUID_HELPER 1
12 
13 /**
14  \defgroup pinger pinger
15  \ingroup ExternalPrograms
16  \par
17  *   Although it would be possible for Squid to send and receive
18  *   ICMP messages directly, we use an external process for
19  *   two important reasons:
20  *
21  \li Because squid handles many filedescriptors simultaneously,
22  *   we get much more accurate RTT measurements when ICMP is
23  *   handled by a separate process.
24  *
25  \li Superuser privileges are required to send and receive ICMP.
26  *   Rather than require Squid to be started as root, we prefer
27  *   to have the smaller and simpler pinger program installed
28  *   with setuid permissions.
29  *
30  \par
31  *   If you want to use Squid's ICMP features (highly recommended!)
32  *   When USE_ICMP is defined, Squid will send ICMP pings
33  *   to origin server sites.
34  *   This information is used in numerous ways:
35  \li  - Sent in ICP replies so neighbor caches know how close
36  *      you are to the source.
37  \li  - For finding the closest instance of a URN.
38  \li  - With the 'test_reachability' option.  Squid will return
39  *      ICP_OP_MISS_NOFETCH for sites which it cannot ping.
40  */
41 
42 #include "squid.h"
43 #include "Debug.h"
44 #include "SquidTime.h"
45 
46 #if USE_ICMP
47 
48 #include "Icmp4.h"
49 #include "Icmp6.h"
50 #include "IcmpPinger.h"
51 #include "ip/tools.h"
52 
53 #if _SQUID_WINDOWS_
54 
55 #if HAVE_WINSOCK2_H
56 #include <winsock2.h>
57 #elif HAVE_WINSOCK_H
58 #include <winsock.h>
59 #endif
60 #include <process.h>
61 #include "fde.h"
62 
63 #define PINGER_TIMEOUT 5
64 
65 /* windows uses the control socket for feedback to squid */
66 #define LINK_TO_SQUID squid_link
67 
68 // windows still requires WSAFD but there are too many dependancy problems
69 // to just link to win32.cc where it is normally defined.
70 
71 int
Win32__WSAFDIsSet(int fd,fd_set FAR * set)72 Win32__WSAFDIsSet(int fd, fd_set FAR * set)
73 {
74     fde *F = &fd_table[fd];
75     SOCKET s = F->win32.handle;
76 
77     return __WSAFDIsSet(s, set);
78 }
79 
80 #else
81 
82 #define PINGER_TIMEOUT 10
83 
84 /* non-windows use STDOUT for feedback to squid */
85 #define LINK_TO_SQUID   1
86 
87 #endif  /* _SQUID_WINDOWS_ */
88 
89 // ICMP Engines are declared global here so they can call each other easily.
90 IcmpPinger control;
91 Icmp4 icmp4;
92 Icmp6 icmp6;
93 
94 int icmp_pkts_sent = 0;
95 
96 /**
97  \ingroup pinger
98  \par This is the pinger external process.
99  *
100  \param argc Ignored.
101  \param argv Ignored.
102  */
103 int
main(int argc,char * argv[])104 main(int argc, char *argv[])
105 {
106     fd_set R;
107     int x;
108     int max_fd = 0;
109 
110     struct timeval tv;
111     const char *debug_args = "ALL,10";
112     char *t;
113     time_t last_check_time = 0;
114 
115     /*
116      * cevans - do this first. It grabs a raw socket. After this we can
117      * drop privs
118      */
119     int icmp4_worker = -1;
120     int icmp6_worker = -1;
121     int squid_link = -1;
122 
123     /** start by initializing the pinger debug cache.log-pinger. */
124     if ((t = getenv("SQUID_DEBUG")))
125         debug_args = xstrdup(t);
126 
127     getCurrentTime();
128 
129     // determine IPv4 or IPv6 capabilities before using sockets.
130     Ip::ProbeTransport();
131 
132     _db_init(NULL, debug_args);
133 
134     debugs(42, DBG_CRITICAL, "pinger: Initialising ICMP pinger ...");
135 
136     icmp4_worker = icmp4.Open();
137     if (icmp4_worker < 0) {
138         debugs(42, DBG_CRITICAL, "pinger: Unable to start ICMP pinger.");
139     }
140     max_fd = max(max_fd, icmp4_worker);
141 
142 #if USE_IPV6
143     icmp6_worker = icmp6.Open();
144     if (icmp6_worker <0 ) {
145         debugs(42, DBG_CRITICAL, "pinger: Unable to start ICMPv6 pinger.");
146     }
147     max_fd = max(max_fd, icmp6_worker);
148 #endif
149 
150     /** abort if neither worker could open a socket. */
151     if (icmp4_worker < 0 && icmp6_worker < 0) {
152         debugs(42, DBG_CRITICAL, "FATAL: pinger: Unable to open any ICMP sockets.");
153         exit(1);
154     }
155 
156     if ( (squid_link = control.Open()) < 0) {
157         debugs(42, DBG_CRITICAL, "FATAL: pinger: Unable to setup Pinger control sockets.");
158         icmp4.Close();
159         icmp6.Close();
160         exit(1); // fatal error if the control channel fails.
161     }
162     max_fd = max(max_fd, squid_link);
163 
164     if (setgid(getgid()) < 0) {
165         int xerrno = errno;
166         debugs(42, DBG_CRITICAL, "FATAL: pinger: setgid(" << getgid() << ") failed: " << xstrerr(xerrno));
167         icmp4.Close();
168         icmp6.Close();
169         exit (1);
170     }
171     if (setuid(getuid()) < 0) {
172         int xerrno = errno;
173         debugs(42, DBG_CRITICAL, "FATAL: pinger: setuid(" << getuid() << ") failed: " << xstrerr(xerrno));
174         icmp4.Close();
175         icmp6.Close();
176         exit (1);
177     }
178 
179 #if USE_LIBCAP
180     // Drop remaining capabilities (if installed as non-setuid setcap cap_net_raw=ep).
181     // If pinger binary was installed setuid root, setuid() above already dropped all
182     // capabilities, and this is no-op.
183     cap_t caps;
184     caps = cap_init();
185     if (!caps) {
186         int xerrno = errno;
187         debugs(42, DBG_CRITICAL, "FATAL: pinger: cap_init() failed: " << xstrerr(xerrno));
188         icmp4.Close();
189         icmp6.Close();
190         exit (1);
191     } else {
192         if (cap_set_proc(caps) != 0) {
193             int xerrno = errno;
194             // cap_set_proc(cap_init()) is expected to never fail
195             debugs(42, DBG_CRITICAL, "FATAL: pinger: cap_set_proc(none) failed: " << xstrerr(xerrno));
196             cap_free(caps);
197             icmp4.Close();
198             icmp6.Close();
199             exit (1);
200         }
201         cap_free(caps);
202     }
203 #endif
204 
205     last_check_time = squid_curtime;
206 
207     for (;;) {
208         tv.tv_sec = PINGER_TIMEOUT;
209         tv.tv_usec = 0;
210         FD_ZERO(&R);
211         if (icmp4_worker >= 0) {
212             FD_SET(icmp4_worker, &R);
213         }
214         if (icmp6_worker >= 0) {
215             FD_SET(icmp6_worker, &R);
216         }
217 
218         FD_SET(squid_link, &R);
219         x = select(max_fd+1, &R, NULL, NULL, &tv);
220         getCurrentTime();
221 
222         if (x < 0) {
223             int xerrno = errno;
224             debugs(42, DBG_CRITICAL, HERE << " FATAL Shutdown. select()==" << x << ", ERR: " << xstrerr(xerrno));
225             control.Close();
226             exit(1);
227         }
228 
229         if (FD_ISSET(squid_link, &R)) {
230             control.Recv();
231         }
232 
233         if (icmp6_worker >= 0 && FD_ISSET(icmp6_worker, &R)) {
234             icmp6.Recv();
235         }
236         if (icmp4_worker >= 0 && FD_ISSET(icmp4_worker, &R)) {
237             icmp4.Recv();
238         }
239 
240         if (PINGER_TIMEOUT + last_check_time < squid_curtime) {
241             if (send(LINK_TO_SQUID, &tv, 0, 0) < 0) {
242                 debugs(42, DBG_CRITICAL, "pinger: Closing. No requests in last " << PINGER_TIMEOUT << " seconds.");
243                 control.Close();
244                 exit(1);
245             }
246 
247             last_check_time = squid_curtime;
248         }
249     }
250 
251     /* NOTREACHED */
252     return 0;
253 }
254 
255 #else /* !USE_ICMP */
256 
257 #include <ostream>
258 int
main(int argc,char * argv[])259 main(int argc, char *argv[])
260 {
261     std::cerr << argv[0] << ": ICMP support not compiled in." << std::endl;
262     return 1;
263 }
264 
265 #endif /* USE_ICMP */
266 
267