1/* $Id: iptpinhole.c,v 1.14 2015/02/10 15:01:03 nanard Exp $ */ 2/* MiniUPnP project 3 * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ 4 * (c) 2012-2015 Thomas Bernard 5 * This software is subject to the conditions detailed 6 * in the LICENCE file provided within the distribution */ 7 8#include <stdlib.h> 9#include <string.h> 10#include <syslog.h> 11#include <errno.h> 12#include <arpa/inet.h> 13#include <sys/queue.h> 14 15#include "../config.h" 16#include "iptpinhole.h" 17#include "../upnpglobalvars.h" 18 19#ifdef ENABLE_UPNPPINHOLE 20 21#include <iptables.h> 22#include <libiptc/libip6tc.h> 23#include "tiny_nf_nat.h" 24 25#define IP6TC_HANDLE struct ip6tc_handle * 26 27static int next_uid = 1; 28 29static LIST_HEAD(pinhole_list_t, pinhole_t) pinhole_list; 30 31static struct pinhole_t * 32get_pinhole(unsigned short uid); 33 34struct pinhole_t { 35 struct in6_addr saddr; 36 struct in6_addr daddr; 37 LIST_ENTRY(pinhole_t) entries; 38 unsigned int timestamp; 39 unsigned short sport; 40 unsigned short dport; 41 unsigned short uid; 42 unsigned char proto; 43 char desc[]; 44}; 45 46void init_iptpinhole(void) 47{ 48 LIST_INIT(&pinhole_list); 49} 50 51void shutdown_iptpinhole(void) 52{ 53 /* TODO empty list */ 54} 55 56/* return uid */ 57static int 58add_to_pinhole_list(struct in6_addr * saddr, unsigned short sport, 59 struct in6_addr * daddr, unsigned short dport, 60 int proto, const char *desc, unsigned int timestamp) 61{ 62 struct pinhole_t * p; 63 64 p = calloc(1, sizeof(struct pinhole_t) + strlen(desc) + 1); 65 if(!p) { 66 syslog(LOG_ERR, "add_to_pinhole_list calloc() error"); 67 return -1; 68 } 69 strcpy(p->desc, desc); 70 memcpy(&p->saddr, saddr, sizeof(struct in6_addr)); 71 p->sport = sport; 72 memcpy(&p->daddr, daddr, sizeof(struct in6_addr)); 73 p->dport = dport; 74 p->timestamp = timestamp; 75 p->proto = (unsigned char)proto; 76 LIST_INSERT_HEAD(&pinhole_list, p, entries); 77 while(get_pinhole(next_uid) != NULL) { 78 next_uid++; 79 if(next_uid > 65535) 80 next_uid = 1; 81 } 82 p->uid = next_uid; 83 next_uid++; 84 if(next_uid > 65535) 85 next_uid = 1; 86 return p->uid; 87} 88 89static struct pinhole_t * 90get_pinhole(unsigned short uid) 91{ 92 struct pinhole_t * p; 93 94 for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) { 95 if(p->uid == uid) 96 return p; 97 } 98 return NULL; /* not found */ 99} 100 101/* new_match() 102 * Allocate and set a new ip6t_entry_match structure 103 * The caller must free() it after usage */ 104static struct ip6t_entry_match * 105new_match(int proto, unsigned short sport, unsigned short dport) 106{ 107 struct ip6t_entry_match *match; 108 struct ip6t_tcp *info; /* TODO : use ip6t_udp if needed */ 109 size_t size; 110 const char * name; 111 size = XT_ALIGN(sizeof(struct ip6t_entry_match)) 112 + XT_ALIGN(sizeof(struct ip6t_tcp)); 113 match = calloc(1, size); 114 match->u.user.match_size = size; 115 switch(proto) { 116 case IPPROTO_TCP: 117 name = "tcp"; 118 break; 119 case IPPROTO_UDP: 120 name = "udp"; 121 break; 122 case IPPROTO_UDPLITE: 123 name = "udplite"; 124 break; 125 default: 126 name = NULL; 127 } 128 if(name) 129 strncpy(match->u.user.name, name, sizeof(match->u.user.name)); 130 else 131 syslog(LOG_WARNING, "no name for protocol %d", proto); 132 info = (struct ip6t_tcp *)match->data; 133 if(sport) { 134 info->spts[0] = sport; /* specified source port */ 135 info->spts[1] = sport; 136 } else { 137 info->spts[0] = 0; /* all source ports */ 138 info->spts[1] = 0xFFFF; 139 } 140 info->dpts[0] = dport; /* specified destination port */ 141 info->dpts[1] = dport; 142 return match; 143} 144 145static struct ip6t_entry_target * 146get_accept_target(void) 147{ 148 struct ip6t_entry_target * target = NULL; 149 size_t size; 150 size = XT_ALIGN(sizeof(struct ip6t_entry_target)) 151 + XT_ALIGN(sizeof(int)); 152 target = calloc(1, size); 153 target->u.user.target_size = size; 154 strncpy(target->u.user.name, "ACCEPT", sizeof(target->u.user.name)); 155 return target; 156} 157 158static int 159ip6tc_init_verify_append(const char * table, 160 const char * chain, 161 struct ip6t_entry * e) 162{ 163 IP6TC_HANDLE h; 164 165 h = ip6tc_init(table); 166 if(!h) { 167 syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); 168 return -1; 169 } 170 if(!ip6tc_is_chain(chain, h)) { 171 syslog(LOG_ERR, "chain %s not found", chain); 172 goto error; 173 } 174 if(!ip6tc_append_entry(chain, e, h)) { 175 syslog(LOG_ERR, "ip6tc_append_entry() error : %s", ip6tc_strerror(errno)); 176 goto error; 177 } 178 if(!ip6tc_commit(h)) { 179 syslog(LOG_ERR, "ip6tc_commit() error : %s", ip6tc_strerror(errno)); 180 goto error; 181 } 182 return 0; /* ok */ 183error: 184 ip6tc_free(h); 185 return -1; 186} 187 188/* 189ip6tables -I %s %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j ACCEPT 190ip6tables -I %s %d -p %s -i %s --sport %hu -d %s --dport %hu -j ACCEPT 191 192miniupnpd_forward_chain, line_number, proto, ext_if_name, raddr, rport, iaddr, iport 193 194ip6tables -t raw -I PREROUTING %d -p %s -i %s -s %s --sport %hu -d %s --dport %hu -j TRACE 195ip6tables -t raw -I PREROUTING %d -p %s -i %s --sport %hu -d %s --dport %hu -j TRACE 196*/ 197int add_pinhole(const char * ifname, 198 const char * rem_host, unsigned short rem_port, 199 const char * int_client, unsigned short int_port, 200 int proto, const char * desc, unsigned int timestamp) 201{ 202 int uid; 203 struct ip6t_entry * e; 204 struct ip6t_entry * tmp; 205 struct ip6t_entry_match *match = NULL; 206 struct ip6t_entry_target *target = NULL; 207 208 e = calloc(1, sizeof(struct ip6t_entry)); 209 if(!e) { 210 syslog(LOG_ERR, "%s: calloc(%d) failed", 211 "add_pinhole", (int)sizeof(struct ip6t_entry)); 212 return -1; 213 } 214 e->ipv6.proto = proto; 215 if (proto) 216 e->ipv6.flags |= IP6T_F_PROTO; 217 218 if(ifname) 219 strncpy(e->ipv6.iniface, ifname, IFNAMSIZ); 220 if(rem_host && (rem_host[0] != '\0')) { 221 inet_pton(AF_INET6, rem_host, &e->ipv6.src); 222 memset(&e->ipv6.smsk, 0xff, sizeof(e->ipv6.smsk)); 223 } 224 inet_pton(AF_INET6, int_client, &e->ipv6.dst); 225 memset(&e->ipv6.dmsk, 0xff, sizeof(e->ipv6.dmsk)); 226 227 /*e->nfcache = NFC_IP_DST_PT;*/ 228 /*e->nfcache |= NFC_UNKNOWN;*/ 229 230 match = new_match(proto, rem_port, int_port); 231 target = get_accept_target(); 232 tmp = realloc(e, sizeof(struct ip6t_entry) 233 + match->u.match_size 234 + target->u.target_size); 235 if(!tmp) { 236 syslog(LOG_ERR, "%s: realloc(%d) failed", 237 "add_pinhole", (int)(sizeof(struct ip6t_entry) + match->u.match_size + target->u.target_size)); 238 free(e); 239 free(match); 240 free(target); 241 return -1; 242 } 243 e = tmp; 244 memcpy(e->elems, match, match->u.match_size); 245 memcpy(e->elems + match->u.match_size, target, target->u.target_size); 246 e->target_offset = sizeof(struct ip6t_entry) 247 + match->u.match_size; 248 e->next_offset = sizeof(struct ip6t_entry) 249 + match->u.match_size 250 + target->u.target_size; 251 free(match); 252 free(target); 253 254 if(ip6tc_init_verify_append("filter", miniupnpd_v6_filter_chain, e) < 0) { 255 free(e); 256 return -1; 257 } 258 uid = add_to_pinhole_list(&e->ipv6.src, rem_port, 259 &e->ipv6.dst, int_port, 260 proto, desc, timestamp); 261 free(e); 262 return uid; 263} 264 265int 266delete_pinhole(unsigned short uid) 267{ 268 struct pinhole_t * p; 269 IP6TC_HANDLE h; 270 const struct ip6t_entry * e; 271 const struct ip6t_entry_match *match = NULL; 272 /*const struct ip6t_entry_target *target = NULL;*/ 273 unsigned int index; 274 275 p = get_pinhole(uid); 276 if(!p) 277 return -2; /* not found */ 278 279 h = ip6tc_init("filter"); 280 if(!h) { 281 syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); 282 return -1; 283 } 284 if(!ip6tc_is_chain(miniupnpd_v6_filter_chain, h)) { 285 syslog(LOG_ERR, "chain %s not found", miniupnpd_v6_filter_chain); 286 goto error; 287 } 288 index = 0; 289 for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h); 290 e; 291 e = ip6tc_next_rule(e, h)) { 292 if((e->ipv6.proto == p->proto) && 293 (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) && 294 (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) { 295 const struct ip6t_tcp * info; 296 match = (const struct ip6t_entry_match *)&e->elems; 297 info = (const struct ip6t_tcp *)&match->data; 298 if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) { 299 if(!ip6tc_delete_num_entry(miniupnpd_v6_filter_chain, index, h)) { 300 syslog(LOG_ERR, "ip6tc_delete_num_entry(%s,%d,...): %s", 301 miniupnpd_v6_filter_chain, index, ip6tc_strerror(errno)); 302 goto error; 303 } 304 if(!ip6tc_commit(h)) { 305 syslog(LOG_ERR, "ip6tc_commit(): %s", 306 ip6tc_strerror(errno)); 307 goto error; 308 } 309 ip6tc_free(h); 310 LIST_REMOVE(p, entries); 311 return 0; /* ok */ 312 } 313 } 314 index++; 315 } 316 ip6tc_free(h); 317 syslog(LOG_WARNING, "delete_pinhole() rule with PID=%hu not found", uid); 318 return -2; /* not found */ 319error: 320 ip6tc_free(h); 321 return -1; 322} 323 324int 325update_pinhole(unsigned short uid, unsigned int timestamp) 326{ 327 struct pinhole_t * p; 328 329 p = get_pinhole(uid); 330 if(p) { 331 p->timestamp = timestamp; 332 return 0; 333 } else { 334 return -2; /* Not found */ 335 } 336} 337 338int 339get_pinhole_info(unsigned short uid, 340 char * rem_host, int rem_hostlen, 341 unsigned short * rem_port, 342 char * int_client, int int_clientlen, 343 unsigned short * int_port, 344 int * proto, char * desc, int desclen, 345 unsigned int * timestamp, 346 u_int64_t * packets, u_int64_t * bytes) 347{ 348 struct pinhole_t * p; 349 350 p = get_pinhole(uid); 351 if(!p) 352 return -2; /* Not found */ 353 if(rem_host && (rem_host[0] != '\0')) { 354 if(inet_ntop(AF_INET6, &p->saddr, rem_host, rem_hostlen) == NULL) 355 return -1; 356 } 357 if(rem_port) 358 *rem_port = p->sport; 359 if(int_client) { 360 if(inet_ntop(AF_INET6, &p->daddr, int_client, int_clientlen) == NULL) 361 return -1; 362 } 363 if(int_port) 364 *int_port = p->dport; 365 if(proto) 366 *proto = p->proto; 367 if(timestamp) 368 *timestamp = p->timestamp; 369 if (desc) 370 strncpy(desc, p->desc, desclen); 371 if(packets || bytes) { 372 /* theses informations need to be read from netfilter */ 373 IP6TC_HANDLE h; 374 const struct ip6t_entry * e; 375 const struct ip6t_entry_match * match; 376 h = ip6tc_init("filter"); 377 if(!h) { 378 syslog(LOG_ERR, "ip6tc_init error : %s", ip6tc_strerror(errno)); 379 return -1; 380 } 381 for(e = ip6tc_first_rule(miniupnpd_v6_filter_chain, h); 382 e; 383 e = ip6tc_next_rule(e, h)) { 384 if((e->ipv6.proto == p->proto) && 385 (0 == memcmp(&e->ipv6.src, &p->saddr, sizeof(e->ipv6.src))) && 386 (0 == memcmp(&e->ipv6.dst, &p->daddr, sizeof(e->ipv6.dst)))) { 387 const struct ip6t_tcp * info; 388 match = (const struct ip6t_entry_match *)&e->elems; 389 info = (const struct ip6t_tcp *)&match->data; 390 if((info->spts[0] == p->sport) && (info->dpts[0] == p->dport)) { 391 if(packets) 392 *packets = e->counters.pcnt; 393 if(bytes) 394 *bytes = e->counters.bcnt; 395 break; 396 } 397 } 398 } 399 ip6tc_free(h); 400 } 401 return 0; 402} 403 404int get_pinhole_uid_by_index(int index) 405{ 406 struct pinhole_t * p; 407 408 for(p = pinhole_list.lh_first; p != NULL; p = p->entries.le_next) 409 if (!index--) 410 return p->uid; 411 return -1; 412} 413 414int 415clean_pinhole_list(unsigned int * next_timestamp) 416{ 417 unsigned int min_ts = UINT_MAX; 418 struct pinhole_t * p; 419 time_t current_time; 420 int n = 0; 421 422 current_time = time(NULL); 423 p = pinhole_list.lh_first; 424 while(p != NULL) { 425 if(p->timestamp <= (unsigned int)current_time) { 426 unsigned short uid = p->uid; 427 syslog(LOG_INFO, "removing expired pinhole with uid=%hu", uid); 428 p = p->entries.le_next; 429 if(delete_pinhole(uid) == 0) 430 n++; 431 else 432 break; 433 } else { 434 if(p->timestamp < min_ts) 435 min_ts = p->timestamp; 436 p = p->entries.le_next; 437 } 438 } 439 if(next_timestamp && (min_ts != UINT_MAX)) 440 *next_timestamp = min_ts; 441 return n; 442} 443 444#endif /* ENABLE_UPNPPINHOLE */ 445