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.
.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
+113 -3
View File
@@ -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;
/**
@@ -882,6 +884,89 @@ static peer_cfg_t *build_peer_cfg(private_stroke_config_t *this,
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
*/
@@ -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);
+153 -33
View File
@@ -342,6 +342,79 @@ static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local)
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:
* - 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 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",
@@ -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: