aboutsummaryrefslogtreecommitdiffstats
path: root/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c')
-rw-r--r--src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c742
1 files changed, 742 insertions, 0 deletions
diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c
new file mode 100644
index 000000000..99b99a274
--- /dev/null
+++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c
@@ -0,0 +1,742 @@
+/*
+ * Copyright (C) 2009 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <ifaddrs.h>
+#include <net/route.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "kernel_pfroute_net.h"
+
+#include <hydra.h>
+#include <debug.h>
+#include <utils/host.h>
+#include <threading/thread.h>
+#include <threading/mutex.h>
+#include <utils/linked_list.h>
+#include <processing/jobs/callback_job.h>
+
+#ifndef HAVE_STRUCT_SOCKADDR_SA_LEN
+#error Cannot compile this plugin on systems where 'struct sockaddr' has no sa_len member.
+#endif
+
+/** delay before firing roam events (ms) */
+#define ROAM_DELAY 100
+
+/** buffer size for PF_ROUTE messages */
+#define PFROUTE_BUFFER_SIZE 4096
+
+typedef struct addr_entry_t addr_entry_t;
+
+/**
+ * IP address in an inface_entry_t
+ */
+struct addr_entry_t {
+
+ /** The ip address */
+ host_t *ip;
+
+ /** virtual IP managed by us */
+ bool virtual;
+
+ /** Number of times this IP is used, if virtual */
+ u_int refcount;
+};
+
+/**
+ * destroy a addr_entry_t object
+ */
+static void addr_entry_destroy(addr_entry_t *this)
+{
+ this->ip->destroy(this->ip);
+ free(this);
+}
+
+typedef struct iface_entry_t iface_entry_t;
+
+/**
+ * A network interface on this system, containing addr_entry_t's
+ */
+struct iface_entry_t {
+
+ /** interface index */
+ int ifindex;
+
+ /** name of the interface */
+ char ifname[IFNAMSIZ];
+
+ /** interface flags, as in netdevice(7) SIOCGIFFLAGS */
+ u_int flags;
+
+ /** list of addresses as host_t */
+ linked_list_t *addrs;
+};
+
+/**
+ * destroy an interface entry
+ */
+static void iface_entry_destroy(iface_entry_t *this)
+{
+ this->addrs->destroy_function(this->addrs, (void*)addr_entry_destroy);
+ free(this);
+}
+
+
+typedef struct private_kernel_pfroute_net_t private_kernel_pfroute_net_t;
+
+/**
+ * Private variables and functions of kernel_pfroute class.
+ */
+struct private_kernel_pfroute_net_t
+{
+ /**
+ * Public part of the kernel_pfroute_t object.
+ */
+ kernel_pfroute_net_t public;
+
+ /**
+ * mutex to lock access to various lists
+ */
+ mutex_t *mutex;
+
+ /**
+ * Cached list of interfaces and their addresses (iface_entry_t)
+ */
+ linked_list_t *ifaces;
+
+ /**
+ * job receiving PF_ROUTE events
+ */
+ callback_job_t *job;
+
+ /**
+ * mutex to lock access to the PF_ROUTE socket
+ */
+ mutex_t *mutex_pfroute;
+
+ /**
+ * PF_ROUTE socket to communicate with the kernel
+ */
+ int socket;
+
+ /**
+ * PF_ROUTE socket to receive events
+ */
+ int socket_events;
+
+ /**
+ * sequence number for messages sent to the kernel
+ */
+ int seq;
+
+ /**
+ * time of last roam event
+ */
+ timeval_t last_roam;
+};
+
+/**
+ * callback function that raises the delayed roam event
+ */
+static job_requeue_t roam_event(uintptr_t address)
+{
+ hydra->kernel_interface->roam(hydra->kernel_interface, address != 0);
+ return JOB_REQUEUE_NONE;
+}
+
+/**
+ * fire a roaming event. we delay it for a bit and fire only one event
+ * for multiple calls. otherwise we would create too many events.
+ */
+static void fire_roam_event(private_kernel_pfroute_net_t *this, bool address)
+{
+ timeval_t now;
+ job_t *job;
+
+ time_monotonic(&now);
+ if (timercmp(&now, &this->last_roam, >))
+ {
+ now.tv_usec += ROAM_DELAY * 1000;
+ while (now.tv_usec > 1000000)
+ {
+ now.tv_sec++;
+ now.tv_usec -= 1000000;
+ }
+ this->last_roam = now;
+
+ job = (job_t*)callback_job_create((callback_job_cb_t)roam_event,
+ (void*)(uintptr_t)(address ? 1 : 0),
+ NULL, NULL);
+ hydra->scheduler->schedule_job_ms(hydra->scheduler, job, ROAM_DELAY);
+ }
+}
+
+/**
+ * Process an RTM_*ADDR message from the kernel
+ */
+static void process_addr(private_kernel_pfroute_net_t *this,
+ struct rt_msghdr *msg)
+{
+ struct ifa_msghdr *ifa = (struct ifa_msghdr*)msg;
+ sockaddr_t *sockaddr = (sockaddr_t*)(ifa + 1);
+ host_t *host = NULL;
+ enumerator_t *ifaces, *addrs;
+ iface_entry_t *iface;
+ addr_entry_t *addr;
+ bool found = FALSE, changed = FALSE, roam = FALSE;
+ int i;
+
+ for (i = 1; i < (1 << RTAX_MAX); i <<= 1)
+ {
+ if (ifa->ifam_addrs & i)
+ {
+ if (RTA_IFA & i)
+ {
+ host = host_create_from_sockaddr(sockaddr);
+ break;
+ }
+ sockaddr = (sockaddr_t*)((char*)sockaddr + sockaddr->sa_len);
+ }
+ }
+
+ if (!host)
+ {
+ return;
+ }
+
+ this->mutex->lock(this->mutex);
+ ifaces = this->ifaces->create_enumerator(this->ifaces);
+ while (ifaces->enumerate(ifaces, &iface))
+ {
+ if (iface->ifindex == ifa->ifam_index)
+ {
+ addrs = iface->addrs->create_enumerator(iface->addrs);
+ while (addrs->enumerate(addrs, &addr))
+ {
+ if (host->ip_equals(host, addr->ip))
+ {
+ found = TRUE;
+ if (ifa->ifam_type == RTM_DELADDR)
+ {
+ iface->addrs->remove_at(iface->addrs, addrs);
+ if (!addr->virtual)
+ {
+ changed = TRUE;
+ DBG1(DBG_KNL, "%H disappeared from %s",
+ host, iface->ifname);
+ }
+ addr_entry_destroy(addr);
+ }
+ else if (ifa->ifam_type == RTM_NEWADDR && addr->virtual)
+ {
+ addr->refcount = 1;
+ }
+ }
+ }
+ addrs->destroy(addrs);
+
+ if (!found && ifa->ifam_type == RTM_NEWADDR)
+ {
+ changed = TRUE;
+ addr = malloc_thing(addr_entry_t);
+ addr->ip = host->clone(host);
+ addr->virtual = FALSE;
+ addr->refcount = 1;
+ iface->addrs->insert_last(iface->addrs, addr);
+ DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname);
+ }
+
+ if (changed && (iface->flags & IFF_UP))
+ {
+ roam = TRUE;
+ }
+ break;
+ }
+ }
+ ifaces->destroy(ifaces);
+ this->mutex->unlock(this->mutex);
+ host->destroy(host);
+
+ if (roam)
+ {
+ fire_roam_event(this, TRUE);
+ }
+}
+
+/**
+ * Process an RTM_IFINFO message from the kernel
+ */
+static void process_link(private_kernel_pfroute_net_t *this,
+ struct rt_msghdr *hdr)
+{
+ struct if_msghdr *msg = (struct if_msghdr*)hdr;
+ enumerator_t *enumerator;
+ iface_entry_t *iface;
+ bool roam = FALSE;
+
+ if (msg->ifm_flags & IFF_LOOPBACK)
+ { /* ignore loopback interfaces */
+ return;
+ }
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->ifaces->create_enumerator(this->ifaces);
+ while (enumerator->enumerate(enumerator, &iface))
+ {
+ if (iface->ifindex == msg->ifm_index)
+ {
+ if (!(iface->flags & IFF_UP) && (msg->ifm_flags & IFF_UP))
+ {
+ roam = TRUE;
+ DBG1(DBG_KNL, "interface %s activated", iface->ifname);
+ }
+ else if ((iface->flags & IFF_UP) && !(msg->ifm_flags & IFF_UP))
+ {
+ roam = TRUE;
+ DBG1(DBG_KNL, "interface %s deactivated", iface->ifname);
+ }
+ iface->flags = msg->ifm_flags;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+
+ if (roam)
+ {
+ fire_roam_event(this, TRUE);
+ }
+}
+
+/**
+ * Process an RTM_*ROUTE message from the kernel
+ */
+static void process_route(private_kernel_pfroute_net_t *this,
+ struct rt_msghdr *msg)
+{
+
+}
+
+/**
+ * Receives events from kernel
+ */
+static job_requeue_t receive_events(private_kernel_pfroute_net_t *this)
+{
+ unsigned char buf[PFROUTE_BUFFER_SIZE];
+ struct rt_msghdr *msg = (struct rt_msghdr*)buf;
+ int len;
+ bool oldstate;
+
+ oldstate = thread_cancelability(TRUE);
+ len = recvfrom(this->socket_events, buf, sizeof(buf), 0, NULL, 0);
+ thread_cancelability(oldstate);
+
+ if (len < 0)
+ {
+ switch (errno)
+ {
+ case EINTR:
+ /* interrupted, try again */
+ return JOB_REQUEUE_DIRECT;
+ case EAGAIN:
+ /* no data ready, select again */
+ return JOB_REQUEUE_DIRECT;
+ default:
+ DBG1(DBG_KNL, "unable to receive from PF_ROUTE event socket");
+ sleep(1);
+ return JOB_REQUEUE_FAIR;
+ }
+ }
+
+ if (len < sizeof(msg->rtm_msglen) || len < msg->rtm_msglen ||
+ msg->rtm_version != RTM_VERSION)
+ {
+ DBG2(DBG_KNL, "received corrupted PF_ROUTE message");
+ return JOB_REQUEUE_DIRECT;
+ }
+
+ switch (msg->rtm_type)
+ {
+ case RTM_NEWADDR:
+ case RTM_DELADDR:
+ process_addr(this, msg);
+ break;
+ case RTM_IFINFO:
+ /*case RTM_IFANNOUNCE <- what about this*/
+ process_link(this, msg);
+ break;
+ case RTM_ADD:
+ case RTM_DELETE:
+ process_route(this, msg);
+ default:
+ break;
+ }
+
+ return JOB_REQUEUE_DIRECT;
+}
+
+
+/** enumerator over addresses */
+typedef struct {
+ private_kernel_pfroute_net_t* this;
+ /** whether to enumerate down interfaces */
+ bool include_down_ifaces;
+ /** whether to enumerate virtual ip addresses */
+ bool include_virtual_ips;
+} address_enumerator_t;
+
+/**
+ * cleanup function for address enumerator
+ */
+static void address_enumerator_destroy(address_enumerator_t *data)
+{
+ data->this->mutex->unlock(data->this->mutex);
+ free(data);
+}
+
+/**
+ * filter for addresses
+ */
+static bool filter_addresses(address_enumerator_t *data, addr_entry_t** in, host_t** out)
+{
+ host_t *ip;
+ if (!data->include_virtual_ips && (*in)->virtual)
+ { /* skip virtual interfaces added by us */
+ return FALSE;
+ }
+ ip = (*in)->ip;
+ if (ip->get_family(ip) == AF_INET6)
+ {
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ip->get_sockaddr(ip);
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ { /* skip addresses with a unusable scope */
+ return FALSE;
+ }
+ }
+ *out = ip;
+ return TRUE;
+}
+
+/**
+ * enumerator constructor for interfaces
+ */
+static enumerator_t *create_iface_enumerator(iface_entry_t *iface, address_enumerator_t *data)
+{
+ return enumerator_create_filter(iface->addrs->create_enumerator(iface->addrs),
+ (void*)filter_addresses, data, NULL);
+}
+
+/**
+ * filter for interfaces
+ */
+static bool filter_interfaces(address_enumerator_t *data, iface_entry_t** in, iface_entry_t** out)
+{
+ if (!data->include_down_ifaces && !((*in)->flags & IFF_UP))
+ { /* skip interfaces not up */
+ return FALSE;
+ }
+ *out = *in;
+ return TRUE;
+}
+
+/**
+ * implementation of kernel_net_t.create_address_enumerator
+ */
+static enumerator_t *create_address_enumerator(private_kernel_pfroute_net_t *this,
+ bool include_down_ifaces, bool include_virtual_ips)
+{
+ address_enumerator_t *data = malloc_thing(address_enumerator_t);
+ data->this = this;
+ data->include_down_ifaces = include_down_ifaces;
+ data->include_virtual_ips = include_virtual_ips;
+
+ this->mutex->lock(this->mutex);
+ return enumerator_create_nested(
+ enumerator_create_filter(this->ifaces->create_enumerator(this->ifaces),
+ (void*)filter_interfaces, data, NULL),
+ (void*)create_iface_enumerator, data, (void*)address_enumerator_destroy);
+}
+
+/**
+ * implementation of kernel_net_t.get_interface_name
+ */
+static char *get_interface_name(private_kernel_pfroute_net_t *this, host_t* ip)
+{
+ enumerator_t *ifaces, *addrs;
+ iface_entry_t *iface;
+ addr_entry_t *addr;
+ char *name = NULL;
+
+ DBG2(DBG_KNL, "getting interface name for %H", ip);
+
+ this->mutex->lock(this->mutex);
+ ifaces = this->ifaces->create_enumerator(this->ifaces);
+ while (ifaces->enumerate(ifaces, &iface))
+ {
+ addrs = iface->addrs->create_enumerator(iface->addrs);
+ while (addrs->enumerate(addrs, &addr))
+ {
+ if (ip->ip_equals(ip, addr->ip))
+ {
+ name = strdup(iface->ifname);
+ break;
+ }
+ }
+ addrs->destroy(addrs);
+ if (name)
+ {
+ break;
+ }
+ }
+ ifaces->destroy(ifaces);
+ this->mutex->unlock(this->mutex);
+
+ if (name)
+ {
+ DBG2(DBG_KNL, "%H is on interface %s", ip, name);
+ }
+ else
+ {
+ DBG2(DBG_KNL, "%H is not a local address", ip);
+ }
+ return name;
+}
+
+/**
+ * Implementation of kernel_net_t.get_source_addr.
+ */
+static host_t* get_source_addr(private_kernel_pfroute_net_t *this,
+ host_t *dest, host_t *src)
+{
+ return NULL;
+}
+
+/**
+ * Implementation of kernel_net_t.get_nexthop.
+ */
+static host_t* get_nexthop(private_kernel_pfroute_net_t *this, host_t *dest)
+{
+ return NULL;
+}
+
+/**
+ * Implementation of kernel_net_t.add_ip.
+ */
+static status_t add_ip(private_kernel_pfroute_net_t *this,
+ host_t *virtual_ip, host_t *iface_ip)
+{
+ return FAILED;
+}
+
+/**
+ * Implementation of kernel_net_t.del_ip.
+ */
+static status_t del_ip(private_kernel_pfroute_net_t *this, host_t *virtual_ip)
+{
+ return FAILED;
+}
+
+/**
+ * Implementation of kernel_net_t.add_route.
+ */
+static status_t add_route(private_kernel_pfroute_net_t *this, chunk_t dst_net,
+ u_int8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name)
+{
+ return FAILED;
+}
+
+/**
+ * Implementation of kernel_net_t.del_route.
+ */
+static status_t del_route(private_kernel_pfroute_net_t *this, chunk_t dst_net,
+ u_int8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name)
+{
+ return FAILED;
+}
+
+/**
+ * Initialize a list of local addresses.
+ */
+static status_t init_address_list(private_kernel_pfroute_net_t *this)
+{
+ struct ifaddrs *ifap, *ifa;
+ iface_entry_t *iface, *current;
+ addr_entry_t *addr;
+ enumerator_t *ifaces, *addrs;
+
+ DBG1(DBG_KNL, "listening on interfaces:");
+
+ if (getifaddrs(&ifap) < 0)
+ {
+ DBG1(DBG_KNL, " failed to get interfaces!");
+ return FAILED;
+ }
+
+ for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next)
+ {
+ if (ifa->ifa_addr == NULL)
+ {
+ continue;
+ }
+ switch(ifa->ifa_addr->sa_family)
+ {
+ case AF_LINK:
+ case AF_INET:
+ case AF_INET6:
+ {
+ if (ifa->ifa_flags & IFF_LOOPBACK)
+ { /* ignore loopback interfaces */
+ continue;
+ }
+
+ iface = NULL;
+ ifaces = this->ifaces->create_enumerator(this->ifaces);
+ while (ifaces->enumerate(ifaces, &current))
+ {
+ if (streq(current->ifname, ifa->ifa_name))
+ {
+ iface = current;
+ break;
+ }
+ }
+ ifaces->destroy(ifaces);
+
+ if (!iface)
+ {
+ iface = malloc_thing(iface_entry_t);
+ memcpy(iface->ifname, ifa->ifa_name, IFNAMSIZ);
+ iface->ifindex = if_nametoindex(ifa->ifa_name);
+ iface->flags = ifa->ifa_flags;
+ iface->addrs = linked_list_create();
+ this->ifaces->insert_last(this->ifaces, iface);
+ }
+
+ if (ifa->ifa_addr->sa_family != AF_LINK)
+ {
+ addr = malloc_thing(addr_entry_t);
+ addr->ip = host_create_from_sockaddr(ifa->ifa_addr);
+ addr->virtual = FALSE;
+ addr->refcount = 1;
+ iface->addrs->insert_last(iface->addrs, addr);
+ }
+ }
+ }
+ }
+ freeifaddrs(ifap);
+
+ ifaces = this->ifaces->create_enumerator(this->ifaces);
+ while (ifaces->enumerate(ifaces, &iface))
+ {
+ if (iface->flags & IFF_UP)
+ {
+ DBG1(DBG_KNL, " %s", iface->ifname);
+ addrs = iface->addrs->create_enumerator(iface->addrs);
+ while (addrs->enumerate(addrs, (void**)&addr))
+ {
+ DBG1(DBG_KNL, " %H", addr->ip);
+ }
+ addrs->destroy(addrs);
+ }
+ }
+ ifaces->destroy(ifaces);
+
+ return SUCCESS;
+}
+
+/**
+ * Implementation of kernel_netlink_net_t.destroy.
+ */
+static void destroy(private_kernel_pfroute_net_t *this)
+{
+ if (this->job)
+ {
+ this->job->cancel(this->job);
+ }
+ if (this->socket > 0)
+ {
+ close(this->socket);
+ }
+ if (this->socket_events)
+ {
+ close(this->socket_events);
+ }
+ this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy);
+ this->mutex->destroy(this->mutex);
+ this->mutex_pfroute->destroy(this->mutex_pfroute);
+ free(this);
+}
+
+/*
+ * Described in header.
+ */
+kernel_pfroute_net_t *kernel_pfroute_net_create()
+{
+ private_kernel_pfroute_net_t *this = malloc_thing(private_kernel_pfroute_net_t);
+
+ /* public functions */
+ this->public.interface.get_interface = (char*(*)(kernel_net_t*,host_t*))get_interface_name;
+ this->public.interface.create_address_enumerator = (enumerator_t*(*)(kernel_net_t*,bool,bool))create_address_enumerator;
+ this->public.interface.get_source_addr = (host_t*(*)(kernel_net_t*, host_t *dest, host_t *src))get_source_addr;
+ this->public.interface.get_nexthop = (host_t*(*)(kernel_net_t*, host_t *dest))get_nexthop;
+ this->public.interface.add_ip = (status_t(*)(kernel_net_t*,host_t*,host_t*)) add_ip;
+ this->public.interface.del_ip = (status_t(*)(kernel_net_t*,host_t*)) del_ip;
+ this->public.interface.add_route = (status_t(*)(kernel_net_t*,chunk_t,u_int8_t,host_t*,host_t*,char*)) add_route;
+ this->public.interface.del_route = (status_t(*)(kernel_net_t*,chunk_t,u_int8_t,host_t*,host_t*,char*)) del_route;
+
+ this->public.interface.destroy = (void(*)(kernel_net_t*)) destroy;
+
+ /* private members */
+ this->ifaces = linked_list_create();
+ this->mutex = mutex_create(MUTEX_TYPE_DEFAULT);
+ this->mutex_pfroute = mutex_create(MUTEX_TYPE_DEFAULT);
+
+ this->seq = 0;
+ this->socket_events = 0;
+ this->job = NULL;
+
+ /* create a PF_ROUTE socket to communicate with the kernel */
+ this->socket = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
+ if (this->socket < 0)
+ {
+ DBG1(DBG_KNL, "unable to create PF_ROUTE socket");
+ destroy(this);
+ return NULL;
+ }
+
+ /* create a PF_ROUTE socket to receive events */
+ this->socket_events = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);
+ if (this->socket_events < 0)
+ {
+ DBG1(DBG_KNL, "unable to create PF_ROUTE event socket");
+ destroy(this);
+ return NULL;
+ }
+
+ this->job = callback_job_create((callback_job_cb_t)receive_events,
+ this, NULL, NULL);
+ hydra->processor->queue_job(hydra->processor, (job_t*)this->job);
+
+ if (init_address_list(this) != SUCCESS)
+ {
+ DBG1(DBG_KNL, "unable to get interface list");
+ destroy(this);
+ return NULL;
+ }
+
+ return &this->public;
+}