/* $Id: obsdrdr.c,v 1.84 2015/02/08 08:55:55 nanard Exp $ */ /* MiniUPnP project * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * (c) 2006-2015 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ /* * pf rules created (with ext_if = xl1) * - OpenBSD up to version 4.6 : * rdr pass on xl1 inet proto udp from any to any port = 54321 \ * keep state label "test label" -> 192.168.0.42 port 12345 * or a rdr rule + a pass rule : * rdr quick on xl1 inet proto udp from any to any port = 54321 \ * keep state label "test label" -> 192.168.0.42 port 12345 * pass in quick on xl1 inet proto udp from any to 192.168.0.42 port = 12345 \ * flags S/SA keep state label "test label" * * - OpenBSD starting from version 4.7 * match in on xl1 inet proto udp from any to any port 54321 \ * label "test label" rdr-to 192.168.0.42 port 12345 * or * pass in quick on xl1 inet proto udp from any to any port 54321 \ * label "test label" rdr-to 192.168.0.42 port 12345 * * * * Macros/#defines : * - PF_ENABLE_FILTER_RULES * If set, two rules are created : rdr + pass. Else a rdr/pass rule * is created. * - USE_IFNAME_IN_RULES * If set the interface name is set in the rule. * - PFRULE_INOUT_COUNTS * Must be set with OpenBSD version 3.8 and up. * - PFRULE_HAS_RTABLEID * Must be set with OpenBSD version 4.0 and up. * - PF_NEWSSTYLE * Must be set with OpenBSD version 4.7 and up. */ #include #include #include #include #include #include #include #ifdef __DragonFly__ #include #else #ifdef __APPLE__ #define PRIVATE 1 #endif #include #endif #include #include #include #include #include #include #include #include "../macros.h" #include "../config.h" #include "obsdrdr.h" #include "../upnpglobalvars.h" #ifndef USE_PF #error "USE_PF macro is undefined, check consistency between config.h and Makefile" #else /* list too keep timestamps for port mappings having a lease duration */ struct timestamp_entry { struct timestamp_entry * next; unsigned int timestamp; unsigned short eport; short protocol; }; static struct timestamp_entry * timestamp_list = NULL; static unsigned int get_timestamp(unsigned short eport, int proto) { struct timestamp_entry * e; e = timestamp_list; while(e) { if(e->eport == eport && e->protocol == (short)proto) return e->timestamp; e = e->next; } return 0; } static void remove_timestamp_entry(unsigned short eport, int proto) { struct timestamp_entry * e; struct timestamp_entry * * p; p = ×tamp_list; e = *p; while(e) { if(e->eport == eport && e->protocol == (short)proto) { /* remove the entry */ *p = e->next; free(e); return; } p = &(e->next); e = *p; } } /* /dev/pf when opened */ int dev = -1; /* shutdown_redirect() : * close the /dev/pf device */ void shutdown_redirect(void) { if(close(dev)<0) syslog(LOG_ERR, "close(\"/dev/pf\"): %m"); dev = -1; } /* open the device */ int init_redirect(void) { struct pf_status status; if(dev>=0) shutdown_redirect(); dev = open("/dev/pf", O_RDWR); if(dev<0) { syslog(LOG_ERR, "open(\"/dev/pf\"): %m"); return -1; } if(ioctl(dev, DIOCGETSTATUS, &status)<0) { syslog(LOG_ERR, "DIOCGETSTATUS: %m"); return -1; } if(!status.running) { syslog(LOG_ERR, "pf is disabled"); return -1; } return 0; } #if TEST /* for debug */ int clear_redirect_rules(void) { struct pfioc_trans io; struct pfioc_trans_e ioe; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&ioe, 0, sizeof(ioe)); io.size = 1; io.esize = sizeof(ioe); io.array = &ioe; #ifndef PF_NEWSTYLE ioe.rs_num = PF_RULESET_RDR; #else ioe.type = PF_TRANS_RULESET; #endif strlcpy(ioe.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCXBEGIN, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m"); goto error; } if(ioctl(dev, DIOCXCOMMIT, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m"); goto error; } return 0; error: return -1; } int clear_filter_rules(void) { #ifndef PF_ENABLE_FILTER_RULES return 0; #else struct pfioc_trans io; struct pfioc_trans_e ioe; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&ioe, 0, sizeof(ioe)); io.size = 1; io.esize = sizeof(ioe); io.array = &ioe; #ifndef PF_NEWSTYLE ioe.rs_num = PF_RULESET_FILTER; #else /* ? */ ioe.type = PF_TRANS_RULESET; #endif strlcpy(ioe.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCXBEGIN, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXBEGIN, ...): %m"); goto error; } if(ioctl(dev, DIOCXCOMMIT, &io) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCXCOMMIT, ...): %m"); goto error; } return 0; error: return -1; #endif } #endif /* add_redirect_rule2() : * create a rdr rule */ int add_redirect_rule2(const char * ifname, const char * rhost, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc, unsigned int timestamp) { int r; struct pfioc_rule pcr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; struct pf_pooladdr *a; #endif if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } r = 0; memset(&pcr, 0, sizeof(pcr)); strlcpy(pcr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); r = -1; } else { pcr.pool_ticket = pp.ticket; #else if(1) { pcr.rule.direction = PF_IN; /*pcr.rule.src.addr.type = PF_ADDR_NONE;*/ pcr.rule.src.addr.type = PF_ADDR_ADDRMASK; pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK; pcr.rule.nat.addr.type = PF_ADDR_NONE; pcr.rule.rdr.addr.type = PF_ADDR_ADDRMASK; #endif #ifdef __APPLE__ pcr.rule.dst.xport.range.op = PF_OP_EQ; pcr.rule.dst.xport.range.port[0] = htons(eport); pcr.rule.dst.xport.range.port[1] = htons(eport); #else pcr.rule.dst.port_op = PF_OP_EQ; pcr.rule.dst.port[0] = htons(eport); pcr.rule.dst.port[1] = htons(eport); #endif #ifndef PF_NEWSTYLE pcr.rule.action = PF_RDR; #ifndef PF_ENABLE_FILTER_RULES pcr.rule.natpass = 1; #else pcr.rule.natpass = 0; #endif #else #ifndef PF_ENABLE_FILTER_RULES pcr.rule.action = PF_PASS; #else pcr.rule.action = PF_MATCH; #endif #endif pcr.rule.af = AF_INET; #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); #endif pcr.rule.proto = proto; pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ #ifdef PFRULE_HAS_RTABLEID pcr.rule.rtableid = -1; /* first appeared in OpenBSD 4.0 */ #endif #ifdef PFRULE_HAS_ONRDOMAIN pcr.rule.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ #endif pcr.rule.quick = 1; pcr.rule.keep_state = PF_STATE_NORMAL; if(tag) strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE); if(rhost && rhost[0] != '\0' && rhost[0] != '*') { inet_pton(AF_INET, rhost, &pcr.rule.src.addr.v.a.addr.v4.s_addr); pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); } #ifndef PF_NEWSTYLE pcr.rule.rpool.proxy_port[0] = iport; pcr.rule.rpool.proxy_port[1] = iport; TAILQ_INIT(&pcr.rule.rpool.list); a = calloc(1, sizeof(struct pf_pooladdr)); inet_pton(AF_INET, iaddr, &a->addr.v.a.addr.v4.s_addr); a->addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); TAILQ_INSERT_TAIL(&pcr.rule.rpool.list, a, entries); memcpy(&pp.addr, a, sizeof(struct pf_pooladdr)); if(ioctl(dev, DIOCADDADDR, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCADDADDR, ...): %m"); r = -1; } else { #else pcr.rule.rdr.proxy_port[0] = iport; pcr.rule.rdr.proxy_port[1] = iport; inet_pton(AF_INET, iaddr, &pcr.rule.rdr.addr.v.a.addr.v4.s_addr); pcr.rule.rdr.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); if(1) { #endif pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; } else { pcr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); r = -1; } } } #ifndef PF_NEWSTYLE free(a); #endif } if(r == 0 && timestamp > 0) { struct timestamp_entry * tmp; tmp = malloc(sizeof(struct timestamp_entry)); if(tmp) { tmp->next = timestamp_list; tmp->timestamp = timestamp; tmp->eport = eport; tmp->protocol = (short)proto; timestamp_list = tmp; } } return r; } /* thanks to Seth Mos for this function */ int add_filter_rule2(const char * ifname, const char * rhost, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { #ifndef PF_ENABLE_FILTER_RULES UNUSED(ifname); UNUSED(rhost); UNUSED(iaddr); UNUSED(eport); UNUSED(iport); UNUSED(proto); UNUSED(desc); return 0; #else int r; struct pfioc_rule pcr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif #ifndef USE_IFNAME_IN_RULES UNUSED(ifname); #endif UNUSED(eport); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } r = 0; memset(&pcr, 0, sizeof(pcr)); strlcpy(pcr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); r = -1; } else { pcr.pool_ticket = pp.ticket; #else if(1) { #endif pcr.rule.dst.port_op = PF_OP_EQ; pcr.rule.dst.port[0] = htons(iport); pcr.rule.direction = PF_IN; pcr.rule.action = PF_PASS; pcr.rule.af = AF_INET; #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); #endif pcr.rule.proto = proto; pcr.rule.quick = (GETFLAG(PFNOQUICKRULESMASK))?0:1; pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ /* see the discussion on the forum : * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */ pcr.rule.flags = TH_SYN; pcr.rule.flagset = (TH_SYN|TH_ACK); #ifdef PFRULE_HAS_RTABLEID pcr.rule.rtableid = -1; /* first appeared in OpenBSD 4.0 */ #endif #ifdef PFRULE_HAS_ONRDOMAIN pcr.rule.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ #endif pcr.rule.keep_state = 1; strlcpy(pcr.rule.label, desc, PF_RULE_LABEL_SIZE); if(queue) strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE); if(tag) strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); if(rhost && rhost[0] != '\0' && rhost[0] != '*') { inet_pton(AF_INET, rhost, &pcr.rule.src.addr.v.a.addr.v4.s_addr); pcr.rule.src.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); } /* we want any - iaddr port = # keep state label */ inet_pton(AF_INET, iaddr, &pcr.rule.dst.addr.v.a.addr.v4.s_addr); pcr.rule.dst.addr.v.a.mask.v4.s_addr = htonl(INADDR_NONE); #ifndef PF_NEWSTYLE pcr.rule.rpool.proxy_port[0] = iport; pcr.rule.rpool.proxy_port[1] = iport; TAILQ_INIT(&pcr.rule.rpool.list); #endif if(1) { pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); r = -1; } else { pcr.action = PF_CHANGE_ADD_TAIL; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); r = -1; } } } } return r; #endif } /* get_redirect_rule() * return value : 0 success (found) * -1 = error or rule not found */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, char * rhost, int rhostlen, unsigned int * timestamp, u_int64_t * packets, u_int64_t * bytes) { int i, n; struct pfioc_rule pr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE pr.rule.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); goto error; } n = pr.nr; for(i=0; i 0) { if (pr.rule.src.addr.v.a.addr.v4.s_addr == 0) { rhost[0] = '\0'; /* empty string */ } else { inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr, rhost, rhostlen); } } if(timestamp) *timestamp = get_timestamp(eport, proto); return 0; } } error: return -1; } static int priv_delete_redirect_rule(const char * ifname, unsigned short eport, int proto, unsigned short * iport, in_addr_t * iaddr) { int i, n; struct pfioc_rule pr; UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE pr.rule.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); goto error; } n = pr.nr; for(i=0; i= n) goto error; pr.nr = index; if(ioctl(dev, DIOCGETRULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); goto error; } *proto = pr.rule.proto; #ifdef __APPLE__ *eport = ntohs(pr.rule.dst.xport.range.port[0]); #else *eport = ntohs(pr.rule.dst.port[0]); #endif #ifndef PF_NEWSTYLE *iport = pr.rule.rpool.proxy_port[0]; #else *iport = pr.rule.rdr.proxy_port[0]; #endif if(ifname) strlcpy(ifname, pr.rule.ifname, IFNAMSIZ); if(desc) strlcpy(desc, pr.rule.label, desclen); #ifdef PFRULE_INOUT_COUNTS if(packets) *packets = pr.rule.packets[0] + pr.rule.packets[1]; if(bytes) *bytes = pr.rule.bytes[0] + pr.rule.bytes[1]; #else if(packets) *packets = pr.rule.packets; if(bytes) *bytes = pr.rule.bytes; #endif #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); pp.r_action = PF_RDR; pp.r_num = index; pp.ticket = pr.ticket; if(ioctl(dev, DIOCGETADDRS, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETADDRS, ...): %m"); goto error; } if(pp.nr != 1) { syslog(LOG_NOTICE, "No address associated with pf rule"); goto error; } pp.nr = 0; /* first */ if(ioctl(dev, DIOCGETADDR, &pp) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETADDR, ...): %m"); goto error; } inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, iaddr, iaddrlen); #else inet_ntop(AF_INET, &pr.rule.rdr.addr.v.a.addr.v4.s_addr, iaddr, iaddrlen); #endif if(rhost && rhostlen > 0) { if (pr.rule.src.addr.v.a.addr.v4.s_addr == 0) { rhost[0] = '\0'; /* empty string */ } else { inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr, rhost, rhostlen); } } if(timestamp) *timestamp = get_timestamp(*eport, *proto); return 0; error: return -1; } /* return an (malloc'ed) array of "external" port for which there is * a port mapping. number is the size of the array */ unsigned short * get_portmappings_in_range(unsigned short startport, unsigned short endport, int proto, unsigned int * number) { unsigned short * array; unsigned int capacity; int i, n; unsigned short eport; struct pfioc_rule pr; *number = 0; if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return NULL; } capacity = 128; array = calloc(capacity, sizeof(unsigned short)); if(!array) { syslog(LOG_ERR, "get_portmappings_in_range() : calloc error"); return NULL; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE pr.rule.action = PF_RDR; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); free(array); return NULL; } n = pr.nr; for(i=0; i= capacity) { /* need to increase the capacity of the array */ unsigned short * tmp; capacity += 128; tmp = realloc(array, sizeof(unsigned short)*capacity); if(!tmp) { syslog(LOG_ERR, "get_portmappings_in_range() : realloc(%lu) error", sizeof(unsigned short)*capacity); *number = 0; free(array); return NULL; } array = tmp; } array[*number] = eport; (*number)++; } } return array; } /* this function is only for testing */ #if TEST void list_rules(void) { char buf[32]; int i, n; struct pfioc_rule pr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif if(dev<0) { perror("pf dev not open"); return ; } memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); pr.rule.action = PF_RDR; if(ioctl(dev, DIOCGETRULES, &pr) < 0) perror("DIOCGETRULES"); printf("ticket = %d, nr = %d\n", pr.ticket, pr.nr); n = pr.nr; for(i=0; i %d:%d proto %d keep_state=%d action=%d\n", pr.rule.ifname, inet_ntop(AF_INET, &pr.rule.src.addr.v.a.addr.v4.s_addr, buf, 32), (int)ntohs(pr.rule.dst.port[0]), (int)ntohs(pr.rule.dst.port[1]), #ifndef PF_NEWSTYLE (int)pr.rule.rpool.proxy_port[0], (int)pr.rule.rpool.proxy_port[1], #else (int)pr.rule.rdr.proxy_port[0], (int)pr.rule.rdr.proxy_port[1], #endif (int)pr.rule.proto, (int)pr.rule.keep_state, (int)pr.rule.action); printf(" description: \"%s\"\n", pr.rule.label); #ifndef PF_NEWSTYLE memset(&pp, 0, sizeof(pp)); strlcpy(pp.anchor, anchor_name, MAXPATHLEN); pp.r_action = PF_RDR; pp.r_num = i; pp.ticket = pr.ticket; if(ioctl(dev, DIOCGETADDRS, &pp) < 0) perror("DIOCGETADDRS"); printf(" nb pool addr = %d ticket=%d\n", pp.nr, pp.ticket); /*if(ioctl(dev, DIOCGETRULE, &pr) < 0) perror("DIOCGETRULE"); */ pp.nr = 0; /* first */ if(ioctl(dev, DIOCGETADDR, &pp) < 0) perror("DIOCGETADDR"); /* addr.v.a.addr.v4.s_addr */ printf(" %s\n", inet_ntop(AF_INET, &pp.addr.addr.v.a.addr.v4.s_addr, buf, 32)); #else printf(" rule_flag=%08x action=%d direction=%d log=%d logif=%d " "quick=%d ifnot=%d af=%d type=%d code=%d rdr.port_op=%d rdr.opts=%d\n", pr.rule.rule_flag, pr.rule.action, pr.rule.direction, pr.rule.log, pr.rule.logif, pr.rule.quick, pr.rule.ifnot, pr.rule.af, pr.rule.type, pr.rule.code, pr.rule.rdr.port_op, pr.rule.rdr.opts); printf(" %s\n", inet_ntop(AF_INET, &pr.rule.rdr.addr.v.a.addr.v4.s_addr, buf, 32)); #endif } } #endif /* TEST */ #endif /* USE_PF */