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