1 /*
2  * Copyright (c) 2009, Giampaolo Rodola'.
3  * All rights reserved.
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  *
7  * Retrieves system-wide open socket connections. This is based off of
8  * sockstat utility source code:
9  * https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c
10  */
11 
12 #include <Python.h>
13 #include <sys/user.h>
14 #include <sys/file.h>
15 #include <sys/socketvar.h>    // for struct xsocket
16 #include <sys/un.h>
17 #include <sys/unpcb.h>
18 #include <sys/sysctl.h>
19 #if defined(__FreeBSD_version) && __FreeBSD_version < 800000
20 #include <netinet/in_systm.h>
21 #endif
22 #include <netinet/in.h>   // for xinpcb struct
23 #include <netinet/ip.h>
24 #include <netinet/in_pcb.h>
25 #include <netinet/tcp_var.h>   // for struct xtcpcb
26 #include <arpa/inet.h>         // for inet_ntop()
27 
28 #include "../../_psutil_common.h"
29 #include "../../_psutil_posix.h"
30 
31 static struct xfile *psutil_xfiles;
32 static int psutil_nxfiles;
33 
34 
35 int
psutil_populate_xfiles(void)36 psutil_populate_xfiles(void) {
37     size_t len;
38 
39     if ((psutil_xfiles = malloc(len = sizeof *psutil_xfiles)) == NULL) {
40         PyErr_NoMemory();
41         return 0;
42     }
43     while (sysctlbyname("kern.file", psutil_xfiles, &len, 0, 0) == -1) {
44         if (errno != ENOMEM) {
45             PyErr_SetFromErrno(0);
46             return 0;
47         }
48         len *= 2;
49         if ((psutil_xfiles = realloc(psutil_xfiles, len)) == NULL) {
50             PyErr_NoMemory();
51             return 0;
52         }
53     }
54     if (len > 0 && psutil_xfiles->xf_size != sizeof *psutil_xfiles) {
55         PyErr_Format(PyExc_RuntimeError, "struct xfile size mismatch");
56         return 0;
57     }
58     psutil_nxfiles = len / sizeof *psutil_xfiles;
59     return 1;
60 }
61 
62 
63 struct xfile *
psutil_get_file_from_sock(void * sock)64 psutil_get_file_from_sock(void *sock) {
65     struct xfile *xf;
66     int n;
67 
68     for (xf = psutil_xfiles, n = 0; n < psutil_nxfiles; ++n, ++xf) {
69         if (xf->xf_data == sock)
70             return xf;
71     }
72     return NULL;
73 }
74 
75 
76 // Reference:
77 // https://github.com/freebsd/freebsd/blob/master/usr.bin/sockstat/sockstat.c
psutil_gather_inet(int proto,PyObject * py_retlist)78 int psutil_gather_inet(int proto, PyObject *py_retlist) {
79     struct xinpgen *xig, *exig;
80     struct xinpcb *xip;
81     struct xtcpcb *xtp;
82 #if __FreeBSD_version >= 1200026
83     struct xinpcb *inp;
84 #else
85     struct inpcb *inp;
86 #endif
87     struct xsocket *so;
88     const char *varname = NULL;
89     size_t len, bufsize;
90     void *buf;
91     int retry;
92     int type;
93 
94     PyObject *py_tuple = NULL;
95     PyObject *py_laddr = NULL;
96     PyObject *py_raddr = NULL;
97 
98     switch (proto) {
99         case IPPROTO_TCP:
100             varname = "net.inet.tcp.pcblist";
101             type = SOCK_STREAM;
102             break;
103         case IPPROTO_UDP:
104             varname = "net.inet.udp.pcblist";
105             type = SOCK_DGRAM;
106             break;
107     }
108 
109     buf = NULL;
110     bufsize = 8192;
111     retry = 5;
112     do {
113         for (;;) {
114             buf = realloc(buf, bufsize);
115             if (buf == NULL)
116                 continue;  // XXX
117             len = bufsize;
118             if (sysctlbyname(varname, buf, &len, NULL, 0) == 0)
119                 break;
120             if (errno != ENOMEM) {
121                 PyErr_SetFromErrno(0);
122                 goto error;
123             }
124             bufsize *= 2;
125         }
126         xig = (struct xinpgen *)buf;
127         exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig);
128         if (xig->xig_len != sizeof *xig || exig->xig_len != sizeof *exig) {
129             PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch");
130             goto error;
131         }
132     } while (xig->xig_gen != exig->xig_gen && retry--);
133 
134     for (;;) {
135         struct xfile *xf;
136         int lport, rport, status, family;
137 
138         xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
139         if (xig >= exig)
140             break;
141 
142         switch (proto) {
143             case IPPROTO_TCP:
144                 xtp = (struct xtcpcb *)xig;
145                 if (xtp->xt_len != sizeof *xtp) {
146                     PyErr_Format(PyExc_RuntimeError,
147                                  "struct xtcpcb size mismatch");
148                     goto error;
149                 }
150                 inp = &xtp->xt_inp;
151 #if __FreeBSD_version >= 1200026
152                 so = &inp->xi_socket;
153                 status = xtp->t_state;
154 #else
155                 so = &xtp->xt_socket;
156                 status = xtp->xt_tp.t_state;
157 #endif
158                 break;
159             case IPPROTO_UDP:
160                 xip = (struct xinpcb *)xig;
161                 if (xip->xi_len != sizeof *xip) {
162                     PyErr_Format(PyExc_RuntimeError,
163                                  "struct xinpcb size mismatch");
164                     goto error;
165                 }
166 #if __FreeBSD_version >= 1200026
167                 inp = xip;
168 #else
169                 inp = &xip->xi_inp;
170 #endif
171                 so = &xip->xi_socket;
172                 status = PSUTIL_CONN_NONE;
173                 break;
174             default:
175                 PyErr_Format(PyExc_RuntimeError, "invalid proto");
176                 goto error;
177         }
178 
179         char lip[200], rip[200];
180 
181         xf = psutil_get_file_from_sock(so->xso_so);
182         if (xf == NULL)
183             continue;
184         lport = ntohs(inp->inp_lport);
185         rport = ntohs(inp->inp_fport);
186 
187         if (inp->inp_vflag & INP_IPV4) {
188             family = AF_INET;
189             inet_ntop(AF_INET, &inp->inp_laddr.s_addr, lip, sizeof(lip));
190             inet_ntop(AF_INET, &inp->inp_faddr.s_addr, rip, sizeof(rip));
191         }
192         else if (inp->inp_vflag & INP_IPV6) {
193             family = AF_INET6;
194             inet_ntop(AF_INET6, &inp->in6p_laddr.s6_addr, lip, sizeof(lip));
195             inet_ntop(AF_INET6, &inp->in6p_faddr.s6_addr, rip, sizeof(rip));
196         }
197 
198         // construct python tuple/list
199         py_laddr = Py_BuildValue("(si)", lip, lport);
200         if (!py_laddr)
201             goto error;
202         if (rport != 0)
203             py_raddr = Py_BuildValue("(si)", rip, rport);
204         else
205             py_raddr = Py_BuildValue("()");
206         if (!py_raddr)
207             goto error;
208         py_tuple = Py_BuildValue(
209             "iiiNNi" _Py_PARSE_PID,
210             xf->xf_fd, // fd
211             family,    // family
212             type,      // type
213             py_laddr,  // laddr
214             py_raddr,  // raddr
215             status,    // status
216             xf->xf_pid // pid
217         );
218         if (!py_tuple)
219             goto error;
220         if (PyList_Append(py_retlist, py_tuple))
221             goto error;
222         Py_CLEAR(py_tuple);
223     }
224 
225     free(buf);
226     return 1;
227 
228 error:
229     Py_XDECREF(py_tuple);
230     Py_XDECREF(py_laddr);
231     Py_XDECREF(py_raddr);
232     free(buf);
233     return 0;
234 }
235 
236 
psutil_gather_unix(int proto,PyObject * py_retlist)237 int psutil_gather_unix(int proto, PyObject *py_retlist) {
238     struct xunpgen *xug, *exug;
239     struct xunpcb *xup;
240     const char *varname = NULL;
241     const char *protoname = NULL;
242     size_t len;
243     size_t bufsize;
244     void *buf;
245     int retry;
246     struct sockaddr_un *sun;
247     char path[PATH_MAX];
248 
249     PyObject *py_tuple = NULL;
250     PyObject *py_lpath = NULL;
251 
252     switch (proto) {
253         case SOCK_STREAM:
254             varname = "net.local.stream.pcblist";
255             protoname = "stream";
256             break;
257         case SOCK_DGRAM:
258             varname = "net.local.dgram.pcblist";
259             protoname = "dgram";
260             break;
261     }
262 
263     buf = NULL;
264     bufsize = 8192;
265     retry = 5;
266 
267     do {
268         for (;;) {
269             buf = realloc(buf, bufsize);
270             if (buf == NULL) {
271                 PyErr_NoMemory();
272                 goto error;
273             }
274             len = bufsize;
275             if (sysctlbyname(varname, buf, &len, NULL, 0) == 0)
276                 break;
277             if (errno != ENOMEM) {
278                 PyErr_SetFromErrno(0);
279                 goto error;
280             }
281             bufsize *= 2;
282         }
283         xug = (struct xunpgen *)buf;
284         exug = (struct xunpgen *)(void *)
285             ((char *)buf + len - sizeof *exug);
286         if (xug->xug_len != sizeof *xug || exug->xug_len != sizeof *exug) {
287             PyErr_Format(PyExc_RuntimeError, "struct xinpgen size mismatch");
288             goto error;
289         }
290     } while (xug->xug_gen != exug->xug_gen && retry--);
291 
292     for (;;) {
293         struct xfile *xf;
294 
295         xug = (struct xunpgen *)(void *)((char *)xug + xug->xug_len);
296         if (xug >= exug)
297             break;
298         xup = (struct xunpcb *)xug;
299         if (xup->xu_len != sizeof *xup)
300             goto error;
301 
302         xf = psutil_get_file_from_sock(xup->xu_socket.xso_so);
303         if (xf == NULL)
304             continue;
305 
306         sun = (struct sockaddr_un *)&xup->xu_addr;
307         snprintf(path, sizeof(path), "%.*s",
308                  (int)(sun->sun_len - (sizeof(*sun) - sizeof(sun->sun_path))),
309                  sun->sun_path);
310         py_lpath = PyUnicode_DecodeFSDefault(path);
311         if (! py_lpath)
312             goto error;
313 
314         py_tuple = Py_BuildValue("(iiiOsii)",
315             xf->xf_fd,         // fd
316             AF_UNIX,           // family
317             proto,             // type
318             py_lpath,          // lpath
319             "",                // rath
320             PSUTIL_CONN_NONE,  // status
321             xf->xf_pid);       // pid
322         if (!py_tuple)
323             goto error;
324         if (PyList_Append(py_retlist, py_tuple))
325             goto error;
326         Py_DECREF(py_lpath);
327         Py_DECREF(py_tuple);
328     }
329 
330     free(buf);
331     return 1;
332 
333 error:
334     Py_XDECREF(py_tuple);
335     Py_XDECREF(py_lpath);
336     free(buf);
337     return 0;
338 }
339 
340 
341 PyObject*
psutil_net_connections(PyObject * self,PyObject * args)342 psutil_net_connections(PyObject* self, PyObject* args) {
343     // Return system-wide open connections.
344     PyObject *py_retlist = PyList_New(0);
345 
346     if (py_retlist == NULL)
347         return NULL;
348     if (psutil_populate_xfiles() != 1)
349         goto error;
350     if (psutil_gather_inet(IPPROTO_TCP, py_retlist) == 0)
351         goto error;
352     if (psutil_gather_inet(IPPROTO_UDP, py_retlist) == 0)
353         goto error;
354     if (psutil_gather_unix(SOCK_STREAM, py_retlist) == 0)
355        goto error;
356     if (psutil_gather_unix(SOCK_DGRAM, py_retlist) == 0)
357         goto error;
358 
359     free(psutil_xfiles);
360     return py_retlist;
361 
362 error:
363     Py_DECREF(py_retlist);
364     free(psutil_xfiles);
365     return NULL;
366 }
367