1 /* $Id: nftpinhole.c,v 1.7 2020/11/11 12:08:43 nanard Exp $ */
2 /* MiniUPnP project
3  * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4  * (c) 2012-2020 Thomas Bernard
5  * This software is subject to the conditions detailed
6  * in the LICENCE file provided within the distribution */
7 
8 #include <stdio.h>
9 #include <stddef.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <syslog.h>
13 #include <errno.h>
14 #include <sys/socket.h>
15 #include <sys/types.h>
16 #include <netinet/in.h>
17 #include <netinet/ip.h>
18 #include <netinet/tcp.h>
19 #include <arpa/inet.h>
20 #include <dlfcn.h>
21 #include <limits.h>
22 
23 #include "../upnputils.h"
24 #include "nftpinhole.h"
25 
26 #include <linux/version.h>
27 
28 #include <linux/netfilter.h>
29 #include <linux/netfilter/nfnetlink.h>
30 #include <linux/netfilter/nf_tables.h>
31 
32 #include <libmnl/libmnl.h>
33 #include <libnftnl/rule.h>
34 #include <libnftnl/expr.h>
35 
36 #include "tiny_nf_nat.h"
37 
38 #include "config.h"
39 #include "../macros.h"
40 #include "nftnlrdr.h"
41 #include "../upnpglobalvars.h"
42 
43 #include "nftnlrdr_misc.h"
44 
45 #ifdef DEBUG
46 #define d_printf(x) do { printf x; } while (0)
47 #else
48 #define d_printf(x)
49 #endif
50 
51 #ifdef ENABLE_UPNPPINHOLE
52 
53 static int next_uid = 1;
54 
55 #define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s"
56 #define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s"
57 
init_iptpinhole(void)58 void init_iptpinhole(void)
59 {
60 	return;
61 }
62 
shutdown_iptpinhole(void)63 void shutdown_iptpinhole(void)
64 {
65 	return;
66 }
67 
68 /*
69 ip saddr <rem_host> ip daddr <int_client> tcp sport <rem_port>  tcp dport <int_port>
70 */
add_pinhole(const char * ifname,const char * rem_host,unsigned short rem_port,const char * int_client,unsigned short int_port,int proto,const char * desc,unsigned int timestamp)71 int add_pinhole(const char * ifname,
72                 const char * rem_host, unsigned short rem_port,
73                 const char * int_client, unsigned short int_port,
74                 int proto, const char * desc, unsigned int timestamp)
75 {
76 	int uid, res;
77 	char comment[NFT_DESCR_SIZE];
78 
79 	struct nftnl_rule *r = NULL;
80 	struct in6_addr rhost_addr, ihost_addr;
81 	struct in6_addr *rhost_addr_p;
82 
83 	uid = next_uid;
84 
85 	d_printf(("add_pinhole(%s, %s, %s, %d, %d, %d, %s)\n",
86 	          ifname, rem_host, int_client, rem_port, int_port, proto, desc));
87 
88 	if (rem_host && rem_host[0] != '\0' && rem_host[0] != '*') {
89 		inet_pton(AF_INET6, rem_host, &rhost_addr);
90 		rhost_addr_p = &rhost_addr;
91 	} else {
92 		rhost_addr_p = NULL;
93 	}
94 
95 	inet_pton(AF_INET6, int_client, &ihost_addr);
96 
97 	snprintf(comment, NFT_DESCR_SIZE,
98 		         PINEHOLE_LABEL_FORMAT, uid, timestamp, desc);
99 
100 	r = rule_set_filter6(NFPROTO_INET, ifname, proto,
101 			    rhost_addr_p, &ihost_addr,
102 				0, int_port, rem_port, comment, 0);
103 
104 	res = nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_FILTER);
105 
106 	if (res < 0)
107 		return -1;
108 
109 	if (++next_uid >= 65535) {
110 		next_uid = 1;
111 	}
112 
113 	return uid;
114 }
115 
116 int
find_pinhole(const char * ifname,const char * rem_host,unsigned short rem_port,const char * int_client,unsigned short int_port,int proto,char * desc,int desc_len,unsigned int * timestamp)117 find_pinhole(const char * ifname,
118              const char * rem_host, unsigned short rem_port,
119              const char * int_client, unsigned short int_port,
120              int proto,
121              char *desc, int desc_len, unsigned int * timestamp)
122 {
123 	rule_t *p;
124 	struct in6_addr saddr;
125 	struct in6_addr daddr;
126 	int uid;
127 	unsigned int ts;
128 	UNUSED(ifname);
129 
130 	if (rem_host && rem_host[0] != '\0' && rem_host[0] != '*') {
131 		inet_pton(AF_INET6, rem_host, &saddr);
132 	} else {
133 		memset(&saddr, 0, sizeof(struct in6_addr));
134 	}
135 	inet_pton(AF_INET6, int_client, &daddr);
136 
137 	d_printf(("find_pinhole()\n"));
138 	refresh_nft_cache_filter();
139 
140 	LIST_FOREACH(p, &head_filter, entry) {
141 
142 		// Only forward entries
143 		if (p->type != RULE_FILTER)
144 			continue;
145 
146 		if (p->desc_len == 0)
147 			continue;
148 
149 		if ((proto == p->proto) && (rem_port == p->rport)
150 		   && (0 == memcmp(&saddr, &p->rhost6, sizeof(struct in6_addr)))
151 		   && (int_port == p->eport) &&
152 		   (0 == memcmp(&daddr, &p->iaddr6, sizeof(struct in6_addr)))) {
153 
154 			if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
155 				syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
156 				continue;
157 			}
158 
159 			if (timestamp)
160 				*timestamp = ts;
161 
162 			if (desc) {
163 				char * pd = strchr(p->desc, ':');
164 				if(pd) {
165 					pd += 2;
166 					strncpy(desc, pd, desc_len);
167 				}
168 			}
169 
170 			return uid;
171 		}
172 	}
173 
174 	return -2;	/* not found */
175 }
176 
177 int
delete_pinhole(unsigned short uid)178 delete_pinhole(unsigned short uid)
179 {
180 	rule_t *p;
181 	struct nftnl_rule *r;
182 	char label_start[NFT_DESCR_SIZE];
183 	char tmp_label[NFT_DESCR_SIZE];
184 
185 	snprintf(label_start, sizeof(label_start),
186 	         "pinhole-%hu", uid);
187 
188 	d_printf(("delete_pinhole()\n"));
189 	refresh_nft_cache_filter();
190 
191 	LIST_FOREACH(p, &head_filter, entry) {
192 		// Only forward entries
193 		if (p->type != RULE_FILTER)
194 			continue;
195 
196 		if (p->desc_len == 0)
197 			continue;
198 
199 		strncpy(tmp_label, p->desc, p->desc_len);
200 		strtok(tmp_label, " ");
201 		if (0 == strcmp(tmp_label, label_start)) {
202 			r = rule_del_handle(p);
203 			nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER);
204 			return 0;
205 		}
206 	}
207 
208 	return -2;
209 }
210 
211 int
update_pinhole(unsigned short uid,unsigned int timestamp)212 update_pinhole(unsigned short uid, unsigned int timestamp)
213 {
214 #ifdef DEBUG
215 	char iaddr[INET6_ADDRSTRLEN];
216 #endif
217 	char raddr[INET6_ADDRSTRLEN];
218 	char label_start[NFT_DESCR_SIZE];
219 	char tmp_label[NFT_DESCR_SIZE];
220 	char desc[NFT_DESCR_SIZE];
221 	char ifname[IFNAMSIZ];
222 	char comment[NFT_DESCR_SIZE];
223 	char * tmp_p;
224 	uint32_t ext_if_indx;
225 	int proto, res;
226 	unsigned short iport, rport;
227 	rule_t *p;
228 	struct in6_addr rhost_addr, ihost_addr;
229 	struct in6_addr * rhost_addr_p;
230 	struct nftnl_rule *r;
231 
232 	d_printf(("update_pinhole()\n"));
233 
234 	snprintf(label_start, sizeof(label_start),
235 	         "pinhole-%hu", uid);
236 
237 	refresh_nft_cache_filter();
238 
239 	proto = -1;
240 	memset(&rhost_addr, 0, sizeof(struct in6_addr));
241 
242 	LIST_FOREACH(p, &head_filter, entry) {
243 		// Only forward entries
244 		if (p->type != RULE_FILTER)
245 			continue;
246 
247 		if (p->desc_len == 0)
248 			continue;
249 
250 		strncpy(tmp_label, p->desc, p->desc_len);
251 		strtok(tmp_label, " ");
252 		if (0 == strcmp(tmp_label, label_start)) {
253 			/* Source IP Address */
254 			// Check if empty
255 			if (0 == memcmp(&rhost_addr, &p->rhost6, sizeof(struct in6_addr))) {
256 				rhost_addr_p = NULL;
257 				raddr[0] = '*';
258 				raddr[1] = '\0';
259 			} else {
260 				rhost_addr_p = &p->rhost6;
261 				inet_ntop(AF_INET6, rhost_addr_p, raddr, INET6_ADDRSTRLEN);
262 			}
263 
264 			/* Source Port */
265 			rport = p->iport;
266 
267 			/* Destination IP Address */
268 			ihost_addr = p->iaddr6;
269 
270 			/* Destination Port */
271 			iport = p->eport;
272 
273 			proto = p->proto;
274 
275 			ext_if_indx = p->ingress_ifidx;
276 			if_indextoname(ext_if_indx, ifname);
277 
278 			tmp_p = tmp_label;
279 			strsep(&tmp_p, " ");
280 			if (tmp_p) {
281 				strncpy(desc, tmp_p, NFT_DESCR_SIZE);
282 			} else {
283 				desc[0] = '\0';
284 			}
285 
286 			break;
287 		}
288 	}
289 
290 	if (proto == -1)
291 		return -2;
292 
293 	// Delete rule
294 	r = rule_del_handle(p);
295 	res = nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER);
296 
297 	if (res < 0)
298 		return -1;
299 
300 	// readd rule with new timestamp
301 	snprintf(comment, NFT_DESCR_SIZE,
302 		         PINEHOLE_LABEL_FORMAT, uid, timestamp, desc);
303 
304 	d_printf(("update add_pinhole(%s, %s, %s, %d, %d, %d, %s)\n",
305 	          ifname, raddr, inet_ntop(AF_INET6, &ihost_addr, iaddr, INET6_ADDRSTRLEN), rport, iport, proto, comment));
306 
307 	r = rule_set_filter6(NFPROTO_INET, ifname, proto,
308 			    rhost_addr_p, &ihost_addr,
309 				0, iport, rport, comment, 0);
310 
311 	res = nft_send_rule(r, NFT_MSG_NEWRULE, RULE_CHAIN_FILTER);
312 
313 	if (res < 0)
314 		return -1;
315 
316 	return 0;
317 }
318 
319 int
get_pinhole_info(unsigned short uid,char * rem_host,int rem_hostlen,unsigned short * rem_port,char * int_client,int int_clientlen,unsigned short * int_port,int * proto,char * desc,int desclen,unsigned int * timestamp,u_int64_t * packets,u_int64_t * bytes)320 get_pinhole_info(unsigned short uid,
321                  char * rem_host, int rem_hostlen,
322                  unsigned short * rem_port,
323                  char * int_client, int int_clientlen,
324                  unsigned short * int_port,
325                  int * proto, char * desc, int desclen,
326                  unsigned int * timestamp,
327                  u_int64_t * packets, u_int64_t * bytes)
328 {
329 	rule_t *p;
330 	unsigned int ts;
331 	char label_start[NFT_DESCR_SIZE];
332 	char tmp_label[NFT_DESCR_SIZE];
333 
334 	snprintf(label_start, sizeof(label_start),
335 	         "pinhole-%hu", uid);
336 
337 	d_printf(("get_pinhole_info()\n"));
338 	refresh_nft_cache_filter();
339 
340 	LIST_FOREACH(p, &head_filter, entry) {
341 		// Only forward entries
342 		if (p->type != RULE_FILTER)
343 			continue;
344 
345 		if (p->desc_len == 0)
346 			continue;
347 
348 		strncpy(tmp_label, p->desc, p->desc_len);
349 		strtok(tmp_label, " ");
350 		if (0 == strcmp(tmp_label, label_start)) {
351 			/* Source IP Address */
352 			if (rem_host && (rem_host[0] != '\0')) {
353 				if(inet_ntop(AF_INET6, &p->rhost6, rem_host, rem_hostlen) == NULL)
354 					return -1;
355 			}
356 
357 			/* Source Port */
358 			if (rem_port)
359 				*rem_port = p->rport;
360 
361 			/* Destination IP Address */
362 			if (int_client) {
363 				if(inet_ntop(AF_INET6, &p->iaddr6, int_client, int_clientlen) == NULL)
364 					return -1;
365 			}
366 
367 			/* Destination Port */
368 			if (int_port)
369 				*int_port = p->eport;
370 
371 			if (proto)
372 				*proto = p->proto;
373 
374 			if (timestamp) {
375 				int uid_temp;
376 				if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid_temp, &ts) != 2) {
377 					syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
378 					continue;
379 				}
380 
381 				*timestamp = ts;
382 			}
383 
384 			if (desc)
385 				strncpy(desc, p->desc, desclen);
386 
387 			if (packets || bytes) {
388 				if (packets)
389 					*packets = p->packets;
390 				if (bytes)
391 					*bytes = p->bytes;
392 			}
393 
394 			break;
395 		}
396 	}
397 
398 	d_printf(("end_pinhole_info()\n"));
399 
400 	return 0;
401 }
402 
get_pinhole_uid_by_index(int index)403 int get_pinhole_uid_by_index(int index)
404 {
405 	UNUSED(index);
406 	return -42;
407 }
408 
409 int
clean_pinhole_list(unsigned int * next_timestamp)410 clean_pinhole_list(unsigned int * next_timestamp)
411 {
412 	rule_t *p;
413 	struct nftnl_rule *r;
414 	time_t current_time;
415 	unsigned int ts;
416 	int uid;
417 	unsigned int min_ts = UINT_MAX;
418 	int min_uid = INT_MAX, max_uid = -1;
419 	int n = 0;
420 
421 	current_time = upnp_time();
422 
423 	d_printf(("clean_pinhole_list()\n"));
424 	refresh_nft_cache_filter();
425 
426 	LIST_FOREACH(p, &head_filter, entry) {
427 		// Only forward entries
428 		if (p->type != RULE_FILTER)
429 			continue;
430 
431 		if (p->desc_len == 0)
432 			continue;
433 
434 		if (sscanf(p->desc, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) {
435 			syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", p->desc);
436 			continue;
437 		}
438 
439 		if (ts <= (unsigned int)current_time) {
440 			syslog(LOG_INFO, "removing expired pinhole '%s'", p->desc);
441 			r = rule_del_handle(p);
442 			nft_send_rule(r, NFT_MSG_DELRULE, RULE_CHAIN_FILTER);
443 			n++;
444 		} else {
445 			if (uid > max_uid)
446 				max_uid = uid;
447 			else if (uid < min_uid)
448 				min_uid = uid;
449 			if (ts < min_ts)
450 				min_ts = ts;
451 		}
452 	}
453 
454 	if (next_timestamp && (min_ts != UINT_MAX))
455 		*next_timestamp = min_ts;
456 
457 	if (max_uid > 0) {
458 		if (((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) {
459 			next_uid = max_uid + 1;
460 		}
461 
462 		if (next_uid >= 65535) {
463 			next_uid = 1;
464 		}
465 	}
466 
467 	return n;	/* number of rules removed */
468 }
469 
470 #endif /* ENABLE_UPNPPINHOLE */
471