Hi all, here's the 2nd and hopefully cleaner version of the dynamic routing patch.
Regards, Sebastian
>From a122b9cfc5962e6fc3591e57a59599381a6d278c Mon Sep 17 00:00:00 2001 From: shundertmark <hundertm...@smcc.net> List-Post: openvpn-devel@lists.sourceforge.net Date: Mon, 7 Jun 2010 13:58:38 +0200 Subject: [PATCH] First test with 'dynamic routing' Signed-off-by: shundertmark <hundertm...@smcc.net> --- Makefile.am | 4 + configure.ac | 12 +++ dynroute.c | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ dynroute.h | 24 +++++ multi.c | 10 ++ 5 files changed, 335 insertions(+), 0 deletions(-) create mode 100644 dynroute.c create mode 100644 dynroute.h diff --git a/Makefile.am b/Makefile.am index 2980dac..84e10d9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -152,3 +152,7 @@ openvpn.8.html: $(srcdir)/openvpn.8 else dist_man_MANS = openvpn.8 endif + +if ENABLE_DYNROUTE + openvpn_SOURCES += dynroute.c +endif diff --git a/configure.ac b/configure.ac index 5575705..c7da5ff 100644 --- a/configure.ac +++ b/configure.ac @@ -212,6 +212,12 @@ AC_ARG_ENABLE(selinux, [SELINUX="yes"] ) +AC_ARG_ENABLE(dynroute, + [ --enable-dynroute Enable dynamic routing], + [DYNROUTE="$enableval"], + [DYNROUTE="no"] +) + AC_ARG_WITH(ssl-headers, [ --with-ssl-headers=DIR Crypto/SSL Include files location], [CS_HDR_DIR="$withval"] @@ -869,6 +875,12 @@ if test "$PASSWORD_SAVE" = "yes"; then AC_DEFINE(ENABLE_PASSWORD_SAVE, 1, [Allow --askpass and --auth-user-pass passwords to be read from a file]) fi +dnl enable dynroute +if test "$DYNROUTE" = "yes"; then + AC_DEFINE(ENABLE_DYNROUTE, 1, [Enable dynamic routing]) +fi +AM_CONDITIONAL(ENABLE_DYNROUTE, test "${DYNROUTE}" = "yes") + dnl dnl check for SELinux library and headers dnl diff --git a/dynroute.c b/dynroute.c new file mode 100644 index 0000000..f9a3ca8 --- /dev/null +++ b/dynroute.c @@ -0,0 +1,285 @@ +#include "dynroute.h" + +struct dynroute dr; + +static struct multi_instance * +multi_learn_in_addr_t (struct multi_context *m, + struct multi_instance *mi, + in_addr_t a, + int netbits, /* -1 if host route, otherwise # of network bits in address */ + bool primary); + +static void netlink_parse_rtattr (struct rtattr **tb, int max, struct rtattr *rta, int len) +{ + while (RTA_OK (rta, len)) + { + if (rta->rta_type <= max) + tb[rta->rta_type] = rta; + rta = RTA_NEXT (rta, len); + } +} + +static uint32_t endian_swap (uint32_t x) +{ + x = (x>>24) | + ((x<<8) & 0x00FF0000) | + ((x>>8) & 0x0000FF00) | + (x<<24); + return x; +} + +static bool dynroute_init (struct dynroute *dr) +{ + if (!dr->initialized) + { + dr->last_error = 0; + dr->action = DRA_NONE; + dr->initialized = false; + dr->nope = true; + dr->sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (dr->sock < 0) + { +#ifdef DYNROUTE_DEBUG + dr->last_error = dr->sock; + fprintf (stderr, "dynroute_init: ERROR socket() : %d\n", dr->last_error); +#endif + return false; + } + struct sockaddr_nl addr; + memset ((void *) &addr, 0, sizeof (addr)); + addr.nl_family = AF_NETLINK; + addr.nl_pid = getpid (); + addr.nl_groups = RTMGRP_IPV4_ROUTE; + dr->last_error = bind (dr->sock, (struct sockaddr *) &addr, sizeof (addr)); + if (dr->last_error < 0) + { +#ifdef DYNROUTE_DEBUG + fprintf (stderr, "dynroute_init: ERROR bind() : %d\n", dr->last_error); +#endif + return false; + } + dr->last_error = fcntl (dr->sock, F_SETFL, O_NONBLOCK); + if (dr->last_error < 0) + { +#ifdef DYNROUTE_DEBUG + fprintf (stderr, "dynroute_init: ERROR fcntl() : %d\n", dr->last_error); +#endif + return false; + } +#ifdef DYNROUTE_DEBUG + fprintf (stderr, "dynroute_init: OK\n"); +#endif + dr->initialized = true; + } +#ifdef DYNROUTE_DEBUG + else + { + fprintf (stderr, "dynroute_init: allready initialised, skipped.\n"); + } +#endif + dr->nope = !dr->initialized; + return dr->initialized; +} + +static bool dynroute_read (struct dynroute *dr) +{ + dr->action = DRA_NONE; + if (!dr->initialized) + { +#ifdef DYNROUTE_DEBUG + fprintf (stderr, "dynroute_read: not ready, trying dynroute_init().\n"); +#endif + if (!dynroute_init (dr)) + return false; + } + + struct nlmsghdr *nlh; + struct rtmsg *rtm; + struct sockaddr_nl addr; + struct rtattr *tb[RTA_MAX + 1]; + + char buf [4096]; + struct iovec iov = { buf, sizeof (buf) }; + struct msghdr msg; + int len; + + msg.msg_name = (void *) &addr; + msg.msg_namelen = sizeof (addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + dr->last_error = recvmsg (dr->sock, &msg, 0); + if (dr->last_error < 0) + { +#ifdef DYNROUTE_DEBUG + if (DYNROUTE_DEBUG == DYNROUTE_DEBUG_CHATTY) + fprintf (stderr, "dynroute_read: recvmsg() : %d\n", dr->last_error); +#endif + return false; + } + nlh = (struct nlmsghdr *) buf; + rtm = NLMSG_DATA(nlh); + + if (rtm->rtm_protocol != RTPROT_ZEBRA) + { +#ifdef DYNROUTE_DEBUG + if (DYNROUTE_DEBUG == DYNROUTE_DEBUG_CHATTY) + fprintf (stderr, "dynroute_read: no zebra\n"); +#endif + return false; + } + + len = nlh->nlmsg_len - NLMSG_LENGTH (sizeof (struct rtmsg)); + dr->last_error = len; + if (dr->last_error < 0) + { +#ifdef DYNROUTE_DEBUG + fprintf (stderr, "dynroute_read: ERROR nlh->nlmsg_len : %d\n", dr->last_error); +#endif + return false; + } + + memset (tb, 0, sizeof tb); + netlink_parse_rtattr (tb, RTA_MAX, RTM_RTA (rtm), len); + + in_addr_t *dstp; + in_addr_t *gw; + + if (tb[RTA_GATEWAY]) + gw = RTA_DATA (tb[RTA_GATEWAY]); + if (tb[RTA_DST]) + dstp = RTA_DATA (tb[RTA_DST]); + + in_addr_t *srcp; + if (tb[RTA_SRC]) + srcp = RTA_DATA (tb[RTA_SRC]); + if (tb[RTA_MULTIPATH]) + { + struct rtnexthop *rtnh = (struct rtnexthop *) RTA_DATA (tb[RTA_MULTIPATH]); + len = RTA_PAYLOAD (tb[RTA_MULTIPATH]); + + if (len < (int) sizeof (*rtnh)) + return 0; + if (rtnh->rtnh_len > len) + return 0; + + int index = rtnh->rtnh_ifindex; + if (rtnh->rtnh_len > sizeof (*rtnh)) + { + memset (tb, 0, sizeof (tb)); + netlink_parse_rtattr (tb, RTA_MAX, RTNH_DATA (rtnh), rtnh->rtnh_len - sizeof (*rtnh)); + if (tb[RTA_GATEWAY]) + gw = RTA_DATA (tb[RTA_GATEWAY]); + } + } + + switch (nlh->nlmsg_type) + { + case RTM_NEWROUTE: + switch (nlh->nlmsg_flags) + { + case 0: + case NLM_F_CREATE: + dr->action = DRA_NEW; + break; + case NLM_F_REPLACE: + dr->action = DRA_REP; + break; + default: + dr->action = DRA_NONE; + break; + } + break; + case RTM_DELROUTE: + dr->action = DRA_DEL; + break; + } + dr->gateway = endian_swap (*gw); + dr->ir.network = endian_swap (*dstp); + dr->ir.netbits = rtm->rtm_dst_len; +#ifdef DYNROUTE_DEBUG + if (DYNROUTE_DEBUG == DYNROUTE_DEBUG_CHATTY) + fprintf (stderr, "dynroute_read: OK\n"); +#endif + return dr->initialized; +} + +static bool dynroute_route_exists (struct multi_context *m, struct iroute ir) +{ + bool ret = false; + struct openvpn_sockaddr remote_si; + struct mroute_addr addr; + struct hash_element *he; + + CLEAR (remote_si); + remote_si.sa.sin_family = AF_INET; + remote_si.sa.sin_addr.s_addr = htonl (ir.network); + ASSERT (mroute_extract_openvpn_sockaddr (&addr, &remote_si, false)); + if (ir.netbits >= 0) + { + addr.type |= MR_WITH_NETBITS; + addr.netbits = (uint8_t) ir.netbits; + } + const uint32_t hv = hash_value (m->vhash, &addr); + struct hash_bucket *bucket = hash_bucket (m->vhash, hv); + struct multi_route *oldroute = NULL; + + hash_bucket_lock (bucket); + /* if route currently exists, get the instance which owns it */ + he = hash_lookup_fast (m->vhash, bucket, &addr, hv); + if (he) + oldroute = (struct multi_route *) he->value; + hash_bucket_unlock (bucket); + ret = (oldroute && multi_route_defined (m, oldroute)); + return ret; +} + +static bool dynroute_eval (struct multi_context *m, struct dynroute *dr) +{ + bool ret = true; + if (dr->action > DRA_NONE) // route added / deleted + { + struct hash_iterator hi; + struct hash_element *he; + hash_iterator_init (m->hash, &hi, true); + while ((he = hash_iterator_next (&hi))) + { + struct gc_arena gc = gc_new (); + struct multi_instance *mi = (struct multi_instance *) he->value; + struct openvpn_sockaddr remote_si; + struct mroute_addr addr; + if (!mi->halt) + { + if (mi->reporting_addr == dr->gateway) // route gateway matches instance ip + { + switch (dr->action) + { + case DRA_NEW: + msg (D_MULTI_LOW, "DYNROUTE: New: %s/%d -> %s", + print_in_addr_t (dr->ir.network, IA_EMPTY_IF_UNDEF, &gc), + dr->ir.netbits, + multi_instance_string (mi, false, &gc)); + mroute_helper_add_iroute (m->route_helper, (struct iroute *) &dr->ir); + multi_learn_in_addr_t (m, mi, dr->ir.network, dr->ir.netbits, false); + break; + case DRA_DEL: + if (dynroute_route_exists(m, dr->ir)) + { + mroute_helper_del_iroute (m->route_helper, (struct iroute *) &dr->ir); + msg (D_MULTI_LOW, "DYNROUTE: Delete: %s/%d -> %s", + print_in_addr_t (dr->ir.network, IA_EMPTY_IF_UNDEF, &gc), dr->ir.netbits, + print_in_addr_t (dr->gateway, IA_EMPTY_IF_UNDEF, &gc)); + } + break; + default: + msg (D_MULTI_LOW, "DYNROUTE: UNKNOWN ACTION: %d", dr->action); + break; + } + } // if (mi->reporting_addr == dr->gateway) + } // if (!mi->halt) + gc_free (&gc); + } // while ((he = hash_iterator_next (&hi))) + hash_iterator_free (&hi); + } // if (dr->action > DRA_NONE) + return ret; +} diff --git a/dynroute.h b/dynroute.h new file mode 100644 index 0000000..6dd5694 --- /dev/null +++ b/dynroute.h @@ -0,0 +1,24 @@ +#include <linux/rtnetlink.h> + +#ifndef DYNROUTE_H +#define DYNROUTE_H + +//#define DYNROUTE_DEBUG_CHATTY 2 +//#define DYNROUTE_DEBUG DYNROUTE_DEBUG_CHATTY-1 + +#define DRA_NONE 0 +#define DRA_NEW 1 +#define DRA_REP 2 +#define DRA_DEL 3 + +struct dynroute +{ + int sock; + int last_error; + bool initialized; + bool nope; + int action; + in_addr_t gateway; + struct iroute ir; +}; +#endif /* DYNROUTE_H */ diff --git a/multi.c b/multi.c index 5c80c46..e1da1f6 100644 --- a/multi.c +++ b/multi.c @@ -37,6 +37,10 @@ #include "forward-inline.h" #include "pf-inline.h" +#ifdef ENABLE_DYNROUTE +#include "dynroute.c" +#endif + /*#define MULTI_DEBUG_EVENT_LOOP*/ #ifdef MULTI_DEBUG_EVENT_LOOP @@ -2273,6 +2277,12 @@ multi_process_timeout (struct multi_context *m, const unsigned int mpp_flags) { bool ret = true; +#ifdef ENABLE_DYNROUTE + if (!dr.nope) + if (dynroute_read (&dr)) + dynroute_eval (m, &dr); +#endif + #ifdef MULTI_DEBUG_EVENT_LOOP printf ("%s -> TIMEOUT\n", id(m->earliest_wakeup)); #endif -- 1.7.0.2.msysgit.0