kernel-pfroute: Implement enumeration of local subnets
authorTobias Brunner <tobias@strongswan.org>
Wed, 12 Oct 2016 16:32:14 +0000 (18:32 +0200)
committerTobias Brunner <tobias@strongswan.org>
Wed, 8 Feb 2017 09:38:28 +0000 (10:38 +0100)
src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c

index 0717a8a..efcf1c2 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
+#include <sys/sysctl.h>
 #include <net/if.h>
 #include <net/if_dl.h>
 #include <ifaddrs.h>
@@ -1705,6 +1706,198 @@ METHOD(kernel_net_t, get_nexthop, host_t*,
 }
 
 /**
+ * Get the number of set bits in the given netmask
+ */
+static uint8_t sockaddr_to_netmask(sockaddr_t *sockaddr, host_t *dst)
+{
+       uint8_t len = 0, i, byte, mask = 0;
+       struct sockaddr_storage ss;
+       char *addr;
+
+       /* at least some older FreeBSD versions send us shorter sockaddrs
+        * with the family set to -1 (255) */
+       if (sockaddr->sa_family == 255)
+       {
+               memset(&ss, 0, sizeof(ss));
+               memcpy(&ss, sockaddr, sockaddr->sa_len);
+               /* use the address family and length of the destination as hint */
+               ss.ss_len = *dst->get_sockaddr_len(dst);
+               ss.ss_family = dst->get_family(dst);
+               sockaddr = (sockaddr_t*)&ss;
+       }
+
+       switch (sockaddr->sa_family)
+       {
+               case AF_INET:
+                       len = 4;
+                       addr = (char*)&((struct sockaddr_in*)sockaddr)->sin_addr;
+                       break;
+               case AF_INET6:
+                       len = 16;
+                       addr = (char*)&((struct sockaddr_in6*)sockaddr)->sin6_addr;
+                       break;
+               default:
+                       break;
+       }
+
+       for (i = 0; i < len; i++)
+       {
+               byte = addr[i];
+
+               if (byte == 0x00)
+               {
+                       break;
+               }
+               if (byte == 0xff)
+               {
+                       mask += 8;
+               }
+               else
+               {
+                       while (byte & 0x80)
+                       {
+                               mask++;
+                               byte <<= 1;
+                       }
+               }
+       }
+       return mask;
+}
+
+/** enumerator over subnets */
+typedef struct {
+       enumerator_t public;
+       /** sysctl result */
+       char *buf;
+       /** length of the complete result */
+       size_t len;
+       /** start of the current route entry */
+       char *current;
+       /** last subnet enumerated */
+       host_t *net;
+       /** interface of current net */
+       char *ifname;
+} subnet_enumerator_t;
+
+METHOD(enumerator_t, destroy_subnet_enumerator, void,
+       subnet_enumerator_t *this)
+{
+       DESTROY_IF(this->net);
+       free(this->ifname);
+       free(this->buf);
+       free(this);
+}
+
+METHOD(enumerator_t, enumerate_subnets, bool,
+       subnet_enumerator_t *this, host_t **net, uint8_t *mask, char **ifname)
+{
+       enumerator_t *enumerator;
+       struct rt_msghdr *rtm;
+       struct sockaddr *addr;
+       int type;
+
+       if (!this->current)
+       {
+               this->current = this->buf;
+       }
+       else
+       {
+               rtm = (struct rt_msghdr*)this->current;
+               this->current += rtm->rtm_msglen;
+               DESTROY_IF(this->net);
+               this->net = NULL;
+               free(this->ifname);
+               this->ifname = NULL;
+       }
+
+       for (; this->current < this->buf + this->len;
+                this->current += rtm->rtm_msglen)
+       {
+               struct sockaddr *netmask;
+               uint8_t netbits = 0;
+
+               rtm = (struct rt_msghdr*)this->current;
+
+               if (rtm->rtm_version != RTM_VERSION)
+               {
+                       continue;
+               }
+               if (rtm->rtm_flags & RTF_GATEWAY ||
+                       rtm->rtm_flags & RTF_HOST ||
+                       rtm->rtm_flags & RTF_REJECT)
+               {
+                       continue;
+               }
+               enumerator = create_rtmsg_enumerator(rtm);
+               while (enumerator->enumerate(enumerator, &type, &addr))
+               {
+                       if (type == RTAX_DST)
+                       {
+                               this->net = this->net ?: host_create_from_sockaddr(addr);
+                       }
+                       if (type == RTAX_NETMASK)
+                       {
+                               netmask = addr;
+                       }
+                       if (type == RTAX_IFP && addr->sa_family == AF_LINK)
+                       {
+                               struct sockaddr_dl *sdl = (struct sockaddr_dl*)addr;
+                               free(this->ifname);
+                               this->ifname = strndup(sdl->sdl_data, sdl->sdl_nlen);
+                       }
+               }
+               if (this->net)
+               {
+                       netbits = sockaddr_to_netmask(netmask, this->net);
+               }
+               enumerator->destroy(enumerator);
+
+               if (this->net && this->ifname)
+               {
+                       *net = this->net;
+                       *mask = netbits ?: this->net->get_address(this->net).len * 8;
+                       *ifname = this->ifname;
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+METHOD(kernel_net_t, create_local_subnet_enumerator, enumerator_t*,
+       private_kernel_pfroute_net_t *this)
+{
+       subnet_enumerator_t *enumerator;
+       char *buf;
+       size_t len;
+       int mib[7] = {
+               CTL_NET, PF_ROUTE, 0, AF_UNSPEC, NET_RT_DUMP, 0, 0
+       };
+
+       if (sysctl(mib, countof(mib), NULL, &len, NULL, 0) < 0)
+       {
+               DBG2(DBG_KNL, "enumerating local subnets failed");
+               return enumerator_create_empty();
+       }
+       buf = malloc(len);
+       if (sysctl(mib, countof(mib), buf, &len, NULL, 0) < 0)
+       {
+               DBG2(DBG_KNL, "enumerating local subnets failed");
+               free(buf);
+               return enumerator_create_empty();
+       }
+
+       INIT(enumerator,
+               .public = {
+                       .enumerate = (void*)_enumerate_subnets,
+                       .destroy = _destroy_subnet_enumerator,
+               },
+               .buf = buf,
+               .len = len,
+       );
+       return &enumerator->public;
+}
+
+/**
  * Initialize a list of local addresses.
  */
 static status_t init_address_list(private_kernel_pfroute_net_t *this)
@@ -1849,6 +2042,7 @@ kernel_pfroute_net_t *kernel_pfroute_net_create()
                                .get_features = _get_features,
                                .get_interface = _get_interface_name,
                                .create_address_enumerator = _create_address_enumerator,
+                               .create_local_subnet_enumerator = _create_local_subnet_enumerator,
                                .get_source_addr = _get_source_addr,
                                .get_nexthop = _get_nexthop,
                                .add_ip = _add_ip,