/* $Id: natpmp.c,v 1.52 2015/05/27 12:43:14 nanard Exp $ */ /* MiniUPnP project * (c) 2007-2015 Thomas Bernard * http://miniupnp.free.fr/ or http://miniupnp.tuxfamily.org/ * This software is subject to the conditions detailed * in the LICENCE file provided within the distribution */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <syslog.h> #include <errno.h> #include <time.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/uio.h> #include "macros.h" #include "config.h" #include "natpmp.h" #include "upnpglobalvars.h" #include "getifaddr.h" #include "upnpredirect.h" #include "commonrdr.h" #include "upnputils.h" #include "portinuse.h" #include "asyncsendto.h" #ifdef ENABLE_NATPMP #define INLINE static inline /* theses macros are designed to read/write unsigned short/long int * from an unsigned char array in network order (big endian). * Avoid pointer casting, so avoid accessing unaligned memory, which * can crash with some cpu's */ INLINE uint32_t readnu32(const uint8_t * p) { return (p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]); } #define READNU32(p) readnu32(p) INLINE uint16_t readnu16(const uint8_t * p) { return (p[0] << 8 | p[1]); } #define READNU16(p) readnu16(p) INLINE void writenu32(uint8_t * p, uint32_t n) { p[0] = (n & 0xff000000) >> 24; p[1] = (n & 0xff0000) >> 16; p[2] = (n & 0xff00) >> 8; p[3] = n & 0xff; } #define WRITENU32(p, n) writenu32(p, n) INLINE void writenu16(uint8_t * p, uint16_t n) { p[0] = (n & 0xff00) >> 8; p[1] = n & 0xff; } #define WRITENU16(p, n) writenu16(p, n) int OpenAndConfNATPMPSocket(in_addr_t addr) { int snatpmp; int i = 1; snatpmp = socket(PF_INET, SOCK_DGRAM, 0/*IPPROTO_UDP*/); if(snatpmp<0) { syslog(LOG_ERR, "%s: socket(): %m", "OpenAndConfNATPMPSocket"); return -1; } if(setsockopt(snatpmp, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) { syslog(LOG_WARNING, "%s: setsockopt(SO_REUSEADDR): %m", "OpenAndConfNATPMPSocket"); } if(!set_non_blocking(snatpmp)) { syslog(LOG_WARNING, "%s: set_non_blocking(): %m", "OpenAndConfNATPMPSocket"); } { struct sockaddr_in natpmp_addr; memset(&natpmp_addr, 0, sizeof(natpmp_addr)); natpmp_addr.sin_family = AF_INET; natpmp_addr.sin_port = htons(NATPMP_PORT); /*natpmp_addr.sin_addr.s_addr = INADDR_ANY; */ natpmp_addr.sin_addr.s_addr = addr; if(bind(snatpmp, (struct sockaddr *)&natpmp_addr, sizeof(natpmp_addr)) < 0) { syslog(LOG_ERR, "%s: bind(): %m", "OpenAndConfNATPMPSocket"); close(snatpmp); return -1; } } return snatpmp; } int OpenAndConfNATPMPSockets(int * sockets) { int i; struct lan_addr_s * lan_addr; for(i = 0, lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { sockets[i] = OpenAndConfNATPMPSocket(lan_addr->addr.s_addr); if(sockets[i] < 0) goto error; i++; } return 0; error: while(--i >= 0) { close(sockets[i]); sockets[i] = -1; } return -1; } static void FillPublicAddressResponse(unsigned char * resp, in_addr_t senderaddr) { #ifndef MULTIPLE_EXTERNAL_IP char tmp[16]; UNUSED(senderaddr); if(use_ext_ip_addr) { inet_pton(AF_INET, use_ext_ip_addr, resp+8); } else { if(!ext_if_name || ext_if_name[0]=='\0') { resp[3] = 3; /* Network Failure (e.g. NAT box itself * has not obtained a DHCP lease) */ } else if(getifaddr(ext_if_name, tmp, INET_ADDRSTRLEN, NULL, NULL) < 0) { syslog(LOG_ERR, "Failed to get IP for interface %s", ext_if_name); resp[3] = 3; /* Network Failure (e.g. NAT box itself * has not obtained a DHCP lease) */ } else { inet_pton(AF_INET, tmp, resp+8); /* ok */ } } #else struct lan_addr_s * lan_addr; for(lan_addr = lan_addrs.lh_first; lan_addr != NULL; lan_addr = lan_addr->list.le_next) { if( (senderaddr & lan_addr->mask.s_addr) == (lan_addr->addr.s_addr & lan_addr->mask.s_addr)) { memcpy(resp+8, &lan_addr->ext_ip_addr, sizeof(lan_addr->ext_ip_addr)); break; } } #endif } /* * Receives NATPMP and PCP packets and stores them in msg_buff. * The sender information is stored in senderaddr. * Returns number of bytes recevied, even if number is negative. */ int ReceiveNATPMPOrPCPPacket(int s, struct sockaddr * senderaddr, socklen_t * senderaddrlen, struct sockaddr_in6 * receiveraddr, unsigned char * msg_buff, size_t msg_buff_size) { #ifdef IPV6_PKTINFO struct iovec iov; uint8_t c[1000]; struct msghdr msg; int n; struct cmsghdr *h; iov.iov_base = msg_buff; iov.iov_len = msg_buff_size; memset(&msg, 0, sizeof(msg)); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = senderaddr; msg.msg_namelen = *senderaddrlen; msg.msg_control = c; msg.msg_controllen = sizeof(c); n = recvmsg(s, &msg, 0); if(n < 0) { /* EAGAIN, EWOULDBLOCK and EINTR : silently ignore (retry next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recvmsg(natpmp): %m"); } return n; } if(receiveraddr) { memset(receiveraddr, 0, sizeof(struct sockaddr_in6)); } if ((msg.msg_flags & MSG_TRUNC) || (msg.msg_flags & MSG_CTRUNC)) { syslog(LOG_WARNING, "%s: truncated message", "ReceiveNATPMPOrPCPPacket"); } for(h = CMSG_FIRSTHDR(&msg); h; h = CMSG_NXTHDR(&msg, h)) { if(h->cmsg_level == IPPROTO_IPV6 && h->cmsg_type == IPV6_PKTINFO) { char tmp[INET6_ADDRSTRLEN]; struct in6_pktinfo *ipi6 = (struct in6_pktinfo *)CMSG_DATA(h); syslog(LOG_DEBUG, "%s: packet destination: %s scope_id=%u", "ReceiveNATPMPOrPCPPacket", inet_ntop(AF_INET6, &ipi6->ipi6_addr, tmp, sizeof(tmp)), ipi6->ipi6_ifindex); if(receiveraddr) { receiveraddr->sin6_addr = ipi6->ipi6_addr; receiveraddr->sin6_scope_id = ipi6->ipi6_ifindex; receiveraddr->sin6_family = AF_INET6; receiveraddr->sin6_port = htons(NATPMP_PORT); } } } #else /* IPV6_PKTINFO */ int n; n = recvfrom(s, msg_buff, msg_buff_size, 0, senderaddr, senderaddrlen); if(n<0) { /* EAGAIN, EWOULDBLOCK and EINTR : silently ignore (retry next time) * other errors : log to LOG_ERR */ if(errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { syslog(LOG_ERR, "recvfrom(natpmp): %m"); } return n; } #endif /* IPV6_PKTINFO */ return n; } /** read the request from the socket, process it and then send the * response back. */ void ProcessIncomingNATPMPPacket(int s, unsigned char *msg_buff, int len, struct sockaddr_in *senderaddr) { unsigned char *req=msg_buff; /* request udp packet */ unsigned char resp[32]; /* response udp packet */ int resplen; int n = len; char senderaddrstr[16]; if(!inet_ntop(AF_INET, &senderaddr->sin_addr, senderaddrstr, sizeof(senderaddrstr))) { syslog(LOG_ERR, "inet_ntop(natpmp): %m"); } syslog(LOG_INFO, "NAT-PMP request received from %s:%hu %dbytes", senderaddrstr, ntohs(senderaddr->sin_port), n); if(n<2 || ((((req[1]-1)&~1)==0) && n<12)) { syslog(LOG_WARNING, "discarding NAT-PMP request (too short) %dBytes", n); return; } if(req[1] & 128) { /* discarding NAT-PMP responses silently */ return; } memset(resp, 0, sizeof(resp)); resplen = 8; resp[1] = 128 + req[1]; /* response OPCODE is request OPCODE + 128 */ /* setting response TIME STAMP : * time elapsed since its port mapping table was initialized on * startup or reset for any other reason */ WRITENU32(resp+4, time(NULL) - startup_time); if(req[0] > 0) { /* invalid version */ syslog(LOG_WARNING, "unsupported NAT-PMP version : %u", (unsigned)req[0]); resp[3] = 1; /* unsupported version */ } else switch(req[1]) { case 0: /* Public address request */ syslog(LOG_INFO, "NAT-PMP public address request"); FillPublicAddressResponse(resp, senderaddr->sin_addr.s_addr); resplen = 12; break; case 1: /* UDP port mapping request */ case 2: /* TCP port mapping request */ { unsigned short iport; /* private port */ unsigned short eport; /* public port */ uint32_t lifetime; /* lifetime=0 => remove port mapping */ int r; int proto; char iaddr_old[16]; unsigned short iport_old; unsigned int timestamp; iport = READNU16(req+4); eport = READNU16(req+6); lifetime = READNU32(req+8); proto = (req[1]==1)?IPPROTO_UDP:IPPROTO_TCP; syslog(LOG_INFO, "NAT-PMP port mapping request : " "%hu->%s:%hu %s lifetime=%us", eport, senderaddrstr, iport, (req[1]==1)?"udp":"tcp", lifetime); /* TODO: accept port mapping if iport ok but eport not ok * (and set eport correctly) */ if(lifetime == 0) { /* remove the mapping */ /* RFC6886 : * A client MAY also send an explicit packet to request deletion of a * mapping that is no longer needed. A client requests explicit * deletion of a mapping by sending a message to the NAT gateway * requesting the mapping, with the Requested Lifetime in Seconds set to * zero. The Suggested External Port MUST be set to zero by the client * on sending, and MUST be ignored by the gateway on reception. */ int index = 0; unsigned short eport2, iport2; char iaddr2[16]; int proto2; char desc[64]; eport = 0; /* to indicate correct removing of port mapping */ while(get_redirect_rule_by_index(index, 0, &eport2, iaddr2, sizeof(iaddr2), &iport2, &proto2, desc, sizeof(desc), 0, 0, ×tamp, 0, 0) >= 0) { syslog(LOG_DEBUG, "%d %d %hu->'%s':%hu '%s'", index, proto2, eport2, iaddr2, iport2, desc); if(0 == strcmp(iaddr2, senderaddrstr) && 0 == memcmp(desc, "NAT-PMP", 7)) { /* (iport == 0) => remove all the mappings for this client */ if((iport == 0) || ((iport == iport2) && (proto == proto2))) { r = _upnp_delete_redir(eport2, proto2); if(r < 0) { syslog(LOG_ERR, "Failed to remove NAT-PMP mapping eport %hu, protocol %s", eport2, (proto2==IPPROTO_TCP)?"TCP":"UDP"); resp[3] = 2; /* Not Authorized/Refused */ break; } else { syslog(LOG_INFO, "NAT-PMP %s port %hu mapping removed", proto2==IPPROTO_TCP?"TCP":"UDP", eport2); index--; } } } index++; } } else if(iport==0) { resp[3] = 2; /* Not Authorized/Refused */ } else { /* iport > 0 && lifetime > 0 */ unsigned short eport_first = 0; int any_eport_allowed = 0; char desc[64]; if(eport==0) /* if no suggested external port, use same a internal port */ eport = iport; while(resp[3] == 0) { if(eport_first == 0) { /* first time in loop */ eport_first = eport; } else if(eport == eport_first) { /* no eport available */ if(any_eport_allowed == 0) { /* all eports rejected by permissions */ syslog(LOG_ERR, "No allowed eport for NAT-PMP %hu %s->%s:%hu", eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport); resp[3] = 2; /* Not Authorized/Refused */ } else { /* at least one eport allowed (but none available) */ syslog(LOG_ERR, "Failed to find available eport for NAT-PMP %hu %s->%s:%hu", eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport); resp[3] = 4; /* Out of resources */ } break; } if(!check_upnp_rule_against_permissions(upnppermlist, num_upnpperm, eport, senderaddr->sin_addr, iport)) { eport++; if(eport == 0) eport++; /* skip port zero */ continue; } any_eport_allowed = 1; /* at lease one eport is allowed */ #ifdef CHECK_PORTINUSE if (port_in_use(ext_if_name, eport, proto, senderaddrstr, iport) > 0) { syslog(LOG_INFO, "port %hu protocol %s already in use", eport, (proto==IPPROTO_TCP)?"tcp":"udp"); eport++; if(eport == 0) eport++; /* skip port zero */ continue; } #endif r = get_redirect_rule(ext_if_name, eport, proto, iaddr_old, sizeof(iaddr_old), &iport_old, 0, 0, 0, 0, ×tamp, 0, 0); if(r==0) { if(strcmp(senderaddrstr, iaddr_old)==0 && iport==iport_old) { /* redirection already existing */ syslog(LOG_INFO, "port %hu %s already redirected to %s:%hu, replacing", eport, (proto==IPPROTO_TCP)?"tcp":"udp", iaddr_old, iport_old); /* remove and then add again */ if(_upnp_delete_redir(eport, proto) < 0) { syslog(LOG_ERR, "failed to remove port mapping"); break; } } else { eport++; if(eport == 0) eport++; /* skip port zero */ continue; } } /* do the redirection */ #if 0 timestamp = (unsigned)(time(NULL) - startup_time) + lifetime; snprintf(desc, sizeof(desc), "NAT-PMP %u", timestamp); #else timestamp = time(NULL) + lifetime; snprintf(desc, sizeof(desc), "NAT-PMP %hu %s", eport, (proto==IPPROTO_TCP)?"tcp":"udp"); #endif /* TODO : check return code */ if(upnp_redirect_internal(NULL, eport, senderaddrstr, iport, proto, desc, timestamp) < 0) { syslog(LOG_ERR, "Failed to add NAT-PMP %hu %s->%s:%hu '%s'", eport, (proto==IPPROTO_TCP)?"tcp":"udp", senderaddrstr, iport, desc); resp[3] = 3; /* Failure */ } break; } } WRITENU16(resp+8, iport); /* private port */ WRITENU16(resp+10, eport); /* public port */ WRITENU32(resp+12, lifetime); /* Port Mapping lifetime */ } resplen = 16; break; default: resp[3] = 5; /* Unsupported OPCODE */ } n = sendto_or_schedule(s, resp, resplen, 0, (struct sockaddr *)senderaddr, sizeof(*senderaddr)); if(n<0) { syslog(LOG_ERR, "sendto(natpmp): %m"); } else if(n<resplen) { syslog(LOG_ERR, "sendto(natpmp): sent only %d bytes out of %d", n, resplen); } } /* SendNATPMPPublicAddressChangeNotification() * should be called when the public IP address changed */ void SendNATPMPPublicAddressChangeNotification(int * sockets, int n_sockets) { struct sockaddr_in sockname; unsigned char notif[12]; int j, n; notif[0] = 0; /* vers */ notif[1] = 128; /* OP code */ notif[2] = 0; /* result code */ notif[3] = 0; /* result code */ /* seconds since "start of epoch" : * time elapsed since the port mapping table was initialized on * startup or reset for any other reason */ WRITENU32(notif+4, time(NULL) - startup_time); #ifndef MULTIPLE_EXTERNAL_IP FillPublicAddressResponse(notif, 0); if(notif[3]) { syslog(LOG_WARNING, "%s: cannot get public IP address, stopping", "SendNATPMPPublicAddressChangeNotification"); return; } #endif memset(&sockname, 0, sizeof(struct sockaddr_in)); sockname.sin_family = AF_INET; sockname.sin_addr.s_addr = inet_addr(NATPMP_NOTIF_ADDR); for(j=0; j<n_sockets; j++) { if(sockets[j] < 0) continue; #ifdef MULTIPLE_EXTERNAL_IP { struct lan_addr_s * lan_addr = lan_addrs.lh_first; int i; for(i=0; i<j; i++) lan_addr = lan_addr->list.le_next; FillPublicAddressResponse(notif, lan_addr->addr.s_addr); } #endif /* Port to use in 2006 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_PORT); n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { syslog(LOG_ERR, "%s: sendto(s_udp=%d): %m", "SendNATPMPPublicAddressChangeNotification", sockets[j]); return; } /* Port to use in 2008 version of the NAT-PMP specification */ sockname.sin_port = htons(NATPMP_NOTIF_PORT); n = sendto_or_schedule(sockets[j], notif, 12, 0, (struct sockaddr *)&sockname, sizeof(struct sockaddr_in)); if(n < 0) { syslog(LOG_ERR, "%s: sendto(s_udp=%d): %m", "SendNATPMPPublicAddressChangeNotification", sockets[j]); return; } } } #endif