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