From 8addb45c033b13f3063ece56823a925c2b8bf9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20Ter=C3=A4s?= Date: Mon, 21 Sep 2015 13:42:18 +0300 Subject: [PATCH] support gre key in ikev1 this implements gre key negotiation in ikev1 similarly to the ipsec-tools patch in alpine. the from/to port pair is internally used as gre key for gre protocol traffic selectors. since from/to pairs 0/0xffff and 0xffff/0 have special meaning, the gre keys 0xffff and 0xffff0000 will not work. this is not standard compliant, and should probably not be upstreamed or used widely, but it is applied for interoperability with alpine racoon for the time being. --- src/libcharon/encoding/payloads/id_payload.c | 68 +++++++++++++++++----- src/libcharon/encoding/payloads/id_payload.h | 6 +- src/libcharon/plugins/stroke/stroke_config.c | 5 ++ src/libcharon/plugins/unity/unity_narrow.c | 2 +- src/libcharon/plugins/vici/vici_config.c | 9 ++- src/libcharon/sa/ikev1/tasks/quick_mode.c | 16 ++--- .../plugins/kernel_netlink/kernel_netlink_ipsec.c | 40 ++++++++++--- src/libstrongswan/selectors/traffic_selector.c | 33 ++++++++++- src/libstrongswan/selectors/traffic_selector.h | 31 ++++++++++ 9 files changed, 171 insertions(+), 39 deletions(-) diff --git a/src/libcharon/encoding/payloads/id_payload.c b/src/libcharon/encoding/payloads/id_payload.c index bb8aab7..2cf08e9 100644 --- a/src/libcharon/encoding/payloads/id_payload.c +++ b/src/libcharon/encoding/payloads/id_payload.c @@ -245,18 +245,20 @@ METHOD(id_payload_t, get_identification, identification_t*, * Create a traffic selector from an range ID */ static traffic_selector_t *get_ts_from_range(private_id_payload_t *this, - ts_type_t type) + ts_type_t type, + u_int16_t from_port, u_int16_t to_port) { return traffic_selector_create_from_bytes(this->protocol_id, type, - chunk_create(this->id_data.ptr, this->id_data.len / 2), this->port, - chunk_skip(this->id_data, this->id_data.len / 2), this->port ?: 65535); + chunk_create(this->id_data.ptr, this->id_data.len / 2), from_port, + chunk_skip(this->id_data, this->id_data.len / 2), to_port); } /** * Create a traffic selector from an subnet ID */ static traffic_selector_t *get_ts_from_subnet(private_id_payload_t *this, - ts_type_t type) + ts_type_t type, + u_int16_t from_port, u_int16_t to_port) { traffic_selector_t *ts; chunk_t net, netmask; @@ -269,7 +271,7 @@ static traffic_selector_t *get_ts_from_subnet(private_id_payload_t *this, netmask.ptr[i] = (netmask.ptr[i] ^ 0xFF) | net.ptr[i]; } ts = traffic_selector_create_from_bytes(this->protocol_id, type, - net, this->port, netmask, this->port ?: 65535); + net, from_port, netmask, to_port); chunk_free(&netmask); return ts; } @@ -278,51 +280,76 @@ static traffic_selector_t *get_ts_from_subnet(private_id_payload_t *this, * Create a traffic selector from an IP ID */ static traffic_selector_t *get_ts_from_ip(private_id_payload_t *this, - ts_type_t type) + ts_type_t type, + u_int16_t from_port, u_int16_t to_port) { return traffic_selector_create_from_bytes(this->protocol_id, type, - this->id_data, this->port, this->id_data, this->port ?: 65535); + this->id_data, from_port, this->id_data, to_port); } METHOD(id_payload_t, get_ts, traffic_selector_t*, - private_id_payload_t *this) + private_id_payload_t *this, id_payload_t *other_, bool initiator) { + private_id_payload_t *other = (private_id_payload_t *) other_; + u_int16_t from_port, to_port; + + if (other && this->protocol_id == IPPROTO_GRE && other->protocol_id == IPPROTO_GRE) + { + if (initiator) + { + from_port = this->port; + to_port = other->port; + } + else + { + from_port = other->port; + to_port = this->port; + } + if (from_port == 0 && to_port == 0) + to_port = 0xffff; + } + else + { + from_port = this->port; + to_port = this->port ?: 0xffff; + } + switch (this->id_type) { case ID_IPV4_ADDR_SUBNET: if (this->id_data.len == 8) { - return get_ts_from_subnet(this, TS_IPV4_ADDR_RANGE); + return get_ts_from_subnet(this, TS_IPV4_ADDR_RANGE, from_port, to_port); } break; case ID_IPV6_ADDR_SUBNET: if (this->id_data.len == 32) { - return get_ts_from_subnet(this, TS_IPV6_ADDR_RANGE); + return get_ts_from_subnet(this, TS_IPV6_ADDR_RANGE, from_port, to_port); } break; case ID_IPV4_ADDR_RANGE: if (this->id_data.len == 8) { - return get_ts_from_range(this, TS_IPV4_ADDR_RANGE); + return get_ts_from_range(this, TS_IPV4_ADDR_RANGE, from_port, to_port); } break; case ID_IPV6_ADDR_RANGE: if (this->id_data.len == 32) { - return get_ts_from_range(this, TS_IPV6_ADDR_RANGE); + return get_ts_from_range(this, TS_IPV6_ADDR_RANGE, from_port, to_port); } break; case ID_IPV4_ADDR: if (this->id_data.len == 4) { - return get_ts_from_ip(this, TS_IPV4_ADDR_RANGE); + return get_ts_from_ip(this, TS_IPV4_ADDR_RANGE, from_port, to_port); } break; case ID_IPV6_ADDR: if (this->id_data.len == 16) { - return get_ts_from_ip(this, TS_IPV6_ADDR_RANGE); + return get_ts_from_ip(this, TS_IPV6_ADDR_RANGE, from_port, to_port); } break; default: @@ -397,7 +424,7 @@ id_payload_t *id_payload_create_from_identification(payload_type_t type, /* * Described in header. */ -id_payload_t *id_payload_create_from_ts(traffic_selector_t *ts) +id_payload_t *id_payload_create_from_ts(traffic_selector_t *ts, bool initiator) { private_id_payload_t *this; u_int8_t mask; @@ -460,8 +487,17 @@ id_payload_t *id_payload_create_from_ts(traffic_selector_t *ts) ts->get_from_address(ts), ts->get_to_address(ts)); net->destroy(net); } - this->port = ts->get_from_port(ts); this->protocol_id = ts->get_protocol(ts); + if (initiator || this->protocol_id != IPPROTO_GRE) + { + this->port = ts->get_from_port(ts); + } + else + { + this->port = ts->get_to_port(ts); + if (this->port == 0xffff && ts->get_from_port(ts) == 0) + this->port = 0; + } this->payload_length += this->id_data.len; return &this->public; diff --git a/src/libcharon/encoding/payloads/id_payload.h b/src/libcharon/encoding/payloads/id_payload.h index df1d075..7558e91 100644 --- a/src/libcharon/encoding/payloads/id_payload.h +++ b/src/libcharon/encoding/payloads/id_payload.h @@ -48,11 +48,11 @@ struct id_payload_t { identification_t *(*get_identification) (id_payload_t *this); /** - * Creates a traffic selector form a ID_ADDR_SUBNET/RANGE identity. + * Creates a traffic selector form a ID_ADDR_SUBNET/RANGE identity pair. * * @return traffic selector, NULL on failure */ - traffic_selector_t* (*get_ts)(id_payload_t *this); + traffic_selector_t* (*get_ts)(id_payload_t *this, id_payload_t *other, bool initiator); /** * Get encoded payload without fixed payload header (used for IKEv1). @@ -91,6 +91,6 @@ id_payload_t *id_payload_create_from_identification(payload_type_t type, * @param ts traffic selector * @return PLV1_ID id_paylad_t object. */ -id_payload_t *id_payload_create_from_ts(traffic_selector_t *ts); +id_payload_t *id_payload_create_from_ts(traffic_selector_t *ts, bool initiator); #endif /** ID_PAYLOAD_H_ @}*/ diff --git a/src/libcharon/plugins/stroke/stroke_config.c b/src/libcharon/plugins/stroke/stroke_config.c index f717194..cde175f 100644 --- a/src/libcharon/plugins/stroke/stroke_config.c +++ b/src/libcharon/plugins/stroke/stroke_config.c @@ -1049,6 +1049,11 @@ static bool parse_protoport(char *token, u_int16_t *from_port, *from_port = 0xffff; *to_port = 0; } + else if (*port && *protocol == IPPROTO_GRE) + { + p = strtol(port, &endptr, 0); + traffic_selector_split_grekey(p, from_port, to_port); + } else if (*port) { svc = getservbyname(port, NULL); diff --git a/src/libcharon/plugins/unity/unity_narrow.c b/src/libcharon/plugins/unity/unity_narrow.c index 227d24b..7749d8c 100644 --- a/src/libcharon/plugins/unity/unity_narrow.c +++ b/src/libcharon/plugins/unity/unity_narrow.c @@ -247,7 +247,7 @@ METHOD(listener_t, message, bool, if (!first) { id_payload = (id_payload_t*)payload; - tsr = id_payload->get_ts(id_payload); + tsr = id_payload->get_ts(id_payload, NULL, FALSE); break; } first = FALSE; diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 5537ed9..70c83d4 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -596,8 +596,13 @@ CALLBACK(parse_ts, bool, } else if (*port && !streq(port, "any")) { - svc = getservbyname(port, NULL); - if (svc) + if (proto == IPPROTO_GRE) + { + p = strtol(port, &end, 0); + if (*end) return FALSE; + traffic_selector_split_grekey(p, &from, &to); + } + else if ((svc = getservbyname(port, NULL)) != NULL) { from = to = ntohs(svc->s_port); } diff --git a/src/libcharon/sa/ikev1/tasks/quick_mode.c b/src/libcharon/sa/ikev1/tasks/quick_mode.c index d6a3f2c..8533112 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -541,9 +541,9 @@ static void add_ts(private_quick_mode_t *this, message_t *message) { id_payload_t *id_payload; - id_payload = id_payload_create_from_ts(this->tsi); + id_payload = id_payload_create_from_ts(this->tsi, TRUE); message->add_payload(message, &id_payload->payload_interface); - id_payload = id_payload_create_from_ts(this->tsr); + id_payload = id_payload_create_from_ts(this->tsr, FALSE); message->add_payload(message, &id_payload->payload_interface); } @@ -554,7 +554,7 @@ static bool get_ts(private_quick_mode_t *this, message_t *message) { traffic_selector_t *tsi = NULL, *tsr = NULL; enumerator_t *enumerator; - id_payload_t *id_payload; + id_payload_t *idi = NULL, *idr = NULL; payload_t *payload; host_t *hsi, *hsr; bool first = TRUE; @@ -564,20 +564,22 @@ static bool get_ts(private_quick_mode_t *this, message_t *message) { if (payload->get_type(payload) == PLV1_ID) { - id_payload = (id_payload_t*)payload; - if (first) { - tsi = id_payload->get_ts(id_payload); + idi = (id_payload_t*)payload; first = FALSE; } else { - tsr = id_payload->get_ts(id_payload); + idr = (id_payload_t*)payload; break; } } } + if (idi && idr) { + tsi = idi->get_ts(idi, idr, TRUE); + tsr = idr->get_ts(idr, idi, FALSE); + } enumerator->destroy(enumerator); /* create host2host selectors if ID payloads missing */ diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c index 605476e..ef94c26 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -745,7 +745,18 @@ static struct xfrm_selector ts2selector(traffic_selector_t *src, ts2subnet(src, &sel.saddr, &sel.prefixlen_s); ts2ports(dst, &sel.dport, &sel.dport_mask); ts2ports(src, &sel.sport, &sel.sport_mask); - if ((sel.proto == IPPROTO_ICMP || sel.proto == IPPROTO_ICMPV6) && + if (sel.proto == IPPROTO_GRE) + { + sel.sport = htons(src->get_from_port(src)); + sel.dport = htons(src->get_to_port(src)); + sel.sport_mask = ~0; + sel.dport_mask = ~0; + if (sel.sport == htons(0) && sel.dport == htons(0xffff)) + { + sel.sport = sel.dport = sel.sport_mask = sel.dport_mask = 0; + } + } + else if ((sel.proto == IPPROTO_ICMP || sel.proto == IPPROTO_ICMPV6) && (sel.dport || sel.sport)) { /* the kernel expects the ICMP type and code in the source and @@ -769,7 +780,7 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) { u_char *addr; u_int8_t prefixlen; - u_int16_t port = 0; + u_int16_t from_port = 0, to_port = 65535; host_t *host = NULL; if (src) @@ -778,7 +789,7 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) prefixlen = sel->prefixlen_s; if (sel->sport_mask) { - port = ntohs(sel->sport); + from_port = to_port = ntohs(sel->sport); } } else @@ -787,14 +798,27 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) prefixlen = sel->prefixlen_d; if (sel->dport_mask) { - port = ntohs(sel->dport); + from_port = to_port = ntohs(sel->dport); + } + } + if (sel->proto == IPPROTO_GRE) + { + if (sel->sport_mask) + { + from_port = ntohs(sel->sport); + to_port = ntohs(sel->dport); + } + else + { + from_port = 0; + to_port = 0xffff; } } - if (sel->proto == IPPROTO_ICMP || sel->proto == IPPROTO_ICMPV6) + else if (sel->proto == IPPROTO_ICMP || sel->proto == IPPROTO_ICMPV6) { /* convert ICMP[v6] message type and code as supplied by the kernel in * source and destination ports (both in network order) */ - port = (sel->sport >> 8) | (sel->dport & 0xff00); - port = ntohs(port); + from_port = (sel->sport >> 8) | (sel->dport & 0xff00); + from_port = to_port = ntohs(from_port); } /* The Linux 2.6 kernel does not set the selector's family field, * so as a kludge we additionally test the prefix length. @@ -811,7 +835,7 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) if (host) { return traffic_selector_create_from_subnet(host, prefixlen, - sel->proto, port, port ?: 65535); + sel->proto, from_port, to_port); } return NULL; } diff --git a/src/libstrongswan/selectors/traffic_selector.c b/src/libstrongswan/selectors/traffic_selector.c index 6686324..776c765 100644 --- a/src/libstrongswan/selectors/traffic_selector.c +++ b/src/libstrongswan/selectors/traffic_selector.c @@ -209,6 +209,14 @@ static int print_icmp(printf_hook_data_t *data, u_int16_t port) } /** + * Print GRE key + */ +static int print_grekey(printf_hook_data_t *data, u_int16_t from_port, u_int16_t to_port) +{ + return print_in_hook(data, "%d", traffic_selector_grekey(from_port, to_port)); +} + +/** * Described in header. */ int traffic_selector_printf_hook(printf_hook_data_t *data, @@ -313,7 +321,11 @@ int traffic_selector_printf_hook(printf_hook_data_t *data, /* build port string */ if (has_ports) { - if (this->from_port == this->to_port) + if (this->protocol == IPPROTO_GRE) + { + written += print_grekey(data, this->from_port, this->to_port); + } + else if (this->from_port == this->to_port) { struct servent *serv; @@ -398,7 +410,24 @@ METHOD(traffic_selector_t, get_subset, traffic_selector_t*, /* select protocol, which is not zero */ protocol = max(this->protocol, other->protocol); - if ((is_opaque(this) && is_opaque(other)) || + if (this->protocol == IPPROTO_GRE) + { + if (is_any(this)) + { + from_port = other->from_port; + to_port = other->to_port; + } + else if (is_any(other) || + (this->from_port == other->from_port && + this->to_port == other->to_port)) + { + from_port = this->from_port; + to_port = this->to_port; + } + else + return NULL; + } + else if ((is_opaque(this) && is_opaque(other)) || (is_opaque(this) && is_any(other)) || (is_opaque(other) && is_any(this))) { diff --git a/src/libstrongswan/selectors/traffic_selector.h b/src/libstrongswan/selectors/traffic_selector.h index cf9a286..d458c68 100644 --- a/src/libstrongswan/selectors/traffic_selector.h +++ b/src/libstrongswan/selectors/traffic_selector.h @@ -120,6 +120,9 @@ struct traffic_selector_t { * 8 bits and the code in the least significant 8 bits. Use the utility * functions to extract them. * + * If the protocol is GRE, the high 16-bits of the 32-bit GRE key is stored + * in the from port. Use the utility function to merge and split them. + * * @return port */ u_int16_t (*get_from_port) (traffic_selector_t *this); @@ -134,6 +137,9 @@ struct traffic_selector_t { * 8 bits and the code in the least significant 8 bits. Use the utility * functions to extract them. * + * If the protocol is GRE, the low 16-bits of the 32-bit GRE key is stored + * in the to port. Use the utility function to merge and split them. + * * @return port */ u_int16_t (*get_to_port) (traffic_selector_t *this); @@ -268,6 +274,31 @@ int traffic_selector_cmp(traffic_selector_t *a, traffic_selector_t *b, void *opts); /** + * Reconstruct the 32-bit GRE KEY in host order from a from/to ports. + * + * @param from_port port number in host order + * @param to_port port number in host order + * @return GRE KEY in host order + */ +static inline u_int32_t traffic_selector_grekey(u_int16_t from_port, u_int16_t to_port) +{ + return (from_port << 16) | to_port; +} + +/** + * Split 32-bit GRE KEY in host order to from/to ports. + * + * @param grekey grekey in host order + * @param from_port from port in host order + * @param to_port to port in host order + */ +static inline void traffic_selector_split_grekey(u_int32_t grekey, u_int16_t *from_port, u_int16_t *to_port) +{ + *from_port = grekey >> 16; + *to_port = grekey & 0xffff; +} + +/** * Create a new traffic selector using human readable params. * * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they -- 2.5.3