diff libpurple/nat-pmp.c @ 15647:552be3958d6a

Added nat-pmp implementation and #ifdef'd out changes to network.c which would utilize it.
author Evan Schoenberg <evan.s@dreskin.net>
date Sun, 18 Feb 2007 18:26:55 +0000
parents
children 32c366eeeb99
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/nat-pmp.c	Sun Feb 18 18:26:55 2007 +0000
@@ -0,0 +1,436 @@
+/**
+ * @file nat-pmp.c NAT-PMP Implementation
+ * @ingroup core
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * Most code in nat-pmp.c copyright (C) 2007, R. Tyler Ballance, bleep, LLC.
+ * This file is distributed under the 3-clause (modified) BSD license:
+ * Redistribution and use in source and binary forms, with or without modification, are permitted
+ * provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and
+ * the following disclaimer.
+ * Neither the name of the bleep. LLC nor the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#include "nat-pmp.h"
+#include "debug.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysctl.h>
+
+#include <net/route.h>
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <err.h>
+
+#include <errno.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <net/if.h>
+
+#ifdef NET_RT_DUMP2
+/*
+ *	Thanks to R. Matthew Emerson for the fixes on this
+ */
+
+/* alignment constraint for routing socket */
+#define ROUNDUP(a)							\
+((a) > 0 ? (1 + (((a) - 1) | (sizeof(long) - 1))) : sizeof(long))
+#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
+
+static void
+get_rtaddrs(int bitmask, struct sockaddr *sa, struct sockaddr *addrs[])
+{
+	int i;
+	
+	for (i = 0; i < RTAX_MAX; i++) 
+	{
+		if (bitmask & (1 << i)) 
+		{
+			addrs[i] = sa;
+			sa = (struct sockaddr *)(ROUNDUP(sa->sa_len) + (char *)sa);
+		} 
+		else
+		{
+			addrs[i] = NULL;
+		}
+	}
+}
+
+static int
+is_default_route(struct sockaddr *sa, struct sockaddr *mask)
+{
+    struct sockaddr_in *sin;
+	
+    if (sa->sa_family != AF_INET)
+		return 0;
+	
+    sin = (struct sockaddr_in *)sa;
+    if ((sin->sin_addr.s_addr == INADDR_ANY) &&
+		mask &&
+		(ntohl(((struct sockaddr_in *)mask)->sin_addr.s_addr) == 0L ||
+		 mask->sa_len == 0))
+		return 1;
+    else
+		return 0;
+}
+
+static struct sockaddr_in *
+default_gw()
+{
+	int mib[6];
+    size_t needed;
+    char *buf, *next, *lim;
+    struct rt_msghdr2 *rtm;
+    struct sockaddr *sa;
+    struct sockaddr *rti_info[RTAX_MAX];
+	struct sockaddr_in *sin;
+	
+    mib[0] = CTL_NET;
+    mib[1] = PF_ROUTE;
+    mib[2] = 0;
+    mib[3] = 0;
+    mib[4] = NET_RT_DUMP2;
+    mib[5] = 0;
+	
+    if (sysctl(mib, 6, NULL, &needed, NULL, 0) < 0) 
+	{
+		err(1, "sysctl: net.route.0.0.dump estimate");
+    }
+	
+    buf = malloc(needed);
+	
+    if (buf == 0) 
+	{
+		err(2, "malloc");
+    }
+	
+    if (sysctl(mib, 6, buf, &needed, NULL, 0) < 0) 
+	{
+		err(1, "sysctl: net.route.0.0.dump");
+    }
+	
+    lim = buf + needed;
+	
+    for (next = buf; next < lim; next += rtm->rtm_msglen) 
+	{
+		rtm = (struct rt_msghdr2 *)next;
+		sa = (struct sockaddr *)(rtm + 1);
+		
+		if (sa->sa_family == AF_INET) 
+		{
+            sin = (struct sockaddr_in *)sa;
+			struct sockaddr addr, mask;
+			
+			get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
+			bzero(&addr, sizeof(addr));
+			
+			if (rtm->rtm_addrs & RTA_DST)
+				bcopy(rti_info[RTAX_DST], &addr, rti_info[RTAX_DST]->sa_len);
+			
+			bzero(&mask, sizeof(mask));
+			
+			if (rtm->rtm_addrs & RTA_NETMASK)
+				bcopy(rti_info[RTAX_NETMASK], &mask, rti_info[RTAX_NETMASK]->sa_len);
+			
+			if (is_default_route(&addr, &mask)) 
+			{
+				sin = (struct sockaddr_in *)rti_info[RTAX_GATEWAY];
+				break;
+			}
+		}
+		
+		rtm = (struct rt_msghdr2 *)next;
+    }
+	
+    free(buf);
+	
+	return sin;
+}
+
+//!	double_timeout(struct timeval *) will handle doubling a timeout for backoffs required by NAT-PMP
+static void
+double_timeout(struct timeval *to)
+{
+	int second = 1000000; // number of useconds
+	
+	to->tv_sec = (to->tv_sec * 2);
+	to->tv_usec = (to->tv_usec * 2);
+	
+	// Overflow useconds if necessary
+	if (to->tv_usec >= second)
+	{
+		int overflow = (to->tv_usec / second);
+		to->tv_usec  = (to->tv_usec - (overflow * second));
+		to->tv_sec = (to->tv_sec + overflow);
+	}
+}
+
+/*!
+ *	gaim_pmp_get_public_ip() will return the publicly facing IP address of the 
+ *	default NAT gateway. The function will return NULL if:
+ *		- The gateway doesn't support NAT-PMP
+ *		- The gateway errors in some other spectacular fashion
+ */
+char *
+gaim_pmp_get_public_ip()
+{
+	struct sockaddr_in *gateway = default_gw();
+	
+	if (gateway == NULL)
+	{
+		gaim_debug_info("nat-pmp", "Cannot request public IP from a NULL gateway!\n");
+		return NULL;
+	}
+	if (gateway->sin_port != PMP_PORT)
+	{
+		gateway->sin_port = htons(PMP_PORT); //	Default port for NAT-PMP is 5351
+	}
+
+	int sendfd;
+	int req_attempts = 1;	
+	struct timeval req_timeout;
+	pmp_ip_request_t req;
+	pmp_ip_response_t resp;
+	struct sockaddr_in *publicsockaddr = NULL;
+
+	req_timeout.tv_sec = 0;
+	req_timeout.tv_usec = PMP_TIMEOUT;
+
+	sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	
+	//	Clean out both req and resp structures
+	bzero(&req, sizeof(pmp_ip_request_t));
+	bzero(&resp, sizeof(pmp_ip_response_t));
+	req.version = 0;
+	req.opcode	= 0;
+	
+	//	Attempt to contact NAT-PMP device 9 times as per: draft-cheshire-nat-pmp-02.txt  
+	while (req_attempts < 10)
+	{	
+#ifdef PMP_DEBUG
+		gaim_debug_info("nat-pmp", "Attempting to retrieve the public ip address for the NAT device at: %s\n", inet_ntoa(gateway->sin_addr));
+		gaim_debug_info("nat-pmp", "\tTimeout: %ds %dus, Request #: %d\n", req_timeout.tv_sec, req_timeout.tv_usec, req_attempts);
+#endif
+		struct sockaddr_in addr;
+		socklen_t len = sizeof(struct sockaddr_in);
+
+		if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
+		{
+			gaim_debug_info("nat-pmp", "There was an error sending the NAT-PMP public IP request! (%s)\n", strerror(errno));
+			return NULL;
+		}
+		
+		if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
+		{
+			gaim_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno));
+			return NULL;
+		}		
+		
+		if (recvfrom(sendfd, &resp, sizeof(pmp_ip_response_t), 0, (struct sockaddr *)(&addr), &len) < 0)
+		{			
+			if ( (errno != EAGAIN) || (req_attempts == 9) )
+			{
+				gaim_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno));
+				return NULL;
+			}
+			else
+			{
+				goto iterate;
+			}
+		}
+		
+		if (addr.sin_addr.s_addr != gateway->sin_addr.s_addr)
+		{
+			gaim_debug_info("nat-pmp", "Response was not received from our gateway! Instead from: %s\n", inet_ntoa(addr.sin_addr));
+			goto iterate;
+		}
+		else
+		{
+			publicsockaddr = &addr;
+			break;
+		}
+
+iterate:
+		++req_attempts;
+		double_timeout(&req_timeout);
+	}
+	
+	if (publicsockaddr == NULL)
+		return NULL;
+	
+#ifdef PMP_DEBUG
+	gaim_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
+	gaim_debug_info("nat-pmp", "version: %d\n", resp.version);
+	gaim_debug_info("nat-pmp", "opcode: %d\n", resp.opcode);
+	gaim_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp.resultcode));
+	gaim_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp.epoch));
+	struct in_addr in;
+	in.s_addr = resp.address;
+	gaim_debug_info("nat-pmp", "address: %s\n", inet_ntoa(in));
+#endif	
+
+	publicsockaddr->sin_addr.s_addr = resp.address;
+	
+	return inet_ntoa(publicsockaddr->sin_addr);
+}
+
+/*!
+ *	will return NULL on error, or a pointer to the pmp_map_response_t type
+ */
+pmp_map_response_t *
+gaim_pmp_create_map(uint8_t type, uint16_t privateport, uint16_t publicport, uint32_t lifetime)
+{
+	struct sockaddr_in *gateway = default_gw();
+	
+	if (gateway == NULL)
+	{
+		gaim_debug_info("nat-pmp", "Cannot create mapping on a NULL gateway!\n");
+		return NULL;
+	}
+	if (gateway->sin_port != PMP_PORT)
+	{
+		gateway->sin_port = htons(PMP_PORT); //	Default port for NAT-PMP is 5351
+	}
+		
+	int sendfd;
+	int req_attempts = 1;	
+	struct timeval req_timeout;
+	pmp_map_request_t req;
+	pmp_map_response_t *resp = (pmp_map_response_t *)(malloc(sizeof(pmp_map_response_t)));
+	
+	req_timeout.tv_sec = 0;
+	req_timeout.tv_usec = PMP_TIMEOUT;
+	
+	sendfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	
+	//	Clean out both req and resp structures
+	bzero(&req, sizeof(pmp_map_request_t));
+	bzero(resp, sizeof(pmp_map_response_t));
+	req.version = 0;
+	req.opcode	= type;	
+	req.privateport = htons(privateport); //	What a difference byte ordering makes...d'oh!
+	req.publicport = htons(publicport);
+	req.lifetime = htonl(lifetime);
+	
+	//	Attempt to contact NAT-PMP device 9 times as per: draft-cheshire-nat-pmp-02.txt  
+	while (req_attempts < 10)
+	{	
+#ifdef PMP_DEBUG
+		gaim_debug_info("nat-pmp", "Attempting to create a NAT-PMP mapping the private port %d, and the public port %d\n", privateport, publicport);
+		gaim_debug_info("nat-pmp", "\tTimeout: %ds %dus, Request #: %d\n", req_timeout.tv_sec, req_timeout.tv_usec, req_attempts);
+#endif
+
+		if (sendto(sendfd, &req, sizeof(req), 0, (struct sockaddr *)(gateway), sizeof(struct sockaddr)) < 0)
+		{
+			gaim_debug_info("nat-pmp", "There was an error sending the NAT-PMP mapping request! (%s)\n", strerror(errno));
+			return NULL;
+		}
+		
+		if (setsockopt(sendfd, SOL_SOCKET, SO_RCVTIMEO, &req_timeout, sizeof(req_timeout)) < 0)
+		{
+			gaim_debug_info("nat-pmp", "There was an error setting the socket's options! (%s)\n", strerror(errno));
+			return NULL;
+		}		
+		
+		if (recvfrom(sendfd, resp, sizeof(pmp_map_response_t), 0, NULL, NULL) < 0)
+		{			
+			if ( (errno != EAGAIN) || (req_attempts == 9) )
+			{
+				gaim_debug_info("nat-pmp", "There was an error receiving the response from the NAT-PMP device! (%s)\n", strerror(errno));
+				return NULL;
+			}
+			else
+			{
+				goto iterate;
+			}
+		}
+		
+		if (resp->opcode != (req.opcode + 128))
+		{
+			gaim_debug_info("nat-pmp", "The opcode for the response from the NAT device does not match the request opcode!\n");
+			goto iterate;
+		}
+		
+		break;
+		
+iterate:
+		++req_attempts;
+		double_timeout(&req_timeout);
+	}
+	
+#ifdef PMP_DEBUG
+	gaim_debug_info("nat-pmp", "Response received from NAT-PMP device:\n");
+	gaim_debug_info("nat-pmp", "version: %d\n", resp->version);
+	gaim_debug_info("nat-pmp", "opcode: %d\n", resp->opcode);
+	gaim_debug_info("nat-pmp", "resultcode: %d\n", ntohs(resp->resultcode));
+	gaim_debug_info("nat-pmp", "epoch: %d\n", ntohl(resp->epoch));
+	gaim_debug_info("nat-pmp", "privateport: %d\n", ntohs(resp->privateport));
+	gaim_debug_info("nat-pmp", "publicport: %d\n", ntohs(resp->publicport));
+	gaim_debug_info("nat-pmp", "lifetime: %d\n", ntohl(resp->lifetime));
+#endif	
+
+	return resp;
+}
+
+/*!
+ *	pmp_destroy_map(uint8_t,uint16_t) 
+ *	will return NULL on error, or a pointer to the pmp_map_response_t type
+ */
+pmp_map_response_t *
+gaim_pmp_destroy_map(uint8_t type, uint16_t privateport)
+{
+	pmp_map_response_t *response = NULL;
+	
+	if ((response = gaim_pmp_create_map(type, privateport, 0, 0)) == NULL)
+	{
+		gaim_debug_info("nat-pmp", "Failed to properly destroy mapping for %d!\n", privateport);
+		return NULL;
+	}
+	else
+	{
+		return response;
+	}
+}
+#else /* #ifdef NET_RT_DUMP2 */
+char *
+gaim_pmp_get_public_ip()
+{
+	return NULL;
+}
+
+pmp_map_response_t *
+gaim_pmp_create_map(uint8_t type, uint16_t privateport, uint16_t publicport, uint32_t lifetime)
+{
+	return NULL;
+}
+
+pmp_map_response_t *
+gaim_pmp_destroy_map(uint8_t type, uint16_t privateport)
+{
+	return NULL;
+}
+#endif /* #ifndef NET_RT_DUMP2 */