From 09f4bccfea9b9b41275f05bcd2468bdb75530d7e Mon Sep 17 00:00:00 2001 From: Noel Kuntze Date: Sun, 9 Feb 2020 14:52:32 +0100 Subject: [PATCH 1/4] kernel-netlink: Implement passthrough type routes and use them on Linux Enables us to ignore any future kernel features for routes unless we actually need to consider them for the source IP routes. Also enables us to actually really skip IPsec processing for those networks (because even the routes don't touch those packets). It's more what users expect. Co-authored-by: Tobias Brunner --- .../jni/libandroidbridge/kernel/android_net.c | 4 +- src/libcharon/kernel/kernel_interface.c | 10 +- src/libcharon/kernel/kernel_interface.h | 6 +- src/libcharon/kernel/kernel_net.h | 6 +- .../plugins/kernel_iph/kernel_iph_net.c | 4 +- .../kernel_libipsec/kernel_libipsec_ipsec.c | 16 +-- .../kernel_netlink/kernel_netlink_ipsec.c | 14 ++- .../kernel_netlink/kernel_netlink_net.c | 119 +++++++++++------- .../plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 14 +-- .../kernel_pfroute/kernel_pfroute_net.c | 4 +- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 7 +- 11 files changed, 124 insertions(+), 80 deletions(-) diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c index 3a312048a..cc6072244 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/kernel/android_net.c @@ -256,14 +256,14 @@ METHOD(kernel_net_t, del_ip, status_t, METHOD(kernel_net_t, add_route, status_t, private_android_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { return NOT_SUPPORTED; } METHOD(kernel_net_t, del_route, status_t, private_android_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { return NOT_SUPPORTED; } diff --git a/src/libcharon/kernel/kernel_interface.c b/src/libcharon/kernel/kernel_interface.c index ced026dfc..300796f8e 100644 --- a/src/libcharon/kernel/kernel_interface.c +++ b/src/libcharon/kernel/kernel_interface.c @@ -614,26 +614,28 @@ METHOD(kernel_interface_t, del_ip, status_t, METHOD(kernel_interface_t, add_route, status_t, private_kernel_interface_t *this, chunk_t dst_net, - uint8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name) + uint8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name, + bool pass) { if (!this->net) { return NOT_SUPPORTED; } return this->net->add_route(this->net, dst_net, prefixlen, gateway, - src_ip, if_name); + src_ip, if_name, pass); } METHOD(kernel_interface_t, del_route, status_t, private_kernel_interface_t *this, chunk_t dst_net, - uint8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name) + uint8_t prefixlen, host_t *gateway, host_t *src_ip, char *if_name, + bool pass) { if (!this->net) { return NOT_SUPPORTED; } return this->net->del_route(this->net, dst_net, prefixlen, gateway, - src_ip, if_name); + src_ip, if_name, pass); } METHOD(kernel_interface_t, bypass_socket, bool, diff --git a/src/libcharon/kernel/kernel_interface.h b/src/libcharon/kernel/kernel_interface.h index 141198ac3..50c1cac53 100644 --- a/src/libcharon/kernel/kernel_interface.h +++ b/src/libcharon/kernel/kernel_interface.h @@ -375,12 +375,13 @@ struct kernel_interface_t { * @param gateway gateway for this route * @param src_ip source ip of the route * @param if_name name of the interface the route is bound to + * @param pass TRUE if route is installed for passthrough policy * @return SUCCESS if operation completed * ALREADY_DONE if the route already exists */ status_t (*add_route) (kernel_interface_t *this, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, host_t *src_ip, - char *if_name); + char *if_name, bool pass); /** * Delete a route. @@ -390,11 +391,12 @@ struct kernel_interface_t { * @param gateway gateway for this route * @param src_ip source ip of the route * @param if_name name of the interface the route is bound to + * @param pass TRUE if route was installed for passthrough policy * @return SUCCESS if operation completed */ status_t (*del_route) (kernel_interface_t *this, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, host_t *src_ip, - char *if_name); + char *if_name, bool pass); /** * Set up a bypass policy for a given socket. diff --git a/src/libcharon/kernel/kernel_net.h b/src/libcharon/kernel/kernel_net.h index 12475b123..11e197013 100644 --- a/src/libcharon/kernel/kernel_net.h +++ b/src/libcharon/kernel/kernel_net.h @@ -165,12 +165,13 @@ struct kernel_net_t { * @param gateway gateway for this route * @param src_ip source ip of the route * @param if_name name of the interface the route is bound to + * @param pass TRUE if route is installed for passthrough policy * @return SUCCESS if operation completed * ALREADY_DONE if the route already exists */ status_t (*add_route) (kernel_net_t *this, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, host_t *src_ip, - char *if_name); + char *if_name, bool pass); /** * Delete a route. @@ -180,11 +181,12 @@ struct kernel_net_t { * @param gateway gateway for this route * @param src_ip source ip of the route * @param if_name name of the interface the route is bound to + * @param pass TRUE if route was installed for passthrough policy * @return SUCCESS if operation completed */ status_t (*del_route) (kernel_net_t *this, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, host_t *src_ip, - char *if_name); + char *if_name, bool pass); /** * Destroy the implementation. diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 18a87b707..6b5d8249a 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -715,14 +715,14 @@ static status_t manage_route(private_kernel_iph_net_t *this, bool add, METHOD(kernel_net_t, add_route, status_t, private_kernel_iph_net_t *this, chunk_t dst, uint8_t prefixlen, - host_t *gateway, host_t *src, char *name) + host_t *gateway, host_t *src, char *name, bool pass) { return manage_route(this, TRUE, dst, prefixlen, gateway, name); } METHOD(kernel_net_t, del_route, status_t, private_kernel_iph_net_t *this, chunk_t dst, uint8_t prefixlen, - host_t *gateway, host_t *src, char *name) + host_t *gateway, host_t *src, char *name, bool pass) { return manage_route(this, FALSE, dst, prefixlen, gateway, name); } diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index 3665d4557..4c2933a03 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -315,7 +315,7 @@ static void add_exclude_route(private_kernel_libipsec_ipsec_t *this, if (charon->kernel->get_interface(charon->kernel, src, &if_name) && charon->kernel->add_route(charon->kernel, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, - gtw, src, if_name) == SUCCESS) + gtw, src, if_name, TRUE) == SUCCESS) { INIT(exclude, .dst = dst->clone(dst), @@ -363,7 +363,7 @@ static void remove_exclude_route(private_kernel_libipsec_ipsec_t *this, charon->kernel->del_route(charon->kernel, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, route->exclude->gtw, route->exclude->src, - if_name) != SUCCESS) + if_name, TRUE) != SUCCESS) { DBG1(DBG_KNL, "uninstalling exclude route for %H failed", dst); } @@ -449,8 +449,8 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, } /* uninstall previously installed route */ if (charon->kernel->del_route(charon->kernel, old->dst_net, - old->prefixlen, old->gateway, - old->src_ip, old->if_name) != SUCCESS) + old->prefixlen, old->gateway, old->src_ip, + old->if_name, FALSE) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " "%R === %R %N", src_ts, dst_ts, policy_dir_names, @@ -481,7 +481,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this, switch (charon->kernel->add_route(charon->kernel, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name)) + route->src_ip, route->if_name, FALSE)) { case ALREADY_DONE: /* route exists, do not uninstall */ @@ -586,8 +586,8 @@ METHOD(kernel_ipsec_t, del_policy, status_t, route_entry_t *route = policy->route; if (charon->kernel->del_route(charon->kernel, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name) != SUCCESS) + route->prefixlen, route->gateway, route->src_ip, + route->if_name, FALSE) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with " "policy %R === %R %N", id->src_ts, id->dst_ts, @@ -618,7 +618,7 @@ METHOD(kernel_ipsec_t, flush_policies, status_t, charon->kernel->del_route(charon->kernel, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name); + route->src_ip, route->if_name, FALSE); remove_exclude_route(this, route); } policy_entry_destroy(pol); diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 327854ff4..9a91549cf 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -390,6 +390,9 @@ struct route_entry_t { /** Destination net prefixlen */ uint8_t prefixlen; + + /** Whether the route was installed for a passthrough policy */ + bool pass; }; /** @@ -410,6 +413,7 @@ static void route_entry_destroy(route_entry_t *this) 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) && + a->pass == b->pass && a->src_ip->ip_equals(a->src_ip, b->src_ip) && chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen) { @@ -2614,6 +2618,7 @@ static void install_route(private_kernel_netlink_ipsec_t *this, INIT(route, .prefixlen = policy->sel.prefixlen_d, + .pass = mapping->type == POLICY_PASS, ); if (charon->kernel->get_address_by_ts(charon->kernel, out->src_ts, @@ -2664,7 +2669,8 @@ static void install_route(private_kernel_netlink_ipsec_t *this, /* uninstall previously installed route */ if (charon->kernel->del_route(charon->kernel, old->dst_net, old->prefixlen, old->gateway, - old->src_ip, old->if_name) != SUCCESS) + old->src_ip, old->if_name, + old->pass) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names, @@ -2678,7 +2684,8 @@ static void install_route(private_kernel_netlink_ipsec_t *this, route->gateway, route->src_ip, route->if_name); switch (charon->kernel->add_route(charon->kernel, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name)) + route->src_ip, route->if_name, + route->pass)) { default: DBG1(DBG_KNL, "unable to install source route for %H", @@ -3207,7 +3214,8 @@ METHOD(kernel_ipsec_t, del_policy, status_t, route_entry_t *route = current->route; if (charon->kernel->del_route(charon->kernel, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name) != SUCCESS) + route->src_ip, route->if_name, + route->pass) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " "%R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names, diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index f95ee3ce5..24d93cc2f 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -286,6 +286,9 @@ struct route_entry_t { /** Destination net prefixlen */ uint8_t prefixlen; + + /** Whether the route was installed for a passthrough policy */ + bool pass; }; /** @@ -301,6 +304,7 @@ static route_entry_t *route_entry_clone(route_entry_t *this) .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL, .dst_net = chunk_clone(this->dst_net), .prefixlen = this->prefixlen, + .pass = this->pass, ); return route; } @@ -332,6 +336,7 @@ static u_int route_entry_hash(route_entry_t *this) 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) && + a->pass == b->pass && a->src_ip->ip_equals(a->src_ip, b->src_ip) && chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen) { @@ -544,7 +549,7 @@ struct private_kernel_netlink_net_t { static status_t manage_srcroute(private_kernel_netlink_net_t *this, int nlmsg_type, int flags, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, - host_t *src_ip, char *if_name); + host_t *src_ip, char *if_name, bool pass); /** * Clear the queued network changes. @@ -580,6 +585,10 @@ static job_requeue_t reinstall_routes(private_kernel_netlink_net_t *this) net_change_t *change, lookup = { .if_name = route->if_name, }; + if (route->pass) + { /* no need to reinstall these, they don't reference interfaces */ + continue; + } /* check if a change for the outgoing interface is queued */ change = this->net_changes->get(this->net_changes, &lookup); if (!change) @@ -598,7 +607,7 @@ static job_requeue_t reinstall_routes(private_kernel_netlink_net_t *this) { manage_srcroute(this, RTM_NEWROUTE, NLM_F_CREATE | NLM_F_EXCL, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name); + route->src_ip, route->if_name, route->pass); } } enumerator->destroy(enumerator); @@ -2632,7 +2641,7 @@ METHOD(kernel_net_t, del_ip, status_t, static status_t manage_srcroute(private_kernel_netlink_net_t *this, int nlmsg_type, int flags, chunk_t dst_net, uint8_t prefixlen, host_t *gateway, - host_t *src_ip, char *if_name) + host_t *src_ip, char *if_name, bool pass) { netlink_buf_t request; struct nlmsghdr *hdr; @@ -2653,12 +2662,12 @@ static status_t manage_srcroute(private_kernel_netlink_net_t *this, half_net = chunk_alloca(dst_net.len); memset(half_net.ptr, 0, half_net.len); half_prefixlen = 1; - + /* no throw routes in the main table */ status = manage_srcroute(this, nlmsg_type, flags, half_net, - half_prefixlen, gateway, src_ip, if_name); + half_prefixlen, gateway, src_ip, if_name, FALSE); half_net.ptr[0] |= 0x80; status |= manage_srcroute(this, nlmsg_type, flags, half_net, - half_prefixlen, gateway, src_ip, if_name); + half_prefixlen, gateway, src_ip, if_name, FALSE); return status; } @@ -2670,10 +2679,10 @@ static status_t manage_srcroute(private_kernel_netlink_net_t *this, hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg)); msg = NLMSG_DATA(hdr); - msg->rtm_family = src_ip->get_family(src_ip); + msg->rtm_family = (dst_net.len == 4) ? AF_INET : AF_INET6; msg->rtm_dst_len = prefixlen; msg->rtm_protocol = RTPROT_STATIC; - msg->rtm_type = RTN_UNICAST; + msg->rtm_type = pass ? RTN_THROW : RTN_UNICAST; msg->rtm_scope = RT_SCOPE_UNIVERSE; if (this->routing_table < 256) @@ -2691,42 +2700,48 @@ static status_t manage_srcroute(private_kernel_netlink_net_t *this, #endif /* HAVE_RTA_TABLE */ } netlink_add_attribute(hdr, RTA_DST, dst_net, sizeof(request)); - chunk = src_ip->get_address(src_ip); - netlink_add_attribute(hdr, RTA_PREFSRC, chunk, sizeof(request)); - if (gateway && gateway->get_family(gateway) == src_ip->get_family(src_ip)) - { - chunk = gateway->get_address(gateway); - netlink_add_attribute(hdr, RTA_GATEWAY, chunk, sizeof(request)); - } - ifindex = get_interface_index(this, if_name); - chunk.ptr = (char*)&ifindex; - chunk.len = sizeof(ifindex); - netlink_add_attribute(hdr, RTA_OIF, chunk, sizeof(request)); - if (this->mtu || this->mss) + /* only when installing regular routes do we need all the parameters, + * deletes are done by destination net (except if metrics are used, which + * we don't support), for throw routes we don't need any of them either */ + if (nlmsg_type == RTM_NEWROUTE && !pass) { - chunk = chunk_alloca(RTA_LENGTH((sizeof(struct rtattr) + - sizeof(uint32_t)) * 2)); - chunk.len = 0; - rta = (struct rtattr*)chunk.ptr; - if (this->mtu) + chunk = src_ip->get_address(src_ip); + netlink_add_attribute(hdr, RTA_PREFSRC, chunk, sizeof(request)); + if (gateway && gateway->get_family(gateway) == src_ip->get_family(src_ip)) { - rta->rta_type = RTAX_MTU; - rta->rta_len = RTA_LENGTH(sizeof(uint32_t)); - memcpy(RTA_DATA(rta), &this->mtu, sizeof(uint32_t)); - chunk.len = rta->rta_len; + chunk = gateway->get_address(gateway); + netlink_add_attribute(hdr, RTA_GATEWAY, chunk, sizeof(request)); } - if (this->mss) - { - rta = (struct rtattr*)(chunk.ptr + RTA_ALIGN(chunk.len)); - rta->rta_type = RTAX_ADVMSS; - rta->rta_len = RTA_LENGTH(sizeof(uint32_t)); - memcpy(RTA_DATA(rta), &this->mss, sizeof(uint32_t)); - chunk.len = RTA_ALIGN(chunk.len) + rta->rta_len; - } - netlink_add_attribute(hdr, RTA_METRICS, chunk, sizeof(request)); - } + ifindex = get_interface_index(this, if_name); + chunk.ptr = (char*)&ifindex; + chunk.len = sizeof(ifindex); + netlink_add_attribute(hdr, RTA_OIF, chunk, sizeof(request)); + if (this->mtu || this->mss) + { + chunk = chunk_alloca(RTA_LENGTH((sizeof(struct rtattr) + + sizeof(uint32_t)) * 2)); + chunk.len = 0; + rta = (struct rtattr*)chunk.ptr; + if (this->mtu) + { + rta->rta_type = RTAX_MTU; + rta->rta_len = RTA_LENGTH(sizeof(uint32_t)); + memcpy(RTA_DATA(rta), &this->mtu, sizeof(uint32_t)); + chunk.len = rta->rta_len; + } + if (this->mss) + { + rta = (struct rtattr*)(chunk.ptr + RTA_ALIGN(chunk.len)); + rta->rta_type = RTAX_ADVMSS; + rta->rta_len = RTA_LENGTH(sizeof(uint32_t)); + memcpy(RTA_DATA(rta), &this->mss, sizeof(uint32_t)); + chunk.len = RTA_ALIGN(chunk.len) + rta->rta_len; + } + netlink_add_attribute(hdr, RTA_METRICS, chunk, sizeof(request)); + } + } return this->socket->send_ack(this->socket, hdr); } @@ -2769,7 +2784,7 @@ static bool route_with_dst(route_entry_lookup_t *a, route_entry_t *b) METHOD(kernel_net_t, add_route, status_t, private_kernel_netlink_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { status_t status; route_entry_t *found; @@ -2780,10 +2795,16 @@ METHOD(kernel_net_t, add_route, status_t, .gateway = gateway, .src_ip = src_ip, .if_name = if_name, + .pass = pass, }, .this = this, }; + if (!this->routing_table) + { /* treat these as regular routes if installing in the main table */ + pass = lookup.route.pass = FALSE; + } + this->routes_lock->lock(this->routes_lock); found = this->routes->get(this->routes, &lookup.route); if (found) @@ -2808,7 +2829,8 @@ METHOD(kernel_net_t, add_route, status_t, else { status = manage_srcroute(this, RTM_NEWROUTE, NLM_F_CREATE|NLM_F_REPLACE, - dst_net, prefixlen, gateway, src_ip, if_name); + dst_net, prefixlen, gateway, src_ip, if_name, + pass); } if (status == SUCCESS) { @@ -2821,7 +2843,7 @@ METHOD(kernel_net_t, add_route, status_t, METHOD(kernel_net_t, del_route, status_t, private_kernel_netlink_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { status_t status; route_entry_t *found; @@ -2832,10 +2854,16 @@ METHOD(kernel_net_t, del_route, status_t, .gateway = gateway, .src_ip = src_ip, .if_name = if_name, + .pass = pass, }, .this = this, }; + if (!this->routing_table) + { /* treat these as regular routes if installing in the main table */ + pass = lookup.route.pass = FALSE; + } + this->routes_lock->lock(this->routes_lock); found = this->routes->remove(this->routes, &lookup.route); if (!found) @@ -2860,12 +2888,12 @@ METHOD(kernel_net_t, del_route, status_t, { status = manage_srcroute(this, RTM_NEWROUTE, NLM_F_CREATE|NLM_F_REPLACE, found->dst_net, found->prefixlen, found->gateway, - found->src_ip, found->if_name); + found->src_ip, found->if_name, found->pass); } else { status = manage_srcroute(this, RTM_DELROUTE, 0, dst_net, prefixlen, - gateway, src_ip, if_name); + gateway, src_ip, if_name, pass); } this->routes_lock->unlock(this->routes_lock); return status; @@ -3111,7 +3139,8 @@ METHOD(kernel_net_t, destroy, void, while (enumerator->enumerate(enumerator, NULL, (void**)&route)) { manage_srcroute(this, RTM_DELROUTE, 0, route->dst_net, route->prefixlen, - route->gateway, route->src_ip, route->if_name); + route->gateway, route->src_ip, route->if_name, + route->pass); route_entry_destroy(route); } enumerator->destroy(enumerator); diff --git a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index a97daf4a9..3bd24d43e 100644 --- a/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libcharon/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -2332,7 +2332,7 @@ static void add_exclude_route(private_kernel_pfkey_ipsec_t *this, charon->kernel->add_route(charon->kernel, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, - gtw, src, if_name) == SUCCESS) + gtw, src, if_name, FALSE) == SUCCESS) { INIT(exclude, .dst = dst->clone(dst), @@ -2399,7 +2399,7 @@ static void remove_exclude_route(private_kernel_pfkey_ipsec_t *this, dst->get_address(dst), dst->get_family(dst) == AF_INET ? 32 : 128, route->exclude->gtw, route->exclude->src, - if_name) != SUCCESS) + if_name, FALSE) != SUCCESS) { DBG1(DBG_KNL, "uninstalling exclude route for %H failed", dst); } @@ -2479,8 +2479,8 @@ static bool install_route(private_kernel_pfkey_ipsec_t *this, } /* uninstall previously installed route */ if (charon->kernel->del_route(charon->kernel, old->dst_net, - old->prefixlen, old->gateway, - old->src_ip, old->if_name) != SUCCESS) + old->prefixlen, old->gateway, + old->src_ip, old->if_name, FALSE) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with policy " "%R === %R %N", out->src_ts, out->dst_ts, @@ -2512,7 +2512,7 @@ static bool install_route(private_kernel_pfkey_ipsec_t *this, switch (charon->kernel->add_route(charon->kernel, route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name)) + route->src_ip, route->if_name, FALSE)) { case ALREADY_DONE: /* route exists, do not uninstall */ @@ -3067,8 +3067,8 @@ METHOD(kernel_ipsec_t, del_policy, status_t, { route_entry_t *route = policy->route; if (charon->kernel->del_route(charon->kernel, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name) != SUCCESS) + route->prefixlen, route->gateway, + route->src_ip, route->if_name, FALSE) != SUCCESS) { DBG1(DBG_KNL, "error uninstalling route installed with " "policy %R === %R %N", id->src_ts, id->dst_ts, diff --git a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c index 0bbdb1bc3..d28a6aa1f 100644 --- a/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libcharon/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -1494,7 +1494,7 @@ static status_t manage_route(private_kernel_pfroute_net_t *this, int op, METHOD(kernel_net_t, add_route, status_t, private_kernel_pfroute_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { status_t status; route_entry_t *found, route = { @@ -1523,7 +1523,7 @@ METHOD(kernel_net_t, add_route, status_t, METHOD(kernel_net_t, del_route, status_t, private_kernel_pfroute_net_t *this, chunk_t dst_net, uint8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + host_t *gateway, host_t *src_ip, char *if_name, bool pass) { status_t status; route_entry_t *found, route = { diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index db684b060..19d4f3ef4 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1402,7 +1402,8 @@ static bool uninstall_route(private_kernel_wfp_ipsec_t *this, if (charon->kernel->get_interface(charon->kernel, src, &name)) { res = charon->kernel->del_route(charon->kernel, - dst->get_address(dst), mask, gtw, src, name) == SUCCESS; + dst->get_address(dst), mask, gtw, src, + name, FALSE) == SUCCESS; free(name); } route = this->routes->remove(this->routes, route); @@ -1446,8 +1447,8 @@ static bool install_route(private_kernel_wfp_ipsec_t *this, { if (charon->kernel->get_interface(charon->kernel, src, &name)) { - if (charon->kernel->add_route(charon->kernel, - dst->get_address(dst), mask, gtw, src, name) == SUCCESS) + if (charon->kernel->add_route(charon->kernel, dst->get_address(dst), + mask, gtw, src, name, FALSE) == SUCCESS) { INIT(route, .dst = dst->clone(dst), From b0b6bd24701edf2998015b79e561641dab2b00b3 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 25 Feb 2020 16:39:35 +0100 Subject: [PATCH 2/4] kernel-netlink: Allow blank source address in routes for passthrough policies --- .../kernel_netlink/kernel_netlink_ipsec.c | 154 +++++++++--------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 9a91549cf..da22c0bbb 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -2622,88 +2622,92 @@ static void install_route(private_kernel_netlink_ipsec_t *this, ); if (charon->kernel->get_address_by_ts(charon->kernel, out->src_ts, - &route->src_ip, NULL) == SUCCESS) + &route->src_ip, NULL) != SUCCESS) { - if (!ipsec->dst->is_anyaddr(ipsec->dst)) + if (!route->pass) { - route->gateway = charon->kernel->get_nexthop(charon->kernel, - ipsec->dst, -1, ipsec->src, - &route->if_name); + free(route); + return; } - else - { /* for shunt policies */ - iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0); - route->gateway = charon->kernel->get_nexthop(charon->kernel, - iface, policy->sel.prefixlen_d, - route->src_ip, &route->if_name); - iface->destroy(iface); - } - route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16); - memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len); + /* allow blank source IP for passthrough policies */ + route->src_ip = host_create_any(policy->sel.family); + } - /* get the interface to install the route for, if we haven't one yet. - * If we have a local address, use it. Otherwise (for shunt policies) - * use the route's source address. */ - if (!route->if_name) - { - iface = ipsec->src; - if (iface->is_anyaddr(iface)) - { - iface = route->src_ip; - } - if (!charon->kernel->get_interface(charon->kernel, iface, - &route->if_name)) - { - route_entry_destroy(route); - return; - } - } - if (policy->route) - { - route_entry_t *old = policy->route; - if (route_entry_equals(old, route)) - { - route_entry_destroy(route); - return; - } - /* uninstall previously installed route */ - if (charon->kernel->del_route(charon->kernel, old->dst_net, - old->prefixlen, old->gateway, - old->src_ip, old->if_name, - old->pass) != SUCCESS) - { - DBG1(DBG_KNL, "error uninstalling route installed with policy " - "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names, - policy->direction); - } - route_entry_destroy(old); - policy->route = NULL; - } - - DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts, - route->gateway, route->src_ip, route->if_name); - switch (charon->kernel->add_route(charon->kernel, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name, - route->pass)) - { - default: - DBG1(DBG_KNL, "unable to install source route for %H", - route->src_ip); - /* FALL */ - case ALREADY_DONE: - /* route exists, do not uninstall */ - route_entry_destroy(route); - break; - case SUCCESS: - /* cache the installed route */ - policy->route = route; - break; - } + if (!ipsec->dst->is_anyaddr(ipsec->dst)) + { + route->gateway = charon->kernel->get_nexthop(charon->kernel, + ipsec->dst, -1, ipsec->src, + &route->if_name); } else + { /* for shunt policies */ + iface = xfrm2host(policy->sel.family, &policy->sel.daddr, 0); + route->gateway = charon->kernel->get_nexthop(charon->kernel, + iface, policy->sel.prefixlen_d, + route->src_ip, &route->if_name); + iface->destroy(iface); + } + route->dst_net = chunk_alloc(policy->sel.family == AF_INET ? 4 : 16); + memcpy(route->dst_net.ptr, &policy->sel.daddr, route->dst_net.len); + + /* get the interface to install the route for, if we haven't one yet. + * If we have a local address, use it. Otherwise (for shunt policies) + * use the route's source address. */ + if (!route->if_name) { - free(route); + iface = ipsec->src; + if (iface->is_anyaddr(iface)) + { + iface = route->src_ip; + } + if (!charon->kernel->get_interface(charon->kernel, iface, + &route->if_name)) + { + route_entry_destroy(route); + return; + } + } + if (policy->route) + { + route_entry_t *old = policy->route; + if (route_entry_equals(old, route)) + { + route_entry_destroy(route); + return; + } + /* uninstall previously installed route */ + if (charon->kernel->del_route(charon->kernel, old->dst_net, + old->prefixlen, old->gateway, + old->src_ip, old->if_name, + old->pass) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with policy " + "%R === %R %N", out->src_ts, out->dst_ts, policy_dir_names, + policy->direction); + } + route_entry_destroy(old); + policy->route = NULL; + } + + DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", out->dst_ts, + route->gateway, route->src_ip, route->if_name); + switch (charon->kernel->add_route(charon->kernel, route->dst_net, + route->prefixlen, route->gateway, + route->src_ip, route->if_name, + route->pass)) + { + default: + DBG1(DBG_KNL, "unable to install source route for %H", + route->src_ip); + /* FALL */ + case ALREADY_DONE: + /* route exists, do not uninstall */ + route_entry_destroy(route); + break; + case SUCCESS: + /* cache the installed route */ + policy->route = route; + break; } } From e23708bdf321bd33b6e12aaf09e5d022d540e57c Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 26 Feb 2020 16:53:06 +0100 Subject: [PATCH 3/4] kernel-netlink: Don't require an interface name for passthrough policies --- src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c | 5 +++-- src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index da22c0bbb..9d0c925c0 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -2661,8 +2661,9 @@ static void install_route(private_kernel_netlink_ipsec_t *this, iface = route->src_ip; } if (!charon->kernel->get_interface(charon->kernel, iface, - &route->if_name)) - { + &route->if_name) && + !route->pass) + { /* don't require an interface for passthrough policies */ route_entry_destroy(route); return; } diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index 24d93cc2f..e8e1f9ce8 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -585,7 +585,7 @@ static job_requeue_t reinstall_routes(private_kernel_netlink_net_t *this) net_change_t *change, lookup = { .if_name = route->if_name, }; - if (route->pass) + if (route->pass || !route->if_name) { /* no need to reinstall these, they don't reference interfaces */ continue; } From dfd261d2de8c3020a1e4ced24b886c076be88562 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 9 Mar 2020 16:41:28 +0100 Subject: [PATCH 4/4] kernel-netlink: Extract shared route handling code in net/ipsec --- .../kernel_netlink/kernel_netlink_ipsec.c | 53 ------------ .../kernel_netlink/kernel_netlink_net.c | 80 ------------------- .../kernel_netlink/kernel_netlink_shared.c | 62 +++++++++++++- .../kernel_netlink/kernel_netlink_shared.h | 48 ++++++++++- 4 files changed, 108 insertions(+), 135 deletions(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 9d0c925c0..ef0d424bd 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -370,59 +370,6 @@ struct private_kernel_netlink_ipsec_t { kernel_ipsec_manage_policy_t *data); }; -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; - - /** Source ip of the route */ - host_t *src_ip; - - /** Gateway for this route */ - host_t *gateway; - - /** Destination net */ - chunk_t dst_net; - - /** Destination net prefixlen */ - uint8_t prefixlen; - - /** Whether the route was installed for a passthrough policy */ - bool pass; -}; - -/** - * Destroy a route_entry_t object - */ -static void route_entry_destroy(route_entry_t *this) -{ - free(this->if_name); - this->src_ip->destroy(this->src_ip); - DESTROY_IF(this->gateway); - chunk_free(&this->dst_net); - free(this); -} - -/** - * 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) && - a->pass == b->pass && - a->src_ip->ip_equals(a->src_ip, b->src_ip) && - 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 ipsec_sa_t ipsec_sa_t; /** diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c index e8e1f9ce8..c667ff425 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_net.c @@ -266,86 +266,6 @@ static bool addr_map_entry_match(addr_map_entry_t *a, addr_map_entry_t *b) return 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; - - /** Source ip of the route */ - host_t *src_ip; - - /** Gateway for this route */ - host_t *gateway; - - /** Destination net */ - chunk_t dst_net; - - /** Destination net prefixlen */ - uint8_t prefixlen; - - /** Whether the route was installed for a passthrough policy */ - bool pass; -}; - -/** - * 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), - .src_ip = this->src_ip->clone(this->src_ip), - .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL, - .dst_net = chunk_clone(this->dst_net), - .prefixlen = this->prefixlen, - .pass = this->pass, - ); - return route; -} - -/** - * Destroy a route_entry_t object - */ -static void route_entry_destroy(route_entry_t *this) -{ - free(this->if_name); - DESTROY_IF(this->src_ip); - 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) && - a->pass == b->pass && - a->src_ip->ip_equals(a->src_ip, b->src_ip) && - 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; /** diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c index f1a9f7aaa..a87f1de88 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.c @@ -2,7 +2,7 @@ * Copyright (C) 2014 Martin Willi * Copyright (C) 2014 revosec AG * - * Copyright (C) 2008-2019 Tobias Brunner + * Copyright (C) 2008-2020 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -767,3 +767,63 @@ void *netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len) } return RTA_DATA(rta); } + +/* + * Described in header + */ +void route_entry_destroy(route_entry_t *this) +{ + free(this->if_name); + DESTROY_IF(this->src_ip); + DESTROY_IF(this->gateway); + chunk_free(&this->dst_net); + free(this); +} + +/* + * Described in header + */ +route_entry_t *route_entry_clone(const route_entry_t *this) +{ + route_entry_t *route; + + INIT(route, + .if_name = strdupnull(this->if_name), + .src_ip = this->src_ip ? this->src_ip->clone(this->src_ip) : NULL, + .gateway = this->gateway ? this->gateway->clone(this->gateway) : NULL, + .dst_net = chunk_clone(this->dst_net), + .prefixlen = this->prefixlen, + .pass = this->pass, + ); + return route; +} + +/* + * Described in header + */ +u_int route_entry_hash(const route_entry_t *this) +{ + return chunk_hash_inc(chunk_from_thing(this->prefixlen), + chunk_hash(this->dst_net)); +} + +/** + * Compare two IP addresses, also accept it if both are NULL + */ +static bool addrs_null_or_equal(host_t *a, host_t *b) +{ + return (!a && !b) || (a && b && a->ip_equals(a, b)); +} + +/* + * Described in header + */ +bool route_entry_equals(const route_entry_t *a, const route_entry_t *b) +{ + return streq(a->if_name, b->if_name) && + a->pass == b->pass && + a->prefixlen == b->prefixlen && + chunk_equals(a->dst_net, b->dst_net) && + addrs_null_or_equal(a->src_ip, b->src_ip) && + addrs_null_or_equal(a->gateway, b->gateway); +} diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h index d68a013de..1e154ad2b 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_shared.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2019 Tobias Brunner + * Copyright (C) 2008-2020 Tobias Brunner * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it @@ -130,4 +130,50 @@ void* netlink_reserve(struct nlmsghdr *hdr, int buflen, int type, int len); */ u_int netlink_get_buflen(); +/** + * Information about an installed route. + */ +struct route_entry_t { + + /** Destination net */ + chunk_t dst_net; + + /** Destination net prefix length */ + uint8_t prefixlen; + + /** Name of the interface the route is bound to (optional) */ + char *if_name; + + /** Source IP of the route (virtual IP or %any) */ + host_t *src_ip; + + /** Gateway for this route (optional) */ + host_t *gateway; + + /** Whether the route was installed for a passthrough policy */ + bool pass; +}; + +typedef struct route_entry_t route_entry_t; + +/** + * Destroy a route entry. + */ +void route_entry_destroy(route_entry_t *this); + +/** + * Clone a route entry. + */ +route_entry_t *route_entry_clone(const route_entry_t *this); + +/** + * Hash a route entry (note that this only hashes the destination). + */ +u_int route_entry_hash(const route_entry_t *this); + +/** + * Compare two route entries. + */ +bool route_entry_equals(const route_entry_t *a, const route_entry_t *b); + #endif /* KERNEL_NETLINK_SHARED_H_ */