diff --git a/man/strongswan.conf.5.in b/man/strongswan.conf.5.in index f86e9ea10..4192bc9af 100644 --- a/man/strongswan.conf.5.in +++ b/man/strongswan.conf.5.in @@ -610,6 +610,9 @@ Set MTU of ipsecN device .BR charon.plugins.kernel-netlink.roam_events " [yes]" Whether to trigger roam events when interfaces, addresses or routes change .TP +.BR charon.plugins.kernel-pfroute.vip_wait " [1000]" +Time in ms to wait until virtual IP addresses appear/disappear before failing. +.TP .BR charon.plugins.load-tester Section to configure the load-tester plugin, see LOAD TESTS .TP diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c index 020b36a0b..c29aff433 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c @@ -1266,7 +1266,7 @@ METHOD(kernel_net_t, get_interface_name, bool, if (name) { *name = strdup(entry->iface->ifname); - DBG2(DBG_KNL, "virtual %H is on interface %s", ip, *name); + DBG2(DBG_KNL, "virtual IP %H is on interface %s", ip, *name); } this->lock->unlock(this->lock); return TRUE; diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index d975f15b9..fb90f5b21 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -981,6 +981,10 @@ static traffic_selector_t* sadb_address2ts(struct sadb_address *address) { traffic_selector_t *ts; host_t *host; + u_int8_t proto; + + proto = address->sadb_address_proto; + proto = proto == IPSEC_PROTO_ANY ? 0 : proto; /* The Linux 2.6 kernel does not set the protocol and port information * in the src and dst sadb_address extensions of the SADB_ACQUIRE message. @@ -988,8 +992,7 @@ static traffic_selector_t* sadb_address2ts(struct sadb_address *address) host = host_create_from_sockaddr((sockaddr_t*)&address[1]); ts = traffic_selector_create_from_subnet(host, address->sadb_address_prefixlen, - address->sadb_address_proto, - host->get_port(host), + proto, host->get_port(host), host->get_port(host) ?: 65535); return ts; } @@ -1990,10 +1993,14 @@ static void add_exclude_route(private_kernel_pfkey_ipsec_t *this, dst, NULL); if (gtw) { - if (hydra->kernel_interface->add_route(hydra->kernel_interface, + char *if_name = NULL; + + if (hydra->kernel_interface->get_interface( + hydra->kernel_interface, src, &if_name) && + hydra->kernel_interface->add_route(hydra->kernel_interface, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, - gtw, src, NULL) == SUCCESS) + gtw, src, if_name) == SUCCESS) { INIT(exclude, .dst = dst->clone(dst), @@ -2009,6 +2016,7 @@ static void add_exclude_route(private_kernel_pfkey_ipsec_t *this, DBG1(DBG_KNL, "installing exclude route for %H failed", dst); } gtw->destroy(gtw); + free(if_name); } else { @@ -2047,18 +2055,24 @@ static void remove_exclude_route(private_kernel_pfkey_ipsec_t *this, if (removed) { + char *if_name = NULL; + dst = route->exclude->dst; DBG2(DBG_KNL, "uninstalling exclude route for %H src %H", dst, route->exclude->src); - if (hydra->kernel_interface->del_route(hydra->kernel_interface, + if (hydra->kernel_interface->get_interface( + hydra->kernel_interface, + route->exclude->src, &if_name) && + hydra->kernel_interface->del_route(hydra->kernel_interface, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, route->exclude->gtw, route->exclude->src, - NULL) != SUCCESS) + if_name) != SUCCESS) { DBG1(DBG_KNL, "uninstalling exclude route for %H failed", dst); } exclude_route_destroy(route->exclude); + free(if_name); } route->exclude = NULL; } @@ -2476,9 +2490,9 @@ METHOD(kernel_ipsec_t, del_policy, status_t, struct sadb_msg *msg, *out; struct sadb_x_policy *pol; policy_entry_t *policy, *found = NULL; - policy_sa_t *mapping; + policy_sa_t *mapping, *to_remove = NULL; enumerator_t *enumerator; - bool is_installed = TRUE; + bool first = TRUE, is_installed = TRUE; u_int32_t priority; size_t len; @@ -2508,19 +2522,26 @@ METHOD(kernel_ipsec_t, del_policy, status_t, policy_entry_destroy(policy, this); policy = found; - /* remove mapping to SA by reqid and priority */ + /* remove mapping to SA by reqid and priority, if multiple match, which + * could happen when rekeying due to an address change, remove the oldest */ priority = get_priority(policy, prio); enumerator = policy->used_by->create_enumerator(policy->used_by); while (enumerator->enumerate(enumerator, (void**)&mapping)) { if (reqid == mapping->sa->cfg.reqid && priority == mapping->priority) { - policy->used_by->remove_at(policy->used_by, enumerator); + to_remove = mapping; + is_installed = first; + } + else if (priority < mapping->priority) + { break; } - is_installed = FALSE; + first = FALSE; } enumerator->destroy(enumerator); + policy->used_by->remove(policy->used_by, to_remove, NULL); + mapping = to_remove; if (policy->used_by->get_count(policy->used_by) > 0) { /* policy is used by more SAs, keep in kernel */ diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index c05ca56ec..a5453d0bb 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -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; /** @@ -133,6 +136,9 @@ struct addr_map_entry_t { /** The IP address */ host_t *ip; + /** The address entry for this IP address */ + addr_entry_t *addr; + /** The interface this address is installed on */ iface_entry_t *iface; }; @@ -163,8 +169,17 @@ static bool addr_map_entry_equals(addr_map_entry_t *a, addr_map_entry_t *b) 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); + return !b->addr->virtual && 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 as virtual + * IP address + */ +static bool addr_map_entry_match_virtual(addr_map_entry_t *a, addr_map_entry_t *b) +{ + return b->addr->virtual && a->ip->ip_equals(a->ip, b->ip); } /** @@ -173,7 +188,112 @@ static bool addr_map_entry_match_up_and_usable(addr_map_entry_t *a, */ 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); + return !b->addr->virtual && 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 +338,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 */ @@ -247,8 +392,108 @@ struct private_kernel_pfroute_net_t * time of last roam event */ timeval_t last_roam; + + /** + * Time in ms to wait for IP addresses to appear/disappear + */ + 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 */ @@ -257,13 +502,9 @@ static void addr_map_entry_add(private_kernel_pfroute_net_t *this, { addr_map_entry_t *entry; - if (addr->virtual) - { /* don't map virtual IPs */ - return; - } - INIT(entry, .ip = addr->ip, + .addr = addr, .iface = iface, ); entry = this->addrs->put(this->addrs, entry, entry); @@ -279,14 +520,10 @@ static void addr_map_entry_remove(addr_entry_t *addr, iface_entry_t *iface, { addr_map_entry_t *entry, lookup = { .ip = addr->ip, + .addr = addr, .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); } @@ -364,11 +601,11 @@ METHOD(enumerator_t, rt_enumerate, bool, } /** - * Create a safe enumerator over sockaddrs in ifa/ifam/rt_msg + * Create an enumerator over sockaddrs in rt/if messages */ -static enumerator_t *create_rtmsg_enumerator(void *hdr, size_t hdrlen) +static enumerator_t *create_rt_enumerator(int types, int remaining, + struct sockaddr *addr) { - struct rt_msghdr *rthdr = hdr; rt_enumerator_t *this; INIT(this, @@ -376,13 +613,31 @@ static enumerator_t *create_rtmsg_enumerator(void *hdr, size_t hdrlen) .enumerate = (void*)_rt_enumerate, .destroy = (void*)free, }, - .types = rthdr->rtm_addrs, - .remaining = rthdr->rtm_msglen - hdrlen, - .addr = hdr + hdrlen, + .types = types, + .remaining = remaining, + .addr = addr, ); return &this->public; } +/** + * Create a safe enumerator over sockaddrs in rt_msghdr + */ +static enumerator_t *create_rtmsg_enumerator(struct rt_msghdr *hdr) +{ + return create_rt_enumerator(hdr->rtm_addrs, hdr->rtm_msglen - sizeof(*hdr), + (struct sockaddr *)(hdr + 1)); +} + +/** + * Create a safe enumerator over sockaddrs in ifa_msghdr + */ +static enumerator_t *create_ifamsg_enumerator(struct ifa_msghdr *hdr) +{ + return create_rt_enumerator(hdr->ifam_addrs, hdr->ifam_msglen - sizeof(*hdr), + (struct sockaddr *)(hdr + 1)); +} + /** * Process an RTM_*ADDR message from the kernel */ @@ -396,9 +651,10 @@ 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)); + enumerator = create_ifamsg_enumerator(ifa); while (enumerator->enumerate(enumerator, &type, &sockaddr)) { if (type == RTAX_IFA) @@ -409,8 +665,9 @@ static void process_addr(private_kernel_pfroute_net_t *this, } enumerator->destroy(enumerator); - if (!host) + if (!host || host->is_anyaddr(host)) { + DESTROY_IF(host); return; } @@ -448,6 +705,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) @@ -467,6 +725,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); @@ -521,7 +788,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); @@ -533,7 +800,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)) @@ -564,6 +831,10 @@ static void process_link(private_kernel_pfroute_net_t *this, hydra->kernel_interface, iface->ifname); repopulate_iface(this, iface); this->ifaces->insert_last(this->ifaces, iface); + if (iface->usable) + { + roam = update_routes = TRUE; + } } else { @@ -572,6 +843,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); @@ -812,6 +1088,19 @@ METHOD(kernel_net_t, get_interface_name, bool, this->lock->unlock(this->lock); return TRUE; } + /* check if it is a virtual IP */ + entry = this->addrs->get_match(this->addrs, &lookup, + (void*)addr_map_entry_match_virtual); + if (entry) + { + if (name) + { + *name = strdup(entry->iface->ifname); + DBG2(DBG_KNL, "virtual IP %H is on interface %s", ip, *name); + } + this->lock->unlock(this->lock); + return TRUE; + } /* maybe it is installed on an ignored interface */ entry = this->addrs->get_match(this->addrs, &lookup, (void*)addr_map_entry_match_up); @@ -852,7 +1141,8 @@ METHOD(kernel_net_t, add_ip, status_t, this->mutex->lock(this->mutex); while (!timeout && !get_interface_name(this, vip, NULL)) { - timeout = this->condvar->timed_wait(this->condvar, this->mutex, 1000); + timeout = this->condvar->timed_wait(this->condvar, this->mutex, + this->vip_wait); } this->mutex->unlock(this->mutex); if (timeout) @@ -880,11 +1170,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); @@ -929,7 +1224,8 @@ METHOD(kernel_net_t, del_ip, status_t, this->mutex->lock(this->mutex); while (!timeout && get_interface_name(this, vip, NULL)) { - timeout = this->condvar->timed_wait(this->condvar, this->mutex, 1000); + timeout = this->condvar->timed_wait(this->condvar, this->mutex, + this->vip_wait); } this->mutex->unlock(this->mutex); if (timeout) @@ -1008,7 +1304,7 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op, .rtm_type = op, .rtm_flags = RTF_UP | RTF_STATIC, .rtm_pid = this->pid, - .rtm_seq = ++this->seq, + .rtm_seq = ref_get(&this->seq), }, }; host_t *dst; @@ -1076,6 +1372,10 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op, if (send(this->socket, &msg, msg.hdr.rtm_msglen, 0) != msg.hdr.rtm_msglen) { + if (errno == EEXIST) + { + return ALREADY_DONE; + } DBG1(DBG_KNL, "%s PF_ROUTE route failed: %s", op == RTM_ADD ? "adding" : "deleting", strerror(errno)); return FAILED; @@ -1087,14 +1387,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; } /** @@ -1112,14 +1451,16 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, .rtm_version = RTM_VERSION, .rtm_type = RTM_GET, .rtm_pid = this->pid, - .rtm_seq = ++this->seq, + .rtm_seq = ref_get(&this->seq), }, }; host_t *host = NULL; enumerator_t *enumerator; struct sockaddr *addr; + bool failed = FALSE; int type; +retry: msg.hdr.rtm_msglen = sizeof(struct rt_msghdr); for (type = 0; type < RTAX_MAX; type++) { @@ -1161,8 +1502,7 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, { continue; } - enumerator = create_rtmsg_enumerator(this->reply, - sizeof(*this->reply)); + enumerator = create_rtmsg_enumerator(this->reply); while (enumerator->enumerate(enumerator, &type, &addr)) { if (nexthop) @@ -1196,18 +1536,47 @@ static host_t *get_route(private_kernel_pfroute_net_t *this, bool nexthop, } else { - DBG1(DBG_KNL, "PF_ROUTE lookup failed: %s", strerror(errno)); + failed = TRUE; } /* signal completion of query to a waiting thread */ this->waiting_seq = 0; this->condvar->signal(this->condvar); this->mutex->unlock(this->mutex); - if (host) + if (failed) { - DBG2(DBG_KNL, "using %H as %s to reach %H", host, - nexthop ? "nexthop" : "address", dest); + if (src) + { /* the given source address might be gone, try again without */ + src = NULL; + msg.hdr.rtm_seq = ref_get(&this->seq); + msg.hdr.rtm_addrs = 0; + memset(msg.buf, sizeof(msg.buf), 0); + goto retry; + } + DBG1(DBG_KNL, "PF_ROUTE lookup failed: %s", strerror(errno)); } + if (!host) + { + return NULL; + } + if (!nexthop) + { /* make sure the source address is not virtual and usable */ + addr_entry_t *entry, lookup = { + .ip = host, + }; + + this->lock->read_lock(this->lock); + entry = this->addrs->get_match(this->addrs, &lookup, + (void*)addr_map_entry_match_up_and_usable); + this->lock->unlock(this->lock); + if (!entry) + { + host->destroy(host); + return NULL; + } + } + DBG2(DBG_KNL, "using %H as %s to reach %H", host, + nexthop ? "nexthop" : "address", dest); return host; } @@ -1314,12 +1683,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)) { @@ -1363,11 +1749,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);