Thread (22 messages) 22 messages, 2 authors, 2021-02-23

Re: [Patch bpf-next v6 4/8] skmsg: move sk_redir from TCP_SKB_CB to skb

From: Jakub Sitnicki <jakub@cloudflare.com>
Date: 2021-02-23 17:54:04
Also in: bpf

On Mon, Feb 22, 2021 at 08:27 PM CET, Cong Wang wrote:
On Mon, Feb 22, 2021 at 4:20 AM Jakub Sitnicki [off-list ref] wrote:
quoted
On Sat, Feb 20, 2021 at 06:29 AM CET, Cong Wang wrote:
quoted
From: Cong Wang <redacted>

Currently TCP_SKB_CB() is hard-coded in skmsg code, it certainly
does not work for any other non-TCP protocols. We can move them to
skb ext, but it introduces a memory allocation on fast path.

Fortunately, we only need to a word-size to store all the information,
because the flags actually only contains 1 bit so can be just packed
into the lowest bit of the "pointer", which is stored as unsigned
long.

Inside struct sk_buff, '_skb_refdst' can be reused because skb dst is
no longer needed after ->sk_data_ready() so we can just drop it.

Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: Jakub Sitnicki <jakub@cloudflare.com>
Cc: Lorenz Bauer <redacted>
Acked-by: John Fastabend <john.fastabend@gmail.com>
Signed-off-by: Cong Wang <redacted>
---
LGTM. I have some questions (below) that would help me confirm if I
understand the changes, and what could be improved, if anything.

Acked-by: Jakub Sitnicki <jakub@cloudflare.com>
quoted
 include/linux/skbuff.h |  3 +++
 include/linux/skmsg.h  | 35 +++++++++++++++++++++++++++++++++++
 include/net/tcp.h      | 19 -------------------
 net/core/skmsg.c       | 32 ++++++++++++++++++++------------
 net/core/sock_map.c    |  8 ++------
 5 files changed, 60 insertions(+), 37 deletions(-)
diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h
index 6d0a33d1c0db..bd84f799c952 100644
--- a/include/linux/skbuff.h
+++ b/include/linux/skbuff.h
@@ -755,6 +755,9 @@ struct sk_buff {
                      void            (*destructor)(struct sk_buff *skb);
              };
              struct list_head        tcp_tsorted_anchor;
+#ifdef CONFIG_NET_SOCK_MSG
+             unsigned long           _sk_redir;
+#endif
      };

 #if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h
index e3bb712af257..fc234d507fd7 100644
--- a/include/linux/skmsg.h
+++ b/include/linux/skmsg.h
@@ -459,4 +459,39 @@ static inline bool sk_psock_strp_enabled(struct sk_psock *psock)
              return false;
      return !!psock->saved_data_ready;
 }
+
+#if IS_ENABLED(CONFIG_NET_SOCK_MSG)
+static inline bool skb_bpf_ingress(const struct sk_buff *skb)
+{
+     unsigned long sk_redir = skb->_sk_redir;
+
+     return sk_redir & BPF_F_INGRESS;
+}
+
+static inline void skb_bpf_set_ingress(struct sk_buff *skb)
+{
+     skb->_sk_redir |= BPF_F_INGRESS;
+}
+
+static inline void skb_bpf_set_redir(struct sk_buff *skb, struct sock *sk_redir,
+                                  bool ingress)
+{
+     skb->_sk_redir = (unsigned long)sk_redir;
+     if (ingress)
+             skb->_sk_redir |= BPF_F_INGRESS;
+}
+
+static inline struct sock *skb_bpf_redirect_fetch(const struct sk_buff *skb)
+{
+     unsigned long sk_redir = skb->_sk_redir;
+
+     sk_redir &= ~0x1UL;
We're using the enum when setting the bit flag, but a hardcoded constant
when masking it. ~BPF_F_INGRESS would be more consistent here.
Well, here we need a mask, not a bit, but we don't have a mask yet,
hence I just use hard-coded 0x1. Does #define BPF_F_MASK 0x1UL
look any better?
Based on what I've seen around, mask for sanitizing tagged pointers is
usually derived from the flag(s). For instance:

#define SKB_DST_NOREF	1UL
#define SKB_DST_PTRMASK	~(SKB_DST_NOREF)

#define SK_USER_DATA_NOCOPY	1UL
#define SK_USER_DATA_BPF	2UL	/* Managed by BPF */
#define SK_USER_DATA_PTRMASK	~(SK_USER_DATA_NOCOPY | SK_USER_DATA_BPF)

Using ~(BPF_F_INGRESS) expression would be like substituting mask
definition.

[..]
quoted
quoted
diff --git a/include/net/tcp.h b/include/net/tcp.h
index 947ef5da6867..075de26f449d 100644
--- a/include/net/tcp.h
+++ b/include/net/tcp.h
@@ -883,30 +883,11 @@ struct tcp_skb_cb {
                      struct inet6_skb_parm   h6;
 #endif
              } header;       /* For incoming skbs */
-             struct {
-                     __u32 flags;
-                     struct sock *sk_redir;
-             } bpf;
      };
 };

 #define TCP_SKB_CB(__skb)    ((struct tcp_skb_cb *)&((__skb)->cb[0]))

