/* $Id: pfpinhole.c,v 1.30 2024/01/14 23:56:45 nanard Exp $ */ /* vim: tabstop=4 shiftwidth=4 noexpandtab * MiniUPnP project * http://miniupnp.free.fr/ or https://miniupnp.tuxfamily.org/ * (c) 2012-2024 Thomas Bernard * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #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 "config.h" #include "pfpinhole.h" #include "../upnpglobalvars.h" #include "../macros.h" #include "../upnputils.h" #include "rtickets.h" /* the pass rules created by add_pinhole() are as follow : * * pass in quick on ep0 inet6 proto udp * from any to dead:beef::42:42 port = 8080 * flags S/SA keep state * label "pinhole-2 ts-4321000" * * with the label "pinhole-$uid ts-$timestamp: $description" */ #ifdef ENABLE_UPNPPINHOLE /* /dev/pf when opened */ extern int dev; static int next_uid = 1; #define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s" #define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s" int 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) { int uid; struct pfioc_rule pcr; #ifndef PF_NEWSTYLE struct pfioc_pooladdr pp; #endif if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } 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"); return -1; } else { pcr.pool_ticket = pp.ticket; #else { #endif pcr.rule.direction = PF_IN; pcr.rule.action = PF_PASS; pcr.rule.af = AF_INET6; #ifdef PF_NEWSTYLE pcr.rule.nat.addr.type = PF_ADDR_NONE; pcr.rule.rdr.addr.type = PF_ADDR_NONE; #endif #ifdef USE_IFNAME_IN_RULES if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); #endif pcr.rule.proto = proto; pcr.rule.quick = 1;/*(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; uid = next_uid; snprintf(pcr.rule.label, PF_RULE_LABEL_SIZE, PINEHOLE_LABEL_FORMAT, uid, timestamp, desc); if(queue) strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE); if(tag) strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); if(rem_port) { pcr.rule.src.port_op = PF_OP_EQ; pcr.rule.src.port[0] = htons(rem_port); } if(rem_host && rem_host[0] != '\0' && rem_host[0] != '*') { pcr.rule.src.addr.type = PF_ADDR_ADDRMASK; if(inet_pton(AF_INET6, rem_host, &pcr.rule.src.addr.v.a.addr.v6) != 1) { syslog(LOG_ERR, "inet_pton(%s) failed", rem_host); } memset(&pcr.rule.src.addr.v.a.mask.addr8, 255, 16); } pcr.rule.dst.port_op = PF_OP_EQ; pcr.rule.dst.port[0] = htons(int_port); pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK; if(inet_pton(AF_INET6, int_client, &pcr.rule.dst.addr.v.a.addr.v6) != 1) { syslog(LOG_ERR, "inet_pton(%s) failed", int_client); } memset(&pcr.rule.dst.addr.v.a.mask.addr8, 255, 16); if(ifname) strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); pcr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); return -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"); return -1; } } } if(++next_uid >= 65535) { next_uid = 1; } return uid; } 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) { int uid; unsigned int ts, tnum; int i, n; struct pfioc_rule pr; struct in6_addr saddr; struct in6_addr daddr; UNUSED(ifname); if(dev<0) { syslog(LOG_ERR, "pf device is not open"); return -1; } if(rem_host && (rem_host[0] != '\0')) { inet_pton(AF_INET6, rem_host, &saddr); } else { memset(&saddr, 0, sizeof(struct in6_addr)); } inet_pton(AF_INET6, int_client, &daddr); memset(&pr, 0, sizeof(pr)); strlcpy(pr.anchor, anchor_name, MAXPATHLEN); #ifndef PF_NEWSTYLE pr.rule.action = PF_PASS; #endif if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } n = pr.nr; #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif /* PF_RELEASETICKETS */ for(i=0; i= 0; i--) { pr.nr = i; if(ioctl(dev, DIOCGETRULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); release_ticket(dev, tnum); return -1; } if(sscanf(pr.rule.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", pr.rule.label); continue; } if(ts <= (unsigned int)current_time) { syslog(LOG_INFO, "removing expired pinhole '%s'", pr.rule.label); pr.action = PF_CHANGE_GET_TICKET; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); release_ticket(dev, tnum); return -1; } pr.action = PF_CHANGE_REMOVE; pr.nr = i; if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m"); release_ticket(dev, tnum); return -1; } n++; #ifndef PF_NEWSTYLE pr.rule.action = PF_PASS; #endif release_ticket(dev, tnum); if(ioctl(dev, DIOCGETRULES, &pr) < 0) { syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); return -1; } #ifdef PF_RELEASETICKETS tnum = pr.ticket; #endif } else { if(uid > max_uid) max_uid = uid; else if(uid < min_uid) min_uid = uid; if(ts < min_ts) min_ts = ts; } } if(next_timestamp && (min_ts != UINT_MAX)) *next_timestamp = min_ts; if(max_uid > 0) { if(((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) { next_uid = max_uid + 1; } if(next_uid >= 65535) { next_uid = 1; } } release_ticket(dev, tnum); return n; /* number of rules removed */ } #endif /* ENABLE_UPNPPINHOLE */