Received: by 2002:a05:6358:3188:b0:123:57c1:9b43 with SMTP id q8csp991148rwd; Tue, 13 Jun 2023 03:21:10 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ628W6KfXMc9m2sy33WySF0jOo/zraAlmC7LJrvOkfzSYxUp0Yllkt8sVVkeETQYjqCvaHK X-Received: by 2002:aa7:cd95:0:b0:514:96f8:d936 with SMTP id x21-20020aa7cd95000000b0051496f8d936mr7343667edv.29.1686651670306; Tue, 13 Jun 2023 03:21:10 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1686651670; cv=none; d=google.com; s=arc-20160816; b=qY/7omPBASRaRWtYhXzboRcKD7VEOGrXvbFRmGbCgvO1c9RkVCTaUmbLUg9xKT3N23 ZsRE7DVPD1/TcGwWZwp1ECs7yQKGBtPEJg8pjrNsH9gMGPoc1cdRWf532tttfs5RNnOp ZmvApxgvjCUweXEg+azCsOdmgqIJxPEvjewc3uJ32JVWOkjeJqM8EGCpwK9nWNllcq6g MTS8clhAizj0yIDO8SSMkndaqq4byAEU1wKjuSEi9daZs1+E+7294jpC6rVH6k5+Zo8G NCBwVJIzGkvAspJiptDxt+ecw0Ew5Qdmjx5hVpjCgwx7cfbraQ/gfQtDA9FcOreUke1P ScTA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:cc:to:in-reply-to:references:message-id :content-transfer-encoding:mime-version:subject:date:from :dkim-signature; bh=rHwA68FEDYeOk8tUAO5Jk0KSGY++DaKS34uczmd9os4=; b=SfH4MbSu6hDWNiyNxhhk65ancGu4eV79D0reKfsfvJoYEjN3gJo4YS26LetY9pKpFV zEE8DX6s1KFg+EwqVFQ+3l6BVJWDnaR3nuQDcrFc2eoV1rSdEINu1/B2EX6jPcUeVFDZ id//CCLuHwjctfoJ6McHWJGaejNZECBuXilzba4axMxV8arnlxEdbesoewGtAr4wUfRx 7yUHCpRtc8WooBpJVtSGjnpnNIPY6ZtXSb4VWmqGvRXVwRnP5mxvGubajklJ8lbz7LO9 PsagwRQVtt9G4+FMe5WrDHDYh3rDoRg7U381Tq0BtDrRClGWWuCJCOmT60btv/C192Zi pEkQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@isovalent.com header.s=google header.b="TNJg/bGc"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=isovalent.com Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id d19-20020a50fb13000000b005186c1253f7si1375186edq.278.2023.06.13.03.20.39; Tue, 13 Jun 2023 03:21:10 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@isovalent.com header.s=google header.b="TNJg/bGc"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=isovalent.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S241596AbjFMKP1 (ORCPT + 99 others); Tue, 13 Jun 2023 06:15:27 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47706 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S241445AbjFMKPJ (ORCPT ); Tue, 13 Jun 2023 06:15:09 -0400 Received: from mail-wr1-x42f.google.com (mail-wr1-x42f.google.com [IPv6:2a00:1450:4864:20::42f]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 369E9E6 for ; Tue, 13 Jun 2023 03:15:06 -0700 (PDT) Received: by mail-wr1-x42f.google.com with SMTP id ffacd0b85a97d-30e412a852dso3572925f8f.0 for ; Tue, 13 Jun 2023 03:15:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=isovalent.com; s=google; t=1686651304; x=1689243304; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:from:to:cc:subject:date:message-id :reply-to; bh=rHwA68FEDYeOk8tUAO5Jk0KSGY++DaKS34uczmd9os4=; b=TNJg/bGcJ1sLA2Qufy7t9ZzUZMyC0Bub5mPNwPylxTrrM8GSgD8nNVvWwJldPtURRp 2m1n1DcTfz+aM3+hm9kWHQymF4McTaIcETHulWpVlGr3gFeXF7eahafGyfZULqiK/FOO vQo23OjmxFfQQOosaMpurTz+6xkExqn4z4L1yyrAZ+XoptkrmKU2Lyvc6Zf+AM1LFLly U8xc7TUNx6bnxKn72SW/9yIq6CSESa8JaKgwHRzDyOKku61Miz8SkTB0u+TZDgUF+sIP n8Es7EY6FeFBqZGuHdiJeGCkIHrRhQZ65dC64MK7K1HtU87A1c8ZIH296asfstgMm74M 4PHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1686651304; x=1689243304; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :mime-version:subject:date:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=rHwA68FEDYeOk8tUAO5Jk0KSGY++DaKS34uczmd9os4=; b=R4UPIP6wxv3VGazsVQVUZ4wL8nAGnB/wAddKllTN52UMEQI32zJkAr+bAIIo0wf8T9 TXoJT3PJl6KfZYT/OuKqQjv6M7NK5dzrHvEsHoNnBYIg53UlYewGkRPNqMkO0JpE7Jky pILAxhZv9ODGXkvTg4lwDbmQZCqp2pG9XmlM+SvEuyQtMQ6nZptPL/RO2YF1C4yxE+Kl q0ns0p7yyJ9TzhZBtbEnG5rtmoJiPsDM5TrOI0CHOdl79fbRplX4BaM3MuHg2lbAEQzl qAynAI0tCYs6EUCtHxSN/zK7ZSdfzPjYEEk8OiLcqhMNF5BijE5DAGrd94hHQyp4rujf 4dig== X-Gm-Message-State: AC+VfDzqFoGjxZLFyvglY0oNPD3L5oDOFbu+l2m9XFWMCfJbUtZFhFQm 4set49NFiQRvMlbJVRDxZ/vYvA== X-Received: by 2002:adf:f642:0:b0:2f0:2dfe:e903 with SMTP id x2-20020adff642000000b002f02dfee903mr5099880wrp.69.1686651304646; Tue, 13 Jun 2023 03:15:04 -0700 (PDT) Received: from [192.168.133.193] ([5.148.46.226]) by smtp.gmail.com with ESMTPSA id k15-20020a5d6e8f000000b0030e6096afb6sm15075020wrz.12.2023.06.13.03.15.03 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 13 Jun 2023 03:15:04 -0700 (PDT) From: Lorenz Bauer Date: Tue, 13 Jun 2023 11:15:00 +0100 Subject: [PATCH bpf-next v2 5/6] bpf, net: Support SO_REUSEPORT sockets with bpf_sk_assign MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20230613-so-reuseport-v2-5-b7c69a342613@isovalent.com> References: <20230613-so-reuseport-v2-0-b7c69a342613@isovalent.com> In-Reply-To: <20230613-so-reuseport-v2-0-b7c69a342613@isovalent.com> To: "David S. Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , David Ahern , Willem de Bruijn , Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Joe Stringer , Mykola Lysenko , Shuah Khan , Kuniyuki Iwashima Cc: Hemanth Malla , netdev@vger.kernel.org, linux-kernel@vger.kernel.org, bpf@vger.kernel.org, linux-kselftest@vger.kernel.org, Lorenz Bauer , Joe Stringer X-Mailer: b4 0.12.2 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_NONE, SPF_HELO_NONE,SPF_PASS,T_SCC_BODY_TEXT_LINE,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Currently the bpf_sk_assign helper in tc BPF context refuses SO_REUSEPORT sockets. This means we can't use the helper to steer traffic to Envoy, which configures SO_REUSEPORT on its sockets. In turn, we're blocked from removing TPROXY from our setup. The reason that bpf_sk_assign refuses such sockets is that the bpf_sk_lookup helpers don't execute SK_REUSEPORT programs. Instead, one of the reuseport sockets is selected by hash. This could cause dispatch to the "wrong" socket: sk = bpf_sk_lookup_tcp(...) // select SO_REUSEPORT by hash bpf_sk_assign(skb, sk) // SK_REUSEPORT wasn't executed Fixing this isn't as simple as invoking SK_REUSEPORT from the lookup helpers unfortunately. In the tc context, L2 headers are at the start of the skb, while SK_REUSEPORT expects L3 headers instead. Instead, we execute the SK_REUSEPORT program when the assigned socket is pulled out of the skb, further up the stack. This creates some trickiness with regards to refcounting as bpf_sk_assign will put both refcounted and RCU freed sockets in skb->sk. reuseport sockets are RCU freed. We can infer that the sk_assigned socket is RCU freed if the reuseport lookup succeeds, but convincing yourself of this fact isn't straight forward. Therefore we defensively check refcounting on the sk_assign sock even though it's probably not required in practice. Fixes: 8e368dc72e86 ("bpf: Fix use of sk->sk_reuseport from sk_assign") Fixes: cf7fbe660f2d ("bpf: Add socket assign support") Co-developed-by: Daniel Borkmann Signed-off-by: Daniel Borkmann Signed-off-by: Lorenz Bauer Cc: Joe Stringer Link: https://lore.kernel.org/bpf/CACAyw98+qycmpQzKupquhkxbvWK4OFyDuuLMBNROnfWMZxUWeA@mail.gmail.com/ --- include/net/inet6_hashtables.h | 59 ++++++++++++++++++++++++++++++++++++++---- include/net/inet_hashtables.h | 52 +++++++++++++++++++++++++++++++++++-- include/net/sock.h | 7 +++-- include/uapi/linux/bpf.h | 3 --- net/core/filter.c | 2 -- net/ipv4/udp.c | 8 ++++-- net/ipv6/udp.c | 10 ++++--- tools/include/uapi/linux/bpf.h | 3 --- 8 files changed, 122 insertions(+), 22 deletions(-) diff --git a/include/net/inet6_hashtables.h b/include/net/inet6_hashtables.h index 4d2a1a3c0be7..4d300af6ccb6 100644 --- a/include/net/inet6_hashtables.h +++ b/include/net/inet6_hashtables.h @@ -103,6 +103,49 @@ static inline struct sock *__inet6_lookup(struct net *net, daddr, hnum, dif, sdif); } +static inline +struct sock *inet6_steal_sock(struct net *net, struct sk_buff *skb, int doff, + const struct in6_addr *saddr, const __be16 sport, + const struct in6_addr *daddr, const __be16 dport, + bool *refcounted, inet6_ehashfn_t ehashfn) +{ + struct sock *sk, *reuse_sk; + bool prefetched; + + sk = skb_steal_sock(skb, refcounted, &prefetched); + if (!sk) + return NULL; + + if (!prefetched) + return sk; + + if (sk->sk_protocol == IPPROTO_TCP) { + if (sk->sk_state != TCP_LISTEN) + return sk; + } else if (sk->sk_protocol == IPPROTO_UDP) { + if (sk->sk_state != TCP_CLOSE) + return sk; + } else { + return sk; + } + + reuse_sk = inet6_lookup_reuseport(net, sk, skb, doff, + saddr, sport, daddr, ntohs(dport), + ehashfn); + if (!reuse_sk || reuse_sk == sk) + return sk; + + /* We've chosen a new reuseport sock which is never refcounted. + * sk might be refcounted however, drop the reference if necessary. + */ + if (*refcounted) { + sock_put(sk); + *refcounted = false; + } + + return reuse_sk; +} + static inline struct sock *__inet6_lookup_skb(struct inet_hashinfo *hashinfo, struct sk_buff *skb, int doff, const __be16 sport, @@ -110,14 +153,20 @@ static inline struct sock *__inet6_lookup_skb(struct inet_hashinfo *hashinfo, int iif, int sdif, bool *refcounted) { - struct sock *sk = skb_steal_sock(skb, refcounted); - + struct net *net = dev_net(skb_dst(skb)->dev); + const struct ipv6hdr *ip6h = ipv6_hdr(skb); + struct sock *sk; + + sk = inet6_steal_sock(net, skb, doff, &ip6h->saddr, sport, &ip6h->daddr, dport, + refcounted, inet6_ehashfn); + if (IS_ERR(sk)) + return NULL; if (sk) return sk; - return __inet6_lookup(dev_net(skb_dst(skb)->dev), hashinfo, skb, - doff, &ipv6_hdr(skb)->saddr, sport, - &ipv6_hdr(skb)->daddr, ntohs(dport), + return __inet6_lookup(net, hashinfo, skb, + doff, &ip6h->saddr, sport, + &ip6h->daddr, ntohs(dport), iif, sdif, refcounted); } diff --git a/include/net/inet_hashtables.h b/include/net/inet_hashtables.h index aa02f1db1f86..2c405d9df300 100644 --- a/include/net/inet_hashtables.h +++ b/include/net/inet_hashtables.h @@ -449,6 +449,49 @@ static inline struct sock *inet_lookup(struct net *net, return sk; } +static inline +struct sock *inet_steal_sock(struct net *net, struct sk_buff *skb, int doff, + const __be32 saddr, const __be16 sport, + const __be32 daddr, const __be16 dport, + bool *refcounted, inet_ehashfn_t ehashfn) +{ + struct sock *sk, *reuse_sk; + bool prefetched; + + sk = skb_steal_sock(skb, refcounted, &prefetched); + if (!sk) + return NULL; + + if (!prefetched) + return sk; + + if (sk->sk_protocol == IPPROTO_TCP) { + if (sk->sk_state != TCP_LISTEN) + return sk; + } else if (sk->sk_protocol == IPPROTO_UDP) { + if (sk->sk_state != TCP_CLOSE) + return sk; + } else { + return sk; + } + + reuse_sk = inet_lookup_reuseport(net, sk, skb, doff, + saddr, sport, daddr, ntohs(dport), + ehashfn); + if (!reuse_sk || reuse_sk == sk) + return sk; + + /* We've chosen a new reuseport sock which is never refcounted. + * sk might be refcounted however, drop the reference if necessary. + */ + if (*refcounted) { + sock_put(sk); + *refcounted = false; + } + + return reuse_sk; +} + static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo, struct sk_buff *skb, int doff, @@ -457,13 +500,18 @@ static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo, const int sdif, bool *refcounted) { - struct sock *sk = skb_steal_sock(skb, refcounted); + struct net *net = dev_net(skb_dst(skb)->dev); const struct iphdr *iph = ip_hdr(skb); + struct sock *sk; + sk = inet_steal_sock(net, skb, doff, iph->saddr, sport, iph->daddr, dport, + refcounted, inet_ehashfn); + if (IS_ERR(sk)) + return NULL; if (sk) return sk; - return __inet_lookup(dev_net(skb_dst(skb)->dev), hashinfo, skb, + return __inet_lookup(net, hashinfo, skb, doff, iph->saddr, sport, iph->daddr, dport, inet_iif(skb), sdif, refcounted); diff --git a/include/net/sock.h b/include/net/sock.h index 656ea89f60ff..5645570c2a64 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2806,20 +2806,23 @@ sk_is_refcounted(struct sock *sk) * skb_steal_sock - steal a socket from an sk_buff * @skb: sk_buff to steal the socket from * @refcounted: is set to true if the socket is reference-counted + * @prefetched: is set to true if the socket was assigned from bpf */ static inline struct sock * -skb_steal_sock(struct sk_buff *skb, bool *refcounted) +skb_steal_sock(struct sk_buff *skb, bool *refcounted, bool *prefetched) { if (skb->sk) { struct sock *sk = skb->sk; *refcounted = true; - if (skb_sk_is_prefetched(skb)) + *prefetched = skb_sk_is_prefetched(skb); + if (*prefetched) *refcounted = sk_is_refcounted(sk); skb->destructor = NULL; skb->sk = NULL; return sk; } + *prefetched = false; *refcounted = false; return NULL; } diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index a7b5e91dd768..d6fb6f43b0f3 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -4158,9 +4158,6 @@ union bpf_attr { * **-EOPNOTSUPP** if the operation is not supported, for example * a call from outside of TC ingress. * - * **-ESOCKTNOSUPPORT** if the socket type is not supported - * (reuseport). - * * long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, u64 flags) * Description * Helper is overloaded depending on BPF program type. This diff --git a/net/core/filter.c b/net/core/filter.c index 428df050d021..d4be0a1d754c 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -7278,8 +7278,6 @@ BPF_CALL_3(bpf_sk_assign, struct sk_buff *, skb, struct sock *, sk, u64, flags) return -EOPNOTSUPP; if (unlikely(dev_net(skb->dev) != sock_net(sk))) return -ENETUNREACH; - if (unlikely(sk_fullsock(sk) && sk->sk_reuseport)) - return -ESOCKTNOSUPPORT; if (sk_is_refcounted(sk) && unlikely(!refcount_inc_not_zero(&sk->sk_refcnt))) return -ENOENT; diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c index 2500e92050a0..a3fa781432b9 100644 --- a/net/ipv4/udp.c +++ b/net/ipv4/udp.c @@ -2373,7 +2373,11 @@ int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, if (udp4_csum_init(skb, uh, proto)) goto csum_error; - sk = skb_steal_sock(skb, &refcounted); + sk = inet_steal_sock(net, skb, sizeof(struct udphdr), saddr, uh->source, daddr, uh->dest, + &refcounted, udp_ehashfn); + if (IS_ERR(sk)) + goto no_sk; + if (sk) { struct dst_entry *dst = skb_dst(skb); int ret; @@ -2394,7 +2398,7 @@ int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable); if (sk) return udp_unicast_rcv_skb(sk, skb, uh); - +no_sk: if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) goto drop; nf_reset_ct(skb); diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c index 961b7e61f02c..0a90f34696ad 100644 --- a/net/ipv6/udp.c +++ b/net/ipv6/udp.c @@ -910,9 +910,9 @@ int __udp6_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, enum skb_drop_reason reason = SKB_DROP_REASON_NOT_SPECIFIED; const struct in6_addr *saddr, *daddr; struct net *net = dev_net(skb->dev); + bool refcounted; struct udphdr *uh; struct sock *sk; - bool refcounted; u32 ulen = 0; if (!pskb_may_pull(skb, sizeof(struct udphdr))) @@ -949,7 +949,11 @@ int __udp6_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, goto csum_error; /* Check if the socket is already available, e.g. due to early demux */ - sk = skb_steal_sock(skb, &refcounted); + sk = inet6_steal_sock(net, skb, sizeof(struct udphdr), saddr, uh->source, daddr, uh->dest, + &refcounted, udp6_ehashfn); + if (IS_ERR(sk)) + goto no_sk; + if (sk) { struct dst_entry *dst = skb_dst(skb); int ret; @@ -983,7 +987,7 @@ int __udp6_lib_rcv(struct sk_buff *skb, struct udp_table *udptable, goto report_csum_error; return udp6_unicast_rcv_skb(sk, skb, uh); } - +no_sk: reason = SKB_DROP_REASON_NO_SOCKET; if (!uh->check) diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index a7b5e91dd768..d6fb6f43b0f3 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -4158,9 +4158,6 @@ union bpf_attr { * **-EOPNOTSUPP** if the operation is not supported, for example * a call from outside of TC ingress. * - * **-ESOCKTNOSUPPORT** if the socket type is not supported - * (reuseport). - * * long bpf_sk_assign(struct bpf_sk_lookup *ctx, struct bpf_sock *sk, u64 flags) * Description * Helper is overloaded depending on BPF program type. This -- 2.40.1