/* * AVTF Custom hacky NAT-PMP daemon utilizing PFCFBP. * (c) Vadim Goncharov , 2008. * * Covered by BSD license. * * It is completely hacky for AVTF conditions, providing no * startup notifications, constante are hardcoded, and so on. * We are also using blocking syscalls because of only two * descriptors and simple task - answer from backend must be * waited for client anyway. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*** Global vars and constants. ***/ #define PAGESIZE 4096 #define MAX_NATPMP_RETRTIME 64 /* Maximum retransmit time (MRT) */ #define MIN_NATPMP_LIFETIME 720 /* for renewal, 1/8 should be > MRT, and multiple of alarm */ #define ALARM_TIME 90 /* should be >= than max retransmit time? */ #define SYSLOG_IDENT "avtnatpmpd" /* NAT PMP protocol constants. */ #define NATPMP_VERSION 0 /* XXX we don't set it, just do bzero() */ #define NATPMP_PORT 5351 #define NATPMP_RESULT_SUCCESS 0 #define NATPMP_RESULT_UNSUPVER 1 #define NATPMP_RESULT_NOTAUTH 2 #define NATPMP_RESULT_NETFAIL 3 #define NATPMP_RESULT_NORESOUR 4 #define NATPMP_RESULT_OPNOTSUPP 5 #define NATPMP_RESP_SHIFT 128 #define NATPMP_PUBADDR_REQ 0 #define NATPMP_PUBADDR_RESP (NATPMP_RESP_SHIFT + NATPMP_PUBADDR_REQ) #define NATPMP_MAPUDP_REQ 1 #define NATPMP_MAPTCP_REQ 2 #define NATPMP_MAPUDP_RESP (NATPMP_RESP_SHIFT + NATPMP_MAPUDP_REQ) #define NATPMP_MAPTCP_RESP (NATPMP_RESP_SHIFT + NATPMP_MAPTCP_RESP) /* NAT PMP requests and answers packet type structure (GCC-specific packed) */ struct _npmp_resp0 { /* reply for opcode 0 */ uint16_t rescode; uint32_t sec_epoch; in_addr_t pubaddr; } __attribute__((packed)); struct _npmp_mapreq { uint16_t reserved; uint16_t priv_port; uint16_t pub_port; uint32_t lifetime; } __attribute__((packed)); struct _npmp_mapresp { uint16_t rescode; uint32_t sec_epoch; uint16_t priv_port; uint16_t pub_port; uint32_t lifetime; } __attribute__((packed)); struct nat_pmp_packet { uint8_t ver; uint8_t op; union { struct _npmp_resp0 npmp_resp0; struct _npmp_mapreq npmp_mapreq; struct _npmp_mapresp npmp_mapresp; } npmp_u; #define rescode npmp_u.npmp_resp0.rescode #define sec_epoch npmp_u.npmp_mapresp.sec_epoch #define gw_pubaddr npmp_u.npmp_resp0.pubaddr #define req_priv_port npmp_u.npmp_mapreq.priv_port #define req_pub_port npmp_u.npmp_mapreq.pub_port #define resp_priv_port npmp_u.npmp_mapresp.priv_port #define resp_pub_port npmp_u.npmp_mapresp.pub_port #define req_lifetime npmp_u.npmp_mapreq.lifetime #define resp_lifetime npmp_u.npmp_mapresp.lifetime } __attribute__((packed)); #define SIGBLOCK do { \ sigprocmask(SIG_SETMASK, &blockedset, NULL); \ } while(0); #define SIGUNBLOCK do { \ sigprocmask(SIG_SETMASK, &mainset, NULL); \ } while(0); static int natpmpsock; static sigset_t mainset, blockedset; /* For sigblock/unblock macros. */ char buf[PAGESIZE]; /* where to read and write from */ int debug = 0; /* debug level */ int quit; /* got term signal */ int alarm_triggered; /* got timer */ time_t startup_time; /* when we are started */ int numpubaddrs; /* number of entries in pubaddrs[] */ struct loc2pub { /* translating int addresses to ext */ struct in_addr host; struct in_addr mask; struct in_addr puba; } *pubaddrs; /*********************************************************************** * PFCFBP backend functions - block of code compatible with miniupnpd. * ***********************************************************************/ /* #include XXX FreeBSD's for LIST* macros - check portability! */ /* Global types and vars. */ #define LIST_UPD_PERIOD 60 /* should be >= ALARM_TIME/2, close to MRT */ #define MAXTXTIPLEN 15 #define MAX_ID_LEN 31 #define MAX_PROTO_LINE 512 #define DESC_LEN 63 struct cache_entry { TAILQ_ENTRY(cache_entry) entries; char id[MAX_ID_LEN+1]; char proto[4]; /* "tcp" or "udp" */ char locaddr[MAXTXTIPLEN+1]; char pubaddr[MAXTXTIPLEN+1]; char remaddr[MAXTXTIPLEN+1]; uint16_t locport; uint16_t pubport; uint16_t remport; char description[DESC_LEN+1]; } *ce; TAILQ_HEAD(cachehdr, cache_entry) cachehead = TAILQ_HEAD_INITIALIZER(cachehead); int backend_sock = -1; FILE *stream; time_t list_last_updated; /* Constant for my daemon, should be configurable in miniupnpd. */ static char sockpath[sizeof(struct sockaddr_un)-24] = "/var/run/pfcfbp.sock"; /* * Common error handling for funcs - simply close sonnection in hope on * next open backend will reinit or something and error will be gone. */ #define STREAMERR do { \ fclose(stream); \ backend_sock = -1; \ syslog(LOG_ERR, "stream read or proto format error, connection to backend closed"); \ return (-1); \ } while (0); /* Read one line from backend with basic error checking and CRLF handling. */ #define READ_LINE(str) do { \ bzero((str), MAX_PROTO_LINE); \ if (!fgets((str), MAX_PROTO_LINE, stream)) \ STREAMERR; \ if (strlen((str)) < 6) \ STREAMERR; \ if ((str)[strlen((str)) - 1] == '\n') \ (str)[strlen((str)) - 1] = '\0'; \ if ((str)[strlen((str)) - 2] == '\n') \ (str)[strlen((str)) - 2] = '\0'; \ if ((str)[strlen((str)) - 1] == '\r') \ (str)[strlen((str)) - 1] = '\0'; \ if ((str)[strlen((str)) - 2] == '\r') \ (str)[strlen((str)) - 2] = '\0'; \ if (debug > 1) \ syslog(LOG_DEBUG, "debug: backend read: %s", (str)); \ } while (0); /* * Helper functions - we are opening connection to backend on demand (because * protocol allows to close it at any time), but keeping connection open to * speed up subsequent calls by not waiting for backend startup/connection * initializing/etc. Also, getting list of redirects for every single * operation is quite exepnsive, so we are keeping a cache of it in * internal LIST and rereading no more often than predefined interval (but * still do because it could be changed by other backend). */ /* If backend is not connected, do this. */ int open_backend_stream(void) { struct sockaddr_un addr; if (backend_sock > 0) return (0); backend_sock = socket(AF_UNIX, SOCK_STREAM, 0); if (backend_sock < 0) return (-1); addr.sun_family = AF_UNIX; strncpy(addr.sun_path, sockpath, sizeof(addr.sun_path)); if (connect(backend_sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) < 0) { syslog(LOG_ERR, "connect error: %m"); return (-1); } /* Successful at last, make a FILE stream from socket. */ stream = fdopen(backend_sock, "r+"); if (stream == NULL) { syslog(LOG_ERR, "fdopen error: %m"); close(backend_sock); backend_sock = -1; return (-1); } return (0); } /* Check if cached redirects list is actual, refresh if needed. */ int update_cached_list(void) { char resp[MAX_PROTO_LINE]; struct cache_entry *ce; struct in_addr iatmp; int i, n; /* We don't need rereading fresh list. */ if (time(NULL) - list_last_updated < LIST_UPD_PERIOD) return 0; /* Ensure we are connected to backend. */ if (open_backend_stream() == -1) return (-1); /* First delete list. */ while (!TAILQ_EMPTY(&cachehead)) { ce = TAILQ_FIRST(&cachehead); TAILQ_REMOVE(&cachehead, ce, entries); free(ce); } /* Request list from backend, read and parse it. */ if (fputs("LIST\n", stream)) STREAMERR; for (;;) { READ_LINE(resp); if (strncmp(resp, "ENDLIST", 7) == 0) break; ce = malloc(sizeof(struct cache_entry)); bzero(ce, sizeof(struct cache_entry)); n = sscanf(resp, "LIST %31s %3s %15s %5hu %15s %5hu %15s %5hu %n%1s", ce->id, ce->proto, ce->locaddr, &ce->locport, ce->pubaddr, &ce->pubport, ce->remaddr, &ce->remport, &i, ce->description); /* Do syntax checks on args before description. */ if (n < 8) { free(ce); STREAMERR; } if (((strcmp(ce->proto, "tcp") != 0) && (strcmp(ce->proto, "udp") != 0)) || (inet_aton(ce->locaddr, &iatmp) == 0) || (inet_aton(ce->pubaddr, &iatmp) == 0) || (inet_aton(ce->remaddr, &iatmp) == 0)) { free(ce); STREAMERR; } /* If have a description, copy it separately. */ if (n == 9) strncpy(ce->description, &resp[i], DESC_LEN); /* Finally it was correct, hook into cache. */ TAILQ_INSERT_TAIL(&cachehead, ce, entries); } list_last_updated = time(NULL); return (0); } int delete_redirect(struct cache_entry *ce, const char *where) { char resp[MAX_PROTO_LINE]; /* Not found. */ if (ce == NULL) return (-1); syslog(LOG_DEBUG, "%s: id=%s pr=%s la=%s pa=%s ra=%s lp=%hu pp=%hu rp=%hu desc=%s", __func__, ce->id, ce->proto, ce->locaddr, ce->pubaddr, ce->remaddr, ce->locport, ce->pubport, ce->remport, ce->description); /* Send request to backend. */ if (fprintf(stream, "DELETE %s\n", ce->id) < 1) STREAMERR; /* Read response. */ READ_LINE(resp); /* * Has backend reported an error or success? * XXX We assume backend works correct if DELETED was returned, so * we don't check ID. */ if (strncmp("DELETED ", resp, 8) != 0) { syslog(LOG_ERR, "%s: backend returned: %s", where, resp); return (-1); } /* Remove entry from list. */ TAILQ_REMOVE(&cachehead, ce, entries); free(ce); return (0); } /* * Actual interface functions, headers copied from miniupnpd's files.. * Return values: 0 success (found) * -1 = error or rule not found * * XXX HACK! We are cheating and using miniupnpd's "ifname" for external IP. * However, we are passing to backend "0.0.0.0" if "ifname" don't looks like * IP address (e.g. if miniupnpd calls with real iface name), so it will * work with default configuration of one external IP. */ int add_redirect_rule2(const char * ifname, unsigned short eport, const char * iaddr, unsigned short iport, int proto, const char * desc) { char resp[MAX_PROTO_LINE]; char pubaddr[MAXTXTIPLEN+1]; char txtproto[4]; struct cache_entry *ce; struct in_addr iatmp; int n; syslog(LOG_DEBUG, "%s(%s, %hu, %s, %hu, %d, %s)", __func__, ifname, eport, iaddr, iport, proto, desc); /* Ensure we are connected. */ if (open_backend_stream() == -1) return (-1); /* Check external IP argument. */ if (inet_aton(ifname, &iatmp)) strcpy(pubaddr, ifname); else strcpy(pubaddr, "0.0.0.0"); strcpy(txtproto, (proto == IPPROTO_TCP) ? "tcp" : "udp"); /* Fill in and send request to backend. */ if (fprintf(stream, "ADD %s %s %d %s %d 0.0.0.0 0 %s\n", txtproto, iaddr, iport, pubaddr, eport, desc) < 1) STREAMERR; /* Read response. */ READ_LINE(resp); /* Has backend reported an error or success? */ if (strncmp("ADDED ", resp, 6) != 0) { syslog(LOG_ERR, "add_redirect_rule2: backend returned: %s", resp); return (-1); } /* If success, then add returned ID to cache. */ ce = malloc(sizeof(struct cache_entry)); bzero(ce, sizeof(struct cache_entry)); n = sscanf(resp, "ADDED %31s", ce->id); strcpy(ce->proto, txtproto); strncpy(ce->locaddr, iaddr, MAXTXTIPLEN); ce->locport = iport; strcpy(ce->pubaddr, pubaddr); ce->pubport = eport; strcpy(ce->remaddr, "0.0.0.0"); ce->remport = 0; strncpy(ce->description, desc, DESC_LEN); /* Hook data into list here to not reread entire list. */ TAILQ_INSERT_TAIL(&cachehead, ce, entries); return (0); } /* * get_redirect_rule(): on input: ext port and proto (and ext IP) * Return: all other data for those, if found. */ int get_redirect_rule(const char * ifname, unsigned short eport, int proto, char * iaddr, int iaddrlen, unsigned short * iport, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes) { char pubaddr[MAXTXTIPLEN+1]; char txtproto[4]; struct cache_entry *ce; struct in_addr iatmp; /* Ensure we are connected and have fresh list. */ if (update_cached_list() == -1) return (-1); /* Check external IP argument. */ if (inet_aton(ifname, &iatmp)) strcpy(pubaddr, ifname); else strcpy(pubaddr, "0.0.0.0"); strcpy(txtproto, (proto == IPPROTO_TCP) ? "tcp" : "udp"); /* Iterate through list. */ TAILQ_FOREACH(ce, &cachehead, entries) { if ((eport == ce->pubport) && (strcmp(txtproto, ce->proto) == 0) && (strcmp(pubaddr, ce->pubaddr) == 0)) break; } /* Not found. */ if (ce == NULL) return (-1); /* Else fill out parameters. */ if (iport) *iport = ce->locport; if (iaddr) strncpy(iaddr, ce->locaddr, iaddrlen-1); if (desc) strncpy(desc, ce->description, desclen-1); if (packets) *packets = 0; if (bytes) *bytes = 0; return (0); } int get_redirect_rule_by_index(int index, char * ifname, unsigned short * eport, char * iaddr, int iaddrlen, unsigned short * iport, int * proto, char * desc, int desclen, u_int64_t * packets, u_int64_t * bytes) { struct cache_entry *ce; int i; /* Ensure we are connected and have fresh list. */ if (update_cached_list() == -1) return (-1); /* Iterate through list. */ i = 0; TAILQ_FOREACH(ce, &cachehead, entries) { if (i == index) break; i++; } /* Not found. */ if (ce == NULL) return (-1); /* Else fill out parameters. */ *proto = (strcmp("tcp", ce->proto) == 0) ? IPPROTO_TCP : IPPROTO_UDP; *eport = ce->pubport; *iport = ce->locport; if (ifname) strncpy(ifname, ce->pubaddr, MAXTXTIPLEN); if (iaddr) strncpy(iaddr, ce->locaddr, iaddrlen-1); if (desc) strncpy(desc, ce->description, desclen-1); if (packets) *packets = 0; if (bytes) *bytes = 0; return (0); } int delete_redirect_rule(const char * ifname, unsigned short eport, int proto) { char pubaddr[MAXTXTIPLEN+1]; char txtproto[4]; struct cache_entry *ce; struct in_addr iatmp; /* Ensure we are connected and have fresh list. */ if (update_cached_list() == -1) return (-1); /* Check external IP argument. */ if (inet_aton(ifname, &iatmp)) strcpy(pubaddr, ifname); else strcpy(pubaddr, "0.0.0.0"); strcpy(txtproto, (proto == IPPROTO_TCP) ? "tcp" : "udp"); /* Iterate through list. */ TAILQ_FOREACH(ce, &cachehead, entries) { if ((eport == ce->pubport) && (strcmp(txtproto, ce->proto) == 0) && (strcmp(pubaddr, ce->pubaddr) == 0)) break; } return delete_redirect(ce, __func__); } /* * Filtering is task of backend, provide stubs only. * int add_filter_rule2(const char * ifname, const char * iaddr, unsigned short eport, unsigned short iport, int proto, const char * desc) { return (0); } int delete_filter_rule(const char * ifname, unsigned short eport, int proto) { return (0); } */ /************************************************************** * End of functions which could be cut-n-pasted to miniupnpd. * **************************************************************/ /*** Functions for main program. ***/ void term_signal(int z) { quit = 1; } /* This will execute every ALARM_TIME seconds. */ void alarm_signal(int z) { alarm_triggered = 1; alarm(ALARM_TIME); } void init_sig(void) { struct sigaction sv; memset(&sv, 0, sizeof(struct sigaction)); sv.sa_flags = 0; sigemptyset(&sv.sa_mask); /* Not restartable. */ sv.sa_handler = term_signal; sigaction(SIGTERM, &sv, NULL); sigaction(SIGINT, &sv, NULL); /* Interrupt recvfrom(), for backend will block signals. */ sv.sa_handler = alarm_signal; sigaction(SIGALRM, &sv, NULL); /* Restartable. */ sv.sa_flags = SA_RESTART; sv.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sv, NULL); sigaction(SIGCHLD, &sv, NULL); sigaction(SIGHUP, &sv, NULL); /* Prepare sets for SIGBLOCK/SIGUNBLOCK macros. */ sigfillset(&blockedset); sigdelset(&blockedset, SIGTERM); sigdelset(&blockedset, SIGINT); sigdelset(&blockedset, SIGQUIT); sigdelset(&blockedset, SIGTRAP); /* XXX for gdb? */ sigprocmask(0, NULL, &mainset); } /* Find public address for corresponding local address. */ in_addr_t locaddr2pubaddr(struct in_addr locaddr) { int i; for (i = 0; i < numpubaddrs; i++) if ((pubaddrs[i].host.s_addr) == (locaddr.s_addr & pubaddrs[i].mask.s_addr)) return pubaddrs[i].puba.s_addr; return inet_addr("0.0.0.0"); } /* * Delete old redirections in a miniupnpd-compatible way. * XXX miniupnpd stores (in description) expire time relative to startup_time, * not absolute time(), so NOTHING will be cleaned in backend after daemon * restart with respect to actual redirection lifetime at client, as it could * be. But we still keep compatibility to miniupnpd, until it changes to more * proper timestamps. * XXX No, the more was uptime, the longer it takes to clean old redirections. * I now switch to absolute expire timestamps. */ int clean_expired(void) { struct cache_entry *ce_temp; time_t timestamp, curtime; syslog(LOG_DEBUG, "debug: running %s()", __func__); /* Ensure we are connected and have fresh list. */ if (update_cached_list() == -1) return (-1); curtime = time(NULL); TAILQ_FOREACH_SAFE(ce, &cachehead, entries, ce_temp) { if (sscanf(ce->description, "NAT-PMP %u", ×tamp) == 1) { if (timestamp <= curtime) { if (delete_redirect(ce, __func__) != 0) return (-1); } } } return (0); } /* Handle one incoming request packet. */ void event_loop(void) { struct sockaddr_in cli_addr; in_addr_t gwpubaddr; /* can't call it gw_pubaddr - was #define... */ socklen_t cli_addr_len = sizeof(cli_addr); struct nat_pmp_packet req, resp; struct cache_entry *ce_temp; char locaddr[MAXTXTIPLEN+1]; char pubaddr[MAXTXTIPLEN+1]; char txtproto[4]; /* "tcp" or "udp" */ int proto; uint32_t lifetime; uint16_t locport; uint16_t pubport; char desc[DESC_LEN+1]; int i, n, resplen; /* Block until packet arrives. */ n = recvfrom(natpmpsock, &req, sizeof(req), 0, (struct sockaddr *)&cli_addr, &cli_addr_len); if (n < 0) { if (errno != EINTR) syslog(LOG_ERR, "recvfrom: %m"); return; } /* We can't work without textual version of IP address. */ if (!inet_ntop(AF_INET, &cli_addr.sin_addr, locaddr, sizeof(locaddr))) { syslog(LOG_ERR, "inet_ntop: %m"); return; } syslog(LOG_DEBUG, "NAT-PMP request received from %s:%hu (%d bytes)", locaddr, ntohs(cli_addr.sin_port), n); if ((n < 2) || ((n < 12) && ((req.op == NATPMP_MAPTCP_REQ) || (req.op == NATPMP_MAPUDP_REQ)))) { syslog(LOG_NOTICE, "discarding too short NAT-PMP request (%d bytes) from %s", n, locaddr); return; } if (req.op >= NATPMP_RESP_SHIFT) { syslog(LOG_NOTICE, "discarding NAT-PMP response (%hhu) from %s", req.op, locaddr); return; } /* First checks successful, now we will always respond. */ bzero(&resp, sizeof(resp)); resplen = 8; /* XXX to return timestamp only */ resp.op = req.op + NATPMP_RESP_SHIFT; /* For every opcode response is that. */ resp.sec_epoch = htonl(time(NULL) - startup_time); /* Set our timestamp here. */ if (req.ver != NATPMP_VERSION) { syslog(LOG_WARNING, "unsupported NAT-PMP version %hhu from %s", req.ver, locaddr); resp.rescode = htons(NATPMP_RESULT_UNSUPVER); goto sendit; } /* If we have no record for this client in our list, deny it. */ gwpubaddr = locaddr2pubaddr(cli_addr.sin_addr); if (gwpubaddr == INADDR_ANY) { syslog(LOG_WARNING, "public address for %s not found in list, denying request", locaddr); resp.rescode = htons(NATPMP_RESULT_NOTAUTH); goto sendit; } else if (!inet_ntop(AF_INET, &gwpubaddr, pubaddr, sizeof(pubaddr))) { syslog(LOG_ERR, "inet_ntop: %m"); resp.rescode = htons(NATPMP_RESULT_NETFAIL); goto sendit; /* XXX not so valid, but inet_ntop() shouldn't fail */ } /* Process request. */ strcpy(txtproto, "tcp"); proto = IPPROTO_TCP; /* XXX udp will override if needed */ switch (req.op) { case NATPMP_PUBADDR_REQ: syslog(LOG_INFO, "NAT-PMP public address request from %s", locaddr); resplen = 2 + sizeof(struct _npmp_resp0); resp.gw_pubaddr = gwpubaddr; break; case NATPMP_MAPUDP_REQ: strcpy(txtproto, "udp"); proto = IPPROTO_UDP; /* FALLTHROUGH */ case NATPMP_MAPTCP_REQ: resplen = 2 + sizeof(struct _npmp_mapresp); locport = ntohs(req.req_priv_port); pubport = ntohs(req.req_pub_port); lifetime= ntohl(req.req_lifetime); resp.resp_priv_port = req.req_priv_port; resp.rescode = NATPMP_RESULT_SUCCESS; resp.resp_lifetime = req.req_lifetime; syslog(LOG_INFO, "NAT-PMP port mapping request: " "%hu -> %s:%hu/%s with lifetime of %u sec", pubport, locaddr, locport, txtproto, lifetime); SIGBLOCK; /* Ensure we are connected and have fresh list. */ if (update_cached_list() == -1) { resp.rescode = htons(NATPMP_RESULT_NETFAIL); goto sendit; } SIGUNBLOCK; /* * Now actually handle the request, either delete or add mapping. * We must be prepared to handle identical repeating packets from * client, both for cases of mapping renewal and response packets * to client lost, in which case we must keep no state and still * produce identical response packet to client. */ if (lifetime == 0) { /* Delete request. */ SIGBLOCK; if (locport == 0) { /* Delete all redirestions for this client. */ TAILQ_FOREACH_SAFE(ce, &cachehead, entries, ce_temp) { if ((strcmp(locaddr, ce->locaddr) == 0) && (strncmp(ce->description, "NAT-PMP ", 8) == 0)) { if (delete_redirect(ce, "delete_all_for_client") != 0) resp.rescode = htons(NATPMP_RESULT_NETFAIL); } } } else { /* Delete one redirection for this client. */ TAILQ_FOREACH_SAFE(ce, &cachehead, entries, ce_temp) { if ((strcmp(locaddr, ce->locaddr) == 0) && (locport == ce->locport)) { /* Don't allow to delete manual mappings. */ if (strncmp(ce->description, "NAT-PMP ", 8) == 0) { if (delete_redirect(ce, "delete_one_for_client") != 0) resp.rescode = htons(NATPMP_RESULT_NETFAIL); } else { resp.rescode = htons(NATPMP_RESULT_NOTAUTH); syslog(LOG_WARNING, "client %s tries to delete " "non NAT-PMP mapping, denying request", locaddr); } break; } } /* * If redirection was not found, reply is still OK * (it could be deleted previously with reply lost) */ } SIGUNBLOCK; } else { /* Addition request. */ time_t timestamp, curtime; if (locport == 0) { resp.rescode = htons(NATPMP_RESULT_NOTAUTH); syslog(LOG_NOTICE, "client %s tries to redirect " "prohibited port, denying request", locaddr); goto sendit; } /* Find if redirection for this address and port already exists. */ SIGBLOCK; curtime = time(NULL); TAILQ_FOREACH_SAFE(ce, &cachehead, entries, ce_temp) { if ((strcmp(locaddr, ce->locaddr) == 0) && (locport == ce->locport) && (strcmp(txtproto, ce->proto) == 0)) { syslog(LOG_DEBUG, "iteration: id=%s pr=%s la=%s pa=%s ra=%s " "lp=%hu pp=%hu rp=%hu desc=%s", ce->id, ce->proto, ce->locaddr, ce->pubaddr, ce->remaddr, ce->locport, ce->pubport, ce->remport, ce->description); if (sscanf(ce->description, "NAT-PMP %u", ×tamp) != 1) { /* This is manual redirect, do nothing. */ resp.resp_pub_port = htons(ce->pubport); goto sendit; } if (timestamp - lifetime >= curtime - MAX_NATPMP_RETRTIME) { /* * This is retransmitted request for mapping in * maximum retransmit time window, as mapping was * already established, do nothing. */ resp.resp_pub_port = htons(ce->pubport); goto sendit; } else /*if (pubport == ce->pubport)*/ { /* * This is renewal packet, we must delete mapping * and re-add it again with updated expire time. */ if (delete_redirect(ce, "delete_for_renewal") != 0) { /* * XXX Failed to delete, it still exists so * let's don't re-add in hope backend will * recover later a few minutes, reply with * only a slightly increased lifetime to make * client to do renewal again in near future. */ resp.resp_pub_port = htons(ce->pubport); resp.resp_lifetime = htonl(timestamp - curtime + MAX_NATPMP_RETRTIME); goto sendit; } break; } break; /* just for safety */ } /* if locaddr && locport && proto */ } /* * Here we will always add. If it was renewal, then don't do * search for first free public port as it was already allocated * to client prior to this. */ if (pubport == 0) /* Client lefts choice to us */ pubport = 1024; if (ce != NULL) pubport = ce->pubport; else do { i = get_redirect_rule(pubaddr, pubport, proto, NULL, 0, NULL, NULL, 0, NULL, NULL); if (i == 0) pubport++; if (pubport >= 65535) { resp.rescode = htons(NATPMP_RESULT_NORESOUR); syslog(LOG_ERR, "no free public ports on %s", pubaddr); goto sendit; } } while (i == 0); /* Finally, do addition. */ if (lifetime < MIN_NATPMP_LIFETIME) lifetime = MIN_NATPMP_LIFETIME; timestamp = (unsigned)time(NULL) + lifetime; snprintf(desc, sizeof(desc), "NAT-PMP %u", timestamp); if (add_redirect_rule2(pubaddr, pubport, locaddr, locport, proto, desc) == 0) { resp.resp_pub_port = htons(pubport); resp.resp_lifetime = htonl(lifetime); } else resp.rescode = htons(NATPMP_RESULT_NETFAIL); SIGUNBLOCK; } break; default: resp.rescode = htons(NATPMP_RESULT_OPNOTSUPP); } sendit: SIGUNBLOCK; syslog(LOG_DEBUG, "sending reply to %s: len=%d op=%hhu sssoe=%u rescode=%hu privport=%hu pubport=%hu lifetime=%u", locaddr, resplen, resp.op, ntohl(resp.sec_epoch), ntohs(resp.rescode), ntohs(resp.resp_priv_port), ntohs(resp.resp_pub_port), ntohl(resp.resp_lifetime)); n = sendto(natpmpsock, &resp, resplen, 0, (struct sockaddr *)&cli_addr, cli_addr_len); if (n < 0) { syslog(LOG_ERR, "sendto(): %m"); return; } else if (n < resplen) syslog(LOG_ERR, "sendto(): sent only %d bytes of %d", n, resplen); } /* Print short help about command line and exit. */ void usage(char *proctitle, char full) { fprintf(stderr, "Usage: %s [-dq] [-b /path/to/backend.sock] pubaddr,locaddr/masklen ...\n\n", proctitle); fprintf(stderr, (full == 'h') ? "This is a custom NAT-PMP (NAT Port Mapping Protocol) daemon for non-standard\n" "configurations where one client IP subnet is divided into several blocks,\n" "each of them is subject of NAT to separate external IP address, so usual UPnP\n" "and NAT-PMP can't be used due to multicast.\n" "Daemon does not do actul port redirections itself, instead, it communicates\n" "with PFCFBP protocol backend, allowing to use any underlying firewall or other\n" "redirection engine without need to recompile by simply changing backend.\n\n" "Options:\n" "\t-d\tDo not fork; enable debug (twice for extra info)\n" "\t-b\tPath to PFCFBP Unix socket, defaults to /var/run/pfcfbp.sock\n" "\t-q\tBe quiet to syslog (log fewer info); ignored when used with -d\n\n" "Then follows one or more block specifications, for each block pubaddr is\n" "external IP address, and locaddr/masklen is gray IP definition in CIDR format.\n" "Definitions are inclusive, you shouldn't care about network/broadcast addresses\n" "because daemon does not do NAT-PMP public address notifications on startup,\n" "neither by multicast nor by any other means.\n\n" "Example:\n\n" " %s 1.2.3.1,10.0.0.240/28 1.2.3.2,10.0.0.224/28 1.2.3.2,10.0.0.220/30\n\n" "Run daemon for two client address blocks: for first, 16 IPs from 10.0.0.240 to\n" "10.0.0.255 are mapped to 1.2.3.1, and for second, 20 IPs from 10.0.0.220 to\n" "10.0.0.239 are mapped to 1.2.3.2; requests from all other clients are denied.\n" : "For more help type %s -h\n", proctitle); exit(EX_USAGE); } /*** Main function ***/ int main(int argc, char *argv[]) { int ch, i, mlen, quiet = 0; struct sockaddr_in serv_addr; char *proctitle, *ptr, *ptr2; proctitle = argv[0]; /* Parse command line options. */ while ((ch = getopt(argc, argv, "b:dqh")) != -1) { switch (ch) { case 'd': debug += 1; break; case 'b': strncpy(sockpath, optarg, sizeof(sockpath)); break; case 'q': quiet = 1; break; case 'h': case '?': default: usage(proctitle, ch); } } argc -= optind; argv += optind; /* Parse and fill address translation list, internal to external. */ if (argc == 0) usage(proctitle, 0); pubaddrs = calloc(argc, sizeof(struct loc2pub)); if (pubaddrs == NULL) { fprintf(stderr, "Error allocating memory for int/ext addrs list\n"); exit(EX_OSERR); } numpubaddrs = argc; #define INVALSPEC do { \ fprintf(stderr, "Invalid specification %s\n", argv[i]); \ exit(EX_DATAERR); \ } while (0); for (i = 0; i < argc; i++) { ptr = strchr(argv[i], ','); if (!ptr) INVALSPEC; *ptr = '\0'; ptr++; if (!inet_aton(argv[i], &pubaddrs[i].puba)) INVALSPEC; ptr2 = strchr(ptr, '/'); if (!ptr2) mlen = 32; else { *ptr2 = '\0'; ptr2++; mlen = atoi(ptr2); if ((mlen < 0) || (mlen > 32)) INVALSPEC; } if (!inet_aton(ptr, &pubaddrs[i].host)) INVALSPEC; pubaddrs[i].mask.s_addr = htonl(mlen ? (~0 << (32 - mlen)) : 0); } /* Check if backend socket file exists and has proper permissions. */ if (access(sockpath, R_OK|W_OK)) { fprintf(stderr, SYSLOG_IDENT ": Error accessing PFCFBP socket %s: %s\n", sockpath, strerror(errno)); exit(EX_IOERR); } /* Open the listening socket. */ bzero(&serv_addr, sizeof(struct sockaddr_in)); if ((natpmpsock = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { fprintf(stderr, SYSLOG_IDENT ": Error - can't create NAT-PMP socket: %s\n", strerror(errno)); return (-1); } memset(&serv_addr, 0, sizeof(struct sockaddr_in)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(NATPMP_PORT); if (bind(natpmpsock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { fprintf(stderr, SYSLOG_IDENT ": Error - can't bind(): %s\n", strerror(errno)); return (-1); } /* Set up signal handlers. */ init_sig(); openlog(SYSLOG_IDENT, debug ? LOG_PERROR : LOG_NDELAY, LOG_USER); if (!debug) { if (quiet) setlogmask(LOG_UPTO(LOG_INFO)); if (daemon(0, 0) < 0) { /* Make program a daemon */ perror("daemon() failed"); exit(EX_OSERR); } } quit = 0; alarm_triggered = 1; startup_time = time(NULL); /* * We did all checks, daemonized and shell returned to caller as fast * as possible because connect to backend to let it perform it's * initialization could possibly be long and time-consuming. * But clients also don't want to wait too long for their requests * to be served, so do it now and actually check if backend is ever * exist and connectable. */ SIGBLOCK; if (open_backend_stream() != 0) { syslog(LOG_ERR, "Can't initially connect to backend socket, exiting!"); exit(EX_IOERR); } SIGUNBLOCK; alarm(ALARM_TIME); while (quit == 0) { if (alarm_triggered) { SIGBLOCK; clean_expired(); SIGUNBLOCK; alarm_triggered = 0; } event_loop(); } return (0); }