1/* $Id: pfpinhole.c,v 1.24 2014/12/05 09:54:55 nanard Exp $ */ 2/* MiniUPnP project 3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ 4 * (c) 2012 Thomas Bernard 5 * This software is subject to the conditions detailed 6 * in the LICENCE file provided within the distribution */ 7 8#include <sys/types.h> 9#include <sys/socket.h> 10#include <sys/param.h> 11#include <net/if.h> 12#include <netinet/in.h> 13#include <netinet/tcp.h> 14#include <arpa/inet.h> 15#ifdef __DragonFly__ 16#include <net/pf/pfvar.h> 17#else 18#ifdef __APPLE__ 19#define PRIVATE 1 20#endif 21#include <net/pfvar.h> 22#endif 23#include <fcntl.h> 24#include <sys/ioctl.h> 25#include <unistd.h> 26#include <string.h> 27#include <syslog.h> 28#include <stdio.h> 29#include <stdlib.h> 30 31#include "../config.h" 32#include "pfpinhole.h" 33#include "../upnpglobalvars.h" 34#include "../macros.h" 35 36/* the pass rules created by add_pinhole() are as follow : 37 * 38 * pass in quick on ep0 inet6 proto udp 39 * from any to dead:beef::42:42 port = 8080 40 * flags S/SA keep state 41 * label "pinhole-2 ts-4321000" 42 * 43 * with the label "pinhole-$uid ts-$timestamp" 44 */ 45 46#ifdef ENABLE_UPNPPINHOLE 47 48/* /dev/pf when opened */ 49extern int dev; 50 51static int next_uid = 1; 52 53#define PINEHOLE_LABEL_FORMAT "pinhole-%d ts-%u: %s" 54#define PINEHOLE_LABEL_FORMAT_SKIPDESC "pinhole-%d ts-%u: %*s" 55 56int add_pinhole(const char * ifname, 57 const char * rem_host, unsigned short rem_port, 58 const char * int_client, unsigned short int_port, 59 int proto, const char * desc, unsigned int timestamp) 60{ 61 int uid; 62 struct pfioc_rule pcr; 63#ifndef PF_NEWSTYLE 64 struct pfioc_pooladdr pp; 65#endif 66 67 if(dev<0) { 68 syslog(LOG_ERR, "pf device is not open"); 69 return -1; 70 } 71 memset(&pcr, 0, sizeof(pcr)); 72 strlcpy(pcr.anchor, anchor_name, MAXPATHLEN); 73 74#ifndef PF_NEWSTYLE 75 memset(&pp, 0, sizeof(pp)); 76 strlcpy(pp.anchor, anchor_name, MAXPATHLEN); 77 if(ioctl(dev, DIOCBEGINADDRS, &pp) < 0) { 78 syslog(LOG_ERR, "ioctl(dev, DIOCBEGINADDRS, ...): %m"); 79 return -1; 80 } else { 81 pcr.pool_ticket = pp.ticket; 82#else 83 { 84#endif 85 pcr.rule.direction = PF_IN; 86 pcr.rule.action = PF_PASS; 87 pcr.rule.af = AF_INET6; 88#ifdef PF_NEWSTYLE 89 pcr.rule.nat.addr.type = PF_ADDR_NONE; 90 pcr.rule.rdr.addr.type = PF_ADDR_NONE; 91#endif 92#ifdef USE_IFNAME_IN_RULES 93 if(ifname) 94 strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); 95#endif 96 pcr.rule.proto = proto; 97 98 pcr.rule.quick = 1;/*(GETFLAG(PFNOQUICKRULESMASK))?0:1;*/ 99 pcr.rule.log = (GETFLAG(LOGPACKETSMASK))?1:0; /*logpackets;*/ 100/* see the discussion on the forum : 101 * http://miniupnp.tuxfamily.org/forum/viewtopic.php?p=638 */ 102 pcr.rule.flags = TH_SYN; 103 pcr.rule.flagset = (TH_SYN|TH_ACK); 104#ifdef PFRULE_HAS_RTABLEID 105 pcr.rule.rtableid = -1; /* first appeared in OpenBSD 4.0 */ 106#endif 107#ifdef PFRULE_HAS_ONRDOMAIN 108 pcr.rule.onrdomain = -1; /* first appeared in OpenBSD 5.0 */ 109#endif 110 pcr.rule.keep_state = 1; 111 uid = next_uid; 112 snprintf(pcr.rule.label, PF_RULE_LABEL_SIZE, 113 PINEHOLE_LABEL_FORMAT, uid, timestamp, desc); 114 if(queue) 115 strlcpy(pcr.rule.qname, queue, PF_QNAME_SIZE); 116 if(tag) 117 strlcpy(pcr.rule.tagname, tag, PF_TAG_NAME_SIZE); 118 119 if(rem_port) { 120 pcr.rule.src.port_op = PF_OP_EQ; 121 pcr.rule.src.port[0] = htons(rem_port); 122 } 123 if(rem_host && rem_host[0] != '\0' && rem_host[0] != '*') { 124 pcr.rule.src.addr.type = PF_ADDR_ADDRMASK; 125 if(inet_pton(AF_INET6, rem_host, &pcr.rule.src.addr.v.a.addr.v6) != 1) { 126 syslog(LOG_ERR, "inet_pton(%s) failed", rem_host); 127 } 128 memset(&pcr.rule.src.addr.v.a.mask.addr8, 255, 16); 129 } 130 131 pcr.rule.dst.port_op = PF_OP_EQ; 132 pcr.rule.dst.port[0] = htons(int_port); 133 pcr.rule.dst.addr.type = PF_ADDR_ADDRMASK; 134 if(inet_pton(AF_INET6, int_client, &pcr.rule.dst.addr.v.a.addr.v6) != 1) { 135 syslog(LOG_ERR, "inet_pton(%s) failed", int_client); 136 } 137 memset(&pcr.rule.dst.addr.v.a.mask.addr8, 255, 16); 138 139 if(ifname) 140 strlcpy(pcr.rule.ifname, ifname, IFNAMSIZ); 141 142 pcr.action = PF_CHANGE_GET_TICKET; 143 if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { 144 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); 145 return -1; 146 } else { 147 pcr.action = PF_CHANGE_ADD_TAIL; 148 if(ioctl(dev, DIOCCHANGERULE, &pcr) < 0) { 149 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_ADD_TAIL: %m"); 150 return -1; 151 } 152 } 153 } 154 155 if(++next_uid >= 65535) { 156 next_uid = 1; 157 } 158 return uid; 159} 160 161int delete_pinhole(unsigned short uid) 162{ 163 int i, n; 164 struct pfioc_rule pr; 165 char label_start[PF_RULE_LABEL_SIZE]; 166 char tmp_label[PF_RULE_LABEL_SIZE]; 167 168 if(dev<0) { 169 syslog(LOG_ERR, "pf device is not open"); 170 return -1; 171 } 172 snprintf(label_start, sizeof(label_start), 173 "pinhole-%hu", uid); 174 memset(&pr, 0, sizeof(pr)); 175 strlcpy(pr.anchor, anchor_name, MAXPATHLEN); 176#ifndef PF_NEWSTYLE 177 pr.rule.action = PF_PASS; 178#endif 179 if(ioctl(dev, DIOCGETRULES, &pr) < 0) { 180 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); 181 return -1; 182 } 183 n = pr.nr; 184 for(i=0; i<n; i++) { 185 pr.nr = i; 186 if(ioctl(dev, DIOCGETRULE, &pr) < 0) { 187 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); 188 return -1; 189 } 190 strlcpy(tmp_label, pr.rule.label, sizeof(tmp_label)); 191 strtok(tmp_label, " "); 192 if(0 == strcmp(tmp_label, label_start)) { 193 pr.action = PF_CHANGE_GET_TICKET; 194 if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { 195 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); 196 return -1; 197 } 198 pr.action = PF_CHANGE_REMOVE; 199 pr.nr = i; 200 if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { 201 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m"); 202 return -1; 203 } 204 return 0; 205 } 206 } 207 /* not found */ 208 return -2; 209} 210 211int 212get_pinhole_info(unsigned short uid, 213 char * rem_host, int rem_hostlen, unsigned short * rem_port, 214 char * int_client, int int_clientlen, unsigned short * int_port, 215 int * proto, char * desc, int desclen, 216 unsigned int * timestamp, 217 u_int64_t * packets, u_int64_t * bytes) 218{ 219 int i, n; 220 struct pfioc_rule pr; 221 char label_start[PF_RULE_LABEL_SIZE]; 222 char tmp_label[PF_RULE_LABEL_SIZE]; 223 char * p; 224 225 if(dev<0) { 226 syslog(LOG_ERR, "pf device is not open"); 227 return -1; 228 } 229 snprintf(label_start, sizeof(label_start), 230 "pinhole-%hu", uid); 231 memset(&pr, 0, sizeof(pr)); 232 strlcpy(pr.anchor, anchor_name, MAXPATHLEN); 233#ifndef PF_NEWSTYLE 234 pr.rule.action = PF_PASS; 235#endif 236 if(ioctl(dev, DIOCGETRULES, &pr) < 0) { 237 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); 238 return -1; 239 } 240 n = pr.nr; 241 for(i=0; i<n; i++) { 242 pr.nr = i; 243 if(ioctl(dev, DIOCGETRULE, &pr) < 0) { 244 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); 245 return -1; 246 } 247 strlcpy(tmp_label, pr.rule.label, sizeof(tmp_label)); 248 p = tmp_label; 249 strsep(&p, " "); 250 if(0 == strcmp(tmp_label, label_start)) { 251 if(rem_host && (inet_ntop(AF_INET6, &pr.rule.src.addr.v.a.addr.v6, rem_host, rem_hostlen) == NULL)) { 252 return -1; 253 } 254 if(rem_port) 255 *rem_port = ntohs(pr.rule.src.port[0]); 256 if(int_client && (inet_ntop(AF_INET6, &pr.rule.dst.addr.v.a.addr.v6, int_client, int_clientlen) == NULL)) { 257 return -1; 258 } 259 if(int_port) 260 *int_port = ntohs(pr.rule.dst.port[0]); 261 if(proto) 262 *proto = pr.rule.proto; 263 if(timestamp) 264 sscanf(p, "ts-%u", timestamp); 265 if(desc) { 266 strsep(&p, " "); 267 if(p) { 268 strlcpy(desc, p, desclen); 269 } else { 270 desc[0] = '\0'; 271 } 272 } 273#ifdef PFRULE_INOUT_COUNTS 274 if(packets) 275 *packets = pr.rule.packets[0] + pr.rule.packets[1]; 276 if(bytes) 277 *bytes = pr.rule.bytes[0] + pr.rule.bytes[1]; 278#else 279 if(packets) 280 *packets = pr.rule.packets; 281 if(bytes) 282 *bytes = pr.rule.bytes; 283#endif 284 return 0; 285 } 286 } 287 /* not found */ 288 return -2; 289} 290 291int update_pinhole(unsigned short uid, unsigned int timestamp) 292{ 293 /* TODO : 294 * As it is not possible to change rule label, we should : 295 * 1 - delete 296 * 2 - Add new 297 * the stats of the rule will then be reset :( */ 298 UNUSED(uid); UNUSED(timestamp); 299 return -42; /* not implemented */ 300} 301 302/* return the number of rules removed 303 * or a negative integer in case of error */ 304int clean_pinhole_list(unsigned int * next_timestamp) 305{ 306 int i; 307 struct pfioc_rule pr; 308 time_t current_time; 309 unsigned int ts; 310 int uid; 311 unsigned int min_ts = UINT_MAX; 312 int min_uid = INT_MAX, max_uid = -1; 313 int n = 0; 314 315 if(dev<0) { 316 syslog(LOG_ERR, "pf device is not open"); 317 return -1; 318 } 319 current_time = time(NULL); 320 memset(&pr, 0, sizeof(pr)); 321 strlcpy(pr.anchor, anchor_name, MAXPATHLEN); 322#ifndef PF_NEWSTYLE 323 pr.rule.action = PF_PASS; 324#endif 325 if(ioctl(dev, DIOCGETRULES, &pr) < 0) { 326 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); 327 return -1; 328 } 329 for(i = pr.nr - 1; i >= 0; i--) { 330 pr.nr = i; 331 if(ioctl(dev, DIOCGETRULE, &pr) < 0) { 332 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULE): %m"); 333 return -1; 334 } 335 if(sscanf(pr.rule.label, PINEHOLE_LABEL_FORMAT_SKIPDESC, &uid, &ts) != 2) { 336 syslog(LOG_DEBUG, "rule with label '%s' is not a IGD pinhole", pr.rule.label); 337 continue; 338 } 339 if(ts <= (unsigned int)current_time) { 340 syslog(LOG_INFO, "removing expired pinhole '%s'", pr.rule.label); 341 pr.action = PF_CHANGE_GET_TICKET; 342 if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { 343 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_GET_TICKET: %m"); 344 return -1; 345 } 346 pr.action = PF_CHANGE_REMOVE; 347 pr.nr = i; 348 if(ioctl(dev, DIOCCHANGERULE, &pr) < 0) { 349 syslog(LOG_ERR, "ioctl(dev, DIOCCHANGERULE, ...) PF_CHANGE_REMOVE: %m"); 350 return -1; 351 } 352 n++; 353#ifndef PF_NEWSTYLE 354 pr.rule.action = PF_PASS; 355#endif 356 if(ioctl(dev, DIOCGETRULES, &pr) < 0) { 357 syslog(LOG_ERR, "ioctl(dev, DIOCGETRULES, ...): %m"); 358 return -1; 359 } 360 } else { 361 if(uid > max_uid) 362 max_uid = uid; 363 else if(uid < min_uid) 364 min_uid = uid; 365 if(ts < min_ts) 366 min_ts = ts; 367 } 368 } 369 if(next_timestamp && (min_ts != UINT_MAX)) 370 *next_timestamp = min_ts; 371 if(max_uid > 0) { 372 if(((min_uid - 32000) <= next_uid) && (next_uid <= max_uid)) { 373 next_uid = max_uid + 1; 374 } 375 if(next_uid >= 65535) { 376 next_uid = 1; 377 } 378 } 379 return n; /* number of rules removed */ 380} 381 382#endif /* ENABLE_UPNPPINHOLE */ 383