]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfrm: add mode_cbs module functionality
authorChristian Hopps <chopps@labn.net>
Thu, 14 Nov 2024 07:07:01 +0000 (02:07 -0500)
committerSteffen Klassert <steffen.klassert@secunet.com>
Thu, 5 Dec 2024 09:01:22 +0000 (10:01 +0100)
Add a set of callbacks xfrm_mode_cbs to xfrm_state. These callbacks
enable the addition of new xfrm modes, such as IP-TFS to be defined
in modules.

Signed-off-by: Christian Hopps <chopps@labn.net>
Tested-by: Antony Antony <antony.antony@secunet.com>
Signed-off-by: Steffen Klassert <steffen.klassert@secunet.com>
include/net/xfrm.h
net/xfrm/xfrm_device.c
net/xfrm/xfrm_input.c
net/xfrm/xfrm_output.c
net/xfrm/xfrm_policy.c
net/xfrm/xfrm_state.c
net/xfrm/xfrm_user.c

index 32c09e85a64ce229c1cf1bda88d49f04e0813df5..1ebc09cde6278e999bec0c4b99abe43d1922f007 100644 (file)
@@ -213,6 +213,7 @@ struct xfrm_state {
                u16             family;
                xfrm_address_t  saddr;
                int             header_len;
+               int             enc_hdr_len;
                int             trailer_len;
                u32             extra_flags;
                struct xfrm_mark        smark;
@@ -303,6 +304,9 @@ struct xfrm_state {
         * interpreted by xfrm_type methods. */
        void                    *data;
        u8                      dir;
+
+       const struct xfrm_mode_cbs      *mode_cbs;
+       void                            *mode_data;
 };
 
 static inline struct net *xs_net(struct xfrm_state *x)
@@ -460,6 +464,45 @@ struct xfrm_type_offload {
 int xfrm_register_type_offload(const struct xfrm_type_offload *type, unsigned short family);
 void xfrm_unregister_type_offload(const struct xfrm_type_offload *type, unsigned short family);
 
+/**
+ * struct xfrm_mode_cbs - XFRM mode callbacks
+ * @owner: module owner or NULL
+ * @init_state: Add/init mode specific state in `xfrm_state *x`
+ * @clone_state: Copy mode specific values from `orig` to new state `x`
+ * @destroy_state: Cleanup mode specific state from `xfrm_state *x`
+ * @user_init: Process mode specific netlink attributes from user
+ * @copy_to_user: Add netlink attributes to `attrs` based on state in `x`
+ * @sa_len: Return space required to store mode specific netlink attributes
+ * @get_inner_mtu: Return avail payload space after removing encap overhead
+ * @input: Process received packet from SA using mode
+ * @output: Output given packet using mode
+ * @prepare_output: Add mode specific encapsulation to packet in skb. On return
+ *     `transport_header` should point at ESP header, `network_header` should
+ *     point at outer IP header and `mac_header` should opint at the
+ *     protocol/nexthdr field of the outer IP.
+ *
+ * One should examine and understand the specific uses of these callbacks in
+ * xfrm for further detail on how and when these functions are called. RTSL.
+ */
+struct xfrm_mode_cbs {
+       struct module   *owner;
+       int     (*init_state)(struct xfrm_state *x);
+       int     (*clone_state)(struct xfrm_state *x, struct xfrm_state *orig);
+       void    (*destroy_state)(struct xfrm_state *x);
+       int     (*user_init)(struct net *net, struct xfrm_state *x,
+                            struct nlattr **attrs,
+                            struct netlink_ext_ack *extack);
+       int     (*copy_to_user)(struct xfrm_state *x, struct sk_buff *skb);
+       unsigned int (*sa_len)(const struct xfrm_state *x);
+       u32     (*get_inner_mtu)(struct xfrm_state *x, int outer_mtu);
+       int     (*input)(struct xfrm_state *x, struct sk_buff *skb);
+       int     (*output)(struct net *net, struct sock *sk, struct sk_buff *skb);
+       int     (*prepare_output)(struct xfrm_state *x, struct sk_buff *skb);
+};
+
+int xfrm_register_mode_cbs(u8 mode, const struct xfrm_mode_cbs *mode_cbs);
+void xfrm_unregister_mode_cbs(u8 mode);
+
 static inline int xfrm_af2proto(unsigned int family)
 {
        switch(family) {
index b33c4591e09a4f289f1e067a5a657854452f7f0f..1fe1b07d879d275967a3ccae0c081e4abb6befb5 100644 (file)
@@ -42,7 +42,8 @@ static void __xfrm_mode_tunnel_prep(struct xfrm_state *x, struct sk_buff *skb,
                skb->transport_header = skb->network_header + hsize;
 
        skb_reset_mac_len(skb);
-       pskb_pull(skb, skb->mac_len + x->props.header_len);
+       pskb_pull(skb,
+                 skb->mac_len + x->props.header_len - x->props.enc_hdr_len);
 }
 
 static void __xfrm_mode_beet_prep(struct xfrm_state *x, struct sk_buff *skb,
index 841a60a6fbfea39c7b63333c387c6841a0afcb78..2c4ae61e7e3a0114c5d8d914efc6bc28dbe5634c 100644 (file)
@@ -446,6 +446,9 @@ static int xfrm_inner_mode_input(struct xfrm_state *x,
                WARN_ON_ONCE(1);
                break;
        default:
+               if (x->mode_cbs && x->mode_cbs->input)
+                       return x->mode_cbs->input(x, skb);
+
                WARN_ON_ONCE(1);
                break;
        }
@@ -453,6 +456,10 @@ static int xfrm_inner_mode_input(struct xfrm_state *x,
        return -EOPNOTSUPP;
 }
 
+/* NOTE: encap_type - In addition to the normal (non-negative) values for
+ * encap_type, a negative value of -1 or -2 can be used to resume/restart this
+ * function after a previous invocation early terminated for async operation.
+ */
 int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 {
        const struct xfrm_state_afinfo *afinfo;
@@ -489,6 +496,10 @@ int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type)
 
                family = x->props.family;
 
+               /* An encap_type of -2 indicates reconstructed inner packet */
+               if (encap_type == -2)
+                       goto resume_decapped;
+
                /* An encap_type of -1 indicates async resumption. */
                if (encap_type == -1) {
                        async = 1;
@@ -679,11 +690,14 @@ resume:
 
                XFRM_MODE_SKB_CB(skb)->protocol = nexthdr;
 
-               if (xfrm_inner_mode_input(x, skb)) {
+               err = xfrm_inner_mode_input(x, skb);
+               if (err == -EINPROGRESS)
+                       return 0;
+               else if (err) {
                        XFRM_INC_STATS(net, LINUX_MIB_XFRMINSTATEMODEERROR);
                        goto drop;
                }
-
+resume_decapped:
                if (x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL) {
                        decaps = 1;
                        break;
index e5722c95b8bb38c528cc518cdc3a05e08a338264..ef81359e403830a5e18991bc031193292cf7a395 100644 (file)
@@ -472,6 +472,8 @@ static int xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *skb)
                WARN_ON_ONCE(1);
                break;
        default:
+               if (x->mode_cbs && x->mode_cbs->prepare_output)
+                       return x->mode_cbs->prepare_output(x, skb);
                WARN_ON_ONCE(1);
                break;
        }
index 4408c11c0835f96aa7f1749797e4b725dd7c147b..c04014ee623fbe17723d08263bf681c0186cea89 100644 (file)
@@ -2748,13 +2748,17 @@ static struct dst_entry *xfrm_bundle_create(struct xfrm_policy *policy,
 
                dst1->input = dst_discard;
 
-               rcu_read_lock();
-               afinfo = xfrm_state_afinfo_get_rcu(inner_mode->family);
-               if (likely(afinfo))
-                       dst1->output = afinfo->output;
-               else
-                       dst1->output = dst_discard_out;
-               rcu_read_unlock();
+               if (xfrm[i]->mode_cbs && xfrm[i]->mode_cbs->output) {
+                       dst1->output = xfrm[i]->mode_cbs->output;
+               } else {
+                       rcu_read_lock();
+                       afinfo = xfrm_state_afinfo_get_rcu(inner_mode->family);
+                       if (likely(afinfo))
+                               dst1->output = afinfo->output;
+                       else
+                               dst1->output = dst_discard_out;
+                       rcu_read_unlock();
+               }
 
                xdst_prev = xdst;
 
index 67ca7ac955a376197c487c4a851cc719324b05f8..cf68ba891729f5fb23121666fb4d1e8f741fb5f6 100644 (file)
@@ -515,6 +515,60 @@ static const struct xfrm_mode *xfrm_get_mode(unsigned int encap, int family)
        return NULL;
 }
 
+static const struct xfrm_mode_cbs  __rcu *xfrm_mode_cbs_map[XFRM_MODE_MAX];
+static DEFINE_SPINLOCK(xfrm_mode_cbs_map_lock);
+
+int xfrm_register_mode_cbs(u8 mode, const struct xfrm_mode_cbs *mode_cbs)
+{
+       if (mode >= XFRM_MODE_MAX)
+               return -EINVAL;
+
+       spin_lock_bh(&xfrm_mode_cbs_map_lock);
+       rcu_assign_pointer(xfrm_mode_cbs_map[mode], mode_cbs);
+       spin_unlock_bh(&xfrm_mode_cbs_map_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(xfrm_register_mode_cbs);
+
+void xfrm_unregister_mode_cbs(u8 mode)
+{
+       if (mode >= XFRM_MODE_MAX)
+               return;
+
+       spin_lock_bh(&xfrm_mode_cbs_map_lock);
+       RCU_INIT_POINTER(xfrm_mode_cbs_map[mode], NULL);
+       spin_unlock_bh(&xfrm_mode_cbs_map_lock);
+       synchronize_rcu();
+}
+EXPORT_SYMBOL(xfrm_unregister_mode_cbs);
+
+static const struct xfrm_mode_cbs *xfrm_get_mode_cbs(u8 mode)
+{
+       const struct xfrm_mode_cbs *cbs;
+       bool try_load = true;
+
+       if (mode >= XFRM_MODE_MAX)
+               return NULL;
+
+retry:
+       rcu_read_lock();
+
+       cbs = rcu_dereference(xfrm_mode_cbs_map[mode]);
+       if (cbs && !try_module_get(cbs->owner))
+               cbs = NULL;
+
+       rcu_read_unlock();
+
+       if (mode == XFRM_MODE_IPTFS && !cbs && try_load) {
+               request_module("xfrm-iptfs");
+               try_load = false;
+               goto retry;
+       }
+
+       return cbs;
+}
+
 void xfrm_state_free(struct xfrm_state *x)
 {
        kmem_cache_free(xfrm_state_cache, x);
@@ -523,6 +577,8 @@ EXPORT_SYMBOL(xfrm_state_free);
 
 static void ___xfrm_state_destroy(struct xfrm_state *x)
 {
+       if (x->mode_cbs && x->mode_cbs->destroy_state)
+               x->mode_cbs->destroy_state(x);
        hrtimer_cancel(&x->mtimer);
        del_timer_sync(&x->rtimer);
        kfree(x->aead);
@@ -682,6 +738,7 @@ struct xfrm_state *xfrm_state_alloc(struct net *net)
                x->replay_maxdiff = 0;
                x->pcpu_num = UINT_MAX;
                spin_lock_init(&x->lock);
+               x->mode_data = NULL;
        }
        return x;
 }
@@ -1945,6 +2002,12 @@ static struct xfrm_state *xfrm_state_clone(struct xfrm_state *orig,
        x->new_mapping_sport = 0;
        x->dir = orig->dir;
 
+       x->mode_cbs = orig->mode_cbs;
+       if (x->mode_cbs && x->mode_cbs->clone_state) {
+               if (x->mode_cbs->clone_state(x, orig))
+                       goto error;
+       }
+
        return x;
 
  error:
@@ -2986,6 +3049,9 @@ u32 xfrm_state_mtu(struct xfrm_state *x, int mtu)
        case XFRM_MODE_TUNNEL:
                break;
        default:
+               if (x->mode_cbs && x->mode_cbs->get_inner_mtu)
+                       return x->mode_cbs->get_inner_mtu(x, mtu);
+
                WARN_ON_ONCE(1);
                break;
        }
@@ -3086,6 +3152,12 @@ int __xfrm_init_state(struct xfrm_state *x, bool init_replay, bool offload,
                }
        }
 
+       x->mode_cbs = xfrm_get_mode_cbs(x->props.mode);
+       if (x->mode_cbs) {
+               if (x->mode_cbs->init_state)
+                       err = x->mode_cbs->init_state(x);
+               module_put(x->mode_cbs->owner);
+       }
 error:
        return err;
 }
index 749ec56101ac6f1a0c14e74f79440bd0bf079208..71b452fff8dbd4f50c462dd4bf0b85e2f1770214 100644 (file)
@@ -932,6 +932,12 @@ static struct xfrm_state *xfrm_state_construct(struct net *net,
                        goto error;
        }
 
+       if (x->mode_cbs && x->mode_cbs->user_init) {
+               err = x->mode_cbs->user_init(net, x, attrs, extack);
+               if (err)
+                       goto error;
+       }
+
        return x;
 
 error:
@@ -1347,6 +1353,10 @@ static int copy_to_user_state_extra(struct xfrm_state *x,
                if (ret)
                        goto out;
        }
+       if (x->mode_cbs && x->mode_cbs->copy_to_user)
+               ret = x->mode_cbs->copy_to_user(x, skb);
+       if (ret)
+               goto out;
        if (x->mapping_maxage) {
                ret = nla_put_u32(skb, XFRMA_MTIMER_THRESH, x->mapping_maxage);
                if (ret)
@@ -3606,6 +3616,9 @@ static inline unsigned int xfrm_sa_len(struct xfrm_state *x)
        if (x->nat_keepalive_interval)
                l += nla_total_size(sizeof(x->nat_keepalive_interval));
 
+       if (x->mode_cbs && x->mode_cbs->sa_len)
+               l += x->mode_cbs->sa_len(x);
+
        return l;
 }