diff options
author | Martin Willi <martin@revosec.ch> | 2013-06-19 16:36:27 +0200 |
---|---|---|
committer | Martin Willi <martin@revosec.ch> | 2013-06-19 16:36:27 +0200 |
commit | 4e8142e8e9b45f11460db2d360f20e207ed7e6d4 (patch) | |
tree | b1aec2210ece0afb2a12f0a9196e20e6f2ac05a4 | |
parent | 4f88ad669a2a5d65bd7e6d60896df14246f58a5e (diff) | |
parent | 24df067810993dc9736f7bcc274d4063d4d1c721 (diff) | |
download | strongswan-4e8142e8e9b45f11460db2d360f20e207ed7e6d4.tar.bz2 strongswan-4e8142e8e9b45f11460db2d360f20e207ed7e6d4.tar.xz |
Merge branch 'nat-transport'
Enable transport mode in NAT situations when using IKEv2. Additionally brings
an extended leftsubnet format, where each subnet can take a separate protocol
and port.
-rw-r--r-- | man/ipsec.conf.5.in | 58 | ||||
-rw-r--r-- | src/libcharon/plugins/stroke/stroke_config.c | 116 | ||||
-rw-r--r-- | src/libcharon/sa/ikev2/tasks/child_create.c | 186 | ||||
-rw-r--r-- | src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c | 6 |
4 files changed, 306 insertions, 60 deletions
diff --git a/man/ipsec.conf.5.in b/man/ipsec.conf.5.in index 4ee884bcc..22efa4908 100644 --- a/man/ipsec.conf.5.in +++ b/man/ipsec.conf.5.in @@ -731,29 +731,10 @@ different from the default additionally requires a socket implementation that listens on this port. .TP .BR leftprotoport " = <protocol>/<port>" -restrict the traffic selector to a single protocol and/or port. -Examples: -.B leftprotoport=tcp/http -or -.B leftprotoport=6/80 -or -.B leftprotoport=udp -or -.BR leftprotoport=/53 . -Instead of omitting either value -.B %any -can be used to the same effect, e.g. -.B leftprotoport=udp/%any -or -.BR leftprotoport=%any/53 . - -The port value can alternatively take the value -.B %opaque -for RFC 4301 OPAQUE selectors, or a numerical range in the form -.BR 1024-65535 . -None of the kernel backends currently supports opaque or port ranges and uses -.B %any -for policy installation instead. +restrict the traffic selector to a single protocol and/or port. This option +is now deprecated, protocol/port information can be defined for each subnet +directly in +.BR leftsubnet . .TP .BR leftsigkey " = <raw public key> | <path to public key>" the left participant's public key for public key signature authentication, @@ -807,7 +788,7 @@ echoed back. Also supported are address pools expressed as or the use of an external IP address pool using %\fIpoolname\fR, where \fIpoolname\fR is the name of the IP address pool used for the lookup. .TP -.BR leftsubnet " = <ip subnet>" +.BR leftsubnet " = <ip subnet>[:<proto/port>][,...]" private subnet behind the left participant, expressed as \fInetwork\fB/\fInetmask\fR; if omitted, essentially assumed to be \fIleft\fB/32\fR, @@ -818,6 +799,35 @@ implementations, make sure to configure identical subnets in such configurations. IKEv2 supports multiple subnets separated by commas. IKEv1 only interprets the first subnet of such a definition, unless the Cisco Unity extension plugin is enabled. + +The part in each subnet following an optional colon specifies a protocol/port +to restrict the selector for that subnet. + +Example: +.BR leftsubnet=10.0.0.1:tcp/http,10.0.0.2:6/80,10.0.0.3:udp,10.0.0.0/16:/53 . +Instead of omitting either value +.B %any +can be used to the same effect, e.g. +.BR leftsubnet=10.0.0.3:udp/%any,10.0.0.0/16=%any/53 . + +The port value can alternatively take the value +.B %opaque +for RFC 4301 OPAQUE selectors, or a numerical range in the form +.BR 1024-65535 . +None of the kernel backends currently supports opaque or port ranges and uses +.B %any +for policy installation instead. + +Instead of specifying a subnet, +.B %dynamic +can be used to replace it with the IKE address, having the same effect +as omitting +.B leftsubnet +completely. Using +.B %dynamic +can be used to define multiple dynamic selectors, each having a potentially +different protocol/port definiton. + .TP .BR leftupdown " = <path>" what ``updown'' script to run to adjust routing and/or firewalling diff --git a/src/libcharon/plugins/stroke/stroke_config.c b/src/libcharon/plugins/stroke/stroke_config.c index 988129f03..64af5bb9c 100644 --- a/src/libcharon/plugins/stroke/stroke_config.c +++ b/src/libcharon/plugins/stroke/stroke_config.c @@ -21,6 +21,8 @@ #include <threading/mutex.h> #include <utils/lexparser.h> +#include <netdb.h> + typedef struct private_stroke_config_t private_stroke_config_t; /** @@ -883,6 +885,89 @@ static peer_cfg_t *build_peer_cfg(private_stroke_config_t *this, } /** + * Parse a protoport specifier + */ +static bool parse_protoport(char *token, u_int16_t *from_port, + u_int16_t *to_port, u_int8_t *protocol) +{ + char *sep, *port = "", *endptr; + struct protoent *proto; + struct servent *svc; + long int p; + + sep = strchr(token, '/'); + if (sep) + { /* protocol/port */ + *sep = '\0'; + port = sep + 1; + } + + if (streq(token, "%any")) + { + *protocol = 0; + } + else + { + proto = getprotobyname(token); + if (proto) + { + *protocol = proto->p_proto; + } + else + { + p = strtol(token, &endptr, 0); + if ((*token && *endptr) || p < 0 || p > 0xff) + { + return FALSE; + } + *protocol = (u_int8_t)p; + } + } + if (streq(port, "%any")) + { + *from_port = 0; + *to_port = 0xffff; + } + else if (streq(port, "%opaque")) + { + *from_port = 0xffff; + *to_port = 0; + } + else if (*port) + { + svc = getservbyname(port, NULL); + if (svc) + { + *from_port = *to_port = ntohs(svc->s_port); + } + else + { + p = strtol(port, &endptr, 0); + if (p < 0 || p > 0xffff) + { + return FALSE; + } + *from_port = p; + if (*endptr == '-') + { + port = endptr + 1; + p = strtol(port, &endptr, 0); + if (p < 0 || p > 0xffff) + { + return FALSE; + } + } + *to_port = p; + if (*endptr) + { + return FALSE; + } + } + } + return TRUE; +} + +/** * build a traffic selector from a stroke_end */ static void add_ts(private_stroke_config_t *this, @@ -913,13 +998,38 @@ static void add_ts(private_stroke_config_t *this, else { enumerator_t *enumerator; - char *subnet; + char *subnet, *pos; + u_int16_t from_port, to_port; + u_int8_t proto; enumerator = enumerator_create_token(end->subnets, ",", " "); while (enumerator->enumerate(enumerator, &subnet)) { - ts = traffic_selector_create_from_cidr(subnet, end->protocol, - end->from_port, end->to_port); + from_port = end->from_port; + to_port = end->to_port; + proto = end->protocol; + + pos = strchr(subnet, ':'); + if (pos) + { + *(pos++) = '\0'; + if (!parse_protoport(pos, &from_port, &to_port, &proto)) + { + DBG1(DBG_CFG, "invalid proto/port: %s, skipped subnet", + pos); + continue; + } + } + if (streq(subnet, "%dynamic")) + { + ts = traffic_selector_create_dynamic(proto, + from_port, to_port); + } + else + { + ts = traffic_selector_create_from_cidr(subnet, proto, + from_port, to_port); + } if (ts) { child_cfg->add_traffic_selector(child_cfg, local, ts); diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index e4d762ad7..3e5dcc82e 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -343,6 +343,79 @@ static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local) } /** + * Substitude any host address with NATed address in traffic selector + */ +static linked_list_t* get_transport_nat_ts(private_child_create_t *this, + bool local, linked_list_t *in) +{ + enumerator_t *enumerator; + linked_list_t *out; + traffic_selector_t *ts; + host_t *ike, *first = NULL; + u_int8_t mask; + + if (local) + { + ike = this->ike_sa->get_my_host(this->ike_sa); + } + else + { + ike = this->ike_sa->get_other_host(this->ike_sa); + } + + out = linked_list_create(); + + enumerator = in->create_enumerator(in); + while (enumerator->enumerate(enumerator, &ts)) + { + /* require that all selectors match the first "host" selector */ + if (ts->is_host(ts, first)) + { + if (!first) + { + ts->to_subnet(ts, &first, &mask); + } + ts = ts->clone(ts); + ts->set_address(ts, ike); + out->insert_last(out, ts); + } + } + enumerator->destroy(enumerator); + DESTROY_IF(first); + + return out; +} + +/** + * Narrow received traffic selectors with configuration + */ +static linked_list_t* narrow_ts(private_child_create_t *this, bool local, + linked_list_t *in) +{ + linked_list_t *hosts, *nat, *ts; + ike_condition_t cond; + + cond = local ? COND_NAT_HERE : COND_NAT_THERE; + hosts = get_dynamic_hosts(this->ike_sa, local); + + if (this->mode == MODE_TRANSPORT && + this->ike_sa->has_condition(this->ike_sa, cond)) + { + nat = get_transport_nat_ts(this, local, in); + ts = this->config->get_traffic_selectors(this->config, local, nat, hosts); + nat->destroy_offset(nat, offsetof(traffic_selector_t, destroy)); + } + else + { + ts = this->config->get_traffic_selectors(this->config, local, in, hosts); + } + + hosts->destroy(hosts); + + return ts; +} + +/** * Install a CHILD_SA for usage, return value: * - FAILED: no acceptable proposal * - INVALID_ARG: diffie hellman group inacceptable @@ -355,7 +428,7 @@ static status_t select_and_install(private_child_create_t *this, chunk_t nonce_i, nonce_r; chunk_t encr_i = chunk_empty, encr_r = chunk_empty; chunk_t integ_i = chunk_empty, integ_r = chunk_empty; - linked_list_t *my_ts, *other_ts, *list; + linked_list_t *my_ts, *other_ts; host_t *me, *other; bool private; @@ -416,24 +489,16 @@ static status_t select_and_install(private_child_create_t *this, { nonce_i = this->my_nonce; nonce_r = this->other_nonce; - my_ts = this->tsi; - other_ts = this->tsr; + my_ts = narrow_ts(this, TRUE, this->tsi); + other_ts = narrow_ts(this, FALSE, this->tsr); } else { nonce_r = this->my_nonce; nonce_i = this->other_nonce; - my_ts = this->tsr; - other_ts = this->tsi; + my_ts = narrow_ts(this, TRUE, this->tsr); + other_ts = narrow_ts(this, FALSE, this->tsi); } - list = get_dynamic_hosts(this->ike_sa, TRUE); - my_ts = this->config->get_traffic_selectors(this->config, - TRUE, my_ts, list); - list->destroy(list); - list = get_dynamic_hosts(this->ike_sa, FALSE); - other_ts = this->config->get_traffic_selectors(this->config, - FALSE, other_ts, list); - list->destroy(list); if (this->initiator) { @@ -490,10 +555,9 @@ static status_t select_and_install(private_child_create_t *this, this->mode = MODE_TUNNEL; DBG1(DBG_IKE, "not using transport mode, not host-to-host"); } - else if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) + if (this->config->get_mode(this->config) != MODE_TRANSPORT) { this->mode = MODE_TUNNEL; - DBG1(DBG_IKE, "not using transport mode, connection NATed"); } break; case MODE_BEET: @@ -503,6 +567,10 @@ static status_t select_and_install(private_child_create_t *this, this->mode = MODE_TUNNEL; DBG1(DBG_IKE, "not using BEET mode, not host-to-host"); } + if (this->config->get_mode(this->config) != MODE_BEET) + { + this->mode = MODE_TUNNEL; + } break; default: break; @@ -895,12 +963,6 @@ METHOD(task_t, build_i, status_t, this->proposals = this->config->get_proposals(this->config, this->dh_group == MODP_NONE); this->mode = this->config->get_mode(this->config); - if (this->mode == MODE_TRANSPORT && - this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) - { - this->mode = MODE_TUNNEL; - DBG1(DBG_IKE, "not using transport mode, connection NATed"); - } this->child_sa = child_sa_create(this->ike_sa->get_my_host(this->ike_sa), this->ike_sa->get_other_host(this->ike_sa), this->config, this->reqid, @@ -996,10 +1058,77 @@ static void handle_child_sa_failure(private_child_create_t *this, } } +/** + * Substitute transport mode NAT selectors, if applicable + */ +static linked_list_t* get_ts_if_nat_transport(private_child_create_t *this, + bool local, linked_list_t *in) +{ + linked_list_t *out = NULL; + ike_condition_t cond; + + if (this->mode == MODE_TRANSPORT) + { + cond = local ? COND_NAT_HERE : COND_NAT_THERE; + if (this->ike_sa->has_condition(this->ike_sa, cond)) + { + out = get_transport_nat_ts(this, local, in); + if (out->get_count(out) == 0) + { + out->destroy(out); + out = NULL; + } + } + } + return out; +} + +/** + * Select a matching CHILD config as responder + */ +static child_cfg_t* select_child_cfg(private_child_create_t *this) +{ + peer_cfg_t *peer_cfg; + child_cfg_t *child_cfg = NULL;; + + peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg && this->tsi && this->tsr) + { + linked_list_t *listr, *listi, *tsr, *tsi; + + tsr = get_ts_if_nat_transport(this, TRUE, this->tsr); + tsi = get_ts_if_nat_transport(this, FALSE, this->tsi); + + listr = get_dynamic_hosts(this->ike_sa, TRUE); + listi = get_dynamic_hosts(this->ike_sa, FALSE); + child_cfg = peer_cfg->select_child_cfg(peer_cfg, + tsr ?: this->tsr, tsi ?: this->tsi, + listr, listi); + if ((tsi || tsr) && child_cfg && + child_cfg->get_mode(child_cfg) != MODE_TRANSPORT) + { + /* found a CHILD config, but it doesn't use transport mode */ + child_cfg->destroy(child_cfg); + child_cfg = NULL; + } + if (!child_cfg && (tsi || tsr)) + { + /* no match for the substituted NAT selectors, try it without */ + child_cfg = peer_cfg->select_child_cfg(peer_cfg, + this->tsr, this->tsi, listr, listi); + } + listr->destroy(listr); + listi->destroy(listi); + DESTROY_OFFSET_IF(tsi, offsetof(traffic_selector_t, destroy)); + DESTROY_OFFSET_IF(tsr, offsetof(traffic_selector_t, destroy)); + } + + return child_cfg; +} + METHOD(task_t, build_r, status_t, private_child_create_t *this, message_t *message) { - peer_cfg_t *peer_cfg; payload_t *payload; enumerator_t *enumerator; bool no_dh = TRUE, ike_auth = FALSE; @@ -1034,19 +1163,10 @@ METHOD(task_t, build_r, status_t, return SUCCESS; } - peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); - if (!this->config && peer_cfg && this->tsi && this->tsr) + if (this->config == NULL) { - linked_list_t *listr, *listi; - - listr = get_dynamic_hosts(this->ike_sa, TRUE); - listi = get_dynamic_hosts(this->ike_sa, FALSE); - this->config = peer_cfg->select_child_cfg(peer_cfg, - this->tsr, this->tsi, listr, listi); - listr->destroy(listr); - listi->destroy(listi); + this->config = select_child_cfg(this); } - if (this->config == NULL) { DBG1(DBG_IKE, "traffic selectors %#R=== %#R inacceptable", diff --git a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c index 47e725c1c..2f8cb6b3e 100644 --- a/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -1224,6 +1224,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t, if(src_ts && dst_ts) { sa->sel = ts2selector(src_ts, dst_ts); + /* don't install proto/port on SA. This would break + * potential secondary SAs for the same address using a + * different prot/port. */ + sa->sel.proto = 0; + sa->sel.dport = sa->sel.dport_mask = 0; + sa->sel.sport = sa->sel.sport_mask = 0; } break; default: |