Merge branch 'nat-transport'
authorMartin Willi <martin@revosec.ch>
Wed, 19 Jun 2013 14:36:27 +0000 (16:36 +0200)
committerMartin Willi <martin@revosec.ch>
Wed, 19 Jun 2013 14:36:27 +0000 (16:36 +0200)
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.

man/ipsec.conf.5.in
src/libcharon/plugins/stroke/stroke_config.c
src/libcharon/sa/ikev2/tasks/child_create.c
src/libhydra/plugins/kernel_netlink/kernel_netlink_ipsec.c

index 4ee884b..22efa49 100644 (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
index 988129f..64af5bb 100644 (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;
 
 /**
@@ -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);
index e4d762a..3e5dcc8 100644 (file)
@@ -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",
index 47e725c..2f8cb6b 100644 (file)
@@ -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: