1 /* $Id: upnpredirect.c,v 1.85 2014/12/09 09:17:54 nanard Exp $ */
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2014 Thomas Bernard
5 * This software is subject to the conditions detailed
6 * in the LICENCE file provided within the distribution */
11 #include <sys/types.h>
12 #include <sys/socket.h>
13 #include <netinet/in.h>
15 #include <arpa/inet.h>
23 #include "upnpredirect.h"
24 #include "upnpglobalvars.h"
25 #include "upnpevents.h"
26 #include "portinuse.h"
27 #if defined(USE_NETFILTER)
28 #include "netfilter/iptcrdr.h"
31 #include "pf/obsdrdr.h"
34 #include "ipf/ipfrdr.h"
37 #include "ipfw/ipfwrdr.h"
39 #ifdef USE_MINIUPNPDCTL
43 #ifdef ENABLE_LEASEFILE
47 /* from <inttypes.h> */
53 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
55 proto_atoi(const char * protocol
)
57 int proto
= IPPROTO_TCP
;
58 if(strcmp(protocol
, "UDP") == 0)
63 #ifdef ENABLE_LEASEFILE
65 lease_file_add(unsigned short eport
,
70 unsigned int timestamp
)
74 if (lease_file
== NULL
) return 0;
76 fd
= fopen( lease_file
, "a");
78 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
82 fprintf(fd
, "%s:%hu:%s:%hu:%u:%s\n",
83 ((proto
==IPPROTO_TCP
)?"TCP":"UDP"), eport
, iaddr
, iport
,
91 lease_file_remove(unsigned short eport
, int proto
)
97 char tmpfilename
[128];
98 int str_size
, buf_size
;
101 if (lease_file
== NULL
) return 0;
103 if (strlen( lease_file
) + 7 > sizeof(tmpfilename
)) {
104 syslog(LOG_ERR
, "Lease filename is too long");
108 strncpy( tmpfilename
, lease_file
, sizeof(tmpfilename
) );
109 strncat( tmpfilename
, "XXXXXX", sizeof(tmpfilename
) - strlen(tmpfilename
));
111 fd
= fopen( lease_file
, "r");
116 snprintf( str
, sizeof(str
), "%s:%u", ((proto
==IPPROTO_TCP
)?"TCP":"UDP"), eport
);
117 str_size
= strlen(str
);
119 tmp
= mkstemp(tmpfilename
);
122 syslog(LOG_ERR
, "could not open temporary lease file");
125 fchmod(tmp
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
126 fdt
= fdopen(tmp
, "a");
128 buf
[sizeof(buf
)-1] = 0;
129 while( fgets(buf
, sizeof(buf
)-1, fd
) != NULL
) {
130 buf_size
= strlen(buf
);
132 if (buf_size
< str_size
|| strncmp(str
, buf
, str_size
)!=0) {
133 fwrite(buf
, buf_size
, 1, fdt
);
139 if (rename(tmpfilename
, lease_file
) < 0) {
140 syslog(LOG_ERR
, "could not rename temporary lease file to %s", lease_file
);
148 /* reload_from_lease_file()
149 * read lease_file and add the rules contained
151 int reload_from_lease_file()
155 unsigned short eport
, iport
;
160 unsigned int leaseduration
;
161 unsigned int timestamp
;
166 if(!lease_file
) return -1;
167 fd
= fopen( lease_file
, "r");
169 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
172 if(unlink(lease_file
) < 0) {
173 syslog(LOG_WARNING
, "could not unlink file %s : %m", lease_file
);
176 current_time
= time(NULL
);
177 while(fgets(line
, sizeof(line
), fd
)) {
178 syslog(LOG_DEBUG
, "parsing lease file line '%s'", line
);
180 p
= strchr(line
, ':');
182 syslog(LOG_ERR
, "unrecognized data in lease file");
186 iaddr
= strchr(p
, ':');
188 syslog(LOG_ERR
, "unrecognized data in lease file");
192 eport
= (unsigned short)atoi(p
);
193 p
= strchr(iaddr
, ':');
195 syslog(LOG_ERR
, "unrecognized data in lease file");
199 iport
= (unsigned short)atoi(p
);
202 syslog(LOG_ERR
, "unrecognized data in lease file");
206 desc
= strchr(p
, ':');
208 syslog(LOG_ERR
, "unrecognized data in lease file");
212 /*timestamp = (unsigned int)atoi(p);*/
213 timestamp
= (unsigned int)strtoul(p
, NULL
, 10);
214 /* trim description */
215 while(isspace(*desc
))
220 while(isspace(*p
) && (p
> desc
))
224 if(timestamp
<= (unsigned int)current_time
) {
225 syslog(LOG_NOTICE
, "already expired lease in lease file");
228 leaseduration
= timestamp
- current_time
;
231 leaseduration
= 0; /* default value */
234 r
= upnp_redirect(rhost
, eport
, iaddr
, iport
, proto
, desc
, leaseduration
);
236 syslog(LOG_ERR
, "Failed to redirect %hu -> %s:%hu protocol %s",
237 eport
, iaddr
, iport
, proto
);
239 /* Add the redirection again to the lease file */
240 lease_file_add(eport
, iaddr
, iport
, proto_atoi(proto
),
251 * calls OS/fw dependant implementation of the redirection.
252 * protocol should be the string "TCP" or "UDP"
253 * returns: 0 on success
254 * -1 failed to redirect
255 * -2 already redirected
256 * -3 permission check failed
259 upnp_redirect(const char * rhost
, unsigned short eport
,
260 const char * iaddr
, unsigned short iport
,
261 const char * protocol
, const char * desc
,
262 unsigned int leaseduration
)
266 unsigned short iport_old
;
267 struct in_addr address
;
268 unsigned int timestamp
;
270 proto
= proto_atoi(protocol
);
271 if(inet_aton(iaddr
, &address
) <= 0) {
272 syslog(LOG_ERR
, "inet_aton(%s) FAILED", iaddr
);
276 if(!check_upnp_rule_against_permissions(upnppermlist
, num_upnpperm
,
277 eport
, address
, iport
)) {
278 syslog(LOG_INFO
, "redirection permission check failed for "
279 "%hu->%s:%hu %s", eport
, iaddr
, iport
, protocol
);
282 r
= get_redirect_rule(ext_if_name
, eport
, proto
,
283 iaddr_old
, sizeof(iaddr_old
), &iport_old
, 0, 0,
287 /* if existing redirect rule matches redirect request return success
288 * xbox 360 does not keep track of the port it redirects and will
289 * redirect another port when receiving ConflictInMappingEntry */
290 if(strcmp(iaddr
, iaddr_old
)==0 && iport
==iport_old
) {
291 syslog(LOG_INFO
, "ignoring redirect request as it matches existing redirect");
294 syslog(LOG_INFO
, "port %hu protocol %s already redirected to %s:%hu",
295 eport
, protocol
, iaddr_old
, iport_old
);
298 #ifdef CHECK_PORTINUSE
299 } else if (port_in_use(ext_if_name
, eport
, proto
, iaddr
, iport
) > 0) {
300 syslog(LOG_INFO
, "port %hu protocol %s already in use",
303 #endif /* CHECK_PORTINUSE */
305 timestamp
= (leaseduration
> 0) ? time(NULL
) + leaseduration
: 0;
306 syslog(LOG_INFO
, "redirecting port %hu to %s:%hu protocol %s for: %s",
307 eport
, iaddr
, iport
, protocol
, desc
);
308 return upnp_redirect_internal(rhost
, eport
, iaddr
, iport
, proto
,
316 upnp_redirect_internal(const char * rhost
, unsigned short eport
,
317 const char * iaddr
, unsigned short iport
,
318 int proto
, const char * desc
,
319 unsigned int timestamp
)
321 /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
322 eport, iaddr, iport, protocol, desc); */
323 if(add_redirect_rule2(ext_if_name
, rhost
, eport
, iaddr
, iport
, proto
,
324 desc
, timestamp
) < 0) {
328 #ifdef ENABLE_LEASEFILE
329 lease_file_add( eport
, iaddr
, iport
, proto
, desc
, timestamp
);
331 /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
332 iaddr, iport, protocol, desc);*/
333 if(add_filter_rule2(ext_if_name
, rhost
, iaddr
, eport
, iport
, proto
, desc
) < 0) {
334 /* clean up the redirect rule */
335 #if !defined(__linux__)
336 delete_redirect_rule(ext_if_name
, eport
, proto
);
341 if(!nextruletoclean_timestamp
|| (timestamp
< nextruletoclean_timestamp
))
342 nextruletoclean_timestamp
= timestamp
;
345 /* the number of port mappings changed, we must
346 * inform the subscribers */
347 upnp_event_var_change_notify(EWanIPC
);
354 /* Firewall independant code which call the FW dependant code. */
356 upnp_get_redirection_infos(unsigned short eport
, const char * protocol
,
357 unsigned short * iport
,
358 char * iaddr
, int iaddrlen
,
359 char * desc
, int desclen
,
360 char * rhost
, int rhostlen
,
361 unsigned int * leaseduration
)
364 unsigned int timestamp
;
367 if(desc
&& (desclen
> 0))
369 if(rhost
&& (rhostlen
> 0))
371 r
= get_redirect_rule(ext_if_name
, eport
, proto_atoi(protocol
),
372 iaddr
, iaddrlen
, iport
, desc
, desclen
,
373 rhost
, rhostlen
, ×tamp
,
377 timestamp
> (unsigned int)(current_time
= time(NULL
))) {
378 *leaseduration
= timestamp
- current_time
;
386 upnp_get_redirection_infos_by_index(int index
,
387 unsigned short * eport
, char * protocol
,
388 unsigned short * iport
,
389 char * iaddr
, int iaddrlen
,
390 char * desc
, int desclen
,
391 char * rhost
, int rhostlen
,
392 unsigned int * leaseduration
)
394 /*char ifname[IFNAMSIZ];*/
396 unsigned int timestamp
;
399 if(desc
&& (desclen
> 0))
401 if(rhost
&& (rhostlen
> 0))
403 if(get_redirect_rule_by_index(index
, 0/*ifname*/, eport
, iaddr
, iaddrlen
,
404 iport
, &proto
, desc
, desclen
,
405 rhost
, rhostlen
, ×tamp
,
410 current_time
= time(NULL
);
411 *leaseduration
= (timestamp
> (unsigned int)current_time
)
412 ? (timestamp
- current_time
)
414 if(proto
== IPPROTO_TCP
)
415 memcpy(protocol
, "TCP", 4);
417 memcpy(protocol
, "UDP", 4);
422 /* called from natpmp.c too */
424 _upnp_delete_redir(unsigned short eport
, int proto
)
427 #if defined(__linux__)
428 r
= delete_redirect_and_filter_rules(eport
, proto
);
429 #elif defined(USE_PF)
430 r
= delete_redirect_and_filter_rules(ext_if_name
, eport
, proto
);
432 r
= delete_redirect_rule(ext_if_name
, eport
, proto
);
433 delete_filter_rule(ext_if_name
, eport
, proto
);
435 #ifdef ENABLE_LEASEFILE
436 lease_file_remove( eport
, proto
);
440 upnp_event_var_change_notify(EWanIPC
);
446 upnp_delete_redirection(unsigned short eport
, const char * protocol
)
448 syslog(LOG_INFO
, "removing redirect rule port %hu %s", eport
, protocol
);
449 return _upnp_delete_redir(eport
, proto_atoi(protocol
));
452 /* upnp_get_portmapping_number_of_entries()
453 * TODO: improve this code. */
455 upnp_get_portmapping_number_of_entries()
458 unsigned short eport
, iport
;
459 char protocol
[4], iaddr
[32], desc
[64], rhost
[32];
460 unsigned int leaseduration
;
462 protocol
[0] = '\0'; iaddr
[0] = '\0'; desc
[0] = '\0';
463 r
= upnp_get_redirection_infos_by_index(n
, &eport
, protocol
, &iport
,
464 iaddr
, sizeof(iaddr
),
466 rhost
, sizeof(rhost
),
473 /* functions used to remove unused rules
474 * As a side effect, delete expired rules (based on LeaseDuration) */
476 get_upnp_rules_state_list(int max_rules_number_target
)
478 /*char ifname[IFNAMSIZ];*/
480 unsigned short iport
;
481 unsigned int timestamp
;
482 struct rule_state
* tmp
;
483 struct rule_state
* list
= 0;
484 struct rule_state
* * p
;
488 /*ifname[0] = '\0';*/
489 tmp
= malloc(sizeof(struct rule_state
));
492 current_time
= time(NULL
);
493 nextruletoclean_timestamp
= 0;
494 while(get_redirect_rule_by_index(i
, /*ifname*/0, &tmp
->eport
, 0, 0,
495 &iport
, &proto
, 0, 0, 0,0, ×tamp
,
496 &tmp
->packets
, &tmp
->bytes
) >= 0)
500 /* need to remove this port mapping ? */
501 if(timestamp
<= (unsigned int)current_time
)
503 else if((nextruletoclean_timestamp
<= (unsigned int)current_time
)
504 || (timestamp
< nextruletoclean_timestamp
))
505 nextruletoclean_timestamp
= timestamp
;
507 tmp
->proto
= (short)proto
;
508 /* add tmp to list */
511 /* prepare next iteration */
513 tmp
= malloc(sizeof(struct rule_state
));
519 while(get_peer_rule_by_index(i
, /*ifname*/0, &tmp
->eport
, 0, 0,
520 &iport
, &proto
, 0, 0, 0,0,0, ×tamp
,
521 &tmp
->packets
, &tmp
->bytes
) >= 0)
525 /* need to remove this port mapping ? */
526 if(timestamp
<= (unsigned int)current_time
)
528 else if((nextruletoclean_timestamp
<= (unsigned int)current_time
)
529 || (timestamp
< nextruletoclean_timestamp
))
530 nextruletoclean_timestamp
= timestamp
;
532 tmp
->proto
= (short)proto
;
533 /* add tmp to list */
536 /* prepare next iteration */
538 tmp
= malloc(sizeof(struct rule_state
));
544 /* remove the redirections that need to be removed */
545 for(p
= &list
, tmp
= list
; tmp
; tmp
= *p
)
549 syslog(LOG_NOTICE
, "remove port mapping %hu %s because it has expired",
550 tmp
->eport
, (tmp
->proto
==IPPROTO_TCP
)?"TCP":"UDP");
551 _upnp_delete_redir(tmp
->eport
, tmp
->proto
);
559 /* return empty list if not enough redirections */
560 if(i
<=max_rules_number_target
)
572 remove_unused_rules(struct rule_state
* list
)
574 char ifname
[IFNAMSIZ
];
575 unsigned short iport
;
576 struct rule_state
* tmp
;
579 unsigned int timestamp
;
584 /* remove the rule if no traffic has used it */
585 if(get_redirect_rule(ifname
, list
->eport
, list
->proto
,
586 0, 0, &iport
, 0, 0, 0, 0, ×tamp
,
587 &packets
, &bytes
) >= 0)
589 if(packets
== list
->packets
&& bytes
== list
->bytes
)
591 _upnp_delete_redir(list
->eport
, list
->proto
);
600 syslog(LOG_NOTICE
, "removed %d unused rules", n
);
603 /* upnp_get_portmappings_in_range()
604 * return a list of all "external" ports for which a port
607 upnp_get_portmappings_in_range(unsigned short startport
,
608 unsigned short endport
,
609 const char * protocol
,
610 unsigned int * number
)
613 proto
= proto_atoi(protocol
);
616 return get_portmappings_in_range(startport
, endport
, proto
, number
);
619 /* stuff for miniupnpdctl */
620 #ifdef USE_MINIUPNPDCTL
622 write_ruleset_details(int s
)
625 unsigned short eport
, iport
;
629 unsigned int timestamp
;
636 write(s
, "Ruleset :\n", 10);
637 while(get_redirect_rule_by_index(i
, 0/*ifname*/, &eport
, iaddr
, sizeof(iaddr
),
638 &iport
, &proto
, desc
, sizeof(desc
),
639 rhost
, sizeof(rhost
),
641 &packets
, &bytes
) >= 0)
643 n
= snprintf(buffer
, sizeof(buffer
),
644 "%2d %s %s:%hu->%s:%hu "
645 "'%s' %u %" PRIu64
" %" PRIu64
"\n",
646 /*"'%s' %llu %llu\n",*/
647 i
, proto
==IPPROTO_TCP
?"TCP":"UDP", rhost
,
648 eport
, iaddr
, iport
, desc
, timestamp
, packets
, bytes
);