commit 21e48df4fa9fbce578ffbf0f2dc8218cb9cbc9c1
Author: John Haxby <john.haxby@oracle.com>
Date:   Tue May 11 14:14:18 2010 +0100

    Calculate the MTU for the tun device that will be created by vpnc.
    
    The correct MTU is calculated by running "ip route get $VPNGATEWAY"
    and subtracting 88 from the reported MTU; this is exactly what the
    vpnc scripts do.
    
    If the MTU is too large then while a ping might make it through the
    vpn gateway something like scp or a view of a largish web page will
    just hang as the vpn protocol doesn't like fragmented packets.
    
    Signed-off-by: John Haxby <john.haxby@oracle.com>

diff --git a/src/nm-vpnc-service-vpnc-helper.c b/src/nm-vpnc-service-vpnc-helper.c
index 1b67811..64b19bf 100644
--- a/src/nm-vpnc-service-vpnc-helper.c
+++ b/src/nm-vpnc-service-vpnc-helper.c
@@ -24,6 +24,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
@@ -32,6 +33,7 @@
 #include <dbus/dbus-glib-lowlevel.h>
 #include <dbus/dbus-glib.h>
 #include <NetworkManager.h>
+#include <linux/rtnetlink.h>
 
 #include "nm-vpnc-service.h"
 #include "nm-utils.h"
@@ -263,6 +265,137 @@ get_routes (void)
 	return value;
 }
 
+static struct rtattr *find_rtattr (struct rtattr *rta, int len, int rta_type)
+{
+	while (RTA_OK (rta, len)) {
+		if (rta->rta_type == rta_type)
+			return rta;
+		rta = RTA_NEXT (rta,len);
+	}
+	return NULL;
+}
+
+static unsigned addr2mtu (const char *address)
+{
+	struct {
+		struct nlmsghdr n;
+		struct rtmsg r;
+		union {
+			struct rtattr rta;
+			char buf[1024];
+		};
+	} req;
+	union {
+		struct nlmsghdr hdr;
+		char buf[16384];
+	} resp;
+	struct sockaddr_nl nladdr;
+	struct iovec iov;
+	struct msghdr msg = {
+		.msg_name = &nladdr,
+		.msg_namelen = sizeof (nladdr),
+		.msg_iov = &iov,
+		.msg_iovlen = 1,
+	};
+	struct in_addr addr_ipv4;
+	struct in6_addr addr_ipv6;
+	int family;
+	struct rtmsg *rtmsg;
+	struct rtattr *rtattr;
+	int rtnl_fd = -1, len;
+	unsigned mtu = 0;
+
+	if (inet_pton(AF_INET, address, &addr_ipv4) == 1)
+		family = AF_INET;
+	else if (inet_pton(AF_INET6, address, &addr_ipv6) == 1)
+		family = AF_INET6;
+	else
+		return 0;
+
+	/* rtnetlink socket */
+	rtnl_fd = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+	if (rtnl_fd < 0)
+		return 0;
+	len = 32768;
+	if (setsockopt (rtnl_fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof (len)) < 0)
+		goto out;
+	len = 32768;
+	if (setsockopt (rtnl_fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof (len)) < 0)
+		goto out;
+
+	/* prepare and send request */
+	memset (&nladdr, 0, sizeof (nladdr));
+	nladdr.nl_family = AF_NETLINK;
+
+	memset (&req, 0, sizeof (req));
+	req.n.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
+	req.n.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+	req.n.nlmsg_type = RTM_GETROUTE;
+	req.r.rtm_family = family;
+	if (family == AF_INET) {
+		req.r.rtm_dst_len = 32;
+		len = RTA_LENGTH(sizeof(addr_ipv4));
+		req.rta.rta_type = RTA_DST;
+		req.rta.rta_len = len;
+		memcpy(RTA_DATA(&req.rta), &addr_ipv4, sizeof(addr_ipv4));
+	} else {
+		req.r.rtm_dst_len = 128;
+		len = RTA_LENGTH(sizeof(addr_ipv6));
+		req.rta.rta_type = RTA_DST;
+		req.rta.rta_len = len;
+		memcpy(RTA_DATA(&req.rta), &addr_ipv6, sizeof(addr_ipv6));
+	}
+	req.n.nlmsg_len = NLMSG_ALIGN (req.n.nlmsg_len) + RTA_ALIGN (len);
+	req.n.nlmsg_seq = time (NULL);
+
+	iov.iov_base = &req.n;
+	iov.iov_len = req.n.nlmsg_len;
+
+	if (sendmsg (rtnl_fd, &msg, 0) < 0)
+		goto out;
+
+	/* read response and check result */
+	iov.iov_base = resp.buf;
+	for (;;) {
+		iov.iov_len = sizeof (resp.buf);
+		if ((len = recvmsg (rtnl_fd, &msg, 0)) < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+			goto out;
+		}
+		break;
+	}
+	if (len == 0 || msg.msg_namelen != sizeof (nladdr))
+		goto out;
+	if (resp.hdr.nlmsg_len > len || resp.hdr.nlmsg_len < sizeof (struct nlmsghdr))
+		/* malformed or short response */
+		goto out;
+	if (resp.hdr.nlmsg_type != RTM_NEWROUTE)
+		/* not a route response */
+		goto out;
+	len = resp.hdr.nlmsg_len - sizeof (struct rtmsg);
+	rtmsg = NLMSG_DATA (&resp.hdr);
+	if (len < 0)
+		/* not long enough for a route response */
+		goto out;
+	if (rtmsg->rtm_family != family)
+		/* not a route for this address family */
+		goto out;
+
+	/* extract mtu */
+	rtattr = find_rtattr (RTM_RTA (rtmsg), len, RTA_METRICS);
+	if (rtattr) {
+		rtattr = find_rtattr (RTA_DATA (rtattr), RTA_PAYLOAD (rtattr), RTAX_MTU);
+		if (rtattr)
+			mtu = *(unsigned *) RTA_DATA (rtattr);
+	}
+
+  out:
+	if (rtnl_fd >= 0)
+		close (rtnl_fd);
+	return mtu;
+}
+
 /*
  * Environment variables passed back from 'vpnc':
  *
@@ -282,10 +415,11 @@ main (int argc, char *argv[])
 	DBusGConnection *connection;
 	char *tmp;
 	GHashTable *config;
+	char *vpngateway = NULL;
 	GValue *val;
 	GError *err = NULL;
 	struct in_addr temp_addr;
-	long int mtu = 1412;
+	unsigned mtu = 1412;
 
 	g_type_init ();
 
@@ -305,7 +439,8 @@ main (int argc, char *argv[])
 	config = g_hash_table_new (g_str_hash, g_str_equal);
 
 	/* Gateway */
