support configuring interface SAs for route-based ipsec vpns.
authordlg <dlg@openbsd.org>
Mon, 7 Aug 2023 04:01:29 +0000 (04:01 +0000)
committerdlg <dlg@openbsd.org>
Mon, 7 Aug 2023 04:01:29 +0000 (04:01 +0000)
add "Interface NUMBER" to the config parser to specify that once
SAs have been negotiated with a peer, install the SAs with the
sadb_x_iface extension set up, but skip installing the flows/SPD
entries.

this allows for the negotiation of multiple esp tunnels covering
all traffic between 0.0.0.0/0 to 0.0.0.0/0, and then being able to
do something useful with them using the routing table and sec(4)
interfaces instead of having SPD entries fight over those packets
in the kernel.

this in turn allows interoperation with other ipsec/vpn solutions
that require the negotiation of such tunnels.

support from many including markus@ tobhe@ claudio@ sthen@ patrick@
now is a good time deraadt@

sbin/isakmpd/ipsec.c
sbin/isakmpd/pf_key_v2.c
sbin/isakmpd/sa.h

index 6c3e264..08a90ce 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: ipsec.c,v 1.152 2022/01/16 14:30:11 naddy Exp $    */
+/* $OpenBSD: ipsec.c,v 1.153 2023/08/07 04:01:29 dlg Exp $      */
 /* $EOM: ipsec.c,v 1.143 2000/12/11 23:57:42 niklas Exp $       */
 
 /*
@@ -38,6 +38,7 @@
 #include <arpa/inet.h>
 #include <stdlib.h>
 #include <string.h>
+#include <limits.h>
 
 #include <net/if.h>
 #include <net/pfvar.h>
@@ -131,6 +132,7 @@ static int      ipsec_validate_transform_id(u_int8_t, u_int8_t);
 static int      ipsec_sa_check_flow(struct sa *, void *);
 static int      ipsec_sa_check_flow_any(struct sa *, void *);
 static int      ipsec_sa_tag(struct exchange *, struct sa *, struct sa *);
+static int      ipsec_sa_iface(struct exchange *, struct sa *, struct sa *);
 
 static struct doi ipsec_doi = {
        {0}, IPSEC_DOI_IPSEC,
@@ -272,6 +274,12 @@ ipsec_sa_check_flow_any(struct sa *sa, void *v_arg)
            isa->dport != isa2->dport)
                return 0;
 
+       if ((sa->flags & SA_FLAG_IFACE) != (sa2->flags & SA_FLAG_IFACE))
+               return 0;
+
+       if (sa->flags & SA_FLAG_IFACE)
+               return sa->iface == sa2->iface;
+
        /*
         * If at least one of the IPsec SAs is incomplete, we're done.
         */
@@ -379,6 +387,30 @@ ipsec_sa_tag(struct exchange *exchange, struct sa *sa, struct sa *isakmp_sa)
        return (error);
 }
 
+static int
+ipsec_sa_iface(struct exchange *exchange, struct sa *sa, struct sa *isakmp_sa)
+{
+       char *section, *value;
+       const char *errstr = NULL;
+
+       sa->tag = NULL;
+
+       if (exchange->name == NULL ||
+           (section = exchange->name) == NULL ||
+           (value = conf_get_str(section, "Interface")) == NULL)
+               return (0);     /* ignore if not present */
+
+       sa->iface = strtonum(value, 0, UINT_MAX, &errstr);
+       if (errstr != NULL) {
+               log_error("[%s]:Interface %s", section, errstr);
+               return (-1);
+       }
+
+       sa->flags |= SA_FLAG_IFACE;
+
+       return (0);
+}
+
 /*
  * Do IPsec DOI specific finalizations task for the exchange where MSG was
  * the final message.
@@ -463,6 +495,9 @@ ipsec_finalize_exchange(struct message *msg)
                                if (ipsec_sa_tag(exchange, sa, isakmp_sa) == -1)
                                        return;
 
+                               if (ipsec_sa_iface(exchange, sa, isakmp_sa) == -1)
+                                       return;
+
                                for (proto = TAILQ_FIRST(&sa->protos),
                                    last_proto = 0; proto;
                                    proto = TAILQ_NEXT(proto, link)) {
@@ -514,6 +549,7 @@ ipsec_finalize_exchange(struct message *msg)
                                 * (a.k.a. flow) set up.
                                 */
                                if (!(sa->flags & SA_FLAG_ONDEMAND ||
+                                   sa->flags & SA_FLAG_IFACE ||
                                    conf_get_str("General", "Acquire-Only") ||
                                    acquire_only) &&
                                    pf_key_v2_enable_sa(sa, isakmp_sa))
