diff --git a/man/ipsec.conf.5.in b/man/ipsec.conf.5.in index f83c45116..92be67000 100644 --- a/man/ipsec.conf.5.in +++ b/man/ipsec.conf.5.in @@ -858,6 +858,14 @@ Instead of omitting either value can be used to the same effect, e.g. .BR leftsubnet=fec1::1[udp/%any],10.0.0.0/16[%any/53] . +If the protocol is +.B icmp +or +.B ipv6-icmp +the port is interpreted as ICMP message type if it is less than 256 or as type +and code if it is greater or equal to 256, with the type in the most significant +8 bits and the code in the least significant 8 bits. + The port value can alternatively take the value .B %opaque for RFC 4301 OPAQUE selectors, or a numerical range in the form diff --git a/src/_updown/_updown.in b/src/_updown/_updown.in index ca0398ab7..c68c23d8a 100644 --- a/src/_updown/_updown.in +++ b/src/_updown/_updown.in @@ -78,7 +78,8 @@ # # PLUTO_MY_PORT # is the UDP/TCP port to which the IPsec SA is -# restricted on our side. +# restricted on our side. For ICMP/ICMPv6 this contains the +# message type, and PLUTO_PEER_PORT the message code. # # PLUTO_PEER # is the IP address of our peer. @@ -97,7 +98,8 @@ # # PLUTO_PEER_PORT # is the UDP/TCP port to which the IPsec SA is -# restricted on the peer side. +# restricted on the peer side. For ICMP/ICMPv6 this contains the +# message code, and PLUTO_MY_PORT the message type. # # PLUTO_XAUTH_ID # is an optional user ID employed by the XAUTH protocol @@ -288,16 +290,41 @@ else IPSEC_POLICY_OUT="$IPSEC_POLICY --dir out" fi +# use protocol specific options to set ports +case "$PLUTO_MY_PROTOCOL" in +1) # ICMP + ICMP_TYPE_OPTION="--icmp-type" + ;; +58) # ICMPv6 + ICMP_TYPE_OPTION="--icmpv6-type" + ;; +*) + ;; +esac + # are there port numbers? if [ "$PLUTO_MY_PORT" != 0 ] then - S_MY_PORT="--sport $PLUTO_MY_PORT" - D_MY_PORT="--dport $PLUTO_MY_PORT" + if [ -n "$ICMP_TYPE_OPTION" ] + then + S_MY_PORT="$ICMP_TYPE_OPTION $PLUTO_MY_PORT" + D_MY_PORT="$ICMP_TYPE_OPTION $PLUTO_MY_PORT" + else + S_MY_PORT="--sport $PLUTO_MY_PORT" + D_MY_PORT="--dport $PLUTO_MY_PORT" + fi fi if [ "$PLUTO_PEER_PORT" != 0 ] then - S_PEER_PORT="--sport $PLUTO_PEER_PORT" - D_PEER_PORT="--dport $PLUTO_PEER_PORT" + if [ -n "$ICMP_TYPE_OPTION" ] + then + # the syntax is --icmp[v6]-type type[/code], so add it to the existing option + S_MY_PORT="$S_MY_PORT/$PLUTO_PEER_PORT" + D_MY_PORT="$D_MY_PORT/$PLUTO_PEER_PORT" + else + S_PEER_PORT="--sport $PLUTO_PEER_PORT" + D_PEER_PORT="--dport $PLUTO_PEER_PORT" + fi fi # resolve octal escape sequences diff --git a/src/libcharon/plugins/updown/updown_listener.c b/src/libcharon/plugins/updown/updown_listener.c index 3c3994b81..12dbc88a0 100644 --- a/src/libcharon/plugins/updown/updown_listener.c +++ b/src/libcharon/plugins/updown/updown_listener.c @@ -174,6 +174,27 @@ static char *make_vip_vars(private_updown_listener_t *this, ike_sa_t *ike_sa) return strdup(total); } +/** + * Determine proper values for port env variable + */ +static u_int16_t get_port(traffic_selector_t *me, + traffic_selector_t *other, bool local) +{ + switch (max(me->get_protocol(me), other->get_protocol(other))) + { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + { + u_int16_t port = me->get_from_port(me); + + port = max(port, other->get_from_port(other)); + return local ? traffic_selector_icmp_type(port) + : traffic_selector_icmp_code(port); + } + } + return local ? me->get_from_port(me) : other->get_from_port(other); +} + METHOD(listener_t, child_updown, bool, private_updown_listener_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa, bool up) @@ -341,11 +362,11 @@ METHOD(listener_t, child_updown, bool, ike_sa->get_unique_id(ike_sa), me, ike_sa->get_my_id(ike_sa), my_client, my_client_mask, - my_ts->get_from_port(my_ts), + get_port(my_ts, other_ts, TRUE), my_ts->get_protocol(my_ts), other, ike_sa->get_other_id(ike_sa), other_client, other_client_mask, - other_ts->get_from_port(other_ts), + get_port(my_ts, other_ts, FALSE), other_ts->get_protocol(other_ts), xauth, virtual_ip, diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c index e23f22023..8352b9311 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2012 Tobias Brunner + * Copyright (C) 2006-2013 Tobias Brunner * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2008 Andreas Steffen * Copyright (C) 2006-2007 Fabian Hartmann, Noah Heusser @@ -744,6 +744,17 @@ 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) && + (sel.dport || sel.sport)) + { + /* the ICMP type is encoded in the most significant 8 bits and the ICMP + * code in the least significant 8 bits of the port. via XFRM we have + * to pass the ICMP type and code in the source and destination port + * fields, respectively. the port is in network byte order. */ + u_int16_t port = max(sel.dport, sel.sport); + sel.sport = htons(port & 0xff); + sel.dport = htons(port >> 8); + } sel.ifindex = 0; sel.user = 0; @@ -766,7 +777,7 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) prefixlen = sel->prefixlen_s; if (sel->sport_mask) { - port = htons(sel->sport); + port = ntohs(sel->sport); } } else @@ -775,10 +786,15 @@ static traffic_selector_t* selector2ts(struct xfrm_selector *sel, bool src) prefixlen = sel->prefixlen_d; if (sel->dport_mask) { - port = htons(sel->dport); + port = ntohs(sel->dport); } } - + 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); + } /* The Linux 2.6 kernel does not set the selector's family field, * so as a kludge we additionally test the prefix length. */ diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index feff3a753..98a6f81d5 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -870,6 +870,28 @@ static int lookup_algorithm(transform_type_t type, int ikev2) return alg; } +/** + * Helper to set a port in a sockaddr_t, the port has to be in host order + */ +static void set_port(sockaddr_t *addr, u_int16_t port) +{ + switch (addr->sa_family) + { + case AF_INET: + { + struct sockaddr_in *sin = (struct sockaddr_in*)addr; + sin->sin_port = htons(port); + break; + } + case AF_INET6: + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6*)addr; + sin6->sin6_port = htons(port); + break; + } + } +} + /** * Copy a host_t as sockaddr_t to the given memory location. * @return the number of bytes copied @@ -878,37 +900,38 @@ static size_t hostcpy(void *dest, host_t *host, bool include_port) { sockaddr_t *addr = host->get_sockaddr(host), *dest_addr = dest; socklen_t *len = host->get_sockaddr_len(host); - u_int16_t port = htons(host->get_port(host)); memcpy(dest, addr, *len); #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN dest_addr->sa_len = *len; #endif - switch (dest_addr->sa_family) + if (!include_port) { - case AF_INET: - { - struct sockaddr_in *sin = dest; - sin->sin_port = include_port ? port : 0; - break; - } - case AF_INET6: - { - struct sockaddr_in6 *sin6 = dest; - sin6->sin6_port = include_port ? port : 0; - break; - } + set_port(dest_addr, 0); } return *len; } /** - * add a host behind an sadb_address extension + * Copy a host_t as sockaddr_t to the given memory location and map the port to + * ICMP/ICMPv6 message type/code as the Linux kernel expects it, that is, the + * type in the source and the code in the destination address. + * @return the number of bytes copied */ -static void host2ext(host_t *host, struct sadb_address *ext, bool include_port) +static size_t hostcpy_icmp(void *dest, host_t *host, u_int16_t type) { - size_t len = hostcpy(ext + 1, host, include_port); - ext->sadb_address_len = PFKEY_LEN(sizeof(*ext) + len); + size_t len; + + len = hostcpy(dest, host, TRUE); + if (type == SADB_EXT_ADDRESS_SRC) + { + set_port(dest, traffic_selector_icmp_type(host->get_port(host))); + } + else + { + set_port(dest, traffic_selector_icmp_code(host->get_port(host))); + } + return len; } /** @@ -918,10 +941,20 @@ static void add_addr_ext(struct sadb_msg *msg, host_t *host, u_int16_t type, u_int8_t proto, u_int8_t prefixlen, bool include_port) { struct sadb_address *addr = (struct sadb_address*)PFKEY_EXT_ADD_NEXT(msg); + size_t len; + addr->sadb_address_exttype = type; addr->sadb_address_proto = proto; addr->sadb_address_prefixlen = prefixlen; - host2ext(host, addr, include_port); + if (proto == IPPROTO_ICMP || proto == IPPROTO_ICMPV6) + { + len = hostcpy_icmp(addr + 1, host, type); + } + else + { + len = hostcpy(addr + 1, host, include_port); + } + addr->sadb_address_len = PFKEY_LEN(sizeof(*addr) + len); PFKEY_EXT_ADD(msg, addr); } diff --git a/src/libstrongswan/library.h b/src/libstrongswan/library.h index da2798014..e53cf09e2 100644 --- a/src/libstrongswan/library.h +++ b/src/libstrongswan/library.h @@ -70,6 +70,9 @@ * @defgroup jobs jobs * @ingroup processing * + * @defgroup selectors selectors + * @ingroup libstrongswan + * * @defgroup threading threading * @ingroup libstrongswan * diff --git a/src/libstrongswan/selectors/traffic_selector.c b/src/libstrongswan/selectors/traffic_selector.c index 75a8717dd..b9d9b6556 100644 --- a/src/libstrongswan/selectors/traffic_selector.c +++ b/src/libstrongswan/selectors/traffic_selector.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Tobias Brunner + * Copyright (C) 2007-2013 Tobias Brunner * Copyright (C) 2005-2007 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -193,6 +193,22 @@ static bool is_any(private_traffic_selector_t *this) return this->from_port == 0 && this->to_port == 0xffff; } +/** + * Print ICMP/ICMPv6 type and code + */ +static int print_icmp(printf_hook_data_t *data, u_int16_t port) +{ + u_int8_t type, code; + + type = traffic_selector_icmp_type(port); + code = traffic_selector_icmp_code(port); + if (code) + { + return print_in_hook(data, "%d(%d)", type, code); + } + return print_in_hook(data, "%d", type); +} + /** * Described in header. */ @@ -302,20 +318,35 @@ int traffic_selector_printf_hook(printf_hook_data_t *data, { struct servent *serv; - serv = getservbyport(htons(this->from_port), serv_proto); - if (serv) + if (this->protocol == IPPROTO_ICMP || + this->protocol == IPPROTO_ICMPV6) { - written += print_in_hook(data, "%s", serv->s_name); + written += print_icmp(data, this->from_port); } else { - written += print_in_hook(data, "%d", this->from_port); + serv = getservbyport(htons(this->from_port), serv_proto); + if (serv) + { + written += print_in_hook(data, "%s", serv->s_name); + } + else + { + written += print_in_hook(data, "%d", this->from_port); + } } } else if (is_opaque(this)) { written += print_in_hook(data, "OPAQUE"); } + else if (this->protocol == IPPROTO_ICMP || + this->protocol == IPPROTO_ICMPV6) + { + written += print_icmp(data, this->from_port); + written += print_in_hook(data, "-"); + written += print_icmp(data, this->to_port); + } else { written += print_in_hook(data, "%d-%d", @@ -910,6 +941,10 @@ static private_traffic_selector_t *traffic_selector_create(u_int8_t protocol, .protocol = protocol, .type = type, ); - + if (protocol == IPPROTO_ICMP || protocol == IPPROTO_ICMPV6) + { + this->from_port = from_port < 256 ? from_port << 8 : from_port; + this->to_port = to_port < 256 ? to_port << 8 : to_port; + } return this; } diff --git a/src/libstrongswan/selectors/traffic_selector.h b/src/libstrongswan/selectors/traffic_selector.h index 0de358b99..ab6813acc 100644 --- a/src/libstrongswan/selectors/traffic_selector.h +++ b/src/libstrongswan/selectors/traffic_selector.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 Tobias Brunner + * Copyright (C) 2007-2013 Tobias Brunner * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -17,7 +17,7 @@ /** * @defgroup traffic_selector traffic_selector - * @{ @ingroup config + * @{ @ingroup selectors */ #ifndef TRAFFIC_SELECTOR_H_ @@ -62,7 +62,12 @@ extern enum_name_t *ts_type_name; * Object representing a traffic selector entry. * * A traffic selector defines an range of addresses - * and a range of ports. IPv6 is not fully supported yet. + * and a range of ports. + * + * If the protocol is ICMP or ICMPv6 the ICMP type and code are stored in the + * port field as follows: The message type is placed in the most significant + * 8 bits and the code in the least significant 8 bits. Utility functions are + * provided to extract the individual values. */ struct traffic_selector_t { @@ -109,7 +114,11 @@ struct traffic_selector_t { * Get starting port of this ts. * * Port is in host order, since the parser converts it. - * Size depends on protocol. + * + * If the protocol is ICMP/ICMPv6 the ICMP type and code are stored in this + * field as follows: The message type is placed in the most significant + * 8 bits and the code in the least significant 8 bits. Use the utility + * functions to extract them. * * @return port */ @@ -119,7 +128,11 @@ struct traffic_selector_t { * Get ending port of this ts. * * Port is in host order, since the parser converts it. - * Size depends on protocol. + * + * If the protocol is ICMP/ICMPv6 the ICMP type and code are stored in this + * field as follows: The message type is placed in the most significant + * 8 bits and the code in the least significant 8 bits. Use the utility + * functions to extract them. * * @return port */ @@ -213,9 +226,36 @@ struct traffic_selector_t { void (*destroy) (traffic_selector_t *this); }; +/** + * Extract the ICMP/ICMPv6 message type from a port in host order + * + * @param port port number in host order + * @return ICMP/ICMPv6 message type + */ +static inline u_int8_t traffic_selector_icmp_type(u_int16_t port) +{ + return port >> 8; +} + +/** + * Extract the ICMP/ICMPv6 message code from a port in host order + * + * @param port port number in host order + * @return ICMP/ICMPv6 message code + */ +static inline u_int8_t traffic_selector_icmp_code(u_int16_t port) +{ + return port & 0xff; +} + /** * Create a new traffic selector using human readable params. * + * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they + * are less than 256 the value is assumed to be a message type, if they are + * greater or equal to 256 they are assumed to be type and code as defined + * for traffic_selector_t. + * * @param protocol protocol for this ts, such as TCP or UDP * @param type type of following addresses, such as TS_IPV4_ADDR_RANGE * @param from_addr start of address range as string @@ -236,6 +276,11 @@ traffic_selector_t *traffic_selector_create_from_string( /** * Create a traffic selector from a CIDR string. * + * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they + * are less than 256 the value is assumed to be a message type, if they are + * greater or equal to 256 they are assumed to be type and code as defined + * for traffic_selector_t. + * * @param string CIDR string, such as 10.1.0.0/16 * @param protocol protocol for this ts, such as TCP or UDP * @param from_port start of allowed port range @@ -253,6 +298,11 @@ traffic_selector_t *traffic_selector_create_from_cidr( * But the parser gives us this data in this format, so we * don't have to convert twice. * + * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they + * are less than 256 the value is assumed to be a message type, if they are + * greater or equal to 256 they are assumed to be type and code as defined + * for traffic_selector_t. + * * @param protocol protocol for this ts, such as TCP or UDP * @param type type of following addresses, such as TS_IPV4_ADDR_RANGE * @param from_address start of address range, network order @@ -284,8 +334,12 @@ traffic_selector_t *traffic_selector_create_from_rfc3779_format(ts_type_t type, * is sufficient. This constructor creates a traffic selector for * all protocols, all ports and the address range specified by the * subnet. - * Additionally, a protocol and a port may be specified. Port ranges - * are not supported via this constructor. + * Additionally, a protocol and ports may be specified. + * + * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they + * are less than 256 the value is assumed to be a message type, if they are + * greater or equal to 256 they are assumed to be type and code as defined + * for traffic_selector_t. * * @param net subnet to use * @param netbits size of the subnet, as used in e.g. 192.168.0.0/24 notation @@ -307,6 +361,10 @@ traffic_selector_t *traffic_selector_create_from_subnet( * created at runtime using the external/virtual IP. Using this constructor, * a call to set_address() sets this traffic selector to the supplied host. * + * If protocol is ICMP or ICMPv6 the ports are interpreted as follows: If they + * are less than 256 the value is assumed to be a message type, if they are + * greater or equal to 256 they are assumed to be type and code as defined + * for traffic_selector_t. * * @param protocol upper layer protocl to allow * @param from_port start of allowed port range