/*
- * Copyright (C) 2009-2012 Tobias Brunner
+ * Copyright (C) 2009-2013 Tobias Brunner
* Hochschule fuer Technik Rapperswil
*
* This program is free software; you can redistribute it and/or modify it
/** delay before firing roam events (ms) */
#define ROAM_DELAY 100
+/** delay before reinstalling routes (ms) */
+#define ROUTE_DELAY 100
+
typedef struct addr_entry_t addr_entry_t;
/**
return iface_entry_up(b->iface) && a->ip->ip_equals(a->ip, b->ip);
}
+typedef struct route_entry_t route_entry_t;
+
+/**
+ * Installed routing entry
+ */
+struct route_entry_t {
+ /** Name of the interface the route is bound to */
+ char *if_name;
+
+ /** Gateway for this route */
+ host_t *gateway;
+
+ /** Destination net */
+ chunk_t dst_net;
+
+ /** Destination net prefixlen */
+ u_int8_t prefixlen;
+};
+
+/**
+ * Clone a route_entry_t object.
+ */
+static route_entry_t *route_entry_clone(route_entry_t *this)
+{
+ route_entry_t *route;
+
+ INIT(route,
+ .if_name = strdup(this->if_name),
+ .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL,
+ .dst_net = chunk_clone(this->dst_net),
+ .prefixlen = this->prefixlen,
+ );
+ return route;
+}
+
+/**
+ * Destroy a route_entry_t object
+ */
+static void route_entry_destroy(route_entry_t *this)
+{
+ free(this->if_name);
+ DESTROY_IF(this->gateway);
+ chunk_free(&this->dst_net);
+ free(this);
+}
+
+/**
+ * Hash a route_entry_t object
+ */
+static u_int route_entry_hash(route_entry_t *this)
+{
+ return chunk_hash_inc(chunk_from_thing(this->prefixlen),
+ chunk_hash(this->dst_net));
+}
+
+/**
+ * Compare two route_entry_t objects
+ */
+static bool route_entry_equals(route_entry_t *a, route_entry_t *b)
+{
+ if (a->if_name && b->if_name && streq(a->if_name, b->if_name) &&
+ chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen)
+ {
+ return (!a->gateway && !b->gateway) || (a->gateway && b->gateway &&
+ a->gateway->ip_equals(a->gateway, b->gateway));
+ }
+ return FALSE;
+}
+
+typedef struct net_change_t net_change_t;
+
+/**
+ * Queued network changes
+ */
+struct net_change_t {
+ /** Name of the interface that got activated (or an IP appeared on) */
+ char *if_name;
+};
+
+/**
+ * Destroy a net_change_t object
+ */
+static void net_change_destroy(net_change_t *this)
+{
+ free(this->if_name);
+ free(this);
+}
+
+/**
+ * Hash a net_change_t object
+ */
+static u_int net_change_hash(net_change_t *this)
+{
+ return chunk_hash(chunk_create(this->if_name, strlen(this->if_name)));
+}
+
+/**
+ * Compare two net_change_t objects
+ */
+static bool net_change_equals(net_change_t *a, net_change_t *b)
+{
+ return streq(a->if_name, b->if_name);
+}
+
typedef struct private_kernel_pfroute_net_t private_kernel_pfroute_net_t;
/**
*/
condvar_t *condvar;
+ /**
+ * installed routes
+ */
+ hashtable_t *routes;
+
+ /**
+ * mutex for routes
+ */
+ mutex_t *routes_lock;
+
+ /**
+ * interface changes which may trigger route reinstallation
+ */
+ hashtable_t *net_changes;
+
+ /**
+ * mutex for route reinstallation triggers
+ */
+ mutex_t *net_changes_lock;
+
+ /**
+ * time of last route reinstallation
+ */
+ timeval_t last_route_reinstall;
+
/**
* pid to send PF_ROUTE messages with
*/
int vip_wait;
};
+
+/**
+ * Forward declaration
+ */
+static status_t manage_route(private_kernel_pfroute_net_t *this, int op,
+ chunk_t dst_net, u_int8_t prefixlen,
+ host_t *gateway, char *if_name);
+
+/**
+ * Clear the queued network changes.
+ */
+static void net_changes_clear(private_kernel_pfroute_net_t *this)
+{
+ enumerator_t *enumerator;
+ net_change_t *change;
+
+ enumerator = this->net_changes->create_enumerator(this->net_changes);
+ while (enumerator->enumerate(enumerator, NULL, (void**)&change))
+ {
+ this->net_changes->remove_at(this->net_changes, enumerator);
+ net_change_destroy(change);
+ }
+ enumerator->destroy(enumerator);
+}
+
+/**
+ * Act upon queued network changes.
+ */
+static job_requeue_t reinstall_routes(private_kernel_pfroute_net_t *this)
+{
+ enumerator_t *enumerator;
+ route_entry_t *route;
+
+ this->net_changes_lock->lock(this->net_changes_lock);
+ this->routes_lock->lock(this->routes_lock);
+
+ enumerator = this->routes->create_enumerator(this->routes);
+ while (enumerator->enumerate(enumerator, NULL, (void**)&route))
+ {
+ net_change_t *change, lookup = {
+ .if_name = route->if_name,
+ };
+ /* check if a change for the outgoing interface is queued */
+ change = this->net_changes->get(this->net_changes, &lookup);
+ if (change)
+ {
+ manage_route(this, RTM_ADD, route->dst_net, route->prefixlen,
+ route->gateway, route->if_name);
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->routes_lock->unlock(this->routes_lock);
+
+ net_changes_clear(this);
+ this->net_changes_lock->unlock(this->net_changes_lock);
+ return JOB_REQUEUE_NONE;
+}
+
+/**
+ * Queue route reinstallation caused by network changes for a given interface.
+ *
+ * The route reinstallation is delayed for a while and only done once for
+ * several calls during this delay, in order to avoid doing it too often.
+ * The interface name is freed.
+ */
+static void queue_route_reinstall(private_kernel_pfroute_net_t *this,
+ char *if_name)
+{
+ net_change_t *update, *found;
+ timeval_t now;
+ job_t *job;
+
+ INIT(update,
+ .if_name = if_name
+ );
+
+ this->net_changes_lock->lock(this->net_changes_lock);
+ found = this->net_changes->put(this->net_changes, update, update);
+ if (found)
+ {
+ net_change_destroy(found);
+ }
+ time_monotonic(&now);
+ if (timercmp(&now, &this->last_route_reinstall, >))
+ {
+ timeval_add_ms(&now, ROUTE_DELAY);
+ this->last_route_reinstall = now;
+
+ job = (job_t*)callback_job_create((callback_job_cb_t)reinstall_routes,
+ this, NULL, NULL);
+ lib->scheduler->schedule_job_ms(lib->scheduler, job, ROUTE_DELAY);
+ }
+ this->net_changes_lock->unlock(this->net_changes_lock);
+}
+
/**
* Add an address map entry
*/
addr_entry_t *addr;
bool found = FALSE, changed = FALSE, roam = FALSE;
enumerator_t *enumerator;
+ char *ifname = NULL;
int type;
enumerator = create_rtmsg_enumerator(ifa, sizeof(*ifa));
.ip = host->clone(host),
);
changed = TRUE;
+ ifname = strdup(iface->ifname);
iface->addrs->insert_last(iface->addrs, addr);
addr_map_entry_add(this, addr, iface);
if (iface->usable)
this->lock->unlock(this->lock);
host->destroy(host);
+ if (roam && ifname)
+ {
+ queue_route_reinstall(this, ifname);
+ }
+ else
+ {
+ free(ifname);
+ }
+
if (roam)
{
fire_roam_event(this, TRUE);
{
enumerator_t *enumerator;
iface_entry_t *iface;
- bool roam = FALSE, found = FALSE;
+ bool roam = FALSE, found = FALSE, update_routes = FALSE;
this->lock->write_lock(this->lock);
enumerator = this->ifaces->create_enumerator(this->ifaces);
{
if (!(iface->flags & IFF_UP) && (msg->ifm_flags & IFF_UP))
{
- roam = TRUE;
+ roam = update_routes = TRUE;
DBG1(DBG_KNL, "interface %s activated", iface->ifname);
}
else if ((iface->flags & IFF_UP) && !(msg->ifm_flags & IFF_UP))
this->ifaces->insert_last(this->ifaces, iface);
if (iface->usable)
{
- roam = TRUE;
+ roam = update_routes = TRUE;
}
}
else
}
this->lock->unlock(this->lock);
+ if (update_routes)
+ {
+ queue_route_reinstall(this, strdup(iface->ifname));
+ }
+
if (roam)
{
fire_roam_event(this, TRUE);
}
}
addrs->destroy(addrs);
+ /* during IKEv1 reauthentication, children get moved from
+ * old the new SA before the virtual IP is available. This
+ * kills the route for our virtual IP, reinstall. */
+ queue_route_reinstall(this, strdup(iface->ifname));
+ break;
}
}
ifaces->destroy(ifaces);
/* lets do this while holding the lock, thus preventing another thread
- * from deleting the TUN device concurrently, hopefully listeneres are quick
+ * from deleting the TUN device concurrently, hopefully listeners are quick
* and cause no deadlocks */
hydra->kernel_interface->tun(hydra->kernel_interface, tun, TRUE);
this->lock->unlock(this->lock);
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 manage_route(this, RTM_ADD, dst_net, prefixlen, gateway, if_name);
+ status_t status;
+ route_entry_t *found, route = {
+ .dst_net = dst_net,
+ .prefixlen = prefixlen,
+ .gateway = gateway,
+ .if_name = if_name,
+ };
+
+ this->routes_lock->lock(this->routes_lock);
+ found = this->routes->get(this->routes, &route);
+ if (found)
+ {
+ this->routes_lock->unlock(this->routes_lock);
+ return ALREADY_DONE;
+ }
+ found = route_entry_clone(&route);
+ this->routes->put(this->routes, found, found);
+ status = manage_route(this, RTM_ADD, dst_net, prefixlen, gateway, if_name);
+ this->routes_lock->unlock(this->routes_lock);
+ return status;
}
METHOD(kernel_net_t, del_route, status_t,
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 manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway, if_name);
+ status_t status;
+ route_entry_t *found, route = {
+ .dst_net = dst_net,
+ .prefixlen = prefixlen,
+ .gateway = gateway,
+ .if_name = if_name,
+ };
+
+ this->routes_lock->lock(this->routes_lock);
+ found = this->routes->get(this->routes, &route);
+ if (!found)
+ {
+ this->routes_lock->unlock(this->routes_lock);
+ return NOT_FOUND;
+ }
+ this->routes->remove(this->routes, found);
+ route_entry_destroy(found);
+ status = manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway,
+ if_name);
+ this->routes_lock->unlock(this->routes_lock);
+ return status;
}
/**
private_kernel_pfroute_net_t *this)
{
enumerator_t *enumerator;
+ route_entry_t *route;
addr_entry_t *addr;
+ enumerator = this->routes->create_enumerator(this->routes);
+ while (enumerator->enumerate(enumerator, NULL, (void**)&route))
+ {
+ manage_route(this, RTM_DELETE, route->dst_net, route->prefixlen,
+ route->gateway, route->if_name);
+ route_entry_destroy(route);
+ }
+ enumerator->destroy(enumerator);
+ this->routes->destroy(this->routes);
+ this->routes_lock->destroy(this->routes_lock);
+
if (this->socket != -1)
{
close(this->socket);
}
+
+ net_changes_clear(this);
+ this->net_changes->destroy(this->net_changes);
+ this->net_changes_lock->destroy(this->net_changes_lock);
+
enumerator = this->addrs->create_enumerator(this->addrs);
while (enumerator->enumerate(enumerator, NULL, (void**)&addr))
{
.addrs = hashtable_create(
(hashtable_hash_t)addr_map_entry_hash,
(hashtable_equals_t)addr_map_entry_equals, 16),
+ .routes = hashtable_create((hashtable_hash_t)route_entry_hash,
+ (hashtable_equals_t)route_entry_equals, 16),
+ .net_changes = hashtable_create(
+ (hashtable_hash_t)net_change_hash,
+ (hashtable_equals_t)net_change_equals, 16),
.tuns = linked_list_create(),
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
+ .routes_lock = mutex_create(MUTEX_TYPE_DEFAULT),
+ .net_changes_lock = mutex_create(MUTEX_TYPE_DEFAULT),
.vip_wait = lib->settings->get_int(lib->settings,
"%s.plugins.kernel-pfroute.vip_wait", 1000, hydra->daemon),
);
+ timerclear(&this->last_route_reinstall);
+ timerclear(&this->last_roam);
/* create a PF_ROUTE socket to communicate with the kernel */
this->socket = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC);