diff --git a/.gitignore b/.gitignore index ce8ed17c1..024d9e452 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ apidoc/ *.diff *.tar.bz2 *.tar.gz +.DS_Store diff --git a/src/charon-tkm/src/tkm/tkm_kernel_ipsec.c b/src/charon-tkm/src/tkm/tkm_kernel_ipsec.c index 69aefea97..a276166e2 100644 --- a/src/charon-tkm/src/tkm/tkm_kernel_ipsec.c +++ b/src/charon-tkm/src/tkm/tkm_kernel_ipsec.c @@ -216,7 +216,7 @@ sad_failure: METHOD(kernel_ipsec_t, query_sa, status_t, private_tkm_kernel_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, u_int64_t *bytes, - u_int64_t *packets) + u_int64_t *packets, u_int32_t *time) { return NOT_SUPPORTED; } diff --git a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c index 8e8534190..c37ca26ab 100644 --- a/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c +++ b/src/frontends/android/jni/libandroidbridge/kernel/android_ipsec.c @@ -86,7 +86,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, METHOD(kernel_ipsec_t, query_sa, status_t, private_kernel_android_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { return NOT_SUPPORTED; } diff --git a/src/libcharon/plugins/load_tester/load_tester_ipsec.c b/src/libcharon/plugins/load_tester/load_tester_ipsec.c index 4f84845a3..49e35c4ca 100644 --- a/src/libcharon/plugins/load_tester/load_tester_ipsec.c +++ b/src/libcharon/plugins/load_tester/load_tester_ipsec.c @@ -71,7 +71,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, METHOD(kernel_ipsec_t, query_sa, status_t, private_load_tester_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { return NOT_SUPPORTED; } diff --git a/src/libcharon/plugins/socket_default/socket_default_socket.c b/src/libcharon/plugins/socket_default/socket_default_socket.c index c0b744a68..a9c298362 100644 --- a/src/libcharon/plugins/socket_default/socket_default_socket.c +++ b/src/libcharon/plugins/socket_default/socket_default_socket.c @@ -162,23 +162,26 @@ METHOD(socket_t, receiver, status_t, FD_ZERO(&rfds); - if (this->ipv4) + if (this->ipv4 != -1) { FD_SET(this->ipv4, &rfds); + max_fd = max(max_fd, this->ipv4); } - if (this->ipv4_natt) + if (this->ipv4_natt != -1) { FD_SET(this->ipv4_natt, &rfds); + max_fd = max(max_fd, this->ipv4_natt); } - if (this->ipv6) + if (this->ipv6 != -1) { FD_SET(this->ipv6, &rfds); + max_fd = max(max_fd, this->ipv6); } - if (this->ipv6_natt) + if (this->ipv6_natt != -1) { FD_SET(this->ipv6_natt, &rfds); + max_fd = max(max_fd, this->ipv6_natt); } - max_fd = max(max(this->ipv4, this->ipv4_natt), max(this->ipv6, this->ipv6_natt)); DBG2(DBG_NET, "waiting for data on sockets"); oldstate = thread_cancelability(TRUE); @@ -326,7 +329,7 @@ METHOD(socket_t, receiver, status_t, METHOD(socket_t, sender, status_t, private_socket_default_socket_t *this, packet_t *packet) { - int sport, skt, family; + int sport, skt = -1, family; ssize_t bytes_sent; chunk_t data; host_t *src, *dst; @@ -376,9 +379,10 @@ METHOD(socket_t, sender, status_t, return FAILED; } } - else + if (skt == -1) { - DBG1(DBG_NET, "unable to locate a send socket for port %d", sport); + DBG1(DBG_NET, "no socket found to send IPv%d packet from port %d", + family == AF_INET ? 4 : 6, sport); return FAILED; } @@ -537,20 +541,20 @@ static int open_socket(private_socket_default_socket_t *this, pktinfo = IPV6_RECVPKTINFO; break; default: - return 0; + return -1; } skt = socket(family, SOCK_DGRAM, IPPROTO_UDP); if (skt < 0) { DBG1(DBG_NET, "could not open socket: %s", strerror(errno)); - return 0; + return -1; } if (setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, (void*)&on, sizeof(on)) < 0) { DBG1(DBG_NET, "unable to set SO_REUSEADDR on socket: %s", strerror(errno)); close(skt); - return 0; + return -1; } /* bind the socket */ @@ -558,7 +562,7 @@ static int open_socket(private_socket_default_socket_t *this, { DBG1(DBG_NET, "unable to bind socket: %s", strerror(errno)); close(skt); - return 0; + return -1; } /* retrieve randomly allocated port if needed */ @@ -568,7 +572,7 @@ static int open_socket(private_socket_default_socket_t *this, { DBG1(DBG_NET, "unable to determine port: %s", strerror(errno)); close(skt); - return 0; + return -1; } switch (family) { @@ -588,7 +592,7 @@ static int open_socket(private_socket_default_socket_t *this, { DBG1(DBG_NET, "unable to set IP_PKTINFO on socket: %s", strerror(errno)); close(skt); - return 0; + return -1; } } @@ -610,22 +614,43 @@ static int open_socket(private_socket_default_socket_t *this, return skt; } +/** + * Open a socket pair (normal an NAT traversal) for a given address family + */ +static void open_socketpair(private_socket_default_socket_t *this, int family, + int *skt, int *skt_natt, char *label) +{ + *skt = open_socket(this, family, &this->port); + if (*skt == -1) + { + DBG1(DBG_NET, "could not open %s socket, %s disabled", label, label); + } + else + { + *skt_natt = open_socket(this, family, &this->natt); + if (*skt_natt == -1) + { + DBG1(DBG_NET, "could not open %s NAT-T socket", label); + } + } +} + METHOD(socket_t, destroy, void, private_socket_default_socket_t *this) { - if (this->ipv4) + if (this->ipv4 != -1) { close(this->ipv4); } - if (this->ipv4_natt) + if (this->ipv4_natt != -1) { close(this->ipv4_natt); } - if (this->ipv6) + if (this->ipv6 != -1) { close(this->ipv6); } - if (this->ipv6_natt) + if (this->ipv6_natt != -1) { close(this->ipv6_natt); } @@ -667,36 +692,17 @@ socket_default_socket_t *socket_default_socket_create() } /* we allocate IPv6 sockets first as that will reserve randomly allocated - * ports also for IPv4 */ - this->ipv6 = open_socket(this, AF_INET6, &this->port); - if (this->ipv6 == 0) - { - DBG1(DBG_NET, "could not open IPv6 socket, IPv6 disabled"); - } - else - { - this->ipv6_natt = open_socket(this, AF_INET6, &this->natt); - if (this->ipv6_natt == 0) - { - DBG1(DBG_NET, "could not open IPv6 NAT-T socket"); - } - } + * ports also for IPv4. On OS X, we have to do it the other way round + * for the same effect. */ +#ifdef __APPLE__ + open_socketpair(this, AF_INET, &this->ipv4, &this->ipv4_natt, "IPv4"); + open_socketpair(this, AF_INET6, &this->ipv6, &this->ipv6_natt, "IPv6"); +#else /* !__APPLE__ */ + open_socketpair(this, AF_INET6, &this->ipv6, &this->ipv6_natt, "IPv6"); + open_socketpair(this, AF_INET, &this->ipv4, &this->ipv4_natt, "IPv4"); +#endif /* __APPLE__ */ - this->ipv4 = open_socket(this, AF_INET, &this->port); - if (this->ipv4 == 0) - { - DBG1(DBG_NET, "could not open IPv4 socket, IPv4 disabled"); - } - else - { - this->ipv4_natt = open_socket(this, AF_INET, &this->natt); - if (this->ipv4_natt == 0) - { - DBG1(DBG_NET, "could not open IPv4 NAT-T socket"); - } - } - - if (!this->ipv4 && !this->ipv6) + if (this->ipv4 == -1 && this->ipv6 == -1) { DBG1(DBG_NET, "could not create any sockets"); destroy(this); diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 463ad2e22..34435a140 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -424,6 +424,7 @@ static status_t update_usebytes(private_child_sa_t *this, bool inbound) { status_t status = FAILED; u_int64_t bytes, packets; + u_int32_t time; if (inbound) { @@ -432,13 +433,17 @@ static status_t update_usebytes(private_child_sa_t *this, bool inbound) status = hydra->kernel_interface->query_sa(hydra->kernel_interface, this->other_addr, this->my_addr, this->my_spi, proto_ike2ip(this->protocol), this->mark_in, - &bytes, &packets); + &bytes, &packets, &time); if (status == SUCCESS) { if (bytes > this->my_usebytes) { this->my_usebytes = bytes; this->my_usepackets = packets; + if (time) + { + this->my_usetime = time; + } return SUCCESS; } return FAILED; @@ -452,13 +457,17 @@ static status_t update_usebytes(private_child_sa_t *this, bool inbound) status = hydra->kernel_interface->query_sa(hydra->kernel_interface, this->my_addr, this->other_addr, this->other_spi, proto_ike2ip(this->protocol), this->mark_out, - &bytes, &packets); + &bytes, &packets, &time); if (status == SUCCESS) { if (bytes > this->other_usebytes) { this->other_usebytes = bytes; this->other_usepackets = packets; + if (time) + { + this->other_usetime = time; + } return SUCCESS; } return FAILED; @@ -471,7 +480,7 @@ static status_t update_usebytes(private_child_sa_t *this, bool inbound) /** * updates the cached usetime */ -static void update_usetime(private_child_sa_t *this, bool inbound) +static bool update_usetime(private_child_sa_t *this, bool inbound) { enumerator_t *enumerator; traffic_selector_t *my_ts, *other_ts; @@ -511,7 +520,7 @@ static void update_usetime(private_child_sa_t *this, bool inbound) if (last_use == 0) { - return; + return FALSE; } if (inbound) { @@ -521,18 +530,26 @@ static void update_usetime(private_child_sa_t *this, bool inbound) { this->other_usetime = last_use; } + return TRUE; } METHOD(child_sa_t, get_usestats, void, private_child_sa_t *this, bool inbound, time_t *time, u_int64_t *bytes, u_int64_t *packets) { - if (update_usebytes(this, inbound) != FAILED) + if ((!bytes && !packets) || update_usebytes(this, inbound) != FAILED) { /* there was traffic since last update or the kernel interface * does not support querying the number of usebytes. */ - update_usetime(this, inbound); + if (time) + { + if (!update_usetime(this, inbound) && !bytes && !packets) + { + /* if policy query did not yield a usetime, query SAs instead */ + update_usebytes(this, inbound); + } + } } if (time) { @@ -668,21 +685,18 @@ METHOD(child_sa_t, install, status_t, lifetime->time.rekey = 0; } - if (this->mode == MODE_BEET || this->mode == MODE_TRANSPORT) + /* BEET requires the bound address from the traffic selectors. + * TODO: We add just the first traffic selector for now, as the + * kernel accepts a single TS per SA only */ + if (inbound) { - /* BEET requires the bound address from the traffic selectors. - * TODO: We add just the first traffic selector for now, as the - * kernel accepts a single TS per SA only */ - if (inbound) - { - my_ts->get_first(my_ts, (void**)&dst_ts); - other_ts->get_first(other_ts, (void**)&src_ts); - } - else - { - my_ts->get_first(my_ts, (void**)&src_ts); - other_ts->get_first(other_ts, (void**)&dst_ts); - } + my_ts->get_first(my_ts, (void**)&dst_ts); + other_ts->get_first(other_ts, (void**)&src_ts); + } + else + { + my_ts->get_first(my_ts, (void**)&src_ts); + other_ts->get_first(other_ts, (void**)&dst_ts); } status = hydra->kernel_interface->add_sa(hydra->kernel_interface, diff --git a/src/libhydra/kernel/kernel_interface.c b/src/libhydra/kernel/kernel_interface.c index 53b8324b7..d81fa33c7 100644 --- a/src/libhydra/kernel/kernel_interface.c +++ b/src/libhydra/kernel/kernel_interface.c @@ -208,14 +208,14 @@ METHOD(kernel_interface_t, update_sa, status_t, METHOD(kernel_interface_t, query_sa, status_t, private_kernel_interface_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { if (!this->ipsec) { return NOT_SUPPORTED; } return this->ipsec->query_sa(this->ipsec, src, dst, spi, protocol, mark, - bytes, packets); + bytes, packets, time); } METHOD(kernel_interface_t, del_sa, status_t, @@ -415,7 +415,8 @@ METHOD(kernel_interface_t, all_interfaces_usable, bool, } METHOD(kernel_interface_t, get_address_by_ts, status_t, - private_kernel_interface_t *this, traffic_selector_t *ts, host_t **ip) + private_kernel_interface_t *this, traffic_selector_t *ts, + host_t **ip, bool *vip) { enumerator_t *addrs; host_t *host; @@ -446,18 +447,41 @@ METHOD(kernel_interface_t, get_address_by_ts, status_t, } host->destroy(host); - addrs = create_address_enumerator(this, ADDR_TYPE_ALL); + addrs = create_address_enumerator(this, ADDR_TYPE_VIRTUAL); while (addrs->enumerate(addrs, (void**)&host)) { if (ts->includes(ts, host)) { found = TRUE; *ip = host->clone(host); + if (vip) + { + *vip = TRUE; + } break; } } addrs->destroy(addrs); + if (!found) + { + addrs = create_address_enumerator(this, ADDR_TYPE_REGULAR); + while (addrs->enumerate(addrs, (void**)&host)) + { + if (ts->includes(ts, host)) + { + found = TRUE; + *ip = host->clone(host); + if (vip) + { + *vip = FALSE; + } + break; + } + } + addrs->destroy(addrs); + } + if (!found) { DBG2(DBG_KNL, "no local address found in traffic selector %R", ts); diff --git a/src/libhydra/kernel/kernel_interface.h b/src/libhydra/kernel/kernel_interface.h index 1d2253b94..b333cae99 100644 --- a/src/libhydra/kernel/kernel_interface.h +++ b/src/libhydra/kernel/kernel_interface.h @@ -65,6 +65,8 @@ typedef enum kernel_feature_t kernel_feature_t; enum kernel_feature_t { /** IPsec can process ESPv3 (RFC 4303) TFC padded packets */ KERNEL_ESP_V3_TFC = (1<<0), + /** Networking requires an "exclude" route for IKE/ESP packets */ + KERNEL_REQUIRE_EXCLUDE_ROUTE = (1<<1), }; /** @@ -195,11 +197,12 @@ struct kernel_interface_t { * @param mark optional mark for this SA * @param[out] bytes the number of bytes processed by SA * @param[out] packets number of packets processed by SA + * @param[out] time last time of SA use * @return SUCCESS if operation completed */ status_t (*query_sa) (kernel_interface_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets); + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time); /** * Delete a previously installed SA from the SAD. @@ -451,10 +454,11 @@ struct kernel_interface_t { * * @param ts traffic selector * @param ip returned IP address (has to be destroyed) + * @param vip set to TRUE if returned address is a virtual IP * @return SUCCESS if address found */ status_t (*get_address_by_ts)(kernel_interface_t *this, - traffic_selector_t *ts, host_t **ip); + traffic_selector_t *ts, host_t **ip, bool *vip); /** * Register an ipsec kernel interface constructor on the manager. diff --git a/src/libhydra/kernel/kernel_ipsec.h b/src/libhydra/kernel/kernel_ipsec.h index ba67238e5..f61f3c3aa 100644 --- a/src/libhydra/kernel/kernel_ipsec.h +++ b/src/libhydra/kernel/kernel_ipsec.h @@ -155,11 +155,12 @@ struct kernel_ipsec_t { * @param mark optional mark for this SA * @param[out] bytes the number of bytes processed by SA * @param[out] packets number of packets processed by SA + * @param[out] time last time of SA use * @return SUCCESS if operation completed */ status_t (*query_sa) (kernel_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets); + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time); /** * Delete a previusly installed SA from the SAD. diff --git a/src/libhydra/kernel/kernel_net.h b/src/libhydra/kernel/kernel_net.h index 0d3417f1d..165036231 100644 --- a/src/libhydra/kernel/kernel_net.h +++ b/src/libhydra/kernel/kernel_net.h @@ -35,17 +35,17 @@ typedef enum kernel_address_type_t kernel_address_type_t; */ enum kernel_address_type_t { /** normal addresses (on regular, up, non-ignored) interfaces */ - ADDR_TYPE_REGULAR = 0, + ADDR_TYPE_REGULAR = (1 << 0), /** addresses on down interfaces */ - ADDR_TYPE_DOWN = (1 << 0), + ADDR_TYPE_DOWN = (1 << 1), /** addresses on ignored interfaces */ - ADDR_TYPE_IGNORED = (1 << 1), + ADDR_TYPE_IGNORED = (1 << 2), /** addresses on loopback interfaces */ - ADDR_TYPE_LOOPBACK = (1 << 2), + ADDR_TYPE_LOOPBACK = (1 << 3), /** virtual IP addresses */ - ADDR_TYPE_VIRTUAL = (1 << 3), + ADDR_TYPE_VIRTUAL = (1 << 4), /** to enumerate all available addresses */ - ADDR_TYPE_ALL = (1 << 4) - 1, + ADDR_TYPE_ALL = (1 << 5) - 1, }; /** diff --git a/src/libhydra/plugins/kernel_klips/kernel_klips_ipsec.c b/src/libhydra/plugins/kernel_klips/kernel_klips_ipsec.c index a120b3d00..32bea7383 100644 --- a/src/libhydra/plugins/kernel_klips/kernel_klips_ipsec.c +++ b/src/libhydra/plugins/kernel_klips/kernel_klips_ipsec.c @@ -1911,7 +1911,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, METHOD(kernel_ipsec_t, query_sa, status_t, private_kernel_klips_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { return NOT_SUPPORTED; /* TODO */ } @@ -2118,7 +2118,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t, this->install_routes) { hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - src_ts, &route->src_ip); + src_ts, &route->src_ip, NULL); } if (!route->src_ip) diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c index 9b4ade533..58bce6247 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1595,7 +1595,7 @@ static void get_replay_state(private_kernel_netlink_ipsec_t *this, METHOD(kernel_ipsec_t, query_sa, status_t, private_kernel_netlink_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { netlink_buf_t request; struct nlmsghdr *out = NULL, *hdr; @@ -1680,6 +1680,12 @@ METHOD(kernel_ipsec_t, query_sa, status_t, { *packets = sa->curlft.packets; } + if (time) + { /* curlft contains an "use" time, but that contains a timestamp + * of the first use, not the last. Last use time must be queried + * on the policy on Linux */ + *time = 0; + } status = SUCCESS; } memwipe(out, len); @@ -2102,7 +2108,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, ); if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - fwd->dst_ts, &route->src_ip) == SUCCESS) + fwd->dst_ts, &route->src_ip, NULL) == SUCCESS) { /* get the nexthop to src (src as we are in POLICY_FWD) */ route->gateway = hydra->kernel_interface->get_nexthop( @@ -2638,13 +2644,7 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() this->replay_bmp = (this->replay_window + sizeof(u_int32_t) * 8 - 1) / (sizeof(u_int32_t) * 8); - if (streq(hydra->daemon, "pluto")) - { /* no routes for pluto, they are installed via updown script */ - this->install_routes = FALSE; - /* no policy history for pluto */ - this->policy_history = FALSE; - } - else if (streq(hydra->daemon, "starter")) + if (streq(hydra->daemon, "starter")) { /* starter has no threads, so we do not register for kernel events */ register_for_events = FALSE; } diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c index 739a1f407..d4cb4e1ac 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_net.c @@ -1165,6 +1165,10 @@ static bool filter_addresses(address_enumerator_t *data, { /* skip virtual interfaces added by us */ return FALSE; } + if (!(data->which & ADDR_TYPE_REGULAR) && !(*in)->refcount) + { /* address is regular, but not requested */ + return FALSE; + } if ((*in)->scope >= RT_SCOPE_LINK) { /* skip addresses with a unusable scope */ return FALSE; @@ -1209,9 +1213,12 @@ static bool filter_interfaces(address_enumerator_t *data, iface_entry_t** in, METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, private_kernel_netlink_net_t *this, kernel_address_type_t which) { - address_enumerator_t *data = malloc_thing(address_enumerator_t); - data->this = this; - data->which = which; + address_enumerator_t *data; + + INIT(data, + .this = this, + .which = which, + ); this->lock->read_lock(this->lock); return enumerator_create_nested( diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index 2521af87d..ecab2827a 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -179,6 +179,11 @@ struct private_kernel_pfkey_ipsec_t */ linked_list_t *policies; + /** + * List of exclude routes (exclude_route_t) + */ + linked_list_t *excludes; + /** * Hash table of IPsec SAs using policies (ipsec_sa_t) */ @@ -210,6 +215,33 @@ struct private_kernel_pfkey_ipsec_t int seq; }; +typedef struct exclude_route_t exclude_route_t; + +/** + * Exclude route definition + */ +struct exclude_route_t { + /** destination address of exclude */ + host_t *dst; + /** source address for route */ + host_t *src; + /** nexthop exclude has been installed */ + host_t *gtw; + /** references to this route */ + int refs; +}; + +/** + * clean up a route exclude entry + */ +static void exclude_route_destroy(exclude_route_t *this) +{ + this->dst->destroy(this->dst); + this->src->destroy(this->src); + this->gtw->destroy(this->gtw); + free(this); +} + typedef struct route_entry_t route_entry_t; /** @@ -230,6 +262,9 @@ struct route_entry_t { /** destination net prefixlen */ u_int8_t prefixlen; + + /** reference to exclude route, if any */ + exclude_route_t *exclude; }; /** @@ -251,6 +286,7 @@ static bool route_entry_equals(route_entry_t *a, route_entry_t *b) { return a->if_name && b->if_name && streq(a->if_name, b->if_name) && a->src_ip->ip_equals(a->src_ip, b->src_ip) && + a->gateway && b->gateway && a->gateway->ip_equals(a->gateway, b->gateway) && chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; } @@ -339,7 +375,7 @@ static void ipsec_sa_destroy(private_kernel_pfkey_ipsec_t *this, } typedef struct policy_sa_t policy_sa_t; -typedef struct policy_sa_fwd_t policy_sa_fwd_t; +typedef struct policy_sa_in_t policy_sa_in_t; /** * Mapping between a policy and an IPsec SA. @@ -356,10 +392,10 @@ struct policy_sa_t { }; /** - * For forward policies we also cache the traffic selectors in order to install + * For input policies we also cache the traffic selectors in order to install * the route. */ -struct policy_sa_fwd_t { +struct policy_sa_in_t { /** Generic interface */ policy_sa_t generic; @@ -371,7 +407,7 @@ struct policy_sa_fwd_t { }; /** - * Create a policy_sa(_fwd)_t object + * Create a policy_sa(_in)_t object */ static policy_sa_t *policy_sa_create(private_kernel_pfkey_ipsec_t *this, policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst, @@ -379,14 +415,14 @@ static policy_sa_t *policy_sa_create(private_kernel_pfkey_ipsec_t *this, { policy_sa_t *policy; - if (dir == POLICY_FWD) + if (dir == POLICY_IN) { - policy_sa_fwd_t *fwd; - INIT(fwd, + policy_sa_in_t *in; + INIT(in, .src_ts = src_ts->clone(src_ts), .dst_ts = dst_ts->clone(dst_ts), ); - policy = &fwd->generic; + policy = &in->generic; } else { @@ -398,16 +434,16 @@ static policy_sa_t *policy_sa_create(private_kernel_pfkey_ipsec_t *this, } /** - * Destroy a policy_sa(_fwd)_t object + * Destroy a policy_sa(_in)_t object */ static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir, private_kernel_pfkey_ipsec_t *this) { - if (*dir == POLICY_FWD) + if (*dir == POLICY_IN) { - policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)policy; - fwd->src_ts->destroy(fwd->src_ts); - fwd->dst_ts->destroy(fwd->dst_ts); + policy_sa_in_t *in = (policy_sa_in_t*)policy; + in->src_ts->destroy(in->src_ts); + in->dst_ts->destroy(in->dst_ts); } ipsec_sa_destroy(this, policy->sa); free(policy); @@ -1090,7 +1126,7 @@ static status_t pfkey_send_socket(private_kernel_pfkey_ipsec_t *this, int socket } if (msg->sadb_msg_seq != this->seq) { - DBG1(DBG_KNL, "received PF_KEY message with unexpected sequence " + DBG2(DBG_KNL, "received PF_KEY message with unexpected sequence " "number, was %d expected %d", msg->sadb_msg_seq, this->seq); if (msg->sadb_msg_seq == 0) @@ -1768,7 +1804,7 @@ METHOD(kernel_ipsec_t, update_sa, status_t, METHOD(kernel_ipsec_t, query_sa, status_t, private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst, u_int32_t spi, u_int8_t protocol, mark_t mark, - u_int64_t *bytes, u_int64_t *packets) + u_int64_t *bytes, u_int64_t *packets, u_int32_t *time) { unsigned char request[PFKEY_BUFFER_SIZE]; struct sadb_msg *msg, *out; @@ -1826,6 +1862,18 @@ METHOD(kernel_ipsec_t, query_sa, status_t, /* not supported by PF_KEY */ *packets = 0; } + if (time) + { +#ifdef __APPLE__ + /* OS X uses the "last" time of use in usetime */ + *time = response.lft_current->sadb_lifetime_usetime; +#else /* !__APPLE__ */ + /* on Linux, sadb_lifetime_usetime is set to the "first" time of use, + * which is actually correct according to PF_KEY. We have to query + * policies for the last usetime. */ + *time = 0; +#endif /* !__APPLE__ */ + } free(out); return SUCCESS; @@ -1914,6 +1962,209 @@ METHOD(kernel_ipsec_t, flush_sas, status_t, return SUCCESS; } +/** + * Add an explicit exclude route to a routing entry + */ +static void add_exclude_route(private_kernel_pfkey_ipsec_t *this, + route_entry_t *route, host_t *src, host_t *dst) +{ + enumerator_t *enumerator; + exclude_route_t *exclude; + host_t *gtw; + + enumerator = this->excludes->create_enumerator(this->excludes); + while (enumerator->enumerate(enumerator, &exclude)) + { + if (dst->ip_equals(dst, exclude->dst)) + { + route->exclude = exclude; + exclude->refs++; + } + } + enumerator->destroy(enumerator); + + if (!route->exclude) + { + DBG2(DBG_KNL, "installing new exclude route for %H src %H", dst, src); + gtw = hydra->kernel_interface->get_nexthop(hydra->kernel_interface, + dst, NULL); + if (gtw) + { + if (hydra->kernel_interface->add_route(hydra->kernel_interface, + dst->get_address(dst), + dst->get_family(dst) == AF_INET ? 32 : 128, + gtw, src, NULL) == SUCCESS) + { + INIT(exclude, + .dst = dst->clone(dst), + .src = src->clone(src), + .gtw = gtw->clone(gtw), + .refs = 1, + ); + route->exclude = exclude; + this->excludes->insert_last(this->excludes, exclude); + } + else + { + DBG1(DBG_KNL, "installing exclude route for %H failed", dst); + } + gtw->destroy(gtw); + } + else + { + DBG1(DBG_KNL, "gateway lookup for for %H failed", dst); + } + } +} + +/** + * Remove an exclude route attached to a routing entry + */ +static void remove_exclude_route(private_kernel_pfkey_ipsec_t *this, + route_entry_t *route) +{ + if (route->exclude) + { + enumerator_t *enumerator; + exclude_route_t *exclude; + bool removed = FALSE; + host_t *dst; + + enumerator = this->excludes->create_enumerator(this->excludes); + while (enumerator->enumerate(enumerator, &exclude)) + { + if (route->exclude == exclude) + { + if (--exclude->refs == 0) + { + this->excludes->remove_at(this->excludes, enumerator); + removed = TRUE; + break; + } + } + } + enumerator->destroy(enumerator); + + if (removed) + { + 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, + dst->get_address(dst), + dst->get_family(dst) == AF_INET ? 32 : 128, + route->exclude->gtw, route->exclude->src, + NULL) != SUCCESS) + { + DBG1(DBG_KNL, "uninstalling exclude route for %H failed", dst); + } + exclude_route_destroy(route->exclude); + } + route->exclude = NULL; + } +} + +/** + * Try to install a route to the given inbound policy + */ +static bool install_route(private_kernel_pfkey_ipsec_t *this, + policy_entry_t *policy, policy_sa_in_t *in) +{ + route_entry_t *route, *old; + host_t *host, *src, *dst; + bool is_virtual; + + if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, + in->dst_ts, &host, &is_virtual) != SUCCESS) + { + return FALSE; + } + + /* switch src/dst, as we handle an IN policy */ + src = in->generic.sa->dst; + dst = in->generic.sa->src; + + INIT(route, + .prefixlen = policy->src.mask, + .src_ip = host, + .gateway = hydra->kernel_interface->get_nexthop( + hydra->kernel_interface, dst, src), + .dst_net = chunk_clone(policy->src.net->get_address(policy->src.net)), + ); + + /* if the IP is virtual, we install the route over the interface it has + * been installed on. Otherwise we use the interface we use for IKE, as + * this is required for example on Linux. */ + if (is_virtual) + { + src = route->src_ip; + } + + /* get interface for route, using source address */ + if (!hydra->kernel_interface->get_interface(hydra->kernel_interface, + src, &route->if_name)) + { + route_entry_destroy(route); + return FALSE; + } + + if (policy->route) + { + old = policy->route; + + if (route_entry_equals(old, route)) + { /* such a route already exists */ + route_entry_destroy(route); + return TRUE; + } + /* uninstall previously installed route */ + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + old->dst_net, old->prefixlen, old->gateway, + old->src_ip, old->if_name) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with policy " + "%R === %R %N", in->src_ts, in->dst_ts, + policy_dir_names, policy->direction); + } + route_entry_destroy(old); + policy->route = NULL; + } + + /* if remote traffic selector covers the IKE peer, add an exclude route */ + if (hydra->kernel_interface->get_features( + hydra->kernel_interface) & KERNEL_REQUIRE_EXCLUDE_ROUTE) + { + if (in->src_ts->includes(in->src_ts, dst)) + { + add_exclude_route(this, route, in->generic.sa->dst, dst); + } + } + + DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", + in->src_ts, route->gateway, route->src_ip, route->if_name); + + switch (hydra->kernel_interface->add_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, route->gateway, + route->src_ip, route->if_name)) + { + case ALREADY_DONE: + /* route exists, do not uninstall */ + remove_exclude_route(this, route); + route_entry_destroy(route); + return TRUE; + case SUCCESS: + /* cache the installed route */ + policy->route = route; + return TRUE; + default: + DBG1(DBG_KNL, "installing route failed: %R via %H src %H dev %s", + in->src_ts, route->gateway, route->src_ip, route->if_name); + remove_exclude_route(this, route); + route_entry_destroy(route); + return FALSE; + } +} + /** * Add or update a policy in the kernel. * @@ -2027,83 +2278,10 @@ static status_t add_policy_internal(private_kernel_pfkey_ipsec_t *this, * - we are in tunnel mode * - routing is not disabled via strongswan.conf */ - if (policy->direction == POLICY_FWD && + if (policy->direction == POLICY_IN && ipsec->cfg.mode != MODE_TRANSPORT && this->install_routes) { - policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)mapping; - route_entry_t *route; - - INIT(route, - .prefixlen = policy->src.mask, - ); - - if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - fwd->dst_ts, &route->src_ip) == SUCCESS) - { - /* get the nexthop to src (src as we are in POLICY_FWD).*/ - route->gateway = hydra->kernel_interface->get_nexthop( - hydra->kernel_interface, ipsec->src, - ipsec->dst); - route->dst_net = chunk_clone(policy->src.net->get_address( - policy->src.net)); - - /* install route via outgoing interface */ - if (!hydra->kernel_interface->get_interface(hydra->kernel_interface, - ipsec->dst, &route->if_name)) - { - this->mutex->unlock(this->mutex); - route_entry_destroy(route); - return SUCCESS; - } - - if (policy->route) - { - route_entry_t *old = policy->route; - if (route_entry_equals(old, route)) - { - this->mutex->unlock(this->mutex); - route_entry_destroy(route); - return SUCCESS; - } - /* uninstall previously installed route */ - if (hydra->kernel_interface->del_route(hydra->kernel_interface, - old->dst_net, old->prefixlen, old->gateway, - old->src_ip, old->if_name) != SUCCESS) - { - DBG1(DBG_KNL, "error uninstalling route installed with " - "policy %R === %R %N", fwd->src_ts, - fwd->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", - fwd->src_ts, route->gateway, route->src_ip, route->if_name); - switch (hydra->kernel_interface->add_route( - hydra->kernel_interface, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name)) - { - 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; - } - } - else - { - free(route); - } + install_route(this, policy, (policy_sa_in_t*)mapping); } this->mutex->unlock(this->mutex); return SUCCESS; @@ -2269,7 +2447,7 @@ METHOD(kernel_ipsec_t, query_policy, status_t, } else if (response.lft_current == NULL) { - DBG1(DBG_KNL, "unable to query policy %R === %R %N: kernel reports no " + DBG2(DBG_KNL, "unable to query policy %R === %R %N: kernel reports no " "use time", src_ts, dst_ts, policy_dir_names, direction); free(out); return FAILED; @@ -2398,6 +2576,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, "policy %R === %R %N", src_ts, dst_ts, policy_dir_names, direction); } + remove_exclude_route(this, route); } this->policies->remove(this->policies, found, NULL); @@ -2548,8 +2727,10 @@ METHOD(kernel_ipsec_t, enable_udp_decap, bool, return FALSE; } #else /* __APPLE__ */ - if (sysctlbyname("net.inet.ipsec.esp_port", NULL, NULL, &port, - sizeof(port)) != 0) + int intport = port; + + if (sysctlbyname("net.inet.ipsec.esp_port", NULL, NULL, &intport, + sizeof(intport)) != 0) { DBG1(DBG_KNL, "could not set net.inet.ipsec.esp_port to %d: %s", port, strerror(errno)); @@ -2575,6 +2756,7 @@ METHOD(kernel_ipsec_t, destroy, void, (linked_list_invoke_t)policy_entry_destroy, this); this->policies->destroy(this->policies); + this->excludes->destroy(this->excludes); this->sas->destroy(this->sas); this->mutex->destroy(this->mutex); this->mutex_pfkey->destroy(this->mutex_pfkey); @@ -2609,6 +2791,7 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() }, }, .policies = linked_list_create(), + .excludes = linked_list_create(), .sas = hashtable_create((hashtable_hash_t)ipsec_sa_hash, (hashtable_equals_t)ipsec_sa_equals, 32), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), @@ -2618,11 +2801,7 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() hydra->daemon), ); - if (streq(hydra->daemon, "pluto")) - { /* no routes for pluto, they are installed via updown script */ - this->install_routes = FALSE; - } - else if (streq(hydra->daemon, "starter")) + if (streq(hydra->daemon, "starter")) { /* starter has no threads, so we do not register for kernel events */ register_for_events = FALSE; } diff --git a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c index 7ac3e8a3c..6c0b457c5 100644 --- a/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c +++ b/src/libhydra/plugins/kernel_pfroute/kernel_pfroute_net.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -26,8 +27,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -40,9 +43,6 @@ /** delay before firing roam events (ms) */ #define ROAM_DELAY 100 -/** buffer size for PF_ROUTE messages */ -#define PFROUTE_BUFFER_SIZE 4096 - typedef struct addr_entry_t addr_entry_t; /** @@ -55,9 +55,6 @@ struct addr_entry_t { /** virtual IP managed by us */ bool virtual; - - /** Number of times this IP is used, if virtual */ - u_int refcount; }; /** @@ -197,25 +194,45 @@ struct private_kernel_pfroute_net_t hashtable_t *addrs; /** - * mutex to lock access to the PF_ROUTE socket + * List of tun devices we installed for virtual IPs */ - mutex_t *mutex_pfroute; + linked_list_t *tuns; + + /** + * mutex to communicate exclusively with PF_KEY + */ + mutex_t *mutex; + + /** + * condvar to signal if PF_KEY query got a response + */ + condvar_t *condvar; + + /** + * pid to send PF_ROUTE messages with + */ + pid_t pid; /** * PF_ROUTE socket to communicate with the kernel */ int socket; - /** - * PF_ROUTE socket to receive events - */ - int socket_events; - /** * sequence number for messages sent to the kernel */ int seq; + /** + * Sequence number a query is waiting for + */ + int waiting_seq; + + /** + * Allocated reply message from kernel + */ + struct rt_msghdr *reply; + /** * time of last roam event */ @@ -295,33 +312,91 @@ static void fire_roam_event(private_kernel_pfroute_net_t *this, bool address) } } +/** + * Data for enumerator over rtmsg sockaddrs + */ +typedef struct { + /** implements enumerator */ + enumerator_t public; + /** copy of attribute bitfield */ + int types; + /** bytes remaining in buffer */ + int remaining; + /** next sockaddr to enumerate */ + struct sockaddr *addr; +} rt_enumerator_t; + +METHOD(enumerator_t, rt_enumerate, bool, + rt_enumerator_t *this, int *xtype, struct sockaddr **addr) +{ + int i, type; + + if (this->remaining < sizeof(this->addr->sa_len) || + this->remaining < this->addr->sa_len) + { + return FALSE; + } + for (i = 0; i < RTAX_MAX; i++) + { + type = (1 << i); + if (this->types & type) + { + this->types &= ~type; + *addr = this->addr; + *xtype = i; + this->remaining -= this->addr->sa_len; + this->addr = (void*)this->addr + this->addr->sa_len; + return TRUE; + } + } + return FALSE; +} + +/** + * Create a safe enumerator over sockaddrs in ifa/ifam/rt_msg + */ +static enumerator_t *create_rtmsg_enumerator(void *hdr, size_t hdrlen) +{ + struct rt_msghdr *rthdr = hdr; + rt_enumerator_t *this; + + INIT(this, + .public = { + .enumerate = (void*)_rt_enumerate, + .destroy = (void*)free, + }, + .types = rthdr->rtm_addrs, + .remaining = rthdr->rtm_msglen - hdrlen, + .addr = hdr + hdrlen, + ); + return &this->public; +} + /** * Process an RTM_*ADDR message from the kernel */ static void process_addr(private_kernel_pfroute_net_t *this, - struct rt_msghdr *msg) + struct ifa_msghdr *ifa) { - struct ifa_msghdr *ifa = (struct ifa_msghdr*)msg; - sockaddr_t *sockaddr = (sockaddr_t*)(ifa + 1); + struct sockaddr *sockaddr; host_t *host = NULL; enumerator_t *ifaces, *addrs; iface_entry_t *iface; addr_entry_t *addr; bool found = FALSE, changed = FALSE, roam = FALSE; - int i; + enumerator_t *enumerator; + int type; - for (i = 1; i < (1 << RTAX_MAX); i <<= 1) + enumerator = create_rtmsg_enumerator(ifa, sizeof(*ifa)); + while (enumerator->enumerate(enumerator, &type, &sockaddr)) { - if (ifa->ifam_addrs & i) + if (type == RTAX_IFA) { - if (RTA_IFA & i) - { - host = host_create_from_sockaddr(sockaddr); - break; - } - sockaddr = (sockaddr_t*)((char*)sockaddr + sockaddr->sa_len); + host = host_create_from_sockaddr(sockaddr); + break; } } + enumerator->destroy(enumerator); if (!host) { @@ -352,21 +427,16 @@ static void process_addr(private_kernel_pfroute_net_t *this, addr_map_entry_remove(addr, iface, this); addr_entry_destroy(addr); } - else if (ifa->ifam_type == RTM_NEWADDR && addr->virtual) - { - addr->refcount = 1; - } } } addrs->destroy(addrs); if (!found && ifa->ifam_type == RTM_NEWADDR) { + INIT(addr, + .ip = host->clone(host), + ); changed = TRUE; - addr = malloc_thing(addr_entry_t); - addr->ip = host->clone(host); - addr->virtual = FALSE; - addr->refcount = 1; iface->addrs->insert_last(iface->addrs, addr); addr_map_entry_add(this, addr, iface); if (iface->usable) @@ -392,16 +462,55 @@ static void process_addr(private_kernel_pfroute_net_t *this, } } +/** + * Re-initialize address list of an interface if it changes state + */ +static void repopulate_iface(private_kernel_pfroute_net_t *this, + iface_entry_t *iface) +{ + struct ifaddrs *ifap, *ifa; + addr_entry_t *addr; + + while (iface->addrs->remove_last(iface->addrs, (void**)&addr) == SUCCESS) + { + addr_map_entry_remove(addr, iface, this); + addr_entry_destroy(addr); + } + + if (getifaddrs(&ifap) == 0) + { + for (ifa = ifap; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr && streq(ifa->ifa_name, iface->ifname)) + { + switch (ifa->ifa_addr->sa_family) + { + case AF_INET: + case AF_INET6: + INIT(addr, + .ip = host_create_from_sockaddr(ifa->ifa_addr), + ); + iface->addrs->insert_last(iface->addrs, addr); + addr_map_entry_add(this, addr, iface); + break; + default: + break; + } + } + } + freeifaddrs(ifap); + } +} + /** * Process an RTM_IFINFO message from the kernel */ static void process_link(private_kernel_pfroute_net_t *this, - struct rt_msghdr *hdr) + struct if_msghdr *msg) { - struct if_msghdr *msg = (struct if_msghdr*)hdr; enumerator_t *enumerator; iface_entry_t *iface; - bool roam = FALSE; + bool roam = FALSE, found = FALSE;; this->lock->write_lock(this->lock); enumerator = this->ifaces->create_enumerator(this->ifaces); @@ -423,10 +532,33 @@ static void process_link(private_kernel_pfroute_net_t *this, } } iface->flags = msg->ifm_flags; + repopulate_iface(this, iface); + found = TRUE; break; } } enumerator->destroy(enumerator); + + if (!found) + { + INIT(iface, + .ifindex = msg->ifm_index, + .flags = msg->ifm_flags, + .addrs = linked_list_create(), + ); + if (if_indextoname(iface->ifindex, iface->ifname)) + { + DBG1(DBG_KNL, "interface %s appeared", iface->ifname); + iface->usable = hydra->kernel_interface->is_interface_usable( + hydra->kernel_interface, iface->ifname); + repopulate_iface(this, iface); + this->ifaces->insert_last(this->ifaces, iface); + } + else + { + free(iface); + } + } this->lock->unlock(this->lock); if (roam) @@ -445,17 +577,23 @@ static void process_route(private_kernel_pfroute_net_t *this, } /** - * Receives events from kernel + * Receives PF_ROUTE messages from kernel */ static job_requeue_t receive_events(private_kernel_pfroute_net_t *this) { - unsigned char buf[PFROUTE_BUFFER_SIZE]; - struct rt_msghdr *msg = (struct rt_msghdr*)buf; - int len; + struct { + union { + struct rt_msghdr rtm; + struct if_msghdr ifm; + struct ifa_msghdr ifam; + }; + char buf[sizeof(struct sockaddr_storage) * RTAX_MAX]; + } msg; + int len, hdrlen; bool oldstate; oldstate = thread_cancelability(TRUE); - len = recvfrom(this->socket_events, buf, sizeof(buf), 0, NULL, 0); + len = recv(this->socket, &msg, sizeof(msg), 0); thread_cancelability(oldstate); if (len < 0) @@ -463,10 +601,7 @@ static job_requeue_t receive_events(private_kernel_pfroute_net_t *this) switch (errno) { case EINTR: - /* interrupted, try again */ - return JOB_REQUEUE_DIRECT; case EAGAIN: - /* no data ready, select again */ return JOB_REQUEUE_DIRECT; default: DBG1(DBG_KNL, "unable to receive from PF_ROUTE event socket"); @@ -475,30 +610,67 @@ static job_requeue_t receive_events(private_kernel_pfroute_net_t *this) } } - if (len < sizeof(msg->rtm_msglen) || len < msg->rtm_msglen || - msg->rtm_version != RTM_VERSION) + if (len < offsetof(struct rt_msghdr, rtm_flags) || len < msg.rtm.rtm_msglen) { - DBG2(DBG_KNL, "received corrupted PF_ROUTE message"); + DBG1(DBG_KNL, "received invalid PF_ROUTE message"); return JOB_REQUEUE_DIRECT; } - - switch (msg->rtm_type) + if (msg.rtm.rtm_version != RTM_VERSION) + { + DBG1(DBG_KNL, "received PF_ROUTE message with unsupported version: %d", + msg.rtm.rtm_version); + return JOB_REQUEUE_DIRECT; + } + switch (msg.rtm.rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: - process_addr(this, msg); + hdrlen = sizeof(msg.ifam); break; case RTM_IFINFO: - /*case RTM_IFANNOUNCE <- what about this*/ - process_link(this, msg); + hdrlen = sizeof(msg.ifm); break; case RTM_ADD: case RTM_DELETE: - process_route(this, msg); + case RTM_GET: + hdrlen = sizeof(msg.rtm); + break; + default: + return JOB_REQUEUE_DIRECT; + } + if (msg.rtm.rtm_msglen < hdrlen) + { + DBG1(DBG_KNL, "ignoring short PF_ROUTE message"); + return JOB_REQUEUE_DIRECT; + } + switch (msg.rtm.rtm_type) + { + case RTM_NEWADDR: + case RTM_DELADDR: + process_addr(this, &msg.ifam); + break; + case RTM_IFINFO: + process_link(this, &msg.ifm); + break; + case RTM_ADD: + case RTM_DELETE: + process_route(this, &msg.rtm); + break; default: break; } + this->mutex->lock(this->mutex); + if (msg.rtm.rtm_pid == this->pid && msg.rtm.rtm_seq == this->waiting_seq) + { + /* seems like the message someone is waiting for, deliver */ + this->reply = realloc(this->reply, msg.rtm.rtm_msglen); + memcpy(this->reply, &msg, msg.rtm.rtm_msglen); + } + /* signal on any event, add_ip()/del_ip() might wait for it */ + this->condvar->broadcast(this->condvar); + this->mutex->unlock(this->mutex); + return JOB_REQUEUE_DIRECT; } @@ -530,6 +702,10 @@ static bool filter_addresses(address_enumerator_t *data, { /* skip virtual interfaces added by us */ return FALSE; } + if (!(data->which & ADDR_TYPE_REGULAR) && !(*in)->virtual) + { /* address is regular, but not requested */ + return FALSE; + } ip = (*in)->ip; if (ip->get_family(ip) == AF_INET6) { @@ -578,9 +754,12 @@ static bool filter_interfaces(address_enumerator_t *data, iface_entry_t** in, METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, private_kernel_pfroute_net_t *this, kernel_address_type_t which) { - address_enumerator_t *data = malloc_thing(address_enumerator_t); - data->this = this; - data->which = which; + address_enumerator_t *data; + + INIT(data, + .this = this, + .which = which, + ); this->lock->read_lock(this->lock); return enumerator_create_nested( @@ -591,6 +770,12 @@ METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, (void*)address_enumerator_destroy); } +METHOD(kernel_net_t, get_features, kernel_feature_t, + private_kernel_pfroute_net_t *this) +{ + return KERNEL_REQUIRE_EXCLUDE_ROUTE; +} + METHOD(kernel_net_t, get_interface_name, bool, private_kernel_pfroute_net_t *this, host_t* ip, char **name) { @@ -633,38 +818,352 @@ METHOD(kernel_net_t, get_source_addr, host_t*, return NULL; } -METHOD(kernel_net_t, get_nexthop, host_t*, - private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) -{ - return NULL; -} - METHOD(kernel_net_t, add_ip, status_t, - private_kernel_pfroute_net_t *this, host_t *virtual_ip, int prefix, - char *iface) + private_kernel_pfroute_net_t *this, host_t *vip, int prefix, + char *ifname) { - return FAILED; + enumerator_t *ifaces, *addrs; + iface_entry_t *iface; + addr_entry_t *addr; + tun_device_t *tun; + bool timeout = FALSE; + + tun = tun_device_create(NULL); + if (!tun) + { + return FAILED; + } + if (prefix == -1) + { + prefix = vip->get_address(vip).len * 8; + } + if (!tun->set_address(tun, vip, prefix) || !tun->up(tun)) + { + tun->destroy(tun); + return FAILED; + } + + /* wait until address appears */ + this->mutex->lock(this->mutex); + while (!timeout && !get_interface_name(this, vip, NULL)) + { + timeout = this->condvar->timed_wait(this->condvar, this->mutex, 1000); + } + this->mutex->unlock(this->mutex); + if (timeout) + { + DBG1(DBG_KNL, "virtual IP %H did not appear on %s", + vip, tun->get_name(tun)); + tun->destroy(tun); + return FAILED; + } + + this->lock->write_lock(this->lock); + this->tuns->insert_last(this->tuns, tun); + + ifaces = this->ifaces->create_enumerator(this->ifaces); + while (ifaces->enumerate(ifaces, &iface)) + { + if (streq(iface->ifname, tun->get_name(tun))) + { + addrs = iface->addrs->create_enumerator(iface->addrs); + while (addrs->enumerate(addrs, &addr)) + { + if (addr->ip->ip_equals(addr->ip, vip)) + { + addr->virtual = TRUE; + } + } + addrs->destroy(addrs); + } + } + ifaces->destroy(ifaces); + + this->lock->unlock(this->lock); + + return SUCCESS; } METHOD(kernel_net_t, del_ip, status_t, - private_kernel_pfroute_net_t *this, host_t *virtual_ip, int prefix, + private_kernel_pfroute_net_t *this, host_t *vip, int prefix, bool wait) { - return FAILED; + enumerator_t *enumerator; + tun_device_t *tun; + host_t *addr; + bool timeout = FALSE, found = FALSE; + + this->lock->write_lock(this->lock); + enumerator = this->tuns->create_enumerator(this->tuns); + while (enumerator->enumerate(enumerator, &tun)) + { + addr = tun->get_address(tun, NULL); + if (addr && addr->ip_equals(addr, vip)) + { + this->tuns->remove_at(this->tuns, enumerator); + tun->destroy(tun); + found = TRUE; + break; + } + } + enumerator->destroy(enumerator); + this->lock->unlock(this->lock); + + if (!found) + { + return NOT_FOUND; + } + /* wait until address disappears */ + if (wait) + { + this->mutex->lock(this->mutex); + while (!timeout && get_interface_name(this, vip, NULL)) + { + timeout = this->condvar->timed_wait(this->condvar, this->mutex, 1000); + } + this->mutex->unlock(this->mutex); + if (timeout) + { + DBG1(DBG_KNL, "virtual IP %H did not disappear from tun", vip); + return FAILED; + } + } + return SUCCESS; +} + +/** + * Append a sockaddr_in/in6 of given type to routing message + */ +static void add_rt_addr(struct rt_msghdr *hdr, int type, host_t *addr) +{ + if (addr) + { + int len; + + len = *addr->get_sockaddr_len(addr); + memcpy((char*)hdr + hdr->rtm_msglen, addr->get_sockaddr(addr), len); + hdr->rtm_msglen += len; + hdr->rtm_addrs |= type; + } +} + +/** + * Append a subnet mask sockaddr using the given prefix to routing message + */ +static void add_rt_mask(struct rt_msghdr *hdr, int type, int family, int prefix) +{ + host_t *mask; + + mask = host_create_netmask(family, prefix); + if (mask) + { + add_rt_addr(hdr, type, mask); + mask->destroy(mask); + } +} + +/** + * Append an interface name sockaddr_dl to routing message + */ +static void add_rt_ifname(struct rt_msghdr *hdr, int type, char *name) +{ + struct sockaddr_dl sdl = { + .sdl_len = sizeof(struct sockaddr_dl), + .sdl_family = AF_LINK, + .sdl_nlen = strlen(name), + }; + + if (strlen(name) <= sizeof(sdl.sdl_data)) + { + memcpy(sdl.sdl_data, name, sdl.sdl_nlen); + memcpy((char*)hdr + hdr->rtm_msglen, &sdl, sdl.sdl_len); + hdr->rtm_msglen += sdl.sdl_len; + hdr->rtm_addrs |= type; + } +} + +/** + * Add or remove a route + */ +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) +{ + struct { + struct rt_msghdr hdr; + char buf[sizeof(struct sockaddr_storage) * RTAX_MAX]; + } msg = { + .hdr = { + .rtm_version = RTM_VERSION, + .rtm_type = op, + .rtm_flags = RTF_UP | RTF_STATIC, + .rtm_pid = this->pid, + .rtm_seq = ++this->seq, + }, + }; + host_t *dst; + int type; + + if (prefixlen == 0 && dst_net.len) + { + status_t status; + chunk_t half; + + half = chunk_clonea(dst_net); + half.ptr[0] |= 0x80; + prefixlen = 1; + status = manage_route(this, op, half, prefixlen, gateway, if_name); + if (status != SUCCESS) + { + return status; + } + } + + dst = host_create_from_chunk(AF_UNSPEC, dst_net, 0); + if (!dst) + { + return FAILED; + } + + if ((dst->get_family(dst) == AF_INET && prefixlen == 32) || + (dst->get_family(dst) == AF_INET6 && prefixlen == 128)) + { + msg.hdr.rtm_flags |= RTF_HOST | RTF_GATEWAY; + } + + msg.hdr.rtm_msglen = sizeof(struct rt_msghdr); + for (type = 0; type < RTAX_MAX; type++) + { + switch (type) + { + case RTAX_DST: + add_rt_addr(&msg.hdr, RTA_DST, dst); + break; + case RTAX_NETMASK: + if (!(msg.hdr.rtm_flags & RTF_HOST)) + { + add_rt_mask(&msg.hdr, RTA_NETMASK, + dst->get_family(dst), prefixlen); + } + break; + case RTAX_GATEWAY: + /* interface name seems to replace gateway on OS X */ + if (if_name) + { + add_rt_ifname(&msg.hdr, RTA_GATEWAY, if_name); + } + else if (gateway) + { + add_rt_addr(&msg.hdr, RTA_GATEWAY, gateway); + } + break; + default: + break; + } + } + dst->destroy(dst); + + if (send(this->socket, &msg, msg.hdr.rtm_msglen, 0) != msg.hdr.rtm_msglen) + { + DBG1(DBG_KNL, "%s PF_ROUTE route failed: %s", + op == RTM_ADD ? "adding" : "deleting", strerror(errno)); + return FAILED; + } + return SUCCESS; } 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 FAILED; + return manage_route(this, RTM_ADD, dst_net, prefixlen, gateway, if_name); } 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 FAILED; + return manage_route(this, RTM_DELETE, dst_net, prefixlen, gateway, if_name); +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_kernel_pfroute_net_t *this, host_t *dest, host_t *src) +{ + struct { + struct rt_msghdr hdr; + char buf[sizeof(struct sockaddr_storage) * RTAX_MAX]; + } msg = { + .hdr = { + .rtm_version = RTM_VERSION, + .rtm_type = RTM_GET, + .rtm_pid = this->pid, + .rtm_seq = ++this->seq, + }, + }; + host_t *hop = NULL; + enumerator_t *enumerator; + struct sockaddr *addr; + int type; + + msg.hdr.rtm_msglen = sizeof(struct rt_msghdr); + for (type = 0; type < RTAX_MAX; type++) + { + switch (type) + { + case RTAX_DST: + add_rt_addr(&msg.hdr, RTA_DST, dest); + break; + case RTAX_IFA: + add_rt_addr(&msg.hdr, RTA_IFA, src); + break; + default: + break; + } + } + this->mutex->lock(this->mutex); + + while (this->waiting_seq) + { + this->condvar->wait(this->condvar, this->mutex); + } + this->waiting_seq = msg.hdr.rtm_seq; + if (send(this->socket, &msg, msg.hdr.rtm_msglen, 0) == msg.hdr.rtm_msglen) + { + while (TRUE) + { + if (this->condvar->timed_wait(this->condvar, this->mutex, 1000)) + { /* timed out? */ + break; + } + if (this->reply->rtm_msglen < sizeof(*this->reply) || + msg.hdr.rtm_seq != this->reply->rtm_seq) + { + continue; + } + enumerator = create_rtmsg_enumerator(this->reply, + sizeof(*this->reply)); + while (enumerator->enumerate(enumerator, &type, &addr)) + { + if (type == RTAX_GATEWAY) + { + hop = host_create_from_sockaddr(addr); + break; + } + } + enumerator->destroy(enumerator); + break; + } + } + else + { + DBG1(DBG_KNL, "PF_ROUTE lookup failed: %s", strerror(errno)); + } + /* signal completion of query to a waiting thread */ + this->waiting_seq = 0; + this->condvar->signal(this->condvar); + this->mutex->unlock(this->mutex); + + return hop; } /** @@ -711,22 +1210,22 @@ static status_t init_address_list(private_kernel_pfroute_net_t *this) if (!iface) { - iface = malloc_thing(iface_entry_t); + INIT(iface, + .ifindex = if_nametoindex(ifa->ifa_name), + .flags = ifa->ifa_flags, + .addrs = linked_list_create(), + .usable = hydra->kernel_interface->is_interface_usable( + hydra->kernel_interface, ifa->ifa_name), + ); memcpy(iface->ifname, ifa->ifa_name, IFNAMSIZ); - iface->ifindex = if_nametoindex(ifa->ifa_name); - iface->flags = ifa->ifa_flags; - iface->addrs = linked_list_create(); - iface->usable = hydra->kernel_interface->is_interface_usable( - hydra->kernel_interface, ifa->ifa_name); this->ifaces->insert_last(this->ifaces, iface); } if (ifa->ifa_addr->sa_family != AF_LINK) { - addr = malloc_thing(addr_entry_t); - addr->ip = host_create_from_sockaddr(ifa->ifa_addr); - addr->virtual = FALSE; - addr->refcount = 1; + INIT(addr, + .ip = host_create_from_sockaddr(ifa->ifa_addr), + ); iface->addrs->insert_last(iface->addrs, addr); addr_map_entry_add(this, addr, iface); } @@ -760,14 +1259,10 @@ METHOD(kernel_net_t, destroy, void, enumerator_t *enumerator; addr_entry_t *addr; - if (this->socket > 0) + if (this->socket != -1) { close(this->socket); } - if (this->socket_events) - { - close(this->socket_events); - } enumerator = this->addrs->create_enumerator(this->addrs); while (enumerator->enumerate(enumerator, NULL, (void**)&addr)) { @@ -776,8 +1271,11 @@ METHOD(kernel_net_t, destroy, void, enumerator->destroy(enumerator); this->addrs->destroy(this->addrs); this->ifaces->destroy_function(this->ifaces, (void*)iface_entry_destroy); + this->tuns->destroy(this->tuns); this->lock->destroy(this->lock); - this->mutex_pfroute->destroy(this->mutex_pfroute); + this->mutex->destroy(this->mutex); + this->condvar->destroy(this->condvar); + free(this->reply); free(this); } @@ -787,11 +1285,11 @@ METHOD(kernel_net_t, destroy, void, kernel_pfroute_net_t *kernel_pfroute_net_create() { private_kernel_pfroute_net_t *this; - bool register_for_events = TRUE; INIT(this, .public = { .interface = { + .get_features = _get_features, .get_interface = _get_interface_name, .create_address_enumerator = _create_address_enumerator, .get_source_addr = _get_source_addr, @@ -803,45 +1301,42 @@ kernel_pfroute_net_t *kernel_pfroute_net_create() .destroy = _destroy, }, }, + .pid = getpid(), .ifaces = linked_list_create(), .addrs = hashtable_create( (hashtable_hash_t)addr_map_entry_hash, (hashtable_equals_t)addr_map_entry_equals, 16), + .tuns = linked_list_create(), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), - .mutex_pfroute = mutex_create(MUTEX_TYPE_DEFAULT), + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), ); - if (streq(hydra->daemon, "starter")) - { /* starter has no threads, so we do not register for kernel events */ - register_for_events = FALSE; - } - /* create a PF_ROUTE socket to communicate with the kernel */ this->socket = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); - if (this->socket < 0) + if (this->socket == -1) { DBG1(DBG_KNL, "unable to create PF_ROUTE socket"); destroy(this); return NULL; } - if (register_for_events) + if (streq(hydra->daemon, "starter")) { - /* create a PF_ROUTE socket to receive events */ - this->socket_events = socket(PF_ROUTE, SOCK_RAW, AF_UNSPEC); - if (this->socket_events < 0) + /* starter has no threads, so we do not register for kernel events */ + if (shutdown(this->socket, SHUT_RD) != 0) { - DBG1(DBG_KNL, "unable to create PF_ROUTE event socket"); - destroy(this); - return NULL; + DBG1(DBG_KNL, "closing read end of PF_ROUTE socket failed: %s", + strerror(errno)); } - + } + else + { lib->processor->queue_job(lib->processor, (job_t*)callback_job_create_with_prio( (callback_job_cb_t)receive_events, this, NULL, (callback_job_cancel_t)return_false, JOB_PRIO_CRITICAL)); } - if (init_address_list(this) != SUCCESS) { DBG1(DBG_KNL, "unable to get interface list"); diff --git a/src/libstrongswan/networking/host.c b/src/libstrongswan/networking/host.c index bffa96064..d275a835e 100644 --- a/src/libstrongswan/networking/host.c +++ b/src/libstrongswan/networking/host.c @@ -54,6 +54,15 @@ struct private_host_t { socklen_t socklen; }; +/** + * Update the sockaddr internal sa_len option, if available + */ +static inline void update_sa_len(private_host_t *this) +{ +#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN + this->address.sa_len = this->socklen; +#endif /* HAVE_STRUCT_SOCKADDR_SA_LEN */ +} METHOD(host_t, get_sockaddr, sockaddr_t*, private_host_t *this) @@ -102,7 +111,7 @@ int host_printf_hook(printf_hook_data_t *data, printf_hook_spec_t *spec, { snprintf(buffer, sizeof(buffer), "(null)"); } - else if (is_anyaddr(this) && !spec->plus) + else if (is_anyaddr(this) && !spec->plus && !spec->hash) { snprintf(buffer, sizeof(buffer), "%%any%s", this->address.sa_family == AF_INET6 ? "6" : ""); @@ -264,26 +273,6 @@ static bool ip_equals(private_host_t *this, private_host_t *other) return FALSE; } -/** - * Implements host_t.get_differences - */ -static host_diff_t get_differences(host_t *this, host_t *other) -{ - host_diff_t ret = HOST_DIFF_NONE; - - if (!this->ip_equals(this, other)) - { - ret |= HOST_DIFF_ADDR; - } - - if (this->get_port(this) != other->get_port(other)) - { - ret |= HOST_DIFF_PORT; - } - - return ret; -} - /** * Implements host_t.equals */ @@ -332,7 +321,6 @@ static private_host_t *host_create_empty(void) .get_address = _get_address, .get_port = _get_port, .set_port = _set_port, - .get_differences = get_differences, .ip_equals = (bool (*)(host_t *,host_t *))ip_equals, .equals = (bool (*)(host_t *,host_t *)) equals, .is_anyaddr = _is_anyaddr, @@ -440,6 +428,7 @@ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr) memcpy(&this->address4, (struct sockaddr_in*)sockaddr, sizeof(struct sockaddr_in)); this->socklen = sizeof(struct sockaddr_in); + update_sa_len(this); return &this->public; } case AF_INET6: @@ -447,6 +436,7 @@ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr) memcpy(&this->address6, (struct sockaddr_in6*)sockaddr, sizeof(struct sockaddr_in6)); this->socklen = sizeof(struct sockaddr_in6); + update_sa_len(this); return &this->public; } default: @@ -529,6 +519,7 @@ host_t *host_create_from_chunk(int family, chunk_t address, u_int16_t port) this->socklen = sizeof(struct sockaddr_in6); break; } + update_sa_len(this); return &this->public; } @@ -567,6 +558,55 @@ host_t *host_create_from_subnet(char *string, int *bits) return net; } +/* + * See header. + */ +host_t *host_create_netmask(int family, int netbits) +{ + private_host_t *this; + int bits, bytes, len = 0; + char *target; + + switch (family) + { + case AF_INET: + if (netbits < 0 || netbits > 32) + { + return NULL; + } + this = host_create_empty(); + this->socklen = sizeof(struct sockaddr_in); + target = (char*)&this->address4.sin_addr; + len = 4; + break; + case AF_INET6: + if (netbits < 0 || netbits > 128) + { + return NULL; + } + this = host_create_empty(); + this->socklen = sizeof(struct sockaddr_in6); + target = (char*)&this->address6.sin6_addr; + len = 16; + break; + default: + return NULL; + } + + memset(&this->address_max, 0, sizeof(struct sockaddr_storage)); + this->address.sa_family = family; + update_sa_len(this); + + bytes = (netbits + 7) / 8; + bits = (bytes * 8) - netbits; + + memset(target, 0xff, bytes); + memset(target + bytes, 0x00, len - bytes); + target[bytes - 1] = bits ? (u_int8_t)(0xff << bits) : 0xff; + + return &this->public; +} + /* * Described in header. */ @@ -582,11 +622,13 @@ host_t *host_create_any(int family) case AF_INET: { this->socklen = sizeof(struct sockaddr_in); + update_sa_len(this); return &(this->public); } case AF_INET6: { this->socklen = sizeof(struct sockaddr_in6); + update_sa_len(this); return &this->public; } default: diff --git a/src/libstrongswan/networking/host.h b/src/libstrongswan/networking/host.h index 25f334779..ae25076ed 100644 --- a/src/libstrongswan/networking/host.h +++ b/src/libstrongswan/networking/host.h @@ -36,16 +36,6 @@ typedef struct host_t host_t; #include -/** - * Differences between two hosts. They differ in - * address, port, or both. - */ -enum host_diff_t { - HOST_DIFF_NONE = 0, - HOST_DIFF_ADDR = 1, - HOST_DIFF_PORT = 2, -}; - /** * Representates a Host * @@ -136,14 +126,6 @@ struct host_t { */ bool (*equals) (host_t *this, host_t *other); - /** - * Compare two hosts and return the differences. - * - * @param other the other to compare - * @return differences in a combination of host_diff_t's - */ - host_diff_t (*get_differences) (host_t *this, host_t *other); - /** * Destroy this host object. */ @@ -209,6 +191,14 @@ host_t *host_create_from_sockaddr(sockaddr_t *sockaddr); */ host_t *host_create_from_subnet(char *string, int *bits); +/** + * Create a netmask host having the first netbits bits set. + * + * @param netbits number of leading bits set in the host + * @return netmask host + */ +host_t *host_create_netmask(int family, int netbits); + /** * Create a host without an address, a "any" host. * diff --git a/src/libstrongswan/networking/tun_device.c b/src/libstrongswan/networking/tun_device.c index 1da87df05..2191198e2 100644 --- a/src/libstrongswan/networking/tun_device.c +++ b/src/libstrongswan/networking/tun_device.c @@ -73,63 +73,28 @@ struct private_tun_device_t { * The current MTU */ int mtu; + + /** + * Associated address + */ + host_t *address; + + /** + * Netmask for address + */ + u_int8_t netmask; }; -/** - * Set the sockaddr_t from the given netmask - */ -static void set_netmask(struct ifreq *ifr, int family, u_int8_t netmask) -{ - int len, bytes, bits; - char *target; - - switch (family) - { - case AF_INET: - { - struct sockaddr_in *addr = (struct sockaddr_in*)&ifr->ifr_addr; - target = (char*)&addr->sin_addr; - len = 4; - break; - } - case AF_INET6: - { - struct sockaddr_in6 *addr = (struct sockaddr_in6*)&ifr->ifr_addr; - target = (char*)&addr->sin6_addr; - len = 16; - break; - } - default: - return; - } - - ifr->ifr_addr.sa_family = family; - - bytes = (netmask + 7) / 8; - bits = (bytes * 8) - netmask; - - memset(target, 0xff, bytes); - memset(target + bytes, 0x00, len - bytes); - target[bytes - 1] = bits ? (u_int8_t)(0xff << bits) : 0xff; -} - METHOD(tun_device_t, set_address, bool, private_tun_device_t *this, host_t *addr, u_int8_t netmask) { struct ifreq ifr; - int family; - - family = addr->get_family(addr); - if ((netmask > 32 && family == AF_INET) || netmask > 128) - { - DBG1(DBG_LIB, "failed to set address on %s: invalid netmask", - this->if_name); - return FALSE; - } + host_t *mask; memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, this->if_name, IFNAMSIZ); - memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr), sizeof(sockaddr_t)); + memcpy(&ifr.ifr_addr, addr->get_sockaddr(addr), + *addr->get_sockaddr_len(addr)); if (ioctl(this->sock, SIOCSIFADDR, &ifr) < 0) { @@ -146,7 +111,15 @@ METHOD(tun_device_t, set_address, bool, } #endif /* __APPLE__ */ - set_netmask(&ifr, family, netmask); + mask = host_create_netmask(addr->get_family(addr), netmask); + if (!mask) + { + DBG1(DBG_LIB, "invalid netmask: %d", netmask); + return FALSE; + } + memcpy(&ifr.ifr_addr, mask->get_sockaddr(mask), + *mask->get_sockaddr_len(mask)); + mask->destroy(mask); if (ioctl(this->sock, SIOCSIFNETMASK, &ifr) < 0) { @@ -154,9 +127,21 @@ METHOD(tun_device_t, set_address, bool, this->if_name, strerror(errno)); return FALSE; } + this->address = addr->clone(addr); + this->netmask = netmask; return TRUE; } +METHOD(tun_device_t, get_address, host_t*, + private_tun_device_t *this, u_int8_t *netmask) +{ + if (netmask && this->address) + { + *netmask = this->netmask; + } + return this->address; +} + METHOD(tun_device_t, up, bool, private_tun_device_t *this) { @@ -229,6 +214,12 @@ METHOD(tun_device_t, get_name, char*, return this->if_name; } +METHOD(tun_device_t, get_fd, int, + private_tun_device_t *this) +{ + return this->tunfd; +} + METHOD(tun_device_t, write_packet, bool, private_tun_device_t *this, chunk_t packet) { @@ -308,6 +299,7 @@ METHOD(tun_device_t, destroy, void, { close(this->sock); } + DESTROY_IF(this->address); free(this); } @@ -435,7 +427,9 @@ tun_device_t *tun_device_create(const char *name_tmpl) .get_mtu = _get_mtu, .set_mtu = _set_mtu, .get_name = _get_name, + .get_fd = _get_fd, .set_address = _set_address, + .get_address = _get_address, .up = _up, .destroy = _destroy, }, diff --git a/src/libstrongswan/networking/tun_device.h b/src/libstrongswan/networking/tun_device.h index b22a5d170..1d330f133 100644 --- a/src/libstrongswan/networking/tun_device.h +++ b/src/libstrongswan/networking/tun_device.h @@ -65,6 +65,14 @@ struct tun_device_t { */ bool (*set_address)(tun_device_t *this, host_t *addr, u_int8_t netmask); + /** + * Get the IP address previously assigned to using set_address(). + * + * @param netmask pointer receiving the configured netmask, or NULL + * @return address previously set, NULL if none + */ + host_t* (*get_address)(tun_device_t *this, u_int8_t *netmask); + /** * Bring the TUN device up * @@ -94,6 +102,13 @@ struct tun_device_t { */ char *(*get_name)(tun_device_t *this); + /** + * Get the underlying tun file descriptor. + * + * @return file descriptor of this tun device + */ + int (*get_fd)(tun_device_t *this); + /** * Destroy a tun_device_t */ diff --git a/src/libstrongswan/threading/semaphore.h b/src/libstrongswan/threading/semaphore.h index cdb0a6f19..34d814971 100644 --- a/src/libstrongswan/threading/semaphore.h +++ b/src/libstrongswan/threading/semaphore.h @@ -21,6 +21,11 @@ #ifndef THREADING_SEMAPHORE_H_ #define THREADING_SEMAPHORE_H_ +#ifdef __APPLE__ +/* Mach uses a semaphore_create() call, use a different name for ours */ +#define semaphore_create(x) strongswan_semaphore_create(x) +#endif /* __APPLE__ */ + typedef struct semaphore_t semaphore_t; /**