summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/openvpn/route.c148
1 files changed, 136 insertions, 12 deletions
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index baa4a23..ed21d15 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -45,6 +45,10 @@
#include "memdbg.h"
+#if defined(TARGET_LINUX) || defined(TARGET_ANDROID)
+#include <linux/rtnetlink.h> /* RTM_GETROUTE etc. */
+#endif
+
#ifdef WIN32
#define METRIC_NOT_USED ((DWORD)-1)
#endif
@@ -1119,10 +1123,9 @@ print_default_gateway(const int msglevel,
{
struct buffer out = alloc_buf_gc (256, &gc);
buf_printf (&out, "ROUTE6_GATEWAY");
+ buf_printf (&out, " %s", print_in6_addr (rgi6->gateway.addr_ipv6, 0, &gc));
if (rgi6->flags & RGI_ON_LINK)
buf_printf (&out, " ON_LINK");
- else
- buf_printf (&out, " %s", print_in6_addr (rgi6->gateway.addr_ipv6, 0, &gc));
if (rgi6->flags & RGI_NETMASK_DEFINED)
buf_printf (&out, "/%d", rgi6->gateway.netbits_ipv6);
#ifdef WIN32
@@ -2677,22 +2680,143 @@ get_default_gateway (struct route_gateway_info *rgi)
gc_free (&gc);
}
-/* IPv6 implementation using netlink (TBD)
+/* IPv6 implementation using netlink
+ * http://www.linuxjournal.com/article/7356
+ * netlink(3), netlink(7), rtnetlink(7)
+ * http://www.virtualbox.org/svn/vbox/trunk/src/VBox/NetworkServices/NAT/rtmon_linux.c
*/
+struct rtreq {
+ struct nlmsghdr nh;
+ struct rtmsg rtm;
+ char attrbuf[512];
+};
+
void
get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
struct in6_addr *dest)
{
- msg(D_ROUTE, "no support for get_default_gateway_ipv6() on this system (Linux and Android)");
+ int nls = -1;
+ struct rtreq rtreq;
+ struct rtattr *rta;
+
+ char rtbuf[2000];
+ ssize_t ssize;
+
CLEAR(*rgi6);
- struct in6_addr g = IN6ADDR_LOOPBACK_INIT;
-
- rgi6->flags = RGI_ADDR_DEFINED | RGI_IFACE_DEFINED | RGI_HWADDR_DEFINED |
- RGI_NETMASK_DEFINED;
- rgi6->gateway.addr_ipv6 = g;
- rgi6->gateway.netbits_ipv6 = 64;
- memcpy( rgi6->hwaddr, "\1\2\3\4\5\6", 6 );
- strcpy( rgi6->iface, "eth1" );
+
+ nls = socket( PF_NETLINK, SOCK_RAW, NETLINK_ROUTE );
+ if ( nls < 0 )
+ { msg(M_WARN|M_ERRNO, "GDG6: socket() failed" ); goto done; }
+
+ /* bind() is not needed, no unsolicited msgs coming in */
+
+ /* request best matching route, see netlink(7) for explanations
+ */
+ CLEAR(rtreq);
+ rtreq.nh.nlmsg_type = RTM_GETROUTE;
+ rtreq.nh.nlmsg_flags = NLM_F_REQUEST; /* best match only */
+ rtreq.rtm.rtm_family = AF_INET6;
+ rtreq.rtm.rtm_src_len = 0; /* not source dependent */
+ rtreq.rtm.rtm_dst_len = 128; /* exact dst */
+ rtreq.rtm.rtm_table = RT_TABLE_MAIN;
+ rtreq.rtm.rtm_protocol = RTPROT_UNSPEC;
+ rtreq.nh.nlmsg_len = NLMSG_SPACE(sizeof(rtreq.rtm));
+
+ /* set RTA_DST for target IPv6 address we want */
+ rta = (struct rtattr *)(((char *) &rtreq)+NLMSG_ALIGN(rtreq.nh.nlmsg_len));
+ rta->rta_type = RTA_DST;
+ rta->rta_len = RTA_LENGTH(16);
+ rtreq.nh.nlmsg_len = NLMSG_ALIGN(rtreq.nh.nlmsg_len) +
+ RTA_LENGTH(16);
+
+ if ( dest == NULL ) /* ::, unspecified */
+ memset( RTA_DATA(rta), 0, 16 ); /* :: = all-zero */
+ else
+ memcpy( RTA_DATA(rta), (void *)dest, 16 );
+
+ /* send and receive reply */
+ if ( send( nls, &rtreq, rtreq.nh.nlmsg_len, 0 ) < 0 )
+ { msg(M_WARN|M_ERRNO, "GDG6: send() failed" ); goto done; }
+
+ ssize = recv(nls, rtbuf, sizeof(rtbuf), MSG_TRUNC);
+
+ if (ssize < 0)
+ { msg(M_WARN|M_ERRNO, "GDG6: recv() failed" ); goto done; }
+
+ if (ssize > sizeof(rtbuf))
+ {
+ msg(M_WARN, "get_default_gateway_ipv6: returned message too big for buffer (%d>%d)", (int)ssize, (int)sizeof(rtbuf) );
+ goto done;
+ }
+
+ struct nlmsghdr *nh;
+
+ for (nh = (struct nlmsghdr *)rtbuf;
+ NLMSG_OK(nh, ssize);
+ nh = NLMSG_NEXT(nh, ssize))
+ {
+ struct rtmsg *rtm;
+ int attrlen;
+
+ if (nh->nlmsg_type == NLMSG_DONE) { break; }
+
+ if (nh->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr *ne = (struct nlmsgerr *)NLMSG_DATA(nh);
+ msg(M_WARN, "GDG6: NLSMG_ERROR: error %d\n", ne->error);
+ break;
+ }
+
+ if (nh->nlmsg_type != RTM_NEWROUTE) {
+ /* shouldn't happen */
+ msg(M_WARN, "GDG6: unexpected msg_type %d", nh->nlmsg_type );
+ continue;
+ }
+
+ rtm = (struct rtmsg *)NLMSG_DATA(nh);
+ attrlen = RTM_PAYLOAD(nh);
+
+ /* we're only looking for routes in the main table, as "we have
+ * no IPv6" will lead to a lookup result in "Local" (::/0 reject)
+ */
+ if (rtm->rtm_family != AF_INET6 ||
+ rtm->rtm_table != RT_TABLE_MAIN)
+ { continue; } /* we're not interested */
+
+ for (rta = RTM_RTA(rtm);
+ RTA_OK(rta, attrlen);
+ rta = RTA_NEXT(rta, attrlen))
+ {
+ if (rta->rta_type == RTA_GATEWAY) {
+ if ( RTA_PAYLOAD(rta) != sizeof(struct in6_addr) )
+ { msg(M_WARN, "GDG6: RTA_GW size mismatch"); continue; }
+ rgi6->gateway.addr_ipv6 = *(struct in6_addr*) RTA_DATA(rta);
+ rgi6->flags |= RGI_ADDR_DEFINED;
+ }
+ else if (rta->rta_type == RTA_OIF) {
+ char ifname[IF_NAMESIZE+1];
+ int oif;
+ if ( RTA_PAYLOAD(rta) != sizeof(oif) )
+ { msg(M_WARN, "GDG6: oif size mismatch"); continue; }
+
+ memcpy(&oif, RTA_DATA(rta), sizeof(oif));
+ if_indextoname(oif,ifname);
+ strncpy( rgi6->iface, ifname, sizeof(rgi6->iface)-1 );
+ rgi6->flags |= RGI_IFACE_DEFINED;
+ }
+ }
+ }
+
+ /* if we have an interface but no gateway, the destination is on-link */
+ if ( ( rgi6->flags & (RGI_IFACE_DEFINED|RGI_ADDR_DEFINED) ) ==
+ RGI_IFACE_DEFINED )
+ {
+ rgi6->flags |= (RGI_ADDR_DEFINED | RGI_ON_LINK);
+ rgi6->gateway.addr_ipv6 = *dest;
+ }
+
+ done:
+ if (nls >= 0)
+ close (nls);
}
#elif defined(TARGET_DARWIN) || defined(TARGET_SOLARIS) || \