From 9ebc52171e912a58288c24b74668ffa7af61bee8 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 +- .../plugins/kernel_netlink/kernel_netlink_ipsec.c | 40 ++++++++++--- 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 ++--- 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 ae0b19a..b8a6500 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, + uint16_t from_port, uint16_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, + uint16_t from_port, uint16_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, + uint16_t from_port, uint16_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_; + uint16_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; uint8_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/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 9c2a7c3..c39db9d 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -812,7 +812,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 @@ -836,7 +847,7 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) { u_char *addr; uint8_t prefixlen; - uint16_t port = 0; + uint16_t from_port = 0, to_port = 65535; host_t *host = NULL; if (src) @@ -845,7 +856,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 @@ -854,14 +865,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. @@ -878,7 +902,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/libcharon/plugins/stroke/stroke_config.c b/src/libcharon/plugins/stroke/stroke_config.c index f2d1104..9caf12e 100644 --- a/src/libcharon/plugins/stroke/stroke_config.c +++ b/src/libcharon/plugins/stroke/stroke_config.c @@ -941,6 +941,11 @@ static bool parse_protoport(char *token, uint16_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 30e3484..553f04c 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -626,8 +626,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 bbd1cb0..fe5d33d 100644 --- a/src/libcharon/sa/ikev1/tasks/quick_mode.c +++ b/src/libcharon/sa/ikev1/tasks/quick_mode.c @@ -555,9 +555,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); } @@ -568,7 +568,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; @@ -578,20 +578,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/libstrongswan/selectors/traffic_selector.c b/src/libstrongswan/selectors/traffic_selector.c index da3ba97..ee2af19 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, uint16_t port) } /** + * Print GRE key + */ +static int print_grekey(printf_hook_data_t *data, uint16_t from_port, uint16_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, @@ -312,7 +320,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; @@ -397,7 +409,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 cc66c34..a2a95b9 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 */ uint16_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 */ uint16_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 uint32_t traffic_selector_grekey(uint16_t from_port, uint16_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(uint32_t grekey, uint16_t *from_port, uint16_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.9.1