Reinstall routes in kernel-netlink plugin, if interfaces get reactivated or IPs reappear.

This commit is contained in:
Tobias Brunner 2011-12-20 14:59:21 +01:00
parent 74ba22c992
commit f834249c59
1 changed files with 206 additions and 4 deletions

View File

@ -52,12 +52,16 @@
#include <threading/thread.h>
#include <threading/condvar.h>
#include <threading/mutex.h>
#include <utils/hashtable.h>
#include <utils/linked_list.h>
#include <processing/jobs/callback_job.h>
/** 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;
/**
@ -187,6 +191,52 @@ static bool route_entry_equals(route_entry_t *a, route_entry_t *b)
chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen;
}
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;
/** IP that appeared, if any */
host_t *ip;
};
/**
* Destroy a net_change_t object
*/
static void net_change_destroy(net_change_t *this)
{
DESTROY_IF(this->ip);
free(this->if_name);
free(this);
}
/**
* Hash a net_change_t object
*/
static u_int net_change_hash(net_change_t *this)
{
if (this->ip)
{
return chunk_hash_inc(this->ip->get_address(this->ip),
chunk_hash(chunk_create(this->if_name,
strlen(this->if_name))));
}
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) && ((!a->ip && !b->ip) ||
(a->ip && b->ip && a->ip->equals(a->ip, b->ip)));
}
typedef struct private_kernel_netlink_net_t private_kernel_netlink_net_t;
/**
@ -248,6 +298,21 @@ struct private_kernel_netlink_net_t {
*/
hashtable_t *routes;
/**
* interface and IP address 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;
/**
* whether to react to RTM_NEWROUTE or RTM_DELROUTE events
*/
@ -264,6 +329,118 @@ struct private_kernel_netlink_net_t {
linked_list_t *rt_exclude;
};
/**
* Forward declaration
*/
static status_t manage_srcroute(private_kernel_netlink_net_t *this,
int nlmsg_type, int flags, chunk_t dst_net,
u_int8_t prefixlen, host_t *gateway,
host_t *src_ip, char *if_name);
/**
* Clear the queued network changes.
*/
static void net_changes_clear(private_kernel_netlink_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_netlink_net_t *this)
{
enumerator_t *enumerator;
route_entry_t *route;
this->net_changes_lock->lock(this->net_changes_lock);
this->mutex->lock(this->mutex);
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 generic change for this interface is queued */
change = this->net_changes->get(this->net_changes, &lookup);
if (!change)
{ /* check if a specific update for this IP is queued */
lookup.ip = route->src_ip;
change = this->net_changes->get(this->net_changes, &lookup);
}
if (change)
{
manage_srcroute(this, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL,
route->dst_net, route->prefixlen, route->gateway,
route->src_ip, route->if_name);
}
}
enumerator->destroy(enumerator);
this->mutex->unlock(this->mutex);
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.
* Provide the IP address if the update is caused by an IP address change.
*
* 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 and IP address are freed.
*/
static void queue_route_reinstall(private_kernel_netlink_net_t *this,
char *if_name, host_t *ip)
{
net_change_t *update, *found;
timeval_t now;
job_t *job;
INIT(update,
.if_name = if_name,
.ip = ip
);
this->net_changes_lock->lock(this->net_changes_lock);
found = this->net_changes->get(this->net_changes, update);
if (found)
{
net_change_destroy(update);
}
else
{
this->net_changes->put(this->net_changes, update, update);
}
time_monotonic(&now);
if (timercmp(&now, &this->last_route_reinstall, >))
{
now.tv_usec += ROUTE_DELAY * 1000;
while (now.tv_usec > 1000000)
{
now.tv_sec++;
now.tv_usec -= 1000000;
}
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);
}
/**
* get the refcount of a virtual ip
*/
@ -382,9 +559,9 @@ static void process_link(private_kernel_netlink_net_t *this,
enumerator_t *enumerator;
iface_entry_t *current, *entry = NULL;
char *name = NULL;
bool update = FALSE;
bool update = FALSE, update_routes = FALSE;
while(RTA_OK(rta, rtasize))
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
@ -432,7 +609,7 @@ static void process_link(private_kernel_netlink_net_t *this,
{
if (!(entry->flags & IFF_UP) && (msg->ifi_flags & IFF_UP))
{
update = TRUE;
update = update_routes = TRUE;
DBG1(DBG_KNL, "interface %s activated", name);
}
if ((entry->flags & IFF_UP) && !(msg->ifi_flags & IFF_UP))
@ -467,6 +644,11 @@ static void process_link(private_kernel_netlink_net_t *this,
}
this->mutex->unlock(this->mutex);
if (update_routes && event)
{
queue_route_reinstall(this, strdup(name), NULL);
}
/* send an update to all IKE_SAs */
if (update && event)
{
@ -488,9 +670,10 @@ static void process_addr(private_kernel_netlink_net_t *this,
iface_entry_t *iface;
addr_entry_t *addr;
chunk_t local = chunk_empty, address = chunk_empty;
char *route_ifname = NULL;
bool update = FALSE, found = FALSE, changed = FALSE;
while(RTA_OK(rta, rtasize))
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
@ -560,6 +743,7 @@ static void process_addr(private_kernel_netlink_net_t *this,
{
found = TRUE;
changed = TRUE;
route_ifname = strdup(iface->ifname);
addr = malloc_thing(addr_entry_t);
addr->ip = host->clone(host);
addr->virtual = FALSE;
@ -582,6 +766,15 @@ static void process_addr(private_kernel_netlink_net_t *this,
}
ifaces->destroy(ifaces);
this->mutex->unlock(this->mutex);
if (update && event && route_ifname)
{
queue_route_reinstall(this, route_ifname, host->clone(host));
}
else
{
free(route_ifname);
}
host->destroy(host);
/* send an update to all IKE_SAs */
@ -1593,6 +1786,10 @@ METHOD(kernel_net_t, destroy, void,
enumerator->destroy(enumerator);
this->routes->destroy(this->routes);
net_changes_clear(this);
this->net_changes->destroy(this->net_changes);
this->net_changes_lock->destroy(this->net_changes_lock);
this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy);
this->rt_exclude->destroy(this->rt_exclude);
this->condvar->destroy(this->condvar);
@ -1628,6 +1825,10 @@ kernel_netlink_net_t *kernel_netlink_net_create()
.rt_exclude = linked_list_create(),
.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),
.net_changes_lock = mutex_create(MUTEX_TYPE_DEFAULT),
.ifaces = linked_list_create(),
.mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
.condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
@ -1640,6 +1841,7 @@ kernel_netlink_net_t *kernel_netlink_net_create()
.install_virtual_ip = lib->settings->get_bool(lib->settings,
"%s.install_virtual_ip", TRUE, hydra->daemon),
);
timerclear(&this->last_route_reinstall);
timerclear(&this->last_roam);
exclude = lib->settings->get_str(lib->settings,