* [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option
2021-02-19 9:51 [PATCH bpf-next v2 0/4] Expose network namespace cookies to user space Lorenz Bauer
@ 2021-02-19 9:51 ` Lorenz Bauer
2021-02-19 11:49 ` Eric Dumazet
2021-02-19 9:51 ` [PATCH bpf-next v2 2/4] nsfs: add an ioctl to discover the network namespace cookie Lorenz Bauer
` (2 subsequent siblings)
3 siblings, 1 reply; 8+ messages in thread
From: Lorenz Bauer @ 2021-02-19 9:51 UTC (permalink / raw)
To: daniel, ast, andrii; +Cc: bpf, netdev, kernel-team, Lorenz Bauer
We need to distinguish which network namespace a socket belongs to.
BPF has the useful bpf_get_netns_cookie helper for this, but accessing
it from user space isn't possible. Add a read-only socket option that
returns the netns cookie, similar to SO_COOKIE. If network namespaces
are disabled, SO_NETNS_COOKIE returns the cookie of init_net.
Signed-off-by: Lorenz Bauer <lmb@cloudflare.com>
---
arch/alpha/include/uapi/asm/socket.h | 2 ++
arch/mips/include/uapi/asm/socket.h | 2 ++
arch/parisc/include/uapi/asm/socket.h | 2 ++
arch/sparc/include/uapi/asm/socket.h | 2 ++
include/uapi/asm-generic/socket.h | 2 ++
net/core/sock.c | 11 +++++++++++
6 files changed, 21 insertions(+)
diff --git a/arch/alpha/include/uapi/asm/socket.h b/arch/alpha/include/uapi/asm/socket.h
index 57420356ce4c..6b3daba60987 100644
--- a/arch/alpha/include/uapi/asm/socket.h
+++ b/arch/alpha/include/uapi/asm/socket.h
@@ -127,6 +127,8 @@
#define SO_PREFER_BUSY_POLL 69
#define SO_BUSY_POLL_BUDGET 70
+#define SO_NETNS_COOKIE 71
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/mips/include/uapi/asm/socket.h b/arch/mips/include/uapi/asm/socket.h
index 2d949969313b..cdf404a831b2 100644
--- a/arch/mips/include/uapi/asm/socket.h
+++ b/arch/mips/include/uapi/asm/socket.h
@@ -138,6 +138,8 @@
#define SO_PREFER_BUSY_POLL 69
#define SO_BUSY_POLL_BUDGET 70
+#define SO_NETNS_COOKIE 71
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/parisc/include/uapi/asm/socket.h b/arch/parisc/include/uapi/asm/socket.h
index f60904329bbc..5b5351cdcb33 100644
--- a/arch/parisc/include/uapi/asm/socket.h
+++ b/arch/parisc/include/uapi/asm/socket.h
@@ -119,6 +119,8 @@
#define SO_PREFER_BUSY_POLL 0x4043
#define SO_BUSY_POLL_BUDGET 0x4044
+#define SO_NETNS_COOKIE 0x4045
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64
diff --git a/arch/sparc/include/uapi/asm/socket.h b/arch/sparc/include/uapi/asm/socket.h
index 848a22fbac20..ff79db753dce 100644
--- a/arch/sparc/include/uapi/asm/socket.h
+++ b/arch/sparc/include/uapi/asm/socket.h
@@ -120,6 +120,8 @@
#define SO_PREFER_BUSY_POLL 0x0048
#define SO_BUSY_POLL_BUDGET 0x0049
+#define SO_NETNS_COOKIE 0x004a
+
#if !defined(__KERNEL__)
diff --git a/include/uapi/asm-generic/socket.h b/include/uapi/asm-generic/socket.h
index 4dcd13d097a9..d588c244ec2f 100644
--- a/include/uapi/asm-generic/socket.h
+++ b/include/uapi/asm-generic/socket.h
@@ -122,6 +122,8 @@
#define SO_PREFER_BUSY_POLL 69
#define SO_BUSY_POLL_BUDGET 70
+#define SO_NETNS_COOKIE 71
+
#if !defined(__KERNEL__)
#if __BITS_PER_LONG == 64 || (defined(__x86_64__) && defined(__ILP32__))
diff --git a/net/core/sock.c b/net/core/sock.c
index 0ed98f20448a..de4644aeb58d 100644
--- a/net/core/sock.c
+++ b/net/core/sock.c
@@ -1614,6 +1614,17 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
v.val = sk->sk_bound_dev_if;
break;
+ case SO_NETNS_COOKIE:
+ lv = sizeof(u64);
+ if (len < lv)
+ return -EINVAL;
+#ifdef CONFIG_NET_NS
+ v.val64 = sock_net(sk)->net_cookie;
+#else
+ v.val64 = init_net.net_cookie;
+#endif
+ break;
+
default:
/* We implement the SO_SNDLOWAT etc to not be settable
* (1003.1g 7).
--
2.27.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option
2021-02-19 9:51 ` [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option Lorenz Bauer
@ 2021-02-19 11:49 ` Eric Dumazet
2021-02-19 12:23 ` Lorenz Bauer
0 siblings, 1 reply; 8+ messages in thread
From: Eric Dumazet @ 2021-02-19 11:49 UTC (permalink / raw)
To: Lorenz Bauer, daniel, ast, andrii; +Cc: bpf, netdev, kernel-team
On 2/19/21 10:51 AM, Lorenz Bauer wrote:
> We need to distinguish which network namespace a socket belongs to.
> BPF has the useful bpf_get_netns_cookie helper for this, but accessing
> it from user space isn't possible. Add a read-only socket option that
> returns the netns cookie, similar to SO_COOKIE. If network namespaces
> are disabled, SO_NETNS_COOKIE returns the cookie of init_net.
>
> Signed-off-by: Lorenz Bauer <lmb@cloudflare.com>
> ---
> diff --git a/net/core/sock.c b/net/core/sock.c
> index 0ed98f20448a..de4644aeb58d 100644
> --- a/net/core/sock.c
> +++ b/net/core/sock.c
> @@ -1614,6 +1614,17 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
> v.val = sk->sk_bound_dev_if;
> break;
>
> + case SO_NETNS_COOKIE:
> + lv = sizeof(u64);
> + if (len < lv)
> + return -EINVAL;
if (len != lv)
return -EINVAL;
(There is no reason to support bigger value before at least hundred years)
> +#ifdef CONFIG_NET_NS
> + v.val64 = sock_net(sk)->net_cookie;
> +#else
> + v.val64 = init_net.net_cookie;
> +#endif
> + break;
> +
Why using this ugly #ifdef ?
The following should work just fine, even if CONFIG_NET_NS is not set.
v.val64 = sock_net(sk)->net_cookie;
> default:
> /* We implement the SO_SNDLOWAT etc to not be settable
> * (1003.1g 7).
>
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option
2021-02-19 11:49 ` Eric Dumazet
@ 2021-02-19 12:23 ` Lorenz Bauer
2021-02-19 15:32 ` Eric Dumazet
0 siblings, 1 reply; 8+ messages in thread
From: Lorenz Bauer @ 2021-02-19 12:23 UTC (permalink / raw)
To: Eric Dumazet
Cc: Daniel Borkmann, Alexei Starovoitov, Andrii Nakryiko, bpf,
Networking, kernel-team
On Fri, 19 Feb 2021 at 11:49, Eric Dumazet <eric.dumazet@gmail.com> wrote:
>
> > + case SO_NETNS_COOKIE:
> > + lv = sizeof(u64);
> > + if (len < lv)
> > + return -EINVAL;
>
> if (len != lv)
> return -EINVAL;
>
> (There is no reason to support bigger value before at least hundred years)
Sorry that was copy pasta from SO_COOKIE which uses the same check. I'll
change it to your suggestion. Want me to fix SO_COOKIE as well?
>
> > +#ifdef CONFIG_NET_NS
> > + v.val64 = sock_net(sk)->net_cookie;
> > +#else
> > + v.val64 = init_net.net_cookie;
> > +#endif
> > + break;
> > +
>
> Why using this ugly #ifdef ?
>
> The following should work just fine, even if CONFIG_NET_NS is not set.
>
> v.val64 = sock_net(sk)->net_cookie;
I looked at sock_net and didn't understand how it avoids a compile error
so I didn't use it, thanks for pointing this out.
--
Lorenz Bauer | Systems Engineer
6th Floor, County Hall/The Riverside Building, SE1 7PB, UK
www.cloudflare.com
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option
2021-02-19 12:23 ` Lorenz Bauer
@ 2021-02-19 15:32 ` Eric Dumazet
0 siblings, 0 replies; 8+ messages in thread
From: Eric Dumazet @ 2021-02-19 15:32 UTC (permalink / raw)
To: Lorenz Bauer, Eric Dumazet
Cc: Daniel Borkmann, Alexei Starovoitov, Andrii Nakryiko, bpf,
Networking, kernel-team
On 2/19/21 1:23 PM, Lorenz Bauer wrote:
> On Fri, 19 Feb 2021 at 11:49, Eric Dumazet <eric.dumazet@gmail.com> wrote:
>>
>>> + case SO_NETNS_COOKIE:
>>> + lv = sizeof(u64);
>>> + if (len < lv)
>>> + return -EINVAL;
>>
>> if (len != lv)
>> return -EINVAL;
>>
>> (There is no reason to support bigger value before at least hundred years)
>
> Sorry that was copy pasta from SO_COOKIE which uses the same check. I'll
> change it to your suggestion. Want me to fix SO_COOKIE as well?
Unfortunately it is too late for SO_COOKIE
Some applications might use len = 256, and just look at what the kernel
gives back.
Better be strict at the time a feature is added, instead of having
to maintain legacy stuff.
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH bpf-next v2 2/4] nsfs: add an ioctl to discover the network namespace cookie
2021-02-19 9:51 [PATCH bpf-next v2 0/4] Expose network namespace cookies to user space Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option Lorenz Bauer
@ 2021-02-19 9:51 ` Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 3/4] tools/testing: add test for NS_GET_COOKIE Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 4/4] tools/testing: add a selftest for SO_NETNS_COOKIE Lorenz Bauer
3 siblings, 0 replies; 8+ messages in thread
From: Lorenz Bauer @ 2021-02-19 9:51 UTC (permalink / raw)
To: daniel, ast, andrii; +Cc: bpf, netdev, kernel-team, Lorenz Bauer
Network namespaces have a globally unique non-zero identifier aka a
cookie, in line with socket cookies. Add an ioctl to retrieve the
cookie from user space without going via BPF.
Signed-off-by: Lorenz Bauer <lmb@cloudflare.com>
---
fs/nsfs.c | 8 ++++++++
include/uapi/linux/nsfs.h | 2 ++
2 files changed, 10 insertions(+)
diff --git a/fs/nsfs.c b/fs/nsfs.c
index 800c1d0eb0d0..b7e70ab80257 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -11,6 +11,7 @@
#include <linux/user_namespace.h>
#include <linux/nsfs.h>
#include <linux/uaccess.h>
+#include <net/net_namespace.h>
#include "internal.h"
@@ -191,6 +192,8 @@ static long ns_ioctl(struct file *filp, unsigned int ioctl,
struct user_namespace *user_ns;
struct ns_common *ns = get_proc_ns(file_inode(filp));
uid_t __user *argp;
+ struct net *net_ns;
+ u64 cookie;
uid_t uid;
switch (ioctl) {
@@ -209,6 +212,11 @@ static long ns_ioctl(struct file *filp, unsigned int ioctl,
argp = (uid_t __user *) arg;
uid = from_kuid_munged(current_user_ns(), user_ns->owner);
return put_user(uid, argp);
+ case NS_GET_COOKIE:
+ if (ns->ops->type != CLONE_NEWNET)
+ return -EINVAL;
+ net_ns = container_of(ns, struct net, ns);
+ return put_user(net_ns->net_cookie, (u64 __user *)arg);
default:
return -ENOTTY;
}
diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h
index a0c8552b64ee..86611c2cf908 100644
--- a/include/uapi/linux/nsfs.h
+++ b/include/uapi/linux/nsfs.h
@@ -15,5 +15,7 @@
#define NS_GET_NSTYPE _IO(NSIO, 0x3)
/* Get owner UID (in the caller's user namespace) for a user namespace */
#define NS_GET_OWNER_UID _IO(NSIO, 0x4)
+/* Returns a unique non-zero identifier for a network namespace */
+#define NS_GET_COOKIE _IO(NSIO, 0x5)
#endif /* __LINUX_NSFS_H */
--
2.27.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH bpf-next v2 3/4] tools/testing: add test for NS_GET_COOKIE
2021-02-19 9:51 [PATCH bpf-next v2 0/4] Expose network namespace cookies to user space Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 1/4] net: add SO_NETNS_COOKIE socket option Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 2/4] nsfs: add an ioctl to discover the network namespace cookie Lorenz Bauer
@ 2021-02-19 9:51 ` Lorenz Bauer
2021-02-19 9:51 ` [PATCH bpf-next v2 4/4] tools/testing: add a selftest for SO_NETNS_COOKIE Lorenz Bauer
3 siblings, 0 replies; 8+ messages in thread
From: Lorenz Bauer @ 2021-02-19 9:51 UTC (permalink / raw)
To: daniel, ast, andrii; +Cc: bpf, netdev, kernel-team, Lorenz Bauer
Check that NS_GET_COOKIE returns a non-zero value, and that distinct
network namespaces have different cookies.
Signed-off-by: Lorenz Bauer <lmb@cloudflare.com>
---
tools/testing/selftests/nsfs/.gitignore | 1 +
tools/testing/selftests/nsfs/Makefile | 2 +-
tools/testing/selftests/nsfs/config | 1 +
tools/testing/selftests/nsfs/netns.c | 57 +++++++++++++++++++++++++
4 files changed, 60 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/nsfs/netns.c
diff --git a/tools/testing/selftests/nsfs/.gitignore b/tools/testing/selftests/nsfs/.gitignore
index ed79ebdf286e..ca31b216215b 100644
--- a/tools/testing/selftests/nsfs/.gitignore
+++ b/tools/testing/selftests/nsfs/.gitignore
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
owner
pidns
+netns
diff --git a/tools/testing/selftests/nsfs/Makefile b/tools/testing/selftests/nsfs/Makefile
index dd9bd50b7b93..93793cdb5a7c 100644
--- a/tools/testing/selftests/nsfs/Makefile
+++ b/tools/testing/selftests/nsfs/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
-TEST_GEN_PROGS := owner pidns
+TEST_GEN_PROGS := owner pidns netns
CFLAGS := -Wall -Werror
diff --git a/tools/testing/selftests/nsfs/config b/tools/testing/selftests/nsfs/config
index 598d0a225fc9..ea654f6a4cd9 100644
--- a/tools/testing/selftests/nsfs/config
+++ b/tools/testing/selftests/nsfs/config
@@ -1,3 +1,4 @@
CONFIG_USER_NS=y
CONFIG_UTS_NS=y
CONFIG_PID_NS=y
+CONFIG_NET_NS=y
diff --git a/tools/testing/selftests/nsfs/netns.c b/tools/testing/selftests/nsfs/netns.c
new file mode 100644
index 000000000000..8ab862667b45
--- /dev/null
+++ b/tools/testing/selftests/nsfs/netns.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+
+#define NSIO 0xb7
+#define NS_GET_COOKIE _IO(NSIO, 0x5)
+
+#define pr_err(fmt, ...) \
+ ({ \
+ fprintf(stderr, "%s:%d:" fmt ": %m\n", \
+ __func__, __LINE__, ##__VA_ARGS__); \
+ 1; \
+ })
+
+int main(int argc, char *argvp[])
+{
+ uint64_t cookie1, cookie2;
+ char path[128];
+ int ns;
+
+ snprintf(path, sizeof(path), "/proc/%d/ns/net", getpid());
+ ns = open(path, O_RDONLY);
+ if (ns < 0)
+ return pr_err("Unable to open %s", path);
+
+ if (ioctl(ns, NS_GET_COOKIE, &cookie1))
+ return pr_err("Unable to get first namespace cookie");
+
+ if (!cookie1)
+ return pr_err("NS_GET_COOKIE returned zero first cookie");
+
+ close(ns);
+ if (unshare(CLONE_NEWNET))
+ return pr_err("unshare");
+
+ ns = open(path, O_RDONLY);
+ if (ns < 0)
+ return pr_err("Unable to open %s", path);
+
+ if (ioctl(ns, NS_GET_COOKIE, &cookie2))
+ return pr_err("Unable to get second namespace cookie");
+
+ if (!cookie2)
+ return pr_err("NS_GET_COOKIE returned zero second cookie");
+
+ if (cookie1 == cookie2)
+ return pr_err("NS_GET_COOKIE returned identical cookies for distinct ns");
+
+ close(ns);
+ return 0;
+}
--
2.27.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH bpf-next v2 4/4] tools/testing: add a selftest for SO_NETNS_COOKIE
2021-02-19 9:51 [PATCH bpf-next v2 0/4] Expose network namespace cookies to user space Lorenz Bauer
` (2 preceding siblings ...)
2021-02-19 9:51 ` [PATCH bpf-next v2 3/4] tools/testing: add test for NS_GET_COOKIE Lorenz Bauer
@ 2021-02-19 9:51 ` Lorenz Bauer
3 siblings, 0 replies; 8+ messages in thread
From: Lorenz Bauer @ 2021-02-19 9:51 UTC (permalink / raw)
To: daniel, ast, andrii; +Cc: bpf, netdev, kernel-team, Lorenz Bauer
Make sure that SO_NETNS_COOKIE returns a non-zero value, and
that sockets from different namespaces have a distinct cookie
value.
Signed-off-by: Lorenz Bauer <lmb@cloudflare.com>
---
tools/testing/selftests/net/.gitignore | 1 +
tools/testing/selftests/net/Makefile | 2 +-
tools/testing/selftests/net/config | 1 +
tools/testing/selftests/net/so_netns_cookie.c | 61 +++++++++++++++++++
4 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 tools/testing/selftests/net/so_netns_cookie.c
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
index 61ae899cfc17..19deb9cdf72f 100644
--- a/tools/testing/selftests/net/.gitignore
+++ b/tools/testing/selftests/net/.gitignore
@@ -30,3 +30,4 @@ hwtstamp_config
rxtimestamp
timestamping
txtimestamp
+so_netns_cookie
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
index 25f198bec0b2..91bb372f5ba5 100644
--- a/tools/testing/selftests/net/Makefile
+++ b/tools/testing/selftests/net/Makefile
@@ -28,7 +28,7 @@ TEST_GEN_FILES = socket nettest
TEST_GEN_FILES += psock_fanout psock_tpacket msg_zerocopy reuseport_addr_any
TEST_GEN_FILES += tcp_mmap tcp_inq psock_snd txring_overwrite
TEST_GEN_FILES += udpgso udpgso_bench_tx udpgso_bench_rx ip_defrag
-TEST_GEN_FILES += so_txtime ipv6_flowlabel ipv6_flowlabel_mgr
+TEST_GEN_FILES += so_txtime ipv6_flowlabel ipv6_flowlabel_mgr so_netns_cookie
TEST_GEN_FILES += tcp_fastopen_backup_key
TEST_GEN_FILES += fin_ack_lat
TEST_GEN_FILES += reuseaddr_ports_exhausted
diff --git a/tools/testing/selftests/net/config b/tools/testing/selftests/net/config
index 614d5477365a..6f905b53904f 100644
--- a/tools/testing/selftests/net/config
+++ b/tools/testing/selftests/net/config
@@ -1,4 +1,5 @@
CONFIG_USER_NS=y
+CONFIG_NET_NS=y
CONFIG_BPF_SYSCALL=y
CONFIG_TEST_BPF=m
CONFIG_NUMA=y
diff --git a/tools/testing/selftests/net/so_netns_cookie.c b/tools/testing/selftests/net/so_netns_cookie.c
new file mode 100644
index 000000000000..b39e87e967cd
--- /dev/null
+++ b/tools/testing/selftests/net/so_netns_cookie.c
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0
+#define _GNU_SOURCE
+#include <sched.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifndef SO_NETNS_COOKIE
+#define SO_NETNS_COOKIE 71
+#endif
+
+#define pr_err(fmt, ...) \
+ ({ \
+ fprintf(stderr, "%s:%d:" fmt ": %m\n", \
+ __func__, __LINE__, ##__VA_ARGS__); \
+ 1; \
+ })
+
+int main(int argc, char *argvp[])
+{
+ uint64_t cookie1, cookie2;
+ socklen_t vallen;
+ int sock1, sock2;
+
+ sock1 = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock1 < 0)
+ return pr_err("Unable to create TCP socket");
+
+ vallen = sizeof(cookie1);
+ if (getsockopt(sock1, SOL_SOCKET, SO_NETNS_COOKIE, &cookie1, &vallen) != 0)
+ return pr_err("getsockopt(SOL_SOCKET, SO_NETNS_COOKIE)");
+
+ if (!cookie1)
+ return pr_err("SO_NETNS_COOKIE returned zero cookie");
+
+ if (unshare(CLONE_NEWNET))
+ return pr_err("unshare");
+
+ sock2 = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock2 < 0)
+ return pr_err("Unable to create TCP socket");
+
+ vallen = sizeof(cookie2);
+ if (getsockopt(sock2, SOL_SOCKET, SO_NETNS_COOKIE, &cookie2, &vallen) != 0)
+ return pr_err("getsockopt(SOL_SOCKET, SO_NETNS_COOKIE)");
+
+ if (!cookie2)
+ return pr_err("SO_NETNS_COOKIE returned zero cookie");
+
+ if (cookie1 == cookie2)
+ return pr_err("SO_NETNS_COOKIE returned identical cookies for distinct ns");
+
+ close(sock1);
+ close(sock2);
+ return 0;
+}
--
2.27.0
^ permalink raw reply related [flat|nested] 8+ messages in thread