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