1 /* $Id: upnpredirect.c,v 1.80 2012/05/01 20:08:22 nanard Exp $ */
3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/
4 * (c) 2006-2012 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 #if defined(USE_NETFILTER)
27 #include "netfilter/iptcrdr.h"
30 #include "pf/obsdrdr.h"
33 #include "ipf/ipfrdr.h"
36 #include "ipfw/ipfwrdr.h"
38 #ifdef USE_MINIUPNPDCTL
42 #ifdef ENABLE_LEASEFILE
46 /* from <inttypes.h> */
52 * convert the string "UDP" or "TCP" to IPPROTO_UDP and IPPROTO_UDP */
54 proto_atoi(const char * protocol
)
56 int proto
= IPPROTO_TCP
;
57 if(strcmp(protocol
, "UDP") == 0)
62 #ifdef ENABLE_LEASEFILE
64 lease_file_add(unsigned short eport
,
69 unsigned int timestamp
)
73 if (lease_file
== NULL
) return 0;
75 fd
= fopen( lease_file
, "a");
77 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
81 fprintf(fd
, "%s:%hu:%s:%hu:%u:%s\n",
82 ((proto
==IPPROTO_TCP
)?"TCP":"UDP"), eport
, iaddr
, iport
,
90 lease_file_remove(unsigned short eport
, int proto
)
96 char tmpfilename
[128];
97 int str_size
, buf_size
;
100 if (lease_file
== NULL
) return 0;
102 if (strlen( lease_file
) + 7 > sizeof(tmpfilename
)) {
103 syslog(LOG_ERR
, "Lease filename is too long");
107 strncpy( tmpfilename
, lease_file
, sizeof(tmpfilename
) );
108 strncat( tmpfilename
, "XXXXXX", sizeof(tmpfilename
) - strlen(tmpfilename
));
110 fd
= fopen( lease_file
, "r");
115 snprintf( str
, sizeof(str
), "%s:%u", ((proto
==IPPROTO_TCP
)?"TCP":"UDP"), eport
);
116 str_size
= strlen(str
);
118 tmp
= mkstemp(tmpfilename
);
121 syslog(LOG_ERR
, "could not open temporary lease file");
124 fchmod(tmp
, S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IROTH
);
125 fdt
= fdopen(tmp
, "a");
127 buf
[sizeof(buf
)-1] = 0;
128 while( fgets(buf
, sizeof(buf
)-1, fd
) != NULL
) {
129 buf_size
= strlen(buf
);
131 if (buf_size
< str_size
|| strncmp(str
, buf
, str_size
)!=0) {
132 fwrite(buf
, buf_size
, 1, fdt
);
138 if (rename(tmpfilename
, lease_file
) < 0) {
139 syslog(LOG_ERR
, "could not rename temporary lease file to %s", lease_file
);
147 /* reload_from_lease_file()
148 * read lease_file and add the rules contained
150 int reload_from_lease_file()
154 unsigned short eport
, iport
;
159 unsigned int leaseduration
;
160 unsigned int timestamp
;
165 if(!lease_file
) return -1;
166 fd
= fopen( lease_file
, "r");
168 syslog(LOG_ERR
, "could not open lease file: %s", lease_file
);
171 if(unlink(lease_file
) < 0) {
172 syslog(LOG_WARNING
, "could not unlink file %s : %m", lease_file
);
175 current_time
= time(NULL
);
176 while(fgets(line
, sizeof(line
), fd
)) {
177 syslog(LOG_DEBUG
, "parsing lease file line '%s'", line
);
179 p
= strchr(line
, ':');
181 syslog(LOG_ERR
, "unrecognized data in lease file");
185 iaddr
= strchr(p
, ':');
187 syslog(LOG_ERR
, "unrecognized data in lease file");
191 eport
= (unsigned short)atoi(p
);
192 p
= strchr(iaddr
, ':');
194 syslog(LOG_ERR
, "unrecognized data in lease file");
198 iport
= (unsigned short)atoi(p
);
201 syslog(LOG_ERR
, "unrecognized data in lease file");
205 desc
= strchr(p
, ':');
207 syslog(LOG_ERR
, "unrecognized data in lease file");
211 /*timestamp = (unsigned int)atoi(p);*/
212 timestamp
= (unsigned int)strtoul(p
, NULL
, 10);
213 /* trim description */
214 while(isspace(*desc
))
219 while(isspace(*p
) && (p
> desc
))
223 if(timestamp
<= (unsigned int)current_time
) {
224 syslog(LOG_NOTICE
, "already expired lease in lease file");
227 leaseduration
= timestamp
- current_time
;
230 leaseduration
= 0; /* default value */
233 r
= upnp_redirect(rhost
, eport
, iaddr
, iport
, proto
, desc
, leaseduration
);
235 syslog(LOG_ERR
, "Failed to redirect %hu -> %s:%hu protocol %s",
236 eport
, iaddr
, iport
, proto
);
238 /* Add the redirection again to the lease file */
239 lease_file_add(eport
, iaddr
, iport
, proto_atoi(proto
),
250 * calls OS/fw dependant implementation of the redirection.
251 * protocol should be the string "TCP" or "UDP"
252 * returns: 0 on success
253 * -1 failed to redirect
254 * -2 already redirected
255 * -3 permission check failed
258 upnp_redirect(const char * rhost
, unsigned short eport
,
259 const char * iaddr
, unsigned short iport
,
260 const char * protocol
, const char * desc
,
261 unsigned int leaseduration
)
265 unsigned short iport_old
;
266 struct in_addr address
;
267 unsigned int timestamp
;
269 proto
= proto_atoi(protocol
);
270 if(inet_aton(iaddr
, &address
) < 0) {
271 syslog(LOG_ERR
, "inet_aton(%s) : %m", iaddr
);
275 if(!check_upnp_rule_against_permissions(upnppermlist
, num_upnpperm
,
276 eport
, address
, iport
)) {
277 syslog(LOG_INFO
, "redirection permission check failed for "
278 "%hu->%s:%hu %s", eport
, iaddr
, iport
, protocol
);
281 r
= get_redirect_rule(ext_if_name
, eport
, proto
,
282 iaddr_old
, sizeof(iaddr_old
), &iport_old
, 0, 0,
286 /* if existing redirect rule matches redirect request return success
287 * xbox 360 does not keep track of the port it redirects and will
288 * redirect another port when receiving ConflictInMappingEntry */
289 if(strcmp(iaddr
, iaddr_old
)==0 && iport
==iport_old
) {
290 /* redirection allready exists */
291 syslog(LOG_INFO
, "port %hu %s already redirected to %s:%hu, replacing", eport
, (proto
==IPPROTO_TCP
)?"tcp":"udp", iaddr_old
, iport_old
);
292 /* remove and then add again */
293 if(_upnp_delete_redir(eport
, proto
) < 0) {
294 syslog(LOG_ERR
, "failed to remove port mapping");
298 syslog(LOG_INFO
, "port %hu protocol %s already redirected to %s:%hu",
299 eport
, protocol
, iaddr_old
, iport_old
);
303 timestamp
= (leaseduration
> 0) ? time(NULL
) + leaseduration
: 0;
304 syslog(LOG_INFO
, "redirecting port %hu to %s:%hu protocol %s for: %s",
305 eport
, iaddr
, iport
, protocol
, desc
);
306 return upnp_redirect_internal(rhost
, eport
, iaddr
, iport
, proto
,
311 upnp_redirect_internal(const char * rhost
, unsigned short eport
,
312 const char * iaddr
, unsigned short iport
,
313 int proto
, const char * desc
,
314 unsigned int timestamp
)
316 /*syslog(LOG_INFO, "redirecting port %hu to %s:%hu protocol %s for: %s",
317 eport, iaddr, iport, protocol, desc); */
318 if(add_redirect_rule2(ext_if_name
, rhost
, eport
, iaddr
, iport
, proto
,
319 desc
, timestamp
) < 0) {
323 #ifdef ENABLE_LEASEFILE
324 lease_file_add( eport
, iaddr
, iport
, proto
, desc
, timestamp
);
326 /* syslog(LOG_INFO, "creating pass rule to %s:%hu protocol %s for: %s",
327 iaddr, iport, protocol, desc);*/
328 if(add_filter_rule2(ext_if_name
, rhost
, iaddr
, eport
, iport
, proto
, desc
) < 0) {
329 /* clean up the redirect rule */
330 #if !defined(__linux__)
331 delete_redirect_rule(ext_if_name
, eport
, proto
);
336 if(!nextruletoclean_timestamp
|| (timestamp
< nextruletoclean_timestamp
))
337 nextruletoclean_timestamp
= timestamp
;
340 /* the number of port mappings changed, we must
341 * inform the subscribers */
342 upnp_event_var_change_notify(EWanIPC
);
349 /* Firewall independant code which call the FW dependant code. */
351 upnp_get_redirection_infos(unsigned short eport
, const char * protocol
,
352 unsigned short * iport
,
353 char * iaddr
, int iaddrlen
,
354 char * desc
, int desclen
,
355 char * rhost
, int rhostlen
,
356 unsigned int * leaseduration
)
359 unsigned int timestamp
;
362 if(desc
&& (desclen
> 0))
364 if(rhost
&& (rhostlen
> 0))
366 r
= get_redirect_rule(ext_if_name
, eport
, proto_atoi(protocol
),
367 iaddr
, iaddrlen
, iport
, desc
, desclen
,
368 rhost
, rhostlen
, ×tamp
,
372 timestamp
> (unsigned int)(current_time
= time(NULL
))) {
373 *leaseduration
= timestamp
- current_time
;
381 upnp_get_redirection_infos_by_index(int index
,
382 unsigned short * eport
, char * protocol
,
383 unsigned short * iport
,
384 char * iaddr
, int iaddrlen
,
385 char * desc
, int desclen
,
386 char * rhost
, int rhostlen
,
387 unsigned int * leaseduration
)
389 /*char ifname[IFNAMSIZ];*/
391 unsigned int timestamp
;
394 if(desc
&& (desclen
> 0))
396 if(rhost
&& (rhostlen
> 0))
398 if(get_redirect_rule_by_index(index
, 0/*ifname*/, eport
, iaddr
, iaddrlen
,
399 iport
, &proto
, desc
, desclen
,
400 rhost
, rhostlen
, ×tamp
,
405 current_time
= time(NULL
);
406 *leaseduration
= (timestamp
> (unsigned int)current_time
)
407 ? (timestamp
- current_time
)
409 if(proto
== IPPROTO_TCP
)
410 memcpy(protocol
, "TCP", 4);
412 memcpy(protocol
, "UDP", 4);
417 /* called from natpmp.c too */
419 _upnp_delete_redir(unsigned short eport
, int proto
)
422 #if defined(__linux__)
423 r
= delete_redirect_and_filter_rules(eport
, proto
);
425 r
= delete_redirect_rule(ext_if_name
, eport
, proto
);
426 delete_filter_rule(ext_if_name
, eport
, proto
);
428 #ifdef ENABLE_LEASEFILE
429 lease_file_remove( eport
, proto
);
433 upnp_event_var_change_notify(EWanIPC
);
439 upnp_delete_redirection(unsigned short eport
, const char * protocol
)
441 syslog(LOG_INFO
, "removing redirect rule port %hu %s", eport
, protocol
);
442 return _upnp_delete_redir(eport
, proto_atoi(protocol
));
445 /* upnp_get_portmapping_number_of_entries()
446 * TODO: improve this code. */
448 upnp_get_portmapping_number_of_entries()
451 unsigned short eport
, iport
;
452 char protocol
[4], iaddr
[32], desc
[64], rhost
[32];
453 unsigned int leaseduration
;
455 protocol
[0] = '\0'; iaddr
[0] = '\0'; desc
[0] = '\0';
456 r
= upnp_get_redirection_infos_by_index(n
, &eport
, protocol
, &iport
,
457 iaddr
, sizeof(iaddr
),
459 rhost
, sizeof(rhost
),
466 /* functions used to remove unused rules
467 * As a side effect, delete expired rules (based on LeaseDuration) */
469 get_upnp_rules_state_list(int max_rules_number_target
)
471 /*char ifname[IFNAMSIZ];*/
473 unsigned short iport
;
474 unsigned int timestamp
;
475 struct rule_state
* tmp
;
476 struct rule_state
* list
= 0;
477 struct rule_state
* * p
;
481 /*ifname[0] = '\0';*/
482 tmp
= malloc(sizeof(struct rule_state
));
485 current_time
= time(NULL
);
486 nextruletoclean_timestamp
= 0;
487 while(get_redirect_rule_by_index(i
, /*ifname*/0, &tmp
->eport
, 0, 0,
488 &iport
, &proto
, 0, 0, 0,0, ×tamp
,
489 &tmp
->packets
, &tmp
->bytes
) >= 0)
493 /* need to remove this port mapping ? */
494 if(timestamp
<= (unsigned int)current_time
)
496 else if((nextruletoclean_timestamp
<= (unsigned int)current_time
)
497 || (timestamp
< nextruletoclean_timestamp
))
498 nextruletoclean_timestamp
= timestamp
;
500 tmp
->proto
= (short)proto
;
501 /* add tmp to list */
504 /* prepare next iteration */
506 tmp
= malloc(sizeof(struct rule_state
));
511 /* remove the redirections that need to be removed */
512 for(p
= &list
, tmp
= list
; tmp
; tmp
= *p
)
516 syslog(LOG_NOTICE
, "remove port mapping %hu %s because it has expired",
517 tmp
->eport
, (tmp
->proto
==IPPROTO_TCP
)?"TCP":"UDP");
518 _upnp_delete_redir(tmp
->eport
, tmp
->proto
);
526 /* return empty list if not enough redirections */
527 if(i
<=max_rules_number_target
)
539 remove_unused_rules(struct rule_state
* list
)
541 char ifname
[IFNAMSIZ
];
542 unsigned short iport
;
543 struct rule_state
* tmp
;
546 unsigned int timestamp
;
551 /* remove the rule if no traffic has used it */
552 if(get_redirect_rule(ifname
, list
->eport
, list
->proto
,
553 0, 0, &iport
, 0, 0, 0, 0, ×tamp
,
554 &packets
, &bytes
) >= 0)
556 if(packets
== list
->packets
&& bytes
== list
->bytes
)
558 if(_upnp_delete_redir(list
->eport
, list
->proto
) >= 0)
567 syslog(LOG_NOTICE
, "removed %d unused rules", n
);
570 /* upnp_get_portmappings_in_range()
571 * return a list of all "external" ports for which a port
574 upnp_get_portmappings_in_range(unsigned short startport
,
575 unsigned short endport
,
576 const char * protocol
,
577 unsigned int * number
)
580 proto
= proto_atoi(protocol
);
583 return get_portmappings_in_range(startport
, endport
, proto
, number
);
586 /* stuff for miniupnpdctl */
587 #ifdef USE_MINIUPNPDCTL
589 write_ruleset_details(int s
)
592 unsigned short eport
, iport
;
596 unsigned int timestamp
;
603 write(s
, "Ruleset :\n", 10);
604 while(get_redirect_rule_by_index(i
, 0/*ifname*/, &eport
, iaddr
, sizeof(iaddr
),
605 &iport
, &proto
, desc
, sizeof(desc
),
606 rhost
, sizeof(rhost
),
608 &packets
, &bytes
) >= 0)
610 n
= snprintf(buffer
, sizeof(buffer
),
611 "%2d %s %s:%hu->%s:%hu "
612 "'%s' %u %" PRIu64
" %" PRIu64
"\n",
613 /*"'%s' %llu %llu\n",*/
614 i
, proto
==IPPROTO_TCP
?"TCP":"UDP", rhost
,
615 eport
, iaddr
, iport
, desc
, timestamp
, packets
, bytes
);