kernel-pfroute: Reinstall routes on interface/address changes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* 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
|
||||
@@ -53,6 +53,9 @@
|
||||
/** 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;
|
||||
|
||||
/**
|
||||
@@ -176,6 +179,110 @@ static bool addr_map_entry_match_up(addr_map_entry_t *a, addr_map_entry_t *b)
|
||||
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;
|
||||
|
||||
/**
|
||||
@@ -218,6 +325,31 @@ struct 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
|
||||
*/
|
||||
@@ -254,6 +386,101 @@ struct private_kernel_pfroute_net_t
|
||||
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
|
||||
*/
|
||||
@@ -401,6 +628,7 @@ static void process_addr(private_kernel_pfroute_net_t *this,
|
||||
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));
|
||||
@@ -453,6 +681,7 @@ static void process_addr(private_kernel_pfroute_net_t *this,
|
||||
.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)
|
||||
@@ -472,6 +701,15 @@ static void process_addr(private_kernel_pfroute_net_t *this,
|
||||
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);
|
||||
@@ -526,7 +764,7 @@ static void process_link(private_kernel_pfroute_net_t *this,
|
||||
{
|
||||
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);
|
||||
@@ -538,7 +776,7 @@ static void process_link(private_kernel_pfroute_net_t *this,
|
||||
{
|
||||
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))
|
||||
@@ -571,7 +809,7 @@ static void process_link(private_kernel_pfroute_net_t *this,
|
||||
this->ifaces->insert_last(this->ifaces, iface);
|
||||
if (iface->usable)
|
||||
{
|
||||
roam = TRUE;
|
||||
roam = update_routes = TRUE;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -581,6 +819,11 @@ static void process_link(private_kernel_pfroute_net_t *this,
|
||||
}
|
||||
this->lock->unlock(this->lock);
|
||||
|
||||
if (update_routes)
|
||||
{
|
||||
queue_route_reinstall(this, strdup(iface->ifname));
|
||||
}
|
||||
|
||||
if (roam)
|
||||
{
|
||||
fire_roam_event(this, TRUE);
|
||||
@@ -890,11 +1133,16 @@ METHOD(kernel_net_t, add_ip, status_t,
|
||||
}
|
||||
}
|
||||
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);
|
||||
@@ -1098,14 +1346,53 @@ METHOD(kernel_net_t, add_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_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;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1340,12 +1627,29 @@ METHOD(kernel_net_t, destroy, void,
|
||||
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))
|
||||
{
|
||||
@@ -1389,13 +1693,22 @@ kernel_pfroute_net_t *kernel_pfroute_net_create()
|
||||
.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);
|
||||
|
||||
Reference in New Issue
Block a user