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.
This commit is contained in:
Martin Willi
2013-06-19 16:36:27 +02:00
4 changed files with 306 additions and 60 deletions
+34 -24
View File
@@ -731,29 +731,10 @@ different from the default additionally requires a socket implementation that
listens on this port. listens on this port.
.TP .TP
.BR leftprotoport " = <protocol>/<port>" .BR leftprotoport " = <protocol>/<port>"
restrict the traffic selector to a single protocol and/or port. restrict the traffic selector to a single protocol and/or port. This option
Examples: is now deprecated, protocol/port information can be defined for each subnet
.B leftprotoport=tcp/http directly in
or .BR leftsubnet .
.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.
.TP .TP
.BR leftsigkey " = <raw public key> | <path to public key>" .BR leftsigkey " = <raw public key> | <path to public key>"
the left participant's public key for public key signature authentication, 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, 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. where \fIpoolname\fR is the name of the IP address pool used for the lookup.
.TP .TP
.BR leftsubnet " = <ip subnet>" .BR leftsubnet " = <ip subnet>[:<proto/port>][,...]"
private subnet behind the left participant, expressed as private subnet behind the left participant, expressed as
\fInetwork\fB/\fInetmask\fR; \fInetwork\fB/\fInetmask\fR;
if omitted, essentially assumed to be \fIleft\fB/32\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 configurations. IKEv2 supports multiple subnets separated by commas. IKEv1 only
interprets the first subnet of such a definition, unless the Cisco Unity interprets the first subnet of such a definition, unless the Cisco Unity
extension plugin is enabled. 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 .TP
.BR leftupdown " = <path>" .BR leftupdown " = <path>"
what ``updown'' script to run to adjust routing and/or firewalling what ``updown'' script to run to adjust routing and/or firewalling
+113 -3
View File
@@ -21,6 +21,8 @@
#include <threading/mutex.h> #include <threading/mutex.h>
#include <utils/lexparser.h> #include <utils/lexparser.h>
#include <netdb.h>
typedef struct private_stroke_config_t private_stroke_config_t; typedef struct private_stroke_config_t private_stroke_config_t;
/** /**
@@ -882,6 +884,89 @@ static peer_cfg_t *build_peer_cfg(private_stroke_config_t *this,
return peer_cfg; return peer_cfg;
} }
/**
* 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 * build a traffic selector from a stroke_end
*/ */
@@ -913,13 +998,38 @@ static void add_ts(private_stroke_config_t *this,
else else
{ {
enumerator_t *enumerator; 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, ",", " "); enumerator = enumerator_create_token(end->subnets, ",", " ");
while (enumerator->enumerate(enumerator, &subnet)) while (enumerator->enumerate(enumerator, &subnet))
{ {
ts = traffic_selector_create_from_cidr(subnet, end->protocol, from_port = end->from_port;
end->from_port, end->to_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) if (ts)
{ {
child_cfg->add_traffic_selector(child_cfg, local, ts); child_cfg->add_traffic_selector(child_cfg, local, ts);
+153 -33
View File
@@ -342,6 +342,79 @@ static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local)
return list; return list;
} }
/**
* 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: * Install a CHILD_SA for usage, return value:
* - FAILED: no acceptable proposal * - FAILED: no acceptable proposal
@@ -355,7 +428,7 @@ static status_t select_and_install(private_child_create_t *this,
chunk_t nonce_i, nonce_r; chunk_t nonce_i, nonce_r;
chunk_t encr_i = chunk_empty, encr_r = chunk_empty; chunk_t encr_i = chunk_empty, encr_r = chunk_empty;
chunk_t integ_i = chunk_empty, integ_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; host_t *me, *other;
bool private; bool private;
@@ -416,24 +489,16 @@ static status_t select_and_install(private_child_create_t *this,
{ {
nonce_i = this->my_nonce; nonce_i = this->my_nonce;
nonce_r = this->other_nonce; nonce_r = this->other_nonce;
my_ts = this->tsi; my_ts = narrow_ts(this, TRUE, this->tsi);
other_ts = this->tsr; other_ts = narrow_ts(this, FALSE, this->tsr);
} }
else else
{ {
nonce_r = this->my_nonce; nonce_r = this->my_nonce;
nonce_i = this->other_nonce; nonce_i = this->other_nonce;
my_ts = this->tsr; my_ts = narrow_ts(this, TRUE, this->tsr);
other_ts = this->tsi; 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) if (this->initiator)
{ {
@@ -490,10 +555,9 @@ static status_t select_and_install(private_child_create_t *this,
this->mode = MODE_TUNNEL; this->mode = MODE_TUNNEL;
DBG1(DBG_IKE, "not using transport mode, not host-to-host"); 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; this->mode = MODE_TUNNEL;
DBG1(DBG_IKE, "not using transport mode, connection NATed");
} }
break; break;
case MODE_BEET: case MODE_BEET:
@@ -503,6 +567,10 @@ static status_t select_and_install(private_child_create_t *this,
this->mode = MODE_TUNNEL; this->mode = MODE_TUNNEL;
DBG1(DBG_IKE, "not using BEET mode, not host-to-host"); 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; break;
default: default:
break; break;
@@ -895,12 +963,6 @@ METHOD(task_t, build_i, status_t,
this->proposals = this->config->get_proposals(this->config, this->proposals = this->config->get_proposals(this->config,
this->dh_group == MODP_NONE); this->dh_group == MODP_NONE);
this->mode = this->config->get_mode(this->config); 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->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, 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, METHOD(task_t, build_r, status_t,
private_child_create_t *this, message_t *message) private_child_create_t *this, message_t *message)
{ {
peer_cfg_t *peer_cfg;
payload_t *payload; payload_t *payload;
enumerator_t *enumerator; enumerator_t *enumerator;
bool no_dh = TRUE, ike_auth = FALSE; bool no_dh = TRUE, ike_auth = FALSE;
@@ -1034,19 +1163,10 @@ METHOD(task_t, build_r, status_t,
return SUCCESS; return SUCCESS;
} }
peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); if (this->config == NULL)
if (!this->config && peer_cfg && this->tsi && this->tsr)
{ {
linked_list_t *listr, *listi; this->config = select_child_cfg(this);
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);
} }
if (this->config == NULL) if (this->config == NULL)
{ {
DBG1(DBG_IKE, "traffic selectors %#R=== %#R inacceptable", DBG1(DBG_IKE, "traffic selectors %#R=== %#R inacceptable",
@@ -1224,6 +1224,12 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
if(src_ts && dst_ts) if(src_ts && dst_ts)
{ {
sa->sel = ts2selector(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; break;
default: default: