Use a hashtable to quickly check for usable IP addresses/interfaces

This commit is contained in:
Tobias Brunner
2012-09-21 18:16:26 +02:00
parent 090c556ce8
commit 1f97e1aaca
2 changed files with 285 additions and 86 deletions
@@ -132,12 +132,72 @@ static bool iface_entry_by_index(iface_entry_t *this, int *ifindex)
return this->ifindex == *ifindex; return this->ifindex == *ifindex;
} }
/**
* check if an interface is up
*/
static inline bool iface_entry_up(iface_entry_t *iface)
{
return (iface->flags & IFF_UP) == IFF_UP;
}
/** /**
* check if an interface is up and usable * check if an interface is up and usable
*/ */
static inline bool iface_entry_up_and_usable(iface_entry_t *iface) static inline bool iface_entry_up_and_usable(iface_entry_t *iface)
{ {
return iface->usable && (iface->flags & IFF_UP) == IFF_UP; return iface->usable && iface_entry_up(iface);
}
typedef struct addr_map_entry_t addr_map_entry_t;
/**
* Entry that maps an IP address to an interface entry
*/
struct addr_map_entry_t {
/** The IP address */
host_t *ip;
/** The interface this address is installed on */
iface_entry_t *iface;
};
/**
* Hash a addr_map_entry_t object, all entries with the same IP address
* are stored in the same bucket
*/
static u_int addr_map_entry_hash(addr_map_entry_t *this)
{
return chunk_hash(this->ip->get_address(this->ip));
}
/**
* Compare two addr_map_entry_t objects, two entries are equal if they are
* installed on the same interface
*/
static bool addr_map_entry_equals(addr_map_entry_t *a, addr_map_entry_t *b)
{
return a->iface->ifindex == b->iface->ifindex &&
a->ip->ip_equals(a->ip, b->ip);
}
/**
* Used with get_match this finds an address entry if it is installed on
* an up and usable interface
*/
static bool addr_map_entry_match_up_and_usable(addr_map_entry_t *a,
addr_map_entry_t *b)
{
return iface_entry_up_and_usable(b->iface) &&
a->ip->ip_equals(a->ip, b->ip);
}
/**
* Used with get_match this finds an address entry if it is installed on
* any active local interface
*/
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; typedef struct route_entry_t route_entry_t;
@@ -272,6 +332,11 @@ struct private_kernel_netlink_net_t {
*/ */
linked_list_t *ifaces; linked_list_t *ifaces;
/**
* Map for IP addresses to iface_entry_t objects (addr_map_entry_t)
*/
hashtable_t *addrs;
/** /**
* netlink rt socket (routing) * netlink rt socket (routing)
*/ */
@@ -485,6 +550,48 @@ static int get_vip_refcount(private_kernel_netlink_net_t *this, host_t* ip)
return refcount; return refcount;
} }
/**
* Add an address map entry
*/
static void addr_map_entry_add(private_kernel_netlink_net_t *this,
addr_entry_t *addr, iface_entry_t *iface)
{
addr_map_entry_t *entry;
if (addr->virtual)
{ /* don't map virtual IPs */
return;
}
INIT(entry,
.ip = addr->ip,
.iface = iface,
);
entry = this->addrs->put(this->addrs, entry, entry);
free(entry);
}
/**
* Remove an address map entry (the argument order is a bit strange because
* it is also used with linked_list_t.invoke_function)
*/
static void addr_map_entry_remove(addr_entry_t *addr, iface_entry_t *iface,
private_kernel_netlink_net_t *this)
{
addr_map_entry_t *entry, lookup = {
.ip = addr->ip,
.iface = iface,
};
if (addr->virtual)
{ /* these are never mapped, but this check avoids problems if a
* virtual IP equals a regular one */
return;
}
entry = this->addrs->remove(this->addrs, &lookup);
free(entry);
}
/** /**
* get the first non-virtual ip address on the given interface. * get the first non-virtual ip address on the given interface.
* if a candidate address is given, we first search for that address and if not * if a candidate address is given, we first search for that address and if not
@@ -681,6 +788,8 @@ static void process_link(private_kernel_netlink_net_t *this,
DBG1(DBG_KNL, "interface %s deleted", current->ifname); DBG1(DBG_KNL, "interface %s deleted", current->ifname);
} }
this->ifaces->remove_at(this->ifaces, enumerator); this->ifaces->remove_at(this->ifaces, enumerator);
current->addrs->invoke_function(current->addrs,
(void*)addr_map_entry_remove, current, this);
iface_entry_destroy(current); iface_entry_destroy(current);
break; break;
} }
@@ -774,6 +883,7 @@ static void process_addr(private_kernel_netlink_net_t *this,
DBG1(DBG_KNL, "%H disappeared from %s", DBG1(DBG_KNL, "%H disappeared from %s",
host, iface->ifname); host, iface->ifname);
} }
addr_map_entry_remove(addr, iface, this);
addr_entry_destroy(addr); addr_entry_destroy(addr);
} }
else if (hdr->nlmsg_type == RTM_NEWADDR && addr->virtual) else if (hdr->nlmsg_type == RTM_NEWADDR && addr->virtual)
@@ -798,6 +908,7 @@ static void process_addr(private_kernel_netlink_net_t *this,
addr->scope = msg->ifa_scope; addr->scope = msg->ifa_scope;
iface->addrs->insert_last(iface->addrs, addr); iface->addrs->insert_last(iface->addrs, addr);
addr_map_entry_add(this, addr, iface);
if (event && iface->usable) if (event && iface->usable)
{ {
DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname); DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname);
@@ -1059,59 +1170,37 @@ METHOD(kernel_net_t, create_address_enumerator, enumerator_t*,
METHOD(kernel_net_t, get_interface_name, bool, METHOD(kernel_net_t, get_interface_name, bool,
private_kernel_netlink_net_t *this, host_t* ip, char **name) private_kernel_netlink_net_t *this, host_t* ip, char **name)
{ {
enumerator_t *ifaces, *addrs; addr_map_entry_t *entry, lookup = {
iface_entry_t *iface; .ip = ip,
addr_entry_t *addr; };
bool found = FALSE, ignored = FALSE;
if (ip->is_anyaddr(ip)) if (ip->is_anyaddr(ip))
{ {
return FALSE; return FALSE;
} }
this->mutex->lock(this->mutex); this->mutex->lock(this->mutex);
ifaces = this->ifaces->create_enumerator(this->ifaces); /* first try to find it on an up and usable interface */
while (ifaces->enumerate(ifaces, &iface)) entry = this->addrs->get_match(this->addrs, &lookup,
(void*)addr_map_entry_match_up_and_usable);
if (entry)
{ {
addrs = iface->addrs->create_enumerator(iface->addrs); if (name)
while (addrs->enumerate(addrs, &addr))
{
if (ip->ip_equals(ip, addr->ip))
{
found = TRUE;
if (!iface->usable)
{
ignored = TRUE;
break;
}
if (name)
{
*name = strdup(iface->ifname);
}
break;
}
}
addrs->destroy(addrs);
if (found)
{
break;
}
}
ifaces->destroy(ifaces);
this->mutex->unlock(this->mutex);
if (!ignored)
{
if (!found)
{
DBG2(DBG_KNL, "%H is not a local address", ip);
}
else if (name)
{ {
*name = strdup(entry->iface->ifname);
DBG2(DBG_KNL, "%H is on interface %s", ip, *name); DBG2(DBG_KNL, "%H is on interface %s", ip, *name);
} }
this->mutex->unlock(this->mutex);
return TRUE;
} }
return found && !ignored; /* maybe it is installed on an ignored interface */
entry = this->addrs->get_match(this->addrs, &lookup,
(void*)addr_map_entry_match_up);
if (!entry)
{
DBG2(DBG_KNL, "%H is not a local address or the interface is down", ip);
}
this->mutex->unlock(this->mutex);
return FALSE;
} }
/** /**
@@ -1955,6 +2044,7 @@ METHOD(kernel_net_t, destroy, void,
{ {
enumerator_t *enumerator; enumerator_t *enumerator;
route_entry_t *route; route_entry_t *route;
addr_map_entry_t *addr;
if (this->routing_table) if (this->routing_table)
{ {
@@ -1982,6 +2072,14 @@ METHOD(kernel_net_t, destroy, void,
this->net_changes->destroy(this->net_changes); this->net_changes->destroy(this->net_changes);
this->net_changes_lock->destroy(this->net_changes_lock); this->net_changes_lock->destroy(this->net_changes_lock);
enumerator = this->addrs->create_enumerator(this->addrs);
while (enumerator->enumerate(enumerator, NULL, (void**)&addr))
{
free(addr);
}
enumerator->destroy(enumerator);
this->addrs->destroy(this->addrs);
this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy); this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy);
this->rt_exclude->destroy(this->rt_exclude); this->rt_exclude->destroy(this->rt_exclude);
this->condvar->destroy(this->condvar); this->condvar->destroy(this->condvar);
@@ -2020,6 +2118,9 @@ kernel_netlink_net_t *kernel_netlink_net_create()
.net_changes = hashtable_create( .net_changes = hashtable_create(
(hashtable_hash_t)net_change_hash, (hashtable_hash_t)net_change_hash,
(hashtable_equals_t)net_change_equals, 16), (hashtable_equals_t)net_change_equals, 16),
.addrs = hashtable_create(
(hashtable_hash_t)addr_map_entry_hash,
(hashtable_equals_t)addr_map_entry_equals, 16),
.net_changes_lock = mutex_create(MUTEX_TYPE_DEFAULT), .net_changes_lock = mutex_create(MUTEX_TYPE_DEFAULT),
.ifaces = linked_list_create(), .ifaces = linked_list_create(),
.mutex = mutex_create(MUTEX_TYPE_RECURSIVE), .mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
@@ -99,14 +99,73 @@ static void iface_entry_destroy(iface_entry_t *this)
free(this); free(this);
} }
/**
* check if an interface is up
*/
static inline bool iface_entry_up(iface_entry_t *iface)
{
return (iface->flags & IFF_UP) == IFF_UP;
}
/** /**
* check if an interface is up and usable * check if an interface is up and usable
*/ */
static inline bool iface_entry_up_and_usable(iface_entry_t *iface) static inline bool iface_entry_up_and_usable(iface_entry_t *iface)
{ {
return iface->usable && (iface->flags & IFF_UP) == IFF_UP; return iface->usable && iface_entry_up(iface);
} }
typedef struct addr_map_entry_t addr_map_entry_t;
/**
* Entry that maps an IP address to an interface entry
*/
struct addr_map_entry_t {
/** The IP address */
host_t *ip;
/** The interface this address is installed on */
iface_entry_t *iface;
};
/**
* Hash a addr_map_entry_t object, all entries with the same IP address
* are stored in the same bucket
*/
static u_int addr_map_entry_hash(addr_map_entry_t *this)
{
return chunk_hash(this->ip->get_address(this->ip));
}
/**
* Compare two addr_map_entry_t objects, two entries are equal if they are
* installed on the same interface
*/
static bool addr_map_entry_equals(addr_map_entry_t *a, addr_map_entry_t *b)
{
return a->iface->ifindex == b->iface->ifindex &&
a->ip->ip_equals(a->ip, b->ip);
}
/**
* Used with get_match this finds an address entry if it is installed on
* an up and usable interface
*/
static bool addr_map_entry_match_up_and_usable(addr_map_entry_t *a,
addr_map_entry_t *b)
{
return iface_entry_up_and_usable(b->iface) &&
a->ip->ip_equals(a->ip, b->ip);
}
/**
* Used with get_match this finds an address entry if it is installed on
* any active local interface
*/
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 private_kernel_pfroute_net_t private_kernel_pfroute_net_t; typedef struct private_kernel_pfroute_net_t private_kernel_pfroute_net_t;
@@ -130,6 +189,11 @@ struct private_kernel_pfroute_net_t
*/ */
linked_list_t *ifaces; linked_list_t *ifaces;
/**
* Map for IP addresses to iface_entry_t objects (addr_map_entry_t)
*/
hashtable_t *addrs;
/** /**
* mutex to lock access to the PF_ROUTE socket * mutex to lock access to the PF_ROUTE socket
*/ */
@@ -156,6 +220,48 @@ struct private_kernel_pfroute_net_t
timeval_t last_roam; timeval_t last_roam;
}; };
/**
* Add an address map entry
*/
static void addr_map_entry_add(private_kernel_netlink_net_t *this,
addr_entry_t *addr, iface_entry_t *iface)
{
addr_map_entry_t *entry;
if (addr->virtual)
{ /* don't map virtual IPs */
return;
}
INIT(entry,
.ip = addr->ip,
.iface = iface,
);
entry = this->addrs->put(this->addrs, entry, entry);
free(entry);
}
/**
* Remove an address map entry (the argument order is a bit strange because
* it is also used with linked_list_t.invoke_function)
*/
static void addr_map_entry_remove(addr_entry_t *addr, iface_entry_t *iface,
private_kernel_netlink_net_t *this)
{
addr_map_entry_t *entry, lookup = {
.ip = addr->ip,
.iface = iface,
};
if (addr->virtual)
{ /* these are never mapped, but this check avoid problems if a virtual IP
* equals a regular one */
return;
}
entry = this->addrs->remove(this->addrs, &lookup);
free(entry);
}
/** /**
* callback function that raises the delayed roam event * callback function that raises the delayed roam event
*/ */
@@ -246,6 +352,7 @@ static void process_addr(private_kernel_pfroute_net_t *this,
DBG1(DBG_KNL, "%H disappeared from %s", DBG1(DBG_KNL, "%H disappeared from %s",
host, iface->ifname); host, iface->ifname);
} }
addr_map_entry_remove(addr, iface, this);
addr_entry_destroy(addr); addr_entry_destroy(addr);
} }
else if (ifa->ifam_type == RTM_NEWADDR && addr->virtual) else if (ifa->ifam_type == RTM_NEWADDR && addr->virtual)
@@ -264,6 +371,7 @@ static void process_addr(private_kernel_pfroute_net_t *this,
addr->virtual = FALSE; addr->virtual = FALSE;
addr->refcount = 1; addr->refcount = 1;
iface->addrs->insert_last(iface->addrs, addr); iface->addrs->insert_last(iface->addrs, addr);
addr_map_entry_add(this, addr, iface);
if (iface->usable) if (iface->usable)
{ {
DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname); DBG1(DBG_KNL, "%H appeared on %s", host, iface->ifname);
@@ -496,59 +604,37 @@ METHOD(kernel_net_t, create_address_enumerator, enumerator_t*,
METHOD(kernel_net_t, get_interface_name, bool, METHOD(kernel_net_t, get_interface_name, bool,
private_kernel_pfroute_net_t *this, host_t* ip, char **name) private_kernel_pfroute_net_t *this, host_t* ip, char **name)
{ {
enumerator_t *ifaces, *addrs; addr_map_entry_t *entry, lookup = {
iface_entry_t *iface; .ip = ip,
addr_entry_t *addr; };
bool found = FALSE, ignored = FALSE;
if (ip->is_anyaddr(ip)) if (ip->is_anyaddr(ip))
{ {
return FALSE; return FALSE;
} }
this->mutex->lock(this->mutex); this->mutex->lock(this->mutex);
ifaces = this->ifaces->create_enumerator(this->ifaces); /* first try to find it on an up and usable interface */
while (ifaces->enumerate(ifaces, &iface)) entry = this->addrs->get_match(this->addrs, &lookup,
(void*)addr_map_entry_match_up_and_usable);
if (entry)
{ {
addrs = iface->addrs->create_enumerator(iface->addrs); if (name)
while (addrs->enumerate(addrs, &addr))
{
if (ip->ip_equals(ip, addr->ip))
{
found = TRUE;
if (!iface->usable)
{
ignored = TRUE;
break;
}
if (name)
{
*name = strdup(iface->ifname);
}
break;
}
}
addrs->destroy(addrs);
if (found)
{
break;
}
}
ifaces->destroy(ifaces);
this->mutex->unlock(this->mutex);
if (!ignored)
{
if (!found)
{
DBG2(DBG_KNL, "%H is not a local address", ip);
}
else if (name)
{ {
*name = strdup(entry->iface->ifname);
DBG2(DBG_KNL, "%H is on interface %s", ip, *name); DBG2(DBG_KNL, "%H is on interface %s", ip, *name);
} }
this->mutex->unlock(this->mutex);
return TRUE;
} }
return found && !ignored; /* maybe it is installed on an ignored interface */
entry = this->addrs->get_match(this->addrs, &lookup,
(void*)addr_map_entry_match_up);
if (!entry)
{ /* the address does not exist, is on a down interface */
DBG2(DBG_KNL, "%H is not a local address or the interface is down", ip);
}
this->mutex->unlock(this->mutex);
return FALSE;
} }
METHOD(kernel_net_t, get_source_addr, host_t*, METHOD(kernel_net_t, get_source_addr, host_t*,
@@ -678,6 +764,8 @@ static status_t init_address_list(private_kernel_pfroute_net_t *this)
METHOD(kernel_net_t, destroy, void, METHOD(kernel_net_t, destroy, void,
private_kernel_pfroute_net_t *this) private_kernel_pfroute_net_t *this)
{ {
enumerator_t *enumerator;
if (this->socket > 0) if (this->socket > 0)
{ {
close(this->socket); close(this->socket);
@@ -686,6 +774,13 @@ METHOD(kernel_net_t, destroy, void,
{ {
close(this->socket_events); close(this->socket_events);
} }
enumerator = this->addrs->create_enumerator(this->addrs);
while (enumerator->enumerate(enumerator, NULL, (void**)&addr))
{
free(addr);
}
enumerator->destroy(enumerator);
this->addrs->destroy(this->addrs);
this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy); this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy);
this->mutex->destroy(this->mutex); this->mutex->destroy(this->mutex);
this->mutex_pfroute->destroy(this->mutex_pfroute); this->mutex_pfroute->destroy(this->mutex_pfroute);
@@ -715,6 +810,9 @@ kernel_pfroute_net_t *kernel_pfroute_net_create()
}, },
}, },
.ifaces = linked_list_create(), .ifaces = linked_list_create(),
.addrs = hashtable_create(
(hashtable_hash_t)addr_map_entry_hash,
(hashtable_equals_t)addr_map_entry_equals, 16),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT), .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.mutex_pfroute = mutex_create(MUTEX_TYPE_DEFAULT), .mutex_pfroute = mutex_create(MUTEX_TYPE_DEFAULT),
); );