aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Willi <martin@revosec.ch>2013-06-19 16:36:27 +0200
committerMartin Willi <martin@revosec.ch>2013-06-19 16:36:27 +0200
commit4e8142e8e9b45f11460db2d360f20e207ed7e6d4 (patch)
treeb1aec2210ece0afb2a12f0a9196e20e6f2ac05a4
parent4f88ad669a2a5d65bd7e6d60896df14246f58a5e (diff)
parent24df067810993dc9736f7bcc274d4063d4d1c721 (diff)
downloadstrongswan-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.in58
-rw-r--r--src/libcharon/plugins/stroke/stroke_config.c116
-rw-r--r--src/libcharon/sa/ikev2/tasks/child_create.c186
-rw-r--r--src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c6
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: