]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
xfrm: iptfs: share page fragments of inner packets
authorChristian Hopps <chopps@labn.net>
Thu, 14 Nov 2024 07:07:05 +0000 (02:07 -0500)
committerSteffen Klassert <steffen.klassert@secunet.com>
Thu, 5 Dec 2024 09:01:50 +0000 (10:01 +0100)
When possible rather than appending secondary (aggregated) inner packets
to the fragment list, share their page fragments with the outer IPTFS
packet. This allows for more efficient packet transmission.

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>
net/xfrm/xfrm_iptfs.c

index c4cff005ea9a79b1bfb155299f355c2bab15facc..7bf18f472fedf39b042cb8d14f10362091b8fb3e 100644 (file)
@@ -9,6 +9,7 @@
 
 #include <linux/kernel.h>
 #include <linux/icmpv6.h>
+#include <linux/skbuff_ref.h>
 #include <net/gro.h>
 #include <net/icmp.h>
 #include <net/ip6_route.h>
@@ -90,6 +91,23 @@ struct xfrm_iptfs_data {
 static u32 iptfs_get_inner_mtu(struct xfrm_state *x, int outer_mtu);
 static enum hrtimer_restart iptfs_delay_timer(struct hrtimer *me);
 
+/* ======================= */
+/* IPTFS SK_BUFF Functions */
+/* ======================= */
+
+/**
+ * iptfs_skb_head_to_frag() - initialize a skb_frag_t based on skb head data
+ * @skb: skb with the head data
+ * @frag: frag to initialize
+ */
+static void iptfs_skb_head_to_frag(const struct sk_buff *skb, skb_frag_t *frag)
+{
+       struct page *page = virt_to_head_page(skb->data);
+       unsigned char *addr = (unsigned char *)page_address(page);
+
+       skb_frag_fill_page_desc(frag, page, skb->data - addr, skb_headlen(skb));
+}
+
 /* ================================= */
 /* IPTFS Sending (ingress) Functions */
 /* ================================= */
@@ -297,14 +315,44 @@ static struct sk_buff **iptfs_rehome_fraglist(struct sk_buff **nextp, struct sk_
        return nextp;
 }
 
+static void iptfs_consume_frags(struct sk_buff *to, struct sk_buff *from)
+{
+       struct skb_shared_info *fromi = skb_shinfo(from);
+       struct skb_shared_info *toi = skb_shinfo(to);
+       unsigned int new_truesize;
+
+       /* If we have data in a head page, grab it */
+       if (!skb_headlen(from)) {
+               new_truesize = SKB_TRUESIZE(skb_end_offset(from));
+       } else {
+               iptfs_skb_head_to_frag(from, &toi->frags[toi->nr_frags]);
+               skb_frag_ref(to, toi->nr_frags++);
+               new_truesize = SKB_DATA_ALIGN(sizeof(struct sk_buff));
+       }
+
+       /* Move any other page fragments rather than copy */
+       memcpy(&toi->frags[toi->nr_frags], fromi->frags,
+              sizeof(fromi->frags[0]) * fromi->nr_frags);
+       toi->nr_frags += fromi->nr_frags;
+       fromi->nr_frags = 0;
+       from->data_len = 0;
+       from->len = 0;
+       to->truesize += from->truesize - new_truesize;
+       from->truesize = new_truesize;
+
+       /* We are done with this SKB */
+       consume_skb(from);
+}
+
 static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list)
 {
        struct xfrm_iptfs_data *xtfs = x->mode_data;
        struct sk_buff *skb, *skb2, **nextp;
-       struct skb_shared_info *shi;
+       struct skb_shared_info *shi, *shi2;
 
        while ((skb = __skb_dequeue(list))) {
                u32 mtu = iptfs_get_cur_pmtu(x, xtfs, skb);
+               bool share_ok = true;
                int remaining;
 
                /* protocol comes to us cleared sometimes */
@@ -349,7 +397,7 @@ static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list)
 
                /* Re-home (un-nest) nested fragment lists. We need to do this
                 * b/c we will simply be appending any following aggregated
-                * inner packets to the frag list.
+                * inner packets using the frag list.
                 */
                shi = skb_shinfo(skb);
                nextp = &shi->frag_list;
@@ -360,6 +408,9 @@ static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list)
                                nextp = &(*nextp)->next;
                }
 
+               if (shi->frag_list || skb_cloned(skb) || skb_shared(skb))
+                       share_ok = false;
+
                /* See if we have enough space to simply append.
                 *
                 * NOTE: Maybe do not append if we will be mis-aligned,
@@ -386,17 +437,35 @@ static void iptfs_output_queued(struct xfrm_state *x, struct sk_buff_head *list)
                                }
                        }
 
+                       /* skb->pp_recycle is passed to __skb_flag_unref for all
+                        * frag pages so we can only share pages with skb's who
+                        * match ourselves.
+                        */
+                       shi2 = skb_shinfo(skb2);
+                       if (share_ok &&
+                           (shi2->frag_list ||
+                            (!skb2->head_frag && skb_headlen(skb)) ||
+                            skb->pp_recycle != skb2->pp_recycle ||
+                            skb_zcopy(skb2) ||
+                            (shi->nr_frags + shi2->nr_frags + 1 > MAX_SKB_FRAGS)))
+                               share_ok = false;
+
                        /* Do accounting */
                        skb->data_len += skb2->len;
                        skb->len += skb2->len;
                        remaining -= skb2->len;
 
-                       /* Append to the frag_list */
-                       *nextp = skb2;
-                       nextp = &skb2->next;
-                       if (skb_has_frag_list(skb2))
-                               nextp = iptfs_rehome_fraglist(nextp, skb2);
-                       skb->truesize += skb2->truesize;
+                       if (share_ok) {
+                               iptfs_consume_frags(skb, skb2);
+                       } else {
+                               /* Append to the frag_list */
+                               *nextp = skb2;
+                               nextp = &skb2->next;
+                               if (skb_has_frag_list(skb2))
+                                       nextp = iptfs_rehome_fraglist(nextp,
+                                                                     skb2);
+                               skb->truesize += skb2->truesize;
+                       }
                }
 
                xfrm_output(NULL, skb);