-static inline bool tcp_skb_bpf_ingress(const struct sk_buff *skb)
-{
-     return TCP_SKB_CB(skb)->bpf.flags & BPF_F_INGRESS;
-}
-
-static inline struct sock *tcp_skb_bpf_redirect_fetch(struct sk_buff *skb)
-{
-     return TCP_SKB_CB(skb)->bpf.sk_redir;
-}
-
-static inline void tcp_skb_bpf_redirect_clear(struct sk_buff *skb)
-{
-     TCP_SKB_CB(skb)->bpf.sk_redir = NULL;
-}
-
 extern const struct inet_connection_sock_af_ops ipv4_specific;

 #if IS_ENABLED(CONFIG_IPV6)
diff --git a/net/core/skmsg.c b/net/core/skmsg.c
index 2d8bbb3fd87c..05b5af09ff42 100644
--- a/net/core/skmsg.c
+++ b/net/core/skmsg.c
@@ -494,6 +494,8 @@ static int sk_psock_skb_ingress_self(struct sk_psock *psock, struct sk_buff *skb
 static int sk_psock_handle_skb(struct sk_psock *psock, struct sk_buff *skb,
                             u32 off, u32 len, bool ingress)
 {
+     skb_bpf_redirect_clear(skb);
This is called to avoid leaking state in skb->_skb_refdst. Correct?
This is to teach kfree_skb() not to consider it as a valid _skb_refdst.
OK
quoted
I'm wondering why we're doing it every time sk_psock_handle_skb() gets
invoked from the do/while loop in sk_psock_backlog(), instead of doing
it once after reading ingress flag with skb_bpf_ingress()?
It should also work, I don't see much difference here, as we almost
always process a full skb, that is, ret == skb->len.
OK
quoted
quoted
+
      if (!ingress) {
              if (!sock_writeable(psock->sk))
                      return -EAGAIN;
@@ -525,7 +527,7 @@ static void sk_psock_backlog(struct work_struct *work)
              len = skb->len;
              off = 0;
 start:
-             ingress = tcp_skb_bpf_ingress(skb);
+             ingress = skb_bpf_ingress(skb);
              do {
                      ret = -EIO;
                      if (likely(psock->sk->sk_socket))
@@ -631,7 +633,12 @@ void __sk_psock_purge_ingress_msg(struct sk_psock *psock)

 static void sk_psock_zap_ingress(struct sk_psock *psock)
 {
-     __skb_queue_purge(&psock->ingress_skb);
+     struct sk_buff *skb;
+
+     while ((skb = __skb_dequeue(&psock->ingress_skb)) != NULL) {
+             skb_bpf_redirect_clear(skb);
I believe we clone the skb before enqueuing it psock->ingress_skb.
Clone happens either in sk_psock_verdict_recv() or in __strp_recv().
There are not other users holding a ref, so clearing the redirect seems
unneeded. Unless I'm missing something?
Yes, skb dst is also cloned:

 980 static void __copy_skb_header(struct sk_buff *new, const struct
sk_buff *old)
 981 {
 982         new->tstamp             = old->tstamp;
 983         /* We do not copy old->sk */
 984         new->dev                = old->dev;
 985         memcpy(new->cb, old->cb, sizeof(old->cb));
 986         skb_dst_copy(new, old);

Also, if without this, dst_release() would complain again. I was not smart
enough to add it in the beginning, dst_release() taught me this lesson. ;)
OK, I think I follow you now.

Alternatively we could clear _skb_refdest after clone, but before
enqueuing the skb in ingress_skb. And only for when we're redirecting.

I believe that would be in sk_psock_skb_redirect, right before skb_queue_tail.
quoted
quoted
+             kfree_skb(skb);
+     }
      __sk_psock_purge_ingress_msg(psock);
 }
@@ -752,7 +759,7 @@ static void sk_psock_skb_redirect(struct sk_buff *skb)
      struct sk_psock *psock_other;
      struct sock *sk_other;

-     sk_other = tcp_skb_bpf_redirect_fetch(skb);
+     sk_other = skb_bpf_redirect_fetch(skb);
      /* This error is a buggy BPF program, it returned a redirect
       * return code, but then didn't set a redirect interface.
       */
@@ -802,9 +809,10 @@ int sk_psock_tls_strp_read(struct sk_psock *psock, struct sk_buff *skb)
               * TLS context.
               */
              skb->sk = psock->sk;
-             tcp_skb_bpf_redirect_clear(skb);
+             skb_dst_drop(skb);
+             skb_bpf_redirect_clear(skb);
After skb_dst_drop(), skb->_skb_refdst is clear. So it seems the
redirect_clear() is not needed. But I'm guessing it is being invoked
to communicate the intention?
Technically true, but I prefer to call them explicitly, not to rely on the
fact skb->_skb_refdst shares the same storage with skb->_sk_redir,
which would also require some comments to explain.
OK
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help