summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGert Doering <gert@greenie.muc.de>2015-09-11 17:33:45 +0200
committerGert Doering <gert@greenie.muc.de>2015-09-20 14:28:42 +0200
commit2ff366f78a2bf8d0a2744db9b59f7274b671a042 (patch)
treed9df93b3a57f76066ad2eeca172b908d92c71f1d
parentd227929b5db049ca6efbef9fb7d84be5e545b41d (diff)
downloadopenvpn-2ff366f78a2bf8d0a2744db9b59f7274b671a042.zip
openvpn-2ff366f78a2bf8d0a2744db9b59f7274b671a042.tar.gz
openvpn-2ff366f78a2bf8d0a2744db9b59f7274b671a042.tar.xz
get_default_gateway_ipv6(): *BSD / MacOS / Solaris PF_ROUTE implementation
As for IPv4, a common implementation for all (supported) BSD families and Solaris. Supporting the latter requires separate implementations for IPv4 and IPv6, unfortunately, so it's quite a bit of duplicate code. Further, extend add_route_ipv6() and delete_route_ipv6() to handle link-local gateway addresses that require "gateway + interface" in scoped notation ("fe80::1%em0"). Tested on FreeBSD 7.4/amd64, 9.3/sparc64, 10.0/amd64, NetBSD 5.1/amd64, OpenBSD 4.9/i386, MacOS X 10.5/32 and 10.10/64 and OpenSolaris 10.11/i386. Signed-off-by: Gert Doering <gert@greenie.muc.de> Acked-by: Arne Schwabe <arne@rfc2549.org> Message-Id: <1441985627-14822-9-git-send-email-gert@greenie.muc.de> URL: http://article.gmane.org/gmane.network.openvpn.devel/10082
-rw-r--r--src/openvpn/route.c187
1 files changed, 183 insertions, 4 deletions
diff --git a/src/openvpn/route.c b/src/openvpn/route.c
index ab8eb27..5f8a44d 100644
--- a/src/openvpn/route.c
+++ b/src/openvpn/route.c
@@ -1687,6 +1687,24 @@ add_route_ipv6 (struct route_ipv6 *r6, const struct tuntap *tt, unsigned int fla
network = print_in6_addr_netbits_only( r6->network, r6->netbits, &gc);
gateway = print_in6_addr( r6->gateway, 0, &gc);
+#if defined(TARGET_DARWIN) || \
+ defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) || \
+ defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)
+
+ /* the BSD platforms cannot specify gateway and interface independently,
+ * but for link-local destinations, we MUST specify the interface, so
+ * we build a combined "$gateway%$interface" gateway string
+ */
+ if ( r6->iface != NULL && gateway_needed &&
+ IN6_IS_ADDR_LINKLOCAL(&r6->gateway) ) /* fe80::...%intf */
+ {
+ int len = strlen(gateway) + 1 + strlen(r6->iface)+1;
+ char * tmp = gc_malloc( len, true, &gc );
+ snprintf( tmp, len, "%s%%%s", gateway, r6->iface );
+ gateway = tmp;
+ }
+#endif
+
if ( !tt->ipv6 )
{
msg( M_INFO, "add_route_ipv6(): not adding %s/%d, no IPv6 on if %s",
@@ -2069,6 +2087,24 @@ delete_route_ipv6 (const struct route_ipv6 *r6, const struct tuntap *tt, unsigne
network = print_in6_addr_netbits_only( r6->network, r6->netbits, &gc);
gateway = print_in6_addr( r6->gateway, 0, &gc);
+#if defined(TARGET_DARWIN) || \
+ defined(TARGET_FREEBSD) || defined(TARGET_DRAGONFLY) || \
+ defined(TARGET_OPENBSD) || defined(TARGET_NETBSD)
+
+ /* the BSD platforms cannot specify gateway and interface independently,
+ * but for link-local destinations, we MUST specify the interface, so
+ * we build a combined "$gateway%$interface" gateway string
+ */
+ if ( r6->iface != NULL && gateway_needed &&
+ IN6_IS_ADDR_LINKLOCAL(&r6->gateway) ) /* fe80::...%intf */
+ {
+ int len = strlen(gateway) + 1 + strlen(r6->iface)+1;
+ char * tmp = gc_malloc( len, true, &gc );
+ snprintf( tmp, len, "%s%%%s", gateway, r6->iface );
+ gateway = tmp;
+ }
+#endif
+
if ( !tt->ipv6 )
{
msg( M_INFO, "delete_route_ipv6(): not deleting %s/%d, no IPv6 on if %s",
@@ -2973,14 +3009,14 @@ struct rtmsg {
#if defined(TARGET_SOLARIS)
#define NEXTADDR(w, u) \
if (rtm_addrs & (w)) {\
- l = ROUNDUP(sizeof(struct sockaddr_in)); memmove(cp, &(u), l); cp += l;\
+ l = ROUNDUP(sizeof(u)); memmove(cp, &(u), l); cp += l;\
}
#define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr_in)))
#else
#define NEXTADDR(w, u) \
if (rtm_addrs & (w)) {\
- l = ROUNDUP(u.sa_len); memmove(cp, &(u), l); cp += l;\
+ l = ROUNDUP( ((struct sockaddr *)&(u))->sa_len); memmove(cp, &(u), l); cp += l;\
}
#define ADVANCE(x, n) (x += ROUNDUP((n)->sa_len))
@@ -3177,14 +3213,157 @@ get_default_gateway (struct route_gateway_info *rgi)
gc_free (&gc);
}
-/* BSD implementation using routing socket (as does IPv4) (TBD)
+/* BSD implementation using routing socket (as does IPv4)
+ * (the code duplication is somewhat unavoidable if we want this to
+ * work on OpenSolaris as well. *sigh*)
*/
+
+/* Solaris has no length field - this is ugly, but less #ifdef in total
+ */
+#if defined(TARGET_SOLARIS)
+# undef ADVANCE
+# define ADVANCE(x, n) (x += ROUNDUP(sizeof(struct sockaddr_in6)))
+#endif
+
void
get_default_gateway_ipv6(struct route_ipv6_gateway_info *rgi6,
const struct in6_addr *dest)
{
- msg(D_ROUTE, "no support for get_default_gateway_ipv6() on this system (BSD and OSX)");
+
+ struct rtmsg m_rtmsg;
+ int sockfd = -1;
+ int seq, l, pid, rtm_addrs, i;
+ struct sockaddr_in6 so_dst, so_mask;
+ char *cp = m_rtmsg.m_space;
+ struct sockaddr *gate = NULL, *ifp = NULL, *sa;
+ struct rt_msghdr *rtm_aux;
+
CLEAR(*rgi6);
+
+ /* setup data to send to routing socket */
+ pid = getpid();
+ seq = 0;
+ rtm_addrs = RTA_DST | RTA_NETMASK | RTA_IFP;
+
+ bzero(&m_rtmsg, sizeof(m_rtmsg));
+ bzero(&so_dst, sizeof(so_dst));
+ bzero(&so_mask, sizeof(so_mask));
+ bzero(&rtm, sizeof(struct rt_msghdr));
+
+ rtm.rtm_type = RTM_GET;
+ rtm.rtm_flags = RTF_UP;
+ rtm.rtm_version = RTM_VERSION;
+ rtm.rtm_seq = ++seq;
+
+ so_dst.sin6_family = AF_INET6;
+ so_mask.sin6_family = AF_INET6;
+
+ if ( dest != NULL && /* specific host? */
+ !IN6_IS_ADDR_UNSPECIFIED(dest) )
+ {
+ so_dst.sin6_addr = *dest;
+ /* :: needs /0 "netmask", host route wants "no netmask */
+ rtm_addrs &= ~RTA_NETMASK;
+ }
+
+ rtm.rtm_addrs = rtm_addrs;
+
+#ifndef TARGET_SOLARIS
+ so_dst.sin6_len = sizeof(struct sockaddr_in6);
+ so_mask.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+
+ NEXTADDR(RTA_DST, so_dst);
+ NEXTADDR(RTA_NETMASK, so_mask);
+
+ rtm.rtm_msglen = l = cp - (char *)&m_rtmsg;
+
+ /* transact with routing socket */
+ sockfd = socket(PF_ROUTE, SOCK_RAW, 0);
+ if (sockfd < 0)
+ {
+ msg (M_WARN, "GDG6: socket #1 failed");
+ goto done;
+ }
+ if (write(sockfd, (char *)&m_rtmsg, l) < 0)
+ {
+ msg (M_WARN, "GDG6: problem writing to routing socket");
+ goto done;
+ }
+
+ do
+ {
+ l = read(sockfd, (char *)&m_rtmsg, sizeof(m_rtmsg));
+ }
+ while (l > 0 && (rtm.rtm_seq != seq || rtm.rtm_pid != pid));
+
+ close(sockfd);
+ sockfd = -1;
+
+ /* extract return data from routing socket */
+ rtm_aux = &rtm;
+ cp = ((char *)(rtm_aux + 1));
+ if (rtm_aux->rtm_addrs)
+ {
+ for (i = 1; i; i <<= 1)
+ {
+ if (i & rtm_aux->rtm_addrs)
+ {
+ sa = (struct sockaddr *)cp;
+ if (i == RTA_GATEWAY )
+ gate = sa;
+ else if (i == RTA_IFP)
+ ifp = sa;
+ ADVANCE(cp, sa);
+ }
+ }
+ }
+ else
+ goto done;
+
+ /* get gateway addr and interface name */
+ if (gate != NULL )
+ {
+ struct sockaddr_in6 * s6 = (struct sockaddr_in6 *)gate;
+ struct in6_addr gw = s6->sin6_addr;
+
+#ifndef TARGET_SOLARIS
+ /* You do not really want to know... from FreeBSD's route.c
+ * (KAME encodes the 16 bit scope_id in s6_addr[2] + [3],
+ * but for a correct link-local address these must be :0000: )
+ */
+ if ( gate->sa_len == sizeof(struct sockaddr_in6) &&
+ IN6_IS_ADDR_LINKLOCAL(&gw) )
+ {
+ gw.s6_addr[2] = gw.s6_addr[3] = 0;
+ }
+
+ if ( gate->sa_len != sizeof(struct sockaddr_in6) ||
+ IN6_IS_ADDR_UNSPECIFIED(&gw) )
+ {
+ rgi6->flags |= RGI_ON_LINK;
+ }
+ else
+#endif
+
+ rgi6->gateway.addr_ipv6 = gw;
+ rgi6->flags |= RGI_ADDR_DEFINED;
+
+ if (ifp)
+ {
+ /* get interface name */
+ const struct sockaddr_dl *adl = (struct sockaddr_dl *) ifp;
+ if (adl->sdl_nlen && adl->sdl_nlen < sizeof(rgi6->iface))
+ {
+ memcpy (rgi6->iface, adl->sdl_data, adl->sdl_nlen);
+ rgi6->flags |= RGI_IFACE_DEFINED;
+ }
+ }
+ }
+
+ done:
+ if (sockfd >= 0)
+ close(sockfd);
}
#undef max