-	val = addr_to_gvalue (getenv ("VPNGATEWAY"));
+	vpngateway = getenv ("VPNGATEWAY");
+	val = addr_to_gvalue (vpngateway);
 	if (val)
 		g_hash_table_insert (config, NM_VPN_PLUGIN_IP4_CONFIG_GATEWAY, val);
 	else
@@ -370,16 +505,16 @@ main (int argc, char *argv[])
 		g_hash_table_insert (config, NM_VPN_PLUGIN_IP4_CONFIG_BANNER, val);
 
 	/* MTU */
-	tmp = getenv ("INTERNAL_IP4_MTU");
-	if (tmp && strlen (tmp)) {
-		errno = 0;
-		mtu = strtol (tmp, NULL, 10);
-		if (errno || mtu < 0 || mtu > 20000) {
-			nm_warning ("Ignoring invalid tunnel MTU '%s'", tmp);
+	if (vpngateway != NULL) {
+		mtu = addr2mtu (vpngateway);
+		if (mtu <= 88) {
+			nm_warning ("Ignoring very small mtu %u", mtu);
 			mtu = 1412;
+		} else {
+			mtu -= 88;
 		}
 	}
-	val = uint_to_gvalue ((guint32) mtu);
+	val = uint_to_gvalue (mtu);
 	g_hash_table_insert (config, NM_VPN_PLUGIN_IP4_CONFIG_MTU, val);
 
 	/* Send the config info to nm-vpnc-service */
