1 /* $Id: portinuse.c,v 1.12 2020/11/04 21:29:50 nanard Exp $ */
2 /* vim: tabstop=4 shiftwidth=4 noexpandtab
3 * MiniUPnP project
4 * (c) 2007-2020 Thomas Bernard
5 * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/
6 * This software is subject to the conditions detailed
7 * in the LICENCE file provided within the distribution */
8
9 #if defined(__DragonFly__) || defined(__FreeBSD__)
10 #include <err.h>
11 #include <stdlib.h>
12 #endif
13
14 #include <stdio.h>
15 #include <string.h>
16 #include <unistd.h>
17 #include <syslog.h>
18 #include <errno.h>
19 #include <time.h>
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <netinet/in.h>
23 #include <arpa/inet.h>
24
25 #if defined(__OpenBSD__)
26 #include <sys/queue.h>
27 #include <sys/select.h>
28 #include <kvm.h>
29 #include <fcntl.h>
30 #include <nlist.h>
31 #include <limits.h>
32 #include <net/route.h>
33 #include <netinet/in_systm.h>
34 #include <netinet/ip.h>
35 #include <netinet/in_pcb.h>
36 #endif
37
38 #if defined(__DragonFly__) || defined(__FreeBSD__)
39 #include <sys/socketvar.h>
40 #include <sys/sysctl.h>
41 /* sys/socketvar.h must be included above the following headers */
42 #include <netinet/in_pcb.h>
43 #include <netinet/tcp_var.h>
44 #endif
45
46 #include "macros.h"
47 #include "config.h"
48 #include "upnpglobalvars.h"
49 #include "getifaddr.h"
50 #include "portinuse.h"
51
52 #if defined(USE_NETFILTER)
53 #include "netfilter/iptcrdr.h"
54 #endif
55
56 #ifdef CHECK_PORTINUSE
57
58 #if defined(USE_NETFILTER)
59 /* Hardcoded for now. Ideally would come from .conf file */
60 # ifdef TOMATO
61 const char *chains_to_check[] = { "WANPREROUTING" , 0 };
62 # else
63 const char *chains_to_check[] = { "PREROUTING" , 0 };
64 # endif
65 #endif
66
67 int
port_in_use(const char * if_name,unsigned eport,int proto,const char * iaddr,unsigned iport)68 port_in_use(const char *if_name,
69 unsigned eport, int proto,
70 const char *iaddr, unsigned iport)
71 {
72 int found = 0;
73 char ip_addr_str[INET_ADDRSTRLEN];
74 struct in_addr ip_addr;
75 #ifdef __linux__
76 /* linux code */
77 char line[256];
78 FILE *f;
79 const char * tcpfile = "/proc/net/tcp";
80 const char * udpfile = "/proc/net/udp";
81 #endif
82
83 if(getifaddr(if_name, ip_addr_str, INET_ADDRSTRLEN, &ip_addr, NULL) < 0) {
84 ip_addr.s_addr = 0;
85 ip_addr_str[0] = '\0';
86 }
87
88 syslog(LOG_DEBUG, "Check protocol %s for port %u on ext_if %s %s, %08X",
89 (proto==IPPROTO_TCP)?"tcp":"udp", eport, if_name,
90 ip_addr_str, (unsigned)ip_addr.s_addr);
91
92 /* Phase 1 : check for local sockets (would be listed by netstat) */
93 #if defined(__linux__)
94 f = fopen((proto==IPPROTO_TCP)?tcpfile:udpfile, "r");
95 if (!f) {
96 syslog(LOG_ERR, "cannot open %s", (proto==IPPROTO_TCP)?tcpfile:udpfile);
97 return -1;
98 }
99
100 while (fgets(line, 255, f)) {
101 char eaddr[68];
102 unsigned tmp_port;
103 if (sscanf(line, "%*d: %64[0-9A-Fa-f]:%x %*x:%*x %*x %*x:%*x "
104 "%*x:%*x %*x %*d %*d %*llu",
105 eaddr, &tmp_port) == 2
106 ) {
107 /* TODO add IPV6 support if enabled
108 * Presently assumes IPV4 */
109 #ifdef DEBUG
110 syslog(LOG_DEBUG, "port_in_use check port %u and address %s", tmp_port, eaddr);
111 #endif
112 if (tmp_port == eport) {
113 char tmp_addr[4];
114 struct in_addr *tmp_ip_addr = (struct in_addr *)tmp_addr;
115 if (sscanf(eaddr,"%2hhx%2hhx%2hhx%2hhx",
116 &tmp_addr[3],&tmp_addr[2],&tmp_addr[1],&tmp_addr[0]) == 4)
117 {
118 if (tmp_ip_addr->s_addr == 0 || tmp_ip_addr->s_addr == ip_addr.s_addr)
119 {
120 found++;
121 break; /* don't care how many, just that we found at least one */
122 }
123 }
124 }
125 }
126 }
127 fclose(f);
128
129 #elif defined(__OpenBSD__)
130 static struct nlist list[] = {
131 #if 0
132 {"_tcpstat", 0, 0, 0, 0},
133 {"_udpstat", 0, 0, 0, 0},
134 {"_tcbinfo", 0, 0, 0, 0},
135 {"_udbinfo", 0, 0, 0, 0},
136 #endif
137 {"_tcbtable", 0, 0, 0, 0},
138 {"_udbtable", 0, 0, 0, 0},
139 {NULL,0, 0, 0, 0}
140 };
141 char errstr[_POSIX2_LINE_MAX];
142 kvm_t *kd;
143 ssize_t n;
144 struct inpcbtable table;
145 struct inpcb *next;
146 struct inpcb inpcb;
147 kd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errstr);
148 if(!kd) {
149 syslog(LOG_ERR, "%s: kvm_openfiles(): %s",
150 "portinuse()", errstr);
151 return -1;
152 }
153 if(kvm_nlist(kd, list) < 0) {
154 syslog(LOG_ERR, "%s: kvm_nlist(): %s",
155 "portinuse()", kvm_geterr(kd));
156 kvm_close(kd);
157 return -1;
158 }
159 n = kvm_read(kd, list[(proto==IPPROTO_TCP)?0:1].n_value, &table, sizeof(table));
160 if(n < 0) {
161 syslog(LOG_ERR, "%s: kvm_read(): %s",
162 "portinuse()", kvm_geterr(kd));
163 kvm_close(kd);
164 return -1;
165 }
166 /* inpt_queue was CIRCLEQ_HEAD, it is TAILQ_HEAD since OpenBSD 5.5 */
167 #ifdef INPT_QUEUE_IS_CIRCLEQ
168 next = CIRCLEQ_FIRST(&table.inpt_queue);
169 #else
170 next = TAILQ_FIRST(&table.inpt_queue);
171 #endif
172 while(next != NULL) {
173 if(((u_long)next & 3) != 0) break;
174 n = kvm_read(kd, (u_long)next, &inpcb, sizeof(inpcb));
175 if(n < 0) {
176 syslog(LOG_ERR, "kvm_read(): %s", kvm_geterr(kd));
177 break;
178 }
179 #ifdef INPT_QUEUE_IS_CIRCLEQ
180 next = CIRCLEQ_NEXT(&inpcb, inp_queue);
181 #else
182 next = TAILQ_NEXT(&inpcb, inp_queue);
183 #endif
184 /* skip IPv6 sockets */
185 if((inpcb.inp_flags & INP_IPV6) != 0)
186 continue;
187 #ifdef DEBUG
188 syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu",
189 (u_long)inpcb.inp_laddr.s_addr, ntohs(inpcb.inp_lport),
190 (u_long)inpcb.inp_faddr.s_addr, ntohs(inpcb.inp_fport));
191 #endif
192 if(eport == (unsigned)ntohs(inpcb.inp_lport)) {
193 if(inpcb.inp_laddr.s_addr == INADDR_ANY || inpcb.inp_laddr.s_addr == ip_addr.s_addr) {
194 found++;
195 break; /* don't care how many, just that we found at least one */
196 }
197 }
198 }
199 kvm_close(kd);
200
201 #elif defined(__DragonFly__)
202 const char *varname;
203 struct xinpcb *xip;
204 struct xtcpcb *xtp;
205 struct inpcb *inp;
206 void *buf = NULL;
207 void *so_begin, *so_end;
208
209 size_t len;
210
211 switch (proto) {
212 case IPPROTO_TCP:
213 varname = "net.inet.tcp.pcblist";
214 break;
215 case IPPROTO_UDP:
216 varname = "net.inet.udp.pcblist";
217 break;
218 default:
219 syslog(LOG_ERR, "port_in_use() unknown proto=%d", proto);
220 return -1;
221 }
222
223 if (sysctlbyname(varname, NULL, &len, NULL, 0) < 0) {
224 syslog(LOG_ERR, "sysctlbyname(%s, NULL, ...): %m", varname);
225 return -1;
226 }
227 buf = malloc(len);
228 if (buf == NULL) {
229 syslog(LOG_ERR, "malloc(%u) failed", (unsigned)len);
230 return -1;
231 }
232 if (sysctlbyname(varname, buf, &len, NULL, 0) < 0) {
233 syslog(LOG_ERR, "sysctlbyname(%s, buf, ...): %m", varname);
234 free(buf);
235 return -1;
236 }
237
238 so_begin = buf;
239 so_end = (uint8_t *)buf + len;
240 for (so_begin = buf, so_end = (uint8_t *)so_begin + len;
241 (uint8_t *)so_begin + sizeof(size_t) < (uint8_t *)so_end &&
242 (uint8_t *)so_begin + *(size_t *)so_begin <= (uint8_t *)so_end;
243 so_begin = (uint8_t *)so_begin + *(size_t *)so_begin) {
244 switch (proto) {
245 case IPPROTO_TCP:
246 xtp = (struct xtcpcb *)so_begin;
247 if (xtp->xt_len != sizeof *xtp) {
248 syslog(LOG_WARNING, "struct xtcpcb size mismatch; %ld vs %ld",
249 (long)xtp->xt_len, sizeof *xtp);
250 free(buf);
251 return -1;
252 }
253 inp = &xtp->xt_inp;
254 break;
255 case IPPROTO_UDP:
256 xip = (struct xinpcb *)so_begin;
257 if (xip->xi_len != sizeof *xip) {
258 syslog(LOG_WARNING, "struct xinpcb size mismatch : %ld vs %ld",
259 (long)xip->xi_len, sizeof *xip);
260 free(buf);
261 return -1;
262 }
263 inp = &xip->xi_inp;
264 break;
265 default:
266 abort();
267 }
268 /* no support for IPv6 */
269 if (INP_ISIPV6(inp) != 0)
270 continue;
271 syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu <=> %u %08lx:%u",
272 (u_long)inp->inp_laddr.s_addr, ntohs(inp->inp_lport),
273 (u_long)inp->inp_faddr.s_addr, ntohs(inp->inp_fport),
274 eport, (u_long)ip_addr.s_addr, iport
275 );
276 if (eport == (unsigned)ntohs(inp->inp_lport)) {
277 if (inp->inp_laddr.s_addr == INADDR_ANY || inp->inp_laddr.s_addr == ip_addr.s_addr) {
278 found++;
279 break; /* don't care how many, just that we found at least one */
280 }
281 }
282 }
283 if (buf) {
284 free(buf);
285 buf = NULL;
286 }
287 #elif defined(__FreeBSD__)
288 const char *varname;
289 struct xinpgen *xig, *exig;
290 struct xinpcb *xip;
291 struct xtcpcb *xtp;
292 struct in_conninfo *inc;
293 void *buf = NULL;
294 size_t len;
295
296 switch (proto) {
297 case IPPROTO_TCP:
298 varname = "net.inet.tcp.pcblist";
299 break;
300 case IPPROTO_UDP:
301 varname = "net.inet.udp.pcblist";
302 break;
303 default:
304 syslog(LOG_ERR, "port_in_use() unknown proto=%d", proto);
305 return -1;
306 }
307
308 if (sysctlbyname(varname, NULL, &len, NULL, 0) < 0) {
309 syslog(LOG_ERR, "sysctlbyname(%s, NULL, ...): %m", varname);
310 return -1;
311 }
312 buf = malloc(len);
313 if (buf == NULL) {
314 syslog(LOG_ERR, "malloc(%u) failed", (unsigned)len);
315 return -1;
316 }
317 if (sysctlbyname(varname, buf, &len, NULL, 0) < 0) {
318 syslog(LOG_ERR, "sysctlbyname(%s, buf, ...): %m", varname);
319 free(buf);
320 return -1;
321 }
322
323 xig = (struct xinpgen *)buf;
324 exig = (struct xinpgen *)(void *)((char *)buf + len - sizeof *exig);
325 if (xig->xig_len != sizeof *xig) {
326 syslog(LOG_WARNING, "struct xinpgen size mismatch; %ld vs %ld",
327 (long)xig->xig_len, sizeof *xig);
328 free(buf);
329 return -1;
330 }
331 if (exig->xig_len != sizeof *exig) {
332 syslog(LOG_WARNING, "struct xinpgen size mismatch; %ld vs %ld",
333 (long)exig->xig_len, sizeof *exig);
334 free(buf);
335 return -1;
336 }
337
338 while (1) {
339 xig = (struct xinpgen *)(void *)((char *)xig + xig->xig_len);
340 if (xig >= exig)
341 break;
342 switch (proto) {
343 case IPPROTO_TCP:
344 xtp = (struct xtcpcb *)xig;
345 if (xtp->xt_len != sizeof *xtp) {
346 syslog(LOG_WARNING, "struct xtcpcb size mismatch; %ld vs %ld",
347 (long)xtp->xt_len, sizeof *xtp);
348 free(buf);
349 return -1;
350 }
351 xip = &xtp->xt_inp;
352 inc = &xip->inp_inc;
353 break;
354 case IPPROTO_UDP:
355 xip = (struct xinpcb *)xig;
356 if (xip->xi_len != sizeof *xip) {
357 syslog(LOG_WARNING, "struct xinpcb size mismatch : %ld vs %ld",
358 (long)xip->xi_len, sizeof *xip);
359 free(buf);
360 return -1;
361 }
362 inc = &xip->inp_inc;
363 break;
364 default:
365 abort();
366 }
367 /* no support for IPv6 */
368 if ((xip->inp_vflag & INP_IPV6) != 0)
369 continue;
370 syslog(LOG_DEBUG, "%08lx:%hu %08lx:%hu <=> %u %08lx:%u",
371 (u_long)inc->inc_laddr.s_addr, ntohs(inc->inc_lport),
372 (u_long)inc->inc_faddr.s_addr, ntohs(inc->inc_fport),
373 eport, (u_long)ip_addr.s_addr, iport
374 );
375 if (eport == (unsigned)ntohs(inc->inc_lport)) {
376 if (inc->inc_laddr.s_addr == INADDR_ANY || inc->inc_laddr.s_addr == ip_addr.s_addr) {
377 found++;
378 break; /* don't care how many, just that we found at least one */
379 }
380 }
381 }
382 if (buf) {
383 free(buf);
384 buf = NULL;
385 }
386 /* #elif __NetBSD__ */
387 #else
388 /* TODO : NetBSD / Darwin (OS X) / Solaris code */
389 #error "No port_in_use() implementation available for this OS"
390 #endif
391
392 /* Phase 2 : check existing mappings
393 * TODO : implement for pf/ipfw/etc. */
394 #if defined(USE_NETFILTER)
395 if (!found) {
396 char iaddr_old[16];
397 unsigned short iport_old;
398 int i;
399 for (i = 0; chains_to_check[i]; i++) {
400 if (get_nat_redirect_rule(chains_to_check[i], if_name, eport, proto,
401 iaddr_old, sizeof(iaddr_old), &iport_old,
402 0, 0, 0, 0, 0, 0, 0) == 0)
403 {
404 syslog(LOG_DEBUG, "port_in_use check port %d on nat chain %s redirected to %s port %d", eport,
405 chains_to_check[i], iaddr_old, iport_old);
406 if (!(strcmp(iaddr, iaddr_old)==0 && iport==iport_old)) {
407 /* only "in use" if redirected to somewhere else */
408 found++;
409 break; /* don't care how many, just that we found at least one */
410 }
411 }
412 }
413 }
414 #else /* USE_NETFILTER */
415 UNUSED(iport); UNUSED(iaddr);
416 #endif /* USE_NETFILTER */
417 return found;
418 }
419 #endif /* CHECK_PORTINUSE */
420