@@ -1596,7 +1632,8 @@ ipsec_delete_spi(struct sa *sa, struct proto *proto, int incoming)
         * We ignore any errors from the disabling of the flow.
         */
        if (sa->flags & SA_FLAG_READY && !(sa->flags & SA_FLAG_ONDEMAND ||
-           sa->flags & SA_FLAG_REPLACED || acquire_only ||
+           sa->flags & SA_FLAG_REPLACED || sa->flags & SA_FLAG_IFACE ||
+           acquire_only ||
            conf_get_str("General", "Acquire-Only")))
                pf_key_v2_disable_sa(sa, incoming);
 
index 758acf6..cf7a4d5 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: pf_key_v2.c,v 1.204 2022/01/31 23:51:15 sthen Exp $  */
+/* $OpenBSD: pf_key_v2.c,v 1.205 2023/08/07 04:01:30 dlg Exp $  */
 /* $EOM: pf_key_v2.c,v 1.79 2000/12/12 00:33:19 niklas Exp $    */
 
 /*
@@ -890,6 +890,7 @@ pf_key_v2_set_spi(struct sa *sa, struct proto *proto, int incoming,
        struct sadb_protocol flowtype, tprotocol;
        struct sadb_x_udpencap udpencap;
        char           *addr_str, *s;
+       char            iface_str[32];
 
        msg.sadb_msg_type = incoming ? SADB_UPDATE : SADB_ADD;
        switch (proto->proto) {
@@ -1378,16 +1379,37 @@ nodid:
                        goto cleanup;
        }
 
+       if (sa->flags & SA_FLAG_IFACE) {
+               struct sadb_x_iface *siface;
+
+               len = sizeof(*siface);
+               siface = calloc(1, len);
+               if (siface == NULL)
+                       goto cleanup;
+
+               siface->sadb_x_iface_len = len / PF_KEY_V2_CHUNK;
+               siface->sadb_x_iface_exttype = SADB_X_EXT_IFACE;
+               siface->sadb_x_iface_unit = sa->iface;
+               siface->sadb_x_iface_direction = incoming ?
+                   IPSP_DIRECTION_IN : IPSP_DIRECTION_OUT;
+
+               if (pf_key_v2_msg_add(update, (struct sadb_ext *)siface,
+                   PF_KEY_V2_NODE_MALLOCED) == -1)
+                       goto cleanup;
+
+               snprintf(iface_str, sizeof(iface_str), "iface %u", sa->iface);
+       }
+
        /* XXX Here can sensitivity extensions be setup.  */
 
        if (sockaddr2text(dst, &addr_str, 0))
                addr_str = 0;
 
        LOG_DBG((LOG_SYSDEP, 10, "pf_key_v2_set_spi: "
-           "satype %d dst %s SPI 0x%x%s%s", msg.sadb_msg_satype,
+           "satype %d dst %s SPI 0x%x%s%s%s", msg.sadb_msg_satype,
            addr_str ? addr_str : "unknown",
            ntohl(ssa.sadb_sa_spi), sa->tag ? " tag " : "",
-           sa->tag ? sa->tag : ""));
+           sa->tag ? sa->tag : "", iface_str));
 
        free(addr_str);
 
index f9769b4..b42bcca 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: sa.h,v 1.54 2018/01/15 09:54:48 mpi Exp $  */
+/* $OpenBSD: sa.h,v 1.55 2023/08/07 04:01:30 dlg Exp $  */
 /* $EOM: sa.h,v 1.58 2000/10/10 12:39:01 provos Exp $   */
 
 /*
@@ -211,6 +211,9 @@ struct sa {
 
        /* The add a pf tag to packets matching the established SA. */
        char           *tag;
+
+       /* IPsec with Interface SAs, enabled with SA_FLAG_IFACE */
+       unsigned int    iface;
 };
 
 /* This SA is alive.  */
@@ -244,6 +247,9 @@ struct sa {
 #define SA_FLAG_NAT_T_ENABLE   0x100
 #define SA_FLAG_NAT_T_KEEPALIVE        0x200
 
+/* Policy is handled by routing/filtering on the specified iface */
+#define SA_FLAG_IFACE          0x400
+
 extern void     proto_free(struct proto * proto);
 extern int     sa_add_transform(struct sa *, struct payload *, int,
                    struct proto **);