* [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP
@ 2018-03-05 19:50 John Fastabend
2018-03-05 19:51 ` [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper John Fastabend
` (15 more replies)
0 siblings, 16 replies; 31+ messages in thread
From: John Fastabend @ 2018-03-05 19:50 UTC (permalink / raw)
To: ast, daniel; +Cc: netdev, davejwatson
This series adds a BPF hook for sendmsg and sendfile by using
the ULP infrastructure and sockmap. A simple pseudocode example
would be,
// load the programs
bpf_prog_load(SOCKMAP_TCP_MSG_PROG, BPF_PROG_TYPE_SK_MSG,
&obj, &msg_prog);
// lookup the sockmap
bpf_map_msg = bpf_object__find_map_by_name(obj, "my_sock_map");
// get fd for sockmap
map_fd_msg = bpf_map__fd(bpf_map_msg);
// attach program to sockmap
bpf_prog_attach(msg_prog, map_fd_msg, BPF_SK_MSG_VERDICT, 0);
// Add a socket 'fd' to sockmap at location 'i'
bpf_map_update_elem(map_fd_msg, &i, fd, BPF_ANY);
After the above snippet any socket attached to the map would run
msg_prog on sendmsg and sendfile system calls.
Two additional helpers are added bpf_msg_apply_bytes() and
bpf_msg_cork_bytes(). With bpf_msg_apply_bytes BPF programs
can tell the infrastructure how many bytes the given verdict
should apply to. This has two cases. First BPF program applies
verdict to fewer bytes than in the current sendmsg/sendfile this
will apply the verdict to the first N bytes of the message then
run the BPF program again with data pointers recalculated to the
N+1 byte. The second case is the BPF program applies a verdict to
more bytes than the current sendmsg or sendfile system call. In
this case the infrastructure will cache the verdict and apply it
to future sendmsg/sendfile calls until the byte limit is reached.
This avoids the overhead of running BPF programs on large payloads.
The helper bpf_msg_cork_bytes() handles a different case where
a BPF program can not reach a verdict on a msg until it receives
more bytes AND the program doesn't want to forward the packet
until it is known to be "good". The example case being a user
(albeit a dumb one probably) sends messages in 1B system calls.
The BPF program can call bpf_msg_cork_bytes with the required byte
limit to reach a verdict and then the program will only be called
again once N bytes are received.
For more examples please review the sample program. There are
examples for all the actions and helpers there.
Patches 1-7 implement the above sockmap/BPF infrastructure. The
remaining patches flush out some minimal selftests and the sample
sockmap program. The sockmap sample program is the main vehicle
for testing this infrastructure and will be moved into selftests
shortly. The final patch in this series is a simple shell script
to run a set of tests. These are the tests I run after any changes
to sockmap. The next task on the list after this series is to
push those into selftests so we can avoid manually testing.
Couple notes on future items in the pipeline,
0. move sample sockmap programs into selftests (noted above)
1. add additional support for tcp flags, most are ignored now.
2. add a Documentation/bpf/sockmap file for details
3. support stacked ULP types to allow this and ktls to cooperate
4. Ingress flag support, redirect only supports egress here. The
other redirect helpers support ingress and egress flags.
Thanks,
John
Notes: I could have squashed the test patches down into a single
patch but I left it as is. It makes the patch count a bit large
but, makes the sample sockmap updates a bit more incremental. Also
the majority of the patches are testing patches so I think 16 patches
is reasonable.
---
John Fastabend (16):
sock: make static tls function alloc_sg generic sock helper
sockmap: convert refcnt to an atomic refcnt
net: do_tcp_sendpages flag to avoid SKBTX_SHARED_FRAG
net: generalize sk_alloc_sg to work with scatterlist rings
bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data
bpf: sockmap, add bpf_msg_apply_bytes() helper
bpf: sockmap, add msg_cork_bytes() helper
bpf: add map tests for BPF_PROG_TYPE_SK_MSG
bpf: add verifier tests for BPF_PROG_TYPE_SK_MSG
bpf: sockmap sample, add option to attach SK_MSG program
bpf: sockmap sample, add sendfile test
bpf: sockmap sample, add data verification option
bpf: sockmap, add sample option to test apply_bytes helper
bpf: sockmap sample support for bpf_msg_cork_bytes()
sockmap: add SK_DROP tests
bpf: sockmap test script
include/linux/bpf.h | 1
include/linux/bpf_types.h | 1
include/linux/filter.h | 17
include/linux/socket.h | 1
include/net/sock.h | 4
include/uapi/linux/bpf.h | 30 +
include/uapi/linux/bpf_common.h | 7
kernel/bpf/sockmap.c | 927 +++++++++++++++++++-
kernel/bpf/syscall.c | 14
kernel/bpf/verifier.c | 5
net/core/filter.c | 138 +++
net/core/sock.c | 61 +
net/ipv4/tcp.c | 4
net/tls/tls_sw.c | 69 -
samples/bpf/bpf_load.c | 8
samples/sockmap/sockmap_kern.c | 146 +++
samples/sockmap/sockmap_test.sh | 387 ++++++++
samples/sockmap/sockmap_user.c | 269 +++++-
tools/include/uapi/linux/bpf.h | 30 +
tools/lib/bpf/libbpf.c | 1
tools/testing/selftests/bpf/Makefile | 2
tools/testing/selftests/bpf/bpf_helpers.h | 8
tools/testing/selftests/bpf/sockmap_parse_prog.c | 15
tools/testing/selftests/bpf/sockmap_verdict_prog.c | 7
tools/testing/selftests/bpf/test_maps.c | 55 +
tools/testing/selftests/bpf/test_verifier.c | 54 +
26 files changed, 2125 insertions(+), 136 deletions(-)
create mode 100755 samples/sockmap/sockmap_test.sh
--
Signature
^ permalink raw reply [flat|nested] 31+ messages in thread* [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper 2018-03-05 19:50 [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP John Fastabend @ 2018-03-05 19:51 ` John Fastabend 2018-03-05 21:32 ` David Miller 2018-03-05 19:51 ` [bpf-next PATCH 02/16] sockmap: convert refcnt to an atomic refcnt John Fastabend ` (14 subsequent siblings) 15 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-05 19:51 UTC (permalink / raw) To: ast, daniel; +Cc: netdev, davejwatson The TLS ULP module builds scatterlists from a sock using page_frag_refill(). This is going to be useful for other ULPs so move it into sock file for more general use. In the process remove useless goto at end of while loop. Signed-off-by: John Fastabend <john.fastabend@gmail.com> --- include/net/sock.h | 4 +++ net/core/sock.c | 56 ++++++++++++++++++++++++++++++++++++++++++ net/tls/tls_sw.c | 69 +++++----------------------------------------------- 3 files changed, 67 insertions(+), 62 deletions(-) diff --git a/include/net/sock.h b/include/net/sock.h index b962458..447150c 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2141,6 +2141,10 @@ static inline struct page_frag *sk_page_frag(struct sock *sk) bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag); +int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, + int *sg_num_elem, unsigned int *sg_size, + int first_coalesce); + /* * Default write policy as shown to user space via poll/select/SIGIO */ diff --git a/net/core/sock.c b/net/core/sock.c index 507d8c6..4bda3e9 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2238,6 +2238,62 @@ bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag) } EXPORT_SYMBOL(sk_page_frag_refill); +int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, + int *sg_num_elem, unsigned int *sg_size, + int first_coalesce) +{ + struct page_frag *pfrag; + unsigned int size = *sg_size; + int num_elem = *sg_num_elem, use = 0, rc = 0; + struct scatterlist *sge; + unsigned int orig_offset; + + len -= size; + pfrag = sk_page_frag(sk); + + while (len > 0) { + if (!sk_page_frag_refill(sk, pfrag)) { + rc = -ENOMEM; + goto out; + } + + use = min_t(int, len, pfrag->size - pfrag->offset); + + if (!sk_wmem_schedule(sk, use)) { + rc = -ENOMEM; + goto out; + } + + sk_mem_charge(sk, use); + size += use; + orig_offset = pfrag->offset; + pfrag->offset += use; + + sge = sg + num_elem - 1; + if (num_elem > first_coalesce && sg_page(sg) == pfrag->page && + sg->offset + sg->length == orig_offset) { + sg->length += use; + } else { + sge++; + sg_unmark_end(sge); + sg_set_page(sge, pfrag->page, use, orig_offset); + get_page(pfrag->page); + ++num_elem; + if (num_elem == MAX_SKB_FRAGS) { + rc = -ENOSPC; + break; + } + } + + len -= use; + } +out: + *sg_size = size; + *sg_num_elem = num_elem; + return rc; +} +EXPORT_SYMBOL(sk_alloc_sg); + static void __lock_sock(struct sock *sk) __releases(&sk->sk_lock.slock) __acquires(&sk->sk_lock.slock) diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index f26376e..0fc8a24 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -87,71 +87,16 @@ static void trim_both_sgl(struct sock *sk, int target_size) target_size); } -static int alloc_sg(struct sock *sk, int len, struct scatterlist *sg, - int *sg_num_elem, unsigned int *sg_size, - int first_coalesce) -{ - struct page_frag *pfrag; - unsigned int size = *sg_size; - int num_elem = *sg_num_elem, use = 0, rc = 0; - struct scatterlist *sge; - unsigned int orig_offset; - - len -= size; - pfrag = sk_page_frag(sk); - - while (len > 0) { - if (!sk_page_frag_refill(sk, pfrag)) { - rc = -ENOMEM; - goto out; - } - - use = min_t(int, len, pfrag->size - pfrag->offset); - - if (!sk_wmem_schedule(sk, use)) { - rc = -ENOMEM; - goto out; - } - - sk_mem_charge(sk, use); - size += use; - orig_offset = pfrag->offset; - pfrag->offset += use; - - sge = sg + num_elem - 1; - if (num_elem > first_coalesce && sg_page(sg) == pfrag->page && - sg->offset + sg->length == orig_offset) { - sg->length += use; - } else { - sge++; - sg_unmark_end(sge); - sg_set_page(sge, pfrag->page, use, orig_offset); - get_page(pfrag->page); - ++num_elem; - if (num_elem == MAX_SKB_FRAGS) { - rc = -ENOSPC; - break; - } - } - - len -= use; - } - goto out; - -out: - *sg_size = size; - *sg_num_elem = num_elem; - return rc; -} - static int alloc_encrypted_sg(struct sock *sk, int len) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_sw_context *ctx = tls_sw_ctx(tls_ctx); int rc = 0; - rc = alloc_sg(sk, len, ctx->sg_encrypted_data, - &ctx->sg_encrypted_num_elem, &ctx->sg_encrypted_size, 0); + rc = sk_alloc_sg(sk, len, + ctx->sg_encrypted_data, + &ctx->sg_encrypted_num_elem, + &ctx->sg_encrypted_size, 0); return rc; } @@ -162,9 +107,9 @@ static int alloc_plaintext_sg(struct sock *sk, int len) struct tls_sw_context *ctx = tls_sw_ctx(tls_ctx); int rc = 0; - rc = alloc_sg(sk, len, ctx->sg_plaintext_data, - &ctx->sg_plaintext_num_elem, &ctx->sg_plaintext_size, - tls_ctx->pending_open_record_frags); + rc = sk_alloc_sg(sk, len, ctx->sg_plaintext_data, + &ctx->sg_plaintext_num_elem, &ctx->sg_plaintext_size, + tls_ctx->pending_open_record_frags); return rc; } ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper 2018-03-05 19:51 ` [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper John Fastabend @ 2018-03-05 21:32 ` David Miller 0 siblings, 0 replies; 31+ messages in thread From: David Miller @ 2018-03-05 21:32 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 05 Mar 2018 11:51:01 -0800 > The TLS ULP module builds scatterlists from a sock using > page_frag_refill(). This is going to be useful for other ULPs > so move it into sock file for more general use. > > In the process remove useless goto at end of while loop. > > Signed-off-by: John Fastabend <john.fastabend@gmail.com> Acked-by: David S. Miller <davem@davemloft.net> ^ permalink raw reply [flat|nested] 31+ messages in thread
* [bpf-next PATCH 02/16] sockmap: convert refcnt to an atomic refcnt 2018-03-05 19:50 [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP John Fastabend 2018-03-05 19:51 ` [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper John Fastabend @ 2018-03-05 19:51 ` John Fastabend 2018-03-05 21:34 ` David Miller 2018-03-05 19:51 ` [bpf-next PATCH 03/16] net: do_tcp_sendpages flag to avoid SKBTX_SHARED_FRAG John Fastabend ` (13 subsequent siblings) 15 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-05 19:51 UTC (permalink / raw) To: ast, daniel; +Cc: netdev, davejwatson The sockmap refcnt up until now has been wrapped in the sk_callback_lock(). So its not actually needed any locking of its own. The counter itself tracks the lifetime of the psock object. Sockets in a sockmap have a lifetime that is independent of the map they are part of. This is possible because a single socket may be in multiple maps. When this happens we can only release the psock data associated with the socket when the refcnt reaches zero. There are three possible delete sock reference decrement paths first through the normal sockmap process, the user deletes the socket from the map. Second the map is removed and all sockets in the map are removed, delete path is similar to case 1. The third case is an asyncronous socket event such as a closing the socket. The last case handles removing sockets that are no longer available. For completeness, although inc does not pose any problems in this patch series, the inc case only happens when a psock is added to a map. Next we plan to add another socket prog type to handle policy and monitoring on the TX path. When we do this however we will need to keep a reference count open across the sendmsg/sendpage call and holding the sk_callback_lock() here (on every send) seems less than ideal, also it may sleep in cases where we hit memory pressure. Instead of dealing with these issues in some clever way simply make the reference counting a refcnt_t type and do proper atomic ops. Signed-off-by: John Fastabend <john.fastabend@gmail.com> --- kernel/bpf/sockmap.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/kernel/bpf/sockmap.c b/kernel/bpf/sockmap.c index a927e89..051b2242 100644 --- a/kernel/bpf/sockmap.c +++ b/kernel/bpf/sockmap.c @@ -62,8 +62,7 @@ struct smap_psock_map_entry { struct smap_psock { struct rcu_head rcu; - /* refcnt is used inside sk_callback_lock */ - u32 refcnt; + refcount_t refcnt; /* datapath variables */ struct sk_buff_head rxqueue; @@ -373,15 +372,13 @@ static void smap_destroy_psock(struct rcu_head *rcu) static void smap_release_sock(struct smap_psock *psock, struct sock *sock) { - psock->refcnt--; - if (psock->refcnt) - return; - - tcp_cleanup_ulp(sock); - smap_stop_sock(psock, sock); - clear_bit(SMAP_TX_RUNNING, &psock->state); - rcu_assign_sk_user_data(sock, NULL); - call_rcu_sched(&psock->rcu, smap_destroy_psock); + if (refcount_dec_and_test(&psock->refcnt)) { + tcp_cleanup_ulp(sock); + smap_stop_sock(psock, sock); + clear_bit(SMAP_TX_RUNNING, &psock->state); + rcu_assign_sk_user_data(sock, NULL); + call_rcu_sched(&psock->rcu, smap_destroy_psock); + } } static int smap_parse_func_strparser(struct strparser *strp, @@ -511,7 +508,7 @@ static struct smap_psock *smap_init_psock(struct sock *sock, INIT_WORK(&psock->tx_work, smap_tx_work); INIT_WORK(&psock->gc_work, smap_gc_work); INIT_LIST_HEAD(&psock->maps); - psock->refcnt = 1; + refcount_set(&psock->refcnt, 1); rcu_assign_sk_user_data(sock, psock); sock_hold(sock); @@ -772,7 +769,7 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, err = -EBUSY; goto out_progs; } - psock->refcnt++; + refcount_inc(&psock->refcnt); } else { psock = smap_init_psock(sock, stab); if (IS_ERR(psock)) { ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 02/16] sockmap: convert refcnt to an atomic refcnt 2018-03-05 19:51 ` [bpf-next PATCH 02/16] sockmap: convert refcnt to an atomic refcnt John Fastabend @ 2018-03-05 21:34 ` David Miller 0 siblings, 0 replies; 31+ messages in thread From: David Miller @ 2018-03-05 21:34 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 05 Mar 2018 11:51:06 -0800 > The sockmap refcnt up until now has been wrapped in the > sk_callback_lock(). So its not actually needed any locking of its > own. The counter itself tracks the lifetime of the psock object. > Sockets in a sockmap have a lifetime that is independent of the > map they are part of. This is possible because a single socket may > be in multiple maps. When this happens we can only release the > psock data associated with the socket when the refcnt reaches > zero. There are three possible delete sock reference decrement > paths first through the normal sockmap process, the user deletes > the socket from the map. Second the map is removed and all sockets > in the map are removed, delete path is similar to case 1. The third > case is an asyncronous socket event such as a closing the socket. The > last case handles removing sockets that are no longer available. > For completeness, although inc does not pose any problems in this > patch series, the inc case only happens when a psock is added to a > map. > > Next we plan to add another socket prog type to handle policy and > monitoring on the TX path. When we do this however we will need to > keep a reference count open across the sendmsg/sendpage call and > holding the sk_callback_lock() here (on every send) seems less than > ideal, also it may sleep in cases where we hit memory pressure. > Instead of dealing with these issues in some clever way simply make > the reference counting a refcnt_t type and do proper atomic ops. > > Signed-off-by: John Fastabend <john.fastabend@gmail.com> Acked-by: David S. Miller <davem@davemloft.net> ^ permalink raw reply [flat|nested] 31+ messages in thread
* [bpf-next PATCH 03/16] net: do_tcp_sendpages flag to avoid SKBTX_SHARED_FRAG 2018-03-05 19:50 [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP John Fastabend 2018-03-05 19:51 ` [bpf-next PATCH 01/16] sock: make static tls function alloc_sg generic sock helper John Fastabend 2018-03-05 19:51 ` [bpf-next PATCH 02/16] sockmap: convert refcnt to an atomic refcnt John Fastabend @ 2018-03-05 19:51 ` John Fastabend 2018-03-05 19:51 ` [bpf-next PATCH 04/16] net: generalize sk_alloc_sg to work with scatterlist rings John Fastabend ` (12 subsequent siblings) 15 siblings, 0 replies; 31+ messages in thread From: John Fastabend @ 2018-03-05 19:51 UTC (permalink / raw) To: ast, daniel; +Cc: netdev, davejwatson When calling do_tcp_sendpages() from in kernel and we know the data has no references from user side we can omit SKBTX_SHARED_FRAG flag. This patch adds an internal flag, NO_SKBTX_SHARED_FRAG that can be used to omit setting SKBTX_SHARED_FRAG. The flag is not exposed to userspace because the sendpage call from the splice logic masks out all bits except MSG_MORE. Signed-off-by: John Fastabend <john.fastabend@gmail.com> --- include/linux/socket.h | 1 + net/ipv4/tcp.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/linux/socket.h b/include/linux/socket.h index 1ce1f76..60e0148 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -287,6 +287,7 @@ struct ucred { #define MSG_SENDPAGE_NOTLAST 0x20000 /* sendpage() internal : not the last page */ #define MSG_BATCH 0x40000 /* sendmmsg(): more messages coming */ #define MSG_EOF MSG_FIN +#define MSG_NO_SHARED_FRAGS 0x80000 /* sendpage() internal : page frags are not shared */ #define MSG_ZEROCOPY 0x4000000 /* Use user data in kernel path */ #define MSG_FASTOPEN 0x20000000 /* Send data in TCP SYN */ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index a335397..ff8a8d3 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -994,7 +994,9 @@ ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset, get_page(page); skb_fill_page_desc(skb, i, page, offset, copy); } - skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG; + + if (!(flags & MSG_NO_SHARED_FRAGS)) + skb_shinfo(skb)->tx_flags |= SKBTX_SHARED_FRAG; skb->len += copy; skb->data_len += copy; ^ permalink raw reply related [flat|nested] 31+ messages in thread
* [bpf-next PATCH 04/16] net: generalize sk_alloc_sg to work with scatterlist rings 2018-03-05 19:50 [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP John Fastabend ` (2 preceding siblings ...) 2018-03-05 19:51 ` [bpf-next PATCH 03/16] net: do_tcp_sendpages flag to avoid SKBTX_SHARED_FRAG John Fastabend @ 2018-03-05 19:51 ` John Fastabend 2018-03-05 21:35 ` David Miller 2018-03-05 19:51 ` [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data John Fastabend ` (11 subsequent siblings) 15 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-05 19:51 UTC (permalink / raw) To: ast, daniel; +Cc: netdev, davejwatson The current implementation of sk_alloc_sg expects scatterlist to always start at entry 0 and complete at entry MAX_SKB_FRAGS. Future patches will want to support starting at arbitrary offset into scatterlist so add an additional sg_start parameters and then default to the current values in TLS code paths. Signed-off-by: John Fastabend <john.fastabend@gmail.com> --- include/net/sock.h | 2 +- net/core/sock.c | 27 ++++++++++++++++----------- net/tls/tls_sw.c | 4 ++-- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/include/net/sock.h b/include/net/sock.h index 447150c..b7c75e0 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2142,7 +2142,7 @@ static inline struct page_frag *sk_page_frag(struct sock *sk) bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag); int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, - int *sg_num_elem, unsigned int *sg_size, + int sg_start, int *sg_curr, unsigned int *sg_size, int first_coalesce); /* diff --git a/net/core/sock.c b/net/core/sock.c index 4bda3e9..d14f64b 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2239,19 +2239,20 @@ bool sk_page_frag_refill(struct sock *sk, struct page_frag *pfrag) EXPORT_SYMBOL(sk_page_frag_refill); int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, - int *sg_num_elem, unsigned int *sg_size, + int sg_start, int *sg_curr_index, unsigned int *sg_curr_size, int first_coalesce) { + int sg_curr = *sg_curr_index, use = 0, rc = 0; + unsigned int size = *sg_curr_size; struct page_frag *pfrag; - unsigned int size = *sg_size; - int num_elem = *sg_num_elem, use = 0, rc = 0; struct scatterlist *sge; - unsigned int orig_offset; len -= size; pfrag = sk_page_frag(sk); while (len > 0) { + unsigned int orig_offset; + if (!sk_page_frag_refill(sk, pfrag)) { rc = -ENOMEM; goto out; @@ -2269,17 +2270,21 @@ int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, orig_offset = pfrag->offset; pfrag->offset += use; - sge = sg + num_elem - 1; - if (num_elem > first_coalesce && sg_page(sg) == pfrag->page && + sge = sg + sg_curr - 1; + if (sg_curr > first_coalesce && sg_page(sg) == pfrag->page && sg->offset + sg->length == orig_offset) { sg->length += use; } else { - sge++; + sge = sg + sg_curr; sg_unmark_end(sge); sg_set_page(sge, pfrag->page, use, orig_offset); get_page(pfrag->page); - ++num_elem; - if (num_elem == MAX_SKB_FRAGS) { + sg_curr++; + + if (sg_curr == MAX_SKB_FRAGS) + sg_curr = 0; + + if (sg_curr == sg_start) { rc = -ENOSPC; break; } @@ -2288,8 +2293,8 @@ int sk_alloc_sg(struct sock *sk, int len, struct scatterlist *sg, len -= use; } out: - *sg_size = size; - *sg_num_elem = num_elem; + *sg_curr_size = size; + *sg_curr_index = sg_curr; return rc; } EXPORT_SYMBOL(sk_alloc_sg); diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 0fc8a24..057a558 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -94,7 +94,7 @@ static int alloc_encrypted_sg(struct sock *sk, int len) int rc = 0; rc = sk_alloc_sg(sk, len, - ctx->sg_encrypted_data, + ctx->sg_encrypted_data, 0, &ctx->sg_encrypted_num_elem, &ctx->sg_encrypted_size, 0); @@ -107,7 +107,7 @@ static int alloc_plaintext_sg(struct sock *sk, int len) struct tls_sw_context *ctx = tls_sw_ctx(tls_ctx); int rc = 0; - rc = sk_alloc_sg(sk, len, ctx->sg_plaintext_data, + rc = sk_alloc_sg(sk, len, ctx->sg_plaintext_data, 0, &ctx->sg_plaintext_num_elem, &ctx->sg_plaintext_size, tls_ctx->pending_open_record_frags); ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 04/16] net: generalize sk_alloc_sg to work with scatterlist rings 2018-03-05 19:51 ` [bpf-next PATCH 04/16] net: generalize sk_alloc_sg to work with scatterlist rings John Fastabend @ 2018-03-05 21:35 ` David Miller 0 siblings, 0 replies; 31+ messages in thread From: David Miller @ 2018-03-05 21:35 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 05 Mar 2018 11:51:17 -0800 > The current implementation of sk_alloc_sg expects scatterlist to always > start at entry 0 and complete at entry MAX_SKB_FRAGS. > > Future patches will want to support starting at arbitrary offset into > scatterlist so add an additional sg_start parameters and then default > to the current values in TLS code paths. > > Signed-off-by: John Fastabend <john.fastabend@gmail.com> Acked-by: David S. Miller <davem@davemloft.net> ^ permalink raw reply [flat|nested] 31+ messages in thread
* [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-05 19:50 [bpf-next PATCH 00/16] bpf,sockmap: sendmsg/sendfile ULP John Fastabend ` (3 preceding siblings ...) 2018-03-05 19:51 ` [bpf-next PATCH 04/16] net: generalize sk_alloc_sg to work with scatterlist rings John Fastabend @ 2018-03-05 19:51 ` John Fastabend 2018-03-05 21:40 ` David Miller 2018-03-05 19:51 ` [bpf-next PATCH 06/16] bpf: sockmap, add bpf_msg_apply_bytes() helper John Fastabend ` (10 subsequent siblings) 15 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-05 19:51 UTC (permalink / raw) To: ast, daniel; +Cc: netdev, davejwatson This implements a BPF ULP layer to allow policy enforcement and monitoring at the socket layer. In order to support this a new program type BPF_PROG_TYPE_SK_MSG is used to run the policy at the sendmsg/sendpage hook. To attach the policy to sockets a sockmap is used with a new program attach type BPF_SK_MSG_VERDICT. Similar to previous sockmap usages when a sock is added to a sockmap, via a map update, if the map contains a BPF_SK_MSG_VERDICT program type attached then the BPF ULP layer is created on the socket and the attached BPF_PROG_TYPE_SK_MSG program is run for every msg in sendmsg case and page/offset in sendpage case. BPF_PROG_TYPE_SK_MSG Semantics/API: BPF_PROG_TYPE_SK_MSG supports only two return codes SK_PASS and SK_DROP. Returning SK_DROP free's the copied data in the sendmsg case and in the sendpage case leaves the data untouched. Both cases return -EACESS to the user. Returning SK_PASS will allow the msg to be sent. In the sendmsg case data is copied into kernel space buffers before running the BPF program. In the sendpage case data is never copied. The implication being users may change data after BPF programs run in the sendpage case. (A flag will be added to always copy shortly if the copy must always be performed). The verdict from the BPF_PROG_TYPE_SK_MSG applies to the entire msg in the sendmsg() case and the entire page/offset in the sendpage case. This avoids ambiguity on how to handle mixed return codes in the sendmsg case. The readable/writeable data provided to the program in the sendmsg case may not be the entire message, in fact for large sends this is likely the case. The data range that can be read is part of the sk_msg_md structure. This is because similar to the tc bpf_cls case the data is stored in a scatter gather list. Future work will address this short-coming to allow users to pull in more data if needed (similar to TC BPF). The helper msg_redirect_map() can be used to select the socket to send the data on. This is used similar to existing redirect use cases. This allows policy to redirect msgs. Pseudo code simple example: The basic logic to attach a program to a socket is as follows, // load the programs bpf_prog_load(SOCKMAP_TCP_MSG_PROG, BPF_PROG_TYPE_SK_MSG, &obj, &msg_prog); // lookup the sockmap bpf_map_msg = bpf_object__find_map_by_name(obj, "my_sock_map"); // get fd for sockmap map_fd_msg = bpf_map__fd(bpf_map_msg); // attach program to sockmap bpf_prog_attach(msg_prog, map_fd_msg, BPF_SK_MSG_VERDICT, 0); Adding sockets to the map is done in the normal way, // Add a socket 'fd' to sockmap at location 'i' bpf_map_update_elem(map_fd_msg, &i, fd, BPF_ANY); After the above any socket attached to "my_sock_map", in this case 'fd', will run the BPF msg verdict program (msg_prog) on every sendmsg and sendpage system call. For a complete example see BPF selftests or sockmap samples. Implementation notes: It seemed the simplest, to me at least, to use a refcnt to ensure psock is not lost across the sendmsg copy into the sg, the bpf program running on the data in sg_data, and the final pass to the TCP stack. Some performance testing may show a better method to do this and avoid the refcnt cost, but for now use the simpler method. Another item that will come after basic support is in place is supporting MSG_MORE flag. At the moment we call sendpages even if the MSG_MORE flag is set. An enhancement would be to collect the pages into a larger scatterlist and pass down the stack. Notice that bpf_tcp_sendmsg() could support this with some additional state saved across sendmsg calls. I built the code to support this without having to do refactoring work. Other features TBD include ZEROCOPY and the TCP_RECV_QUEUE/TCP_NO_QUEUE support. This will follow initial series shortly. Future work could improve size limits on the scatterlist rings used here. Currently, we use MAX_SKB_FRAGS simply because this was being used already in the TLS case. Future work could extend the kernel sk APIs to tune this depending on workload. This is a trade-off between memory usage and throughput performance. Signed-off-by: John Fastabend <john.fastabend@gmail.com> --- include/linux/bpf.h | 1 include/linux/bpf_types.h | 1 include/linux/filter.h | 14 + include/uapi/linux/bpf.h | 28 ++ kernel/bpf/sockmap.c | 517 ++++++++++++++++++++++++++++++++++++++++++++- kernel/bpf/syscall.c | 14 + kernel/bpf/verifier.c | 5 net/core/filter.c | 106 +++++++++ 8 files changed, 668 insertions(+), 18 deletions(-) diff --git a/include/linux/bpf.h b/include/linux/bpf.h index 66df387..819229c 100644 --- a/include/linux/bpf.h +++ b/include/linux/bpf.h @@ -21,6 +21,7 @@ struct perf_event; struct bpf_prog; struct bpf_map; +struct sock; /* map is generic key/value storage optionally accesible by eBPF programs */ struct bpf_map_ops { diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index 19b8349..5e2e8a4 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -13,6 +13,7 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_LWT_XMIT, lwt_xmit) BPF_PROG_TYPE(BPF_PROG_TYPE_SOCK_OPS, sock_ops) BPF_PROG_TYPE(BPF_PROG_TYPE_SK_SKB, sk_skb) +BPF_PROG_TYPE(BPF_PROG_TYPE_SK_MSG, sk_msg) #endif #ifdef CONFIG_BPF_EVENTS BPF_PROG_TYPE(BPF_PROG_TYPE_KPROBE, kprobe) diff --git a/include/linux/filter.h b/include/linux/filter.h index fdb691b..15c663e 100644 --- a/include/linux/filter.h +++ b/include/linux/filter.h @@ -507,6 +507,19 @@ struct xdp_buff { struct xdp_rxq_info *rxq; }; +struct sk_msg_buff { + void *data; + void *data_end; + int sg_start; + int sg_curr; + int sg_end; + int sg_size; + struct scatterlist sg_data[MAX_SKB_FRAGS]; + __u32 key; + __u32 flags; + struct bpf_map *map; +}; + /* Compute the linear packet data range [data, data_end) which * will be accessed by various program types (cls_bpf, act_bpf, * lwt, ...). Subsystems allowing direct data access must (!) @@ -771,6 +784,7 @@ int xdp_do_redirect(struct net_device *dev, void bpf_warn_invalid_xdp_action(u32 act); struct sock *do_sk_redirect_map(struct sk_buff *skb); +struct sock *do_msg_redirect_map(struct sk_msg_buff *md); #ifdef CONFIG_BPF_JIT extern int bpf_jit_enable; diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 2a66769..b8275f0 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -133,6 +133,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_SOCK_OPS, BPF_PROG_TYPE_SK_SKB, BPF_PROG_TYPE_CGROUP_DEVICE, + BPF_PROG_TYPE_SK_MSG, }; enum bpf_attach_type { @@ -143,6 +144,7 @@ enum bpf_attach_type { BPF_SK_SKB_STREAM_PARSER, BPF_SK_SKB_STREAM_VERDICT, BPF_CGROUP_DEVICE, + BPF_SK_MSG_VERDICT, __MAX_BPF_ATTACH_TYPE }; @@ -696,6 +698,15 @@ enum bpf_attach_type { * int bpf_override_return(pt_regs, rc) * @pt_regs: pointer to struct pt_regs * @rc: the return value to set + * + * int bpf_msg_redirect_map(map, key, flags) + * Redirect msg to a sock in map using key as a lookup key for the + * sock in map. + * @map: pointer to sockmap + * @key: key to lookup sock in map + * @flags: reserved for future use + * Return: SK_PASS + * */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -757,7 +768,8 @@ enum bpf_attach_type { FN(perf_prog_read_value), \ FN(getsockopt), \ FN(override_return), \ - FN(sock_ops_cb_flags_set), + FN(sock_ops_cb_flags_set), \ + FN(msg_redirect_map), /* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call @@ -920,6 +932,20 @@ enum sk_action { SK_PASS, }; +/* User return codes for SK_MSG prog type. */ +enum sk_msg_action { + SK_MSG_DROP = 0, + SK_MSG_PASS, +}; + +/* user accessible metadata for SK_MSG packet hook, new fields must + * be added to the end of this structure + */ +struct sk_msg_md { + __u32 data; + __u32 data_end; +}; + #define BPF_TAG_SIZE 8 struct bpf_prog_info { diff --git a/kernel/bpf/sockmap.c b/kernel/bpf/sockmap.c index 051b2242..0fd5556 100644 --- a/kernel/bpf/sockmap.c +++ b/kernel/bpf/sockmap.c @@ -38,6 +38,7 @@ #include <linux/skbuff.h> #include <linux/workqueue.h> #include <linux/list.h> +#include <linux/mm.h> #include <net/strparser.h> #include <net/tcp.h> @@ -47,6 +48,7 @@ struct bpf_stab { struct bpf_map map; struct sock **sock_map; + struct bpf_prog *bpf_tx_msg; struct bpf_prog *bpf_parse; struct bpf_prog *bpf_verdict; }; @@ -74,6 +76,7 @@ struct smap_psock { struct sk_buff *save_skb; struct strparser strp; + struct bpf_prog *bpf_tx_msg; struct bpf_prog *bpf_parse; struct bpf_prog *bpf_verdict; struct list_head maps; @@ -91,6 +94,11 @@ struct smap_psock { void (*save_write_space)(struct sock *sk); }; +static void smap_release_sock(struct smap_psock *psock, struct sock *sock); +static int bpf_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size); +static int bpf_tcp_sendpage(struct sock *sk, struct page *page, + int offset, size_t size, int flags); + static inline struct smap_psock *smap_psock_sk(const struct sock *sk) { return rcu_dereference_sk_user_data(sk); @@ -115,6 +123,12 @@ static int bpf_tcp_init(struct sock *sk) psock->save_close = sk->sk_prot->close; psock->sk_proto = sk->sk_prot; + + if (psock->bpf_tx_msg) { + tcp_bpf_proto.sendmsg = bpf_tcp_sendmsg; + tcp_bpf_proto.sendpage = bpf_tcp_sendpage; + } + sk->sk_prot = &tcp_bpf_proto; rcu_read_unlock(); return 0; @@ -174,6 +188,7 @@ enum __sk_action { __SK_DROP = 0, __SK_PASS, __SK_REDIRECT, + __SK_NONE, }; static struct tcp_ulp_ops bpf_tcp_ulp_ops __read_mostly = { @@ -185,10 +200,459 @@ enum __sk_action { .release = bpf_tcp_release, }; +static int memcopy_from_iter(struct sock *sk, + struct sk_msg_buff *md, + struct iov_iter *from, int bytes) +{ + struct scatterlist *sg = md->sg_data; + int i = md->sg_curr, rc = 0; + + do { + int copy; + char *to; + + copy = sg[i].length; + to = sg_virt(&sg[i]); + + if (sk->sk_route_caps & NETIF_F_NOCACHE_COPY) + rc = copy_from_iter_nocache(to, copy, from); + else + rc = copy_from_iter(to, copy, from); + + if (rc != copy) { + rc = -EFAULT; + goto out; + } + + bytes -= copy; + if (!bytes) + break; + + if (++i == MAX_SKB_FRAGS) + i = 0; + } while (i != md->sg_end); +out: + md->sg_curr = i; + return rc; +} + +static int bpf_tcp_push(struct sock *sk, + struct smap_psock *psock, struct sk_msg_buff *md, + int flags, bool uncharge) +{ + struct scatterlist *sg; + int offset, ret = 0; + struct page *p; + size_t size; + + while (1) { + sg = md->sg_data + md->sg_start; + size = sg->length; + offset = sg->offset; + + tcp_rate_check_app_limited(sk); + p = sg_page(sg); +retry: + ret = do_tcp_sendpages(sk, p, offset, size, flags); + if (ret != size) { + if (ret > 0) { + size -= ret; + offset += ret; + if (uncharge) + sk_mem_uncharge(sk, ret); + goto retry; + } + + sg->length = size; + sg->offset = offset; + return ret; + } + + put_page(p); + sg->offset += ret; + sg->length -= ret; + if (uncharge) + sk_mem_uncharge(sk, ret); + + if (!sg->length) { + put_page(p); + md->sg_start++; + if (md->sg_start == MAX_SKB_FRAGS) + md->sg_start = 0; + memset(sg, 0, sizeof(*sg)); + } + + if (md->sg_start == md->sg_end) + break; + } + return 0; +} + +static inline void bpf_compute_data_pointers_sg(struct sk_msg_buff *md) +{ + struct scatterlist *sg = md->sg_data + md->sg_start; + + md->data = sg_virt(sg); + md->data_end = md->data + sg->length; +} + +static void return_mem_sg(struct sock *sk, struct sk_msg_buff *md) +{ + struct scatterlist *sg = md->sg_data; + int i; + + i = md->sg_start; + do { + sk_mem_uncharge(sk, sg[i].length); + + i++; + if (i == MAX_SKB_FRAGS) + i = 0; + } while (i != md->sg_end); +} + +static int free_sg(struct sock *sk, int start, struct sk_msg_buff *md) +{ + struct scatterlist *sg = md->sg_data; + int i = start, free = 0; + + while (sg[i].length) { + free += sg[i].length; + sk_mem_uncharge(sk, sg[i].length); + put_page(sg_page(&sg[i])); + sg[i].length = 0; + sg[i].page_link = 0; + sg[i].offset = 0; + i++; + + if (i == MAX_SKB_FRAGS) + i = 0; + } + + return free; +} + +static int free_start_sg(struct sock *sk, struct sk_msg_buff *md) +{ + int free = free_sg(sk, md->sg_start, md); + + md->sg_start = md->sg_end; + return free; +} + +static int free_curr_sg(struct sock *sk, struct sk_msg_buff *md) +{ + return free_sg(sk, md->sg_curr, md); +} + +static int bpf_map_msg_verdict(int _rc, struct sk_msg_buff *md) +{ + return ((_rc == SK_PASS) ? + (md->map ? __SK_REDIRECT : __SK_PASS) : + __SK_DROP); +} + +static unsigned int smap_do_tx_msg(struct sock *sk, + struct smap_psock *psock, + struct sk_msg_buff *md) +{ + struct bpf_prog *prog; + unsigned int rc, _rc; + + preempt_disable(); + rcu_read_lock(); + + /* If the policy was removed mid-send then default to 'accept' */ + prog = READ_ONCE(psock->bpf_tx_msg); + if (unlikely(!prog)) { + _rc = SK_PASS; + goto verdict; + } + + bpf_compute_data_pointers_sg(md); + rc = (*prog->bpf_func)(md, prog->insnsi); + + /* Moving return codes from UAPI namespace into internal namespace */ + _rc = bpf_map_msg_verdict(rc, md); +verdict: + rcu_read_unlock(); + preempt_enable(); + + return _rc; +} + +static int bpf_tcp_sendmsg_do_redirect(struct sk_msg_buff *md, + int flags) +{ + struct smap_psock *psock; + struct scatterlist *sg; + int i, err, free = 0; + struct sock *sk; + + sg = md->sg_data; + + rcu_read_lock(); + sk = do_msg_redirect_map(md); + if (unlikely(!sk)) + goto out_rcu; + + psock = smap_psock_sk(sk); + if (unlikely(!psock)) + goto out_rcu; + + if (!refcount_inc_not_zero(&psock->refcnt)) + goto out_rcu; + + rcu_read_unlock(); + lock_sock(sk); + err = bpf_tcp_push(sk, psock, md, flags, false); + release_sock(sk); + smap_release_sock(psock, sk); + if (unlikely(err)) + goto out; + return 0; +out_rcu: + rcu_read_unlock(); +out: + i = md->sg_start; + while (sg[i].length) { + free += sg[i].length; + put_page(sg_page(&sg[i])); + sg[i].length = 0; + i++; + if (i == MAX_SKB_FRAGS) + i = 0; + } + return free; +} + +static inline void bpf_md_init(struct sk_msg_buff *md) +{ + md->sg_size = 0; +} + +static int bpf_tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) +{ + int flags = msg->msg_flags | MSG_NO_SHARED_FRAGS; + int err = 0, eval = __SK_NONE; + struct sk_msg_buff md = {0}; + unsigned int sg_copy = 0; + struct smap_psock *psock; + size_t copy, copied = 0; + struct scatterlist *sg; + long timeo; + + /* Its possible a sock event or user removed the psock _but_ the ops + * have not been reprogrammed yet so we get here. In this case fallback + * to tcp_sendmsg. Note this only works because we _only_ ever allow + * a single ULP there is no hierarchy here. + */ + rcu_read_lock(); + psock = smap_psock_sk(sk); + if (unlikely(!psock)) { + rcu_read_unlock(); + return tcp_sendmsg(sk, msg, size); + } + + /* Increment the psock refcnt to ensure its not released while sending a + * message. Required because sk lookup and bpf programs are used in + * separate rcu critical sections. Its OK if we lose the map entry + * but we can't lose the sock reference, possible when the refcnt hits + * zero and garbage collection calls sock_put(). + */ + if (!refcount_inc_not_zero(&psock->refcnt)) { + rcu_read_unlock(); + return tcp_sendmsg(sk, msg, size); + } + + sg = md.sg_data; + sg_init_table(sg, MAX_SKB_FRAGS); + rcu_read_unlock(); + + lock_sock(sk); + timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT); + + md.sg_size = 0; + + while (msg_data_left(msg)) { + if (sk->sk_err) { + err = sk->sk_err; + goto out_err; + } + + copy = msg_data_left(msg); + if (!sk_stream_memory_free(sk)) + goto wait_for_sndbuf; + + md.sg_curr = md.sg_end; + err = sk_alloc_sg(sk, copy, sg, + md.sg_start, &md.sg_end, &sg_copy, + md.sg_end); + if (err) { + if (err != -ENOSPC) + goto wait_for_memory; + copy = sg_copy; + } + + err = memcopy_from_iter(sk, &md, &msg->msg_iter, copy); + if (err < 0) { + free_curr_sg(sk, &md); + goto out_err; + } + + copied += copy; + sg_copy = 0; + /* If msg is larger than MAX_SKB_FRAGS we can send multiple + * scatterlists per msg. However BPF decisions apply to the + * entire msg. + */ + if (eval == __SK_NONE) + eval = smap_do_tx_msg(sk, psock, &md); + + switch (eval) { + case __SK_PASS: + err = bpf_tcp_push(sk, psock, &md, flags, true); + if (unlikely(err)) { + copied -= free_start_sg(sk, &md); + goto out_err; + } + break; + case __SK_REDIRECT: + return_mem_sg(sk, &md); + release_sock(sk); + err = bpf_tcp_sendmsg_do_redirect(&md, flags); + if (unlikely(err)) { + copied -= err; + goto out_redir; + } + lock_sock(sk); + break; + case __SK_DROP: + default: + copied -= free_start_sg(sk, &md); + goto out_err; + } + + bpf_md_init(&md); + continue; +wait_for_sndbuf: + set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); +wait_for_memory: + err = sk_stream_wait_memory(sk, &timeo); + if (err) + goto out_err; + } +out_err: + bpf_md_init(&md); + if (err < 0) + err = sk_stream_error(sk, msg->msg_flags, err); + release_sock(sk); +out_redir: + smap_release_sock(psock, sk); + return copied ? copied : err; +} + +static int bpf_tcp_sendpage_do_redirect(struct page *page, int offset, + size_t size, int flags, + struct sk_msg_buff *md) +{ + struct smap_psock *psock; + struct sock *sk; + int rc; + + rcu_read_lock(); + sk = do_msg_redirect_map(md); + if (unlikely(!sk)) + goto out_rcu; + + psock = smap_psock_sk(sk); + if (unlikely(!psock)) + goto out_rcu; + + if (!refcount_inc_not_zero(&psock->refcnt)) + goto out_rcu; + + rcu_read_unlock(); + + lock_sock(sk); + rc = tcp_sendpage_locked(sk, page, offset, size, flags); + release_sock(sk); + + smap_release_sock(psock, sk); + return rc; +out_rcu: + rcu_read_unlock(); + return -EINVAL; +} + +static int bpf_tcp_sendpage(struct sock *sk, struct page *page, + int offset, size_t size, int flags) +{ + struct sk_msg_buff md = {0}; + struct smap_psock *psock; + int rc, _rc = __SK_PASS; + struct bpf_prog *prog; + + preempt_disable(); + rcu_read_lock(); + psock = smap_psock_sk(sk); + if (unlikely(!psock)) + goto verdict; + + /* If the policy was removed mid-send then default to 'accept' */ + prog = READ_ONCE(psock->bpf_tx_msg); + if (unlikely(!prog)) + goto verdict; + + /* Calculate pkt data pointers and run BPF program */ + md.data = page_address(page) + offset; + md.data_end = md.data + size; + _rc = (*prog->bpf_func)(&md, prog->insnsi); + +verdict: + rcu_read_unlock(); + preempt_enable(); + + /* Moving return codes from UAPI namespace into internal namespace */ + rc = bpf_map_msg_verdict(_rc, &md); + + switch (rc) { + case __SK_PASS: + lock_sock(sk); + rc = tcp_sendpage_locked(sk, page, offset, size, flags); + release_sock(sk); + break; + case __SK_REDIRECT: + rc = bpf_tcp_sendpage_do_redirect(page, offset, size, flags, + &md); + break; + case __SK_DROP: + default: + rc = -EACCES; + } + + return rc; +} + +static void bpf_tcp_msg_add(struct smap_psock *psock, + struct sock *sk, + struct bpf_prog *tx_msg) +{ + struct bpf_prog *orig_tx_msg; + + orig_tx_msg = xchg(&psock->bpf_tx_msg, tx_msg); + if (orig_tx_msg) + bpf_prog_put(orig_tx_msg); +} + static int bpf_tcp_ulp_register(void) { tcp_bpf_proto = tcp_prot; tcp_bpf_proto.close = bpf_tcp_close; + /* Once BPF TX ULP is registered it is never unregistered. It + * will be in the ULP list for the lifetime of the system. Doing + * duplicate registers is not a problem. + */ return tcp_register_ulp(&bpf_tcp_ulp_ops); } @@ -412,7 +876,6 @@ static int smap_parse_func_strparser(struct strparser *strp, return rc; } - static int smap_read_sock_done(struct strparser *strp, int err) { return err; @@ -482,6 +945,8 @@ static void smap_gc_work(struct work_struct *w) bpf_prog_put(psock->bpf_parse); if (psock->bpf_verdict) bpf_prog_put(psock->bpf_verdict); + if (psock->bpf_tx_msg) + bpf_prog_put(psock->bpf_tx_msg); list_for_each_entry_safe(e, tmp, &psock->maps, list) { list_del(&e->list); @@ -668,8 +1133,6 @@ static int sock_map_delete_elem(struct bpf_map *map, void *key) if (!psock) goto out; - if (psock->bpf_parse) - smap_stop_sock(psock, sock); smap_list_remove(psock, &stab->sock_map[k]); smap_release_sock(psock, sock); out: @@ -711,10 +1174,11 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, { struct bpf_stab *stab = container_of(map, struct bpf_stab, map); struct smap_psock_map_entry *e = NULL; - struct bpf_prog *verdict, *parse; + struct bpf_prog *verdict, *parse, *tx_msg; struct sock *osock, *sock; struct smap_psock *psock; u32 i = *(u32 *)key; + bool new = false; int err; if (unlikely(flags > BPF_EXIST)) @@ -737,6 +1201,7 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, */ verdict = READ_ONCE(stab->bpf_verdict); parse = READ_ONCE(stab->bpf_parse); + tx_msg = READ_ONCE(stab->bpf_tx_msg); if (parse && verdict) { /* bpf prog refcnt may be zero if a concurrent attach operation @@ -755,6 +1220,17 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, } } + if (tx_msg) { + tx_msg = bpf_prog_inc_not_zero(stab->bpf_tx_msg); + if (IS_ERR(tx_msg)) { + if (verdict) + bpf_prog_put(verdict); + if (parse) + bpf_prog_put(parse); + return PTR_ERR(tx_msg); + } + } + write_lock_bh(&sock->sk_callback_lock); psock = smap_psock_sk(sock); @@ -769,7 +1245,14 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, err = -EBUSY; goto out_progs; } - refcount_inc(&psock->refcnt); + if (READ_ONCE(psock->bpf_tx_msg) && tx_msg) { + err = -EBUSY; + goto out_progs; + } + if (!refcount_inc_not_zero(&psock->refcnt)) { + err = -EAGAIN; + goto out_progs; + } } else { psock = smap_init_psock(sock, stab); if (IS_ERR(psock)) { @@ -777,11 +1260,8 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, goto out_progs; } - err = tcp_set_ulp_id(sock, TCP_ULP_BPF); - if (err) - goto out_progs; - set_bit(SMAP_TX_RUNNING, &psock->state); + new = true; } e = kzalloc(sizeof(*e), GFP_ATOMIC | __GFP_NOWARN); @@ -794,6 +1274,14 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, /* 3. At this point we have a reference to a valid psock that is * running. Attach any BPF programs needed. */ + if (tx_msg) + bpf_tcp_msg_add(psock, sock, tx_msg); + if (new) { + err = tcp_set_ulp_id(sock, TCP_ULP_BPF); + if (err) + goto out_free; + } + if (parse && verdict && !psock->strp_enabled) { err = smap_init_sock(psock, sock); if (err) @@ -815,8 +1303,6 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, struct smap_psock *opsock = smap_psock_sk(osock); write_lock_bh(&osock->sk_callback_lock); - if (osock != sock && parse) - smap_stop_sock(opsock, osock); smap_list_remove(opsock, &stab->sock_map[i]); smap_release_sock(opsock, osock); write_unlock_bh(&osock->sk_callback_lock); @@ -829,6 +1315,8 @@ static int sock_map_ctx_update_elem(struct bpf_sock_ops_kern *skops, bpf_prog_put(verdict); if (parse) bpf_prog_put(parse); + if (tx_msg) + bpf_prog_put(tx_msg); write_unlock_bh(&sock->sk_callback_lock); kfree(e); return err; @@ -843,6 +1331,9 @@ int sock_map_prog(struct bpf_map *map, struct bpf_prog *prog, u32 type) return -EINVAL; switch (type) { + case BPF_SK_MSG_VERDICT: + orig = xchg(&stab->bpf_tx_msg, prog); + break; case BPF_SK_SKB_STREAM_PARSER: orig = xchg(&stab->bpf_parse, prog); break; @@ -904,6 +1395,10 @@ static void sock_map_release(struct bpf_map *map, struct file *map_file) orig = xchg(&stab->bpf_verdict, NULL); if (orig) bpf_prog_put(orig); + + orig = xchg(&stab->bpf_tx_msg, NULL); + if (orig) + bpf_prog_put(orig); } const struct bpf_map_ops sock_map_ops = { diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index e24aa32..3aeb4ea 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -1315,7 +1315,8 @@ static int bpf_obj_get(const union bpf_attr *attr) #define BPF_PROG_ATTACH_LAST_FIELD attach_flags -static int sockmap_get_from_fd(const union bpf_attr *attr, bool attach) +static int sockmap_get_from_fd(const union bpf_attr *attr, + int type, bool attach) { struct bpf_prog *prog = NULL; int ufd = attr->target_fd; @@ -1329,8 +1330,7 @@ static int sockmap_get_from_fd(const union bpf_attr *attr, bool attach) return PTR_ERR(map); if (attach) { - prog = bpf_prog_get_type(attr->attach_bpf_fd, - BPF_PROG_TYPE_SK_SKB); + prog = bpf_prog_get_type(attr->attach_bpf_fd, type); if (IS_ERR(prog)) { fdput(f); return PTR_ERR(prog); @@ -1382,9 +1382,11 @@ static int bpf_prog_attach(const union bpf_attr *attr) case BPF_CGROUP_DEVICE: ptype = BPF_PROG_TYPE_CGROUP_DEVICE; break; + case BPF_SK_MSG_VERDICT: + return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_MSG, true); case BPF_SK_SKB_STREAM_PARSER: case BPF_SK_SKB_STREAM_VERDICT: - return sockmap_get_from_fd(attr, true); + return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_SKB, true); default: return -EINVAL; } @@ -1437,9 +1439,11 @@ static int bpf_prog_detach(const union bpf_attr *attr) case BPF_CGROUP_DEVICE: ptype = BPF_PROG_TYPE_CGROUP_DEVICE; break; + case BPF_SK_MSG_VERDICT: + return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_MSG, false); case BPF_SK_SKB_STREAM_PARSER: case BPF_SK_SKB_STREAM_VERDICT: - return sockmap_get_from_fd(attr, false); + return sockmap_get_from_fd(attr, BPF_PROG_TYPE_SK_SKB, false); default: return -EINVAL; } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 3c74b16..3d14059 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1248,6 +1248,7 @@ static bool may_access_direct_pkt_data(struct bpf_verifier_env *env, case BPF_PROG_TYPE_XDP: case BPF_PROG_TYPE_LWT_XMIT: case BPF_PROG_TYPE_SK_SKB: + case BPF_PROG_TYPE_SK_MSG: if (meta) return meta->pkt_access; @@ -2062,7 +2063,8 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env, case BPF_MAP_TYPE_SOCKMAP: if (func_id != BPF_FUNC_sk_redirect_map && func_id != BPF_FUNC_sock_map_update && - func_id != BPF_FUNC_map_delete_elem) + func_id != BPF_FUNC_map_delete_elem && + func_id != BPF_FUNC_msg_redirect_map) goto error; break; default: @@ -2100,6 +2102,7 @@ static int check_map_func_compatibility(struct bpf_verifier_env *env, goto error; break; case BPF_FUNC_sk_redirect_map: + case BPF_FUNC_msg_redirect_map: if (map->map_type != BPF_MAP_TYPE_SOCKMAP) goto error; break; diff --git a/net/core/filter.c b/net/core/filter.c index 33edfa8..314c311 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -1890,6 +1890,44 @@ struct sock *do_sk_redirect_map(struct sk_buff *skb) .arg4_type = ARG_ANYTHING, }; +BPF_CALL_4(bpf_msg_redirect_map, struct sk_msg_buff *, msg, + struct bpf_map *, map, u32, key, u64, flags) +{ + /* If user passes invalid input drop the packet. */ + if (unlikely(flags)) + return SK_DROP; + + msg->key = key; + msg->flags = flags; + msg->map = map; + + return SK_PASS; +} + +struct sock *do_msg_redirect_map(struct sk_msg_buff *msg) +{ + struct sock *sk = NULL; + + if (msg->map) { + sk = __sock_map_lookup_elem(msg->map, msg->key); + + msg->key = 0; + msg->map = NULL; + } + + return sk; +} + +static const struct bpf_func_proto bpf_msg_redirect_map_proto = { + .func = bpf_msg_redirect_map, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_CONST_MAP_PTR, + .arg3_type = ARG_ANYTHING, + .arg4_type = ARG_ANYTHING, +}; + BPF_CALL_1(bpf_get_cgroup_classid, const struct sk_buff *, skb) { return task_get_classid(skb); @@ -3591,6 +3629,16 @@ static unsigned long bpf_xdp_copy(void *dst_buff, const void *src_buff, } } +static const struct bpf_func_proto *sk_msg_func_proto(enum bpf_func_id func_id) +{ + switch (func_id) { + case BPF_FUNC_msg_redirect_map: + return &bpf_msg_redirect_map_proto; + default: + return bpf_base_func_proto(func_id); + } +} + static const struct bpf_func_proto *sk_skb_func_proto(enum bpf_func_id func_id) { switch (func_id) { @@ -3980,6 +4028,32 @@ static bool sk_skb_is_valid_access(int off, int size, return bpf_skb_is_valid_access(off, size, type, info); } +static bool sk_msg_is_valid_access(int off, int size, + enum bpf_access_type type, + struct bpf_insn_access_aux *info) +{ + if (type == BPF_WRITE) + return false; + + switch (off) { + case offsetof(struct sk_msg_md, data): + info->reg_type = PTR_TO_PACKET; + break; + case offsetof(struct sk_msg_md, data_end): + info->reg_type = PTR_TO_PACKET_END; + break; + } + + if (off < 0 || off >= sizeof(struct sk_msg_md)) + return false; + if (off % size != 0) + return false; + if (size != sizeof(__u32)) + return false; + + return true; +} + static u32 bpf_convert_ctx_access(enum bpf_access_type type, const struct bpf_insn *si, struct bpf_insn *insn_buf, @@ -4778,6 +4852,29 @@ static u32 sk_skb_convert_ctx_access(enum bpf_access_type type, return insn - insn_buf; } +static u32 sk_msg_convert_ctx_access(enum bpf_access_type type, + const struct bpf_insn *si, + struct bpf_insn *insn_buf, + struct bpf_prog *prog, u32 *target_size) +{ + struct bpf_insn *insn = insn_buf; + + switch (si->off) { + case offsetof(struct sk_msg_md, data): + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg_buff, data), + si->dst_reg, si->src_reg, + offsetof(struct sk_msg_buff, data)); + break; + case offsetof(struct sk_msg_md, data_end): + *insn++ = BPF_LDX_MEM(BPF_FIELD_SIZEOF(struct sk_msg_buff, data_end), + si->dst_reg, si->src_reg, + offsetof(struct sk_msg_buff, data_end)); + break; + } + + return insn - insn_buf; +} + const struct bpf_verifier_ops sk_filter_verifier_ops = { .get_func_proto = sk_filter_func_proto, .is_valid_access = sk_filter_is_valid_access, @@ -4868,6 +4965,15 @@ static u32 sk_skb_convert_ctx_access(enum bpf_access_type type, const struct bpf_prog_ops sk_skb_prog_ops = { }; +const struct bpf_verifier_ops sk_msg_verifier_ops = { + .get_func_proto = sk_msg_func_proto, + .is_valid_access = sk_msg_is_valid_access, + .convert_ctx_access = sk_msg_convert_ctx_access, +}; + +const struct bpf_prog_ops sk_msg_prog_ops = { +}; + int sk_detach_filter(struct sock *sk) { int ret = -ENOENT; ^ permalink raw reply related [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-05 19:51 ` [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data John Fastabend @ 2018-03-05 21:40 ` David Miller 2018-03-05 22:53 ` John Fastabend 0 siblings, 1 reply; 31+ messages in thread From: David Miller @ 2018-03-05 21:40 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 05 Mar 2018 11:51:22 -0800 > BPF_PROG_TYPE_SK_MSG supports only two return codes SK_PASS and > SK_DROP. Returning SK_DROP free's the copied data in the sendmsg > case and in the sendpage case leaves the data untouched. Both cases > return -EACESS to the user. Returning SK_PASS will allow the msg to > be sent. > > In the sendmsg case data is copied into kernel space buffers before > running the BPF program. In the sendpage case data is never copied. > The implication being users may change data after BPF programs run in > the sendpage case. (A flag will be added to always copy shortly > if the copy must always be performed). I don't see how the sendpage case can be right. The user can asynchronously change the page contents whenever they want, and if the BPF program runs on the old contents then the verdict is not for what actually ends up being sent on the socket. There is really no way to cheaply freeze the page contents other than to make a copy. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-05 21:40 ` David Miller @ 2018-03-05 22:53 ` John Fastabend 2018-03-06 5:42 ` David Miller 0 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-05 22:53 UTC (permalink / raw) To: David Miller; +Cc: ast, daniel, netdev, davejwatson On 03/05/2018 01:40 PM, David Miller wrote: > From: John Fastabend <john.fastabend@gmail.com> > Date: Mon, 05 Mar 2018 11:51:22 -0800 > >> BPF_PROG_TYPE_SK_MSG supports only two return codes SK_PASS and >> SK_DROP. Returning SK_DROP free's the copied data in the sendmsg >> case and in the sendpage case leaves the data untouched. Both cases >> return -EACESS to the user. Returning SK_PASS will allow the msg to >> be sent. >> >> In the sendmsg case data is copied into kernel space buffers before >> running the BPF program. In the sendpage case data is never copied. >> The implication being users may change data after BPF programs run in >> the sendpage case. (A flag will be added to always copy shortly >> if the copy must always be performed). > > I don't see how the sendpage case can be right. > > The user can asynchronously change the page contents whenever they > want, and if the BPF program runs on the old contents then the verdict > is not for what actually ends up being sent on the socket> > There is really no way to cheaply freeze the page contents other than > to make a copy. > Right, so we have two cases. The first is we are not trying to protect against malicious users but merely monitor the connection. This case is primarily for L7 statistics, number of bytes sent to URL foo for example. If users are changing data (for a real program not something malicious) mid sendfile() this is really buggy anyways. There is no way to know when/if the data is being copied lower in the stack. Even worse would be if it changed a msg header, such as the http or kafka header, then I don't see how such a program would work reliable at all. Some of my L7 monitoring BPF programs fall into this category. The second case is we want to implement a strict policy. For example never allow user 'bar' to send to URL foo. In the current patches this would be vulnerable to async data changes. I was planning to have a follow up patch to this series to add a flag "always copy" which handles the asynchronous case by always copying the data if the BPF policy can not tolerate user changing data mid-send. Another class of BPF programs I have fall into this bucket. However, the performance cost of copy can be significant so allowing the BPF policy to decide which mode they require seems best to me. I decided to make the default no-copy to mirror the existing sendpage() semantics and then to add the flag later. The flag support is not in this series simply because I wanted to get the base support in first. Make sense? The default could be to copy sendpage data and then a flag could be made to allow it to skip the copy. But I prefer the current defaults. Thanks, John ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-05 22:53 ` John Fastabend @ 2018-03-06 5:42 ` David Miller 2018-03-06 6:22 ` John Fastabend 0 siblings, 1 reply; 31+ messages in thread From: David Miller @ 2018-03-06 5:42 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 5 Mar 2018 14:53:08 -0800 > I decided to make the default no-copy to mirror the existing > sendpage() semantics and then to add the flag later. The flag > support is not in this series simply because I wanted to get the > base support in first. What existing sendpage semantics are you referring to? ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 5:42 ` David Miller @ 2018-03-06 6:22 ` John Fastabend 2018-03-06 6:42 ` David Miller 0 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-06 6:22 UTC (permalink / raw) To: David Miller; +Cc: ast, daniel, netdev, davejwatson On 03/05/2018 09:42 PM, David Miller wrote: > From: John Fastabend <john.fastabend@gmail.com> > Date: Mon, 5 Mar 2018 14:53:08 -0800 > >> I decided to make the default no-copy to mirror the existing >> sendpage() semantics and then to add the flag later. The flag >> support is not in this series simply because I wanted to get the >> base support in first. > > What existing sendpage semantics are you referring to? > All I meant by this is if an application uses sendfile() call there is no good way to know when/if the kernel side will copy or xmit the data. So a reliable user space application will need to only modify the data if it "knows" there are no outstanding sends in-flight. So if we assume applications follow this then it is OK to avoid the copy. Of course this is not good enough for security, but for monitoring/statistics (my use case 1 it works). By keep existing sendpage semantics I just meant applications should already follow the above. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 6:22 ` John Fastabend @ 2018-03-06 6:42 ` David Miller 2018-03-06 7:06 ` John Fastabend 0 siblings, 1 reply; 31+ messages in thread From: David Miller @ 2018-03-06 6:42 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 5 Mar 2018 22:22:21 -0800 > All I meant by this is if an application uses sendfile() call > there is no good way to know when/if the kernel side will copy or > xmit the data. So a reliable user space application will need to > only modify the data if it "knows" there are no outstanding sends > in-flight. So if we assume applications follow this then it > is OK to avoid the copy. Of course this is not good enough for > security, but for monitoring/statistics (my use case 1 it works). For an application implementing a networking file system, it's pretty legitimate for file contents to change before the page gets DMA's to the networking card. And that's perfectly fine, and we everything such that this will work properly. The card checksums what ends up being DMA'd so nothing from the networking side is broken. So this assumption you mention really does not hold. There needs to be some feedback from the BPF program that parses the packet. This way it can say, "I need at least X more bytes before I can generate a verdict". And you keep copying more and more bytes into a linear buffer and calling the parser over and over until it can generate a full verdict or you run out of networking data. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 6:42 ` David Miller @ 2018-03-06 7:06 ` John Fastabend 2018-03-06 15:47 ` David Miller 0 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-06 7:06 UTC (permalink / raw) To: David Miller; +Cc: ast, daniel, netdev, davejwatson On 03/05/2018 10:42 PM, David Miller wrote: > From: John Fastabend <john.fastabend@gmail.com> > Date: Mon, 5 Mar 2018 22:22:21 -0800 > >> All I meant by this is if an application uses sendfile() call >> there is no good way to know when/if the kernel side will copy or >> xmit the data. So a reliable user space application will need to >> only modify the data if it "knows" there are no outstanding sends >> in-flight. So if we assume applications follow this then it >> is OK to avoid the copy. Of course this is not good enough for >> security, but for monitoring/statistics (my use case 1 it works). > > For an application implementing a networking file system, it's pretty > legitimate for file contents to change before the page gets DMA's to > the networking card. > Still there are useful BPF programs that can tolerate this. So I would prefer to allow BPF programs to operate in the no-copy mode if wanted. It doesn't have to be the default though as it currently is. A l7 load balancer is a good example of this. > And that's perfectly fine, and we everything such that this will work > properly. > > The card checksums what ends up being DMA'd so nothing from the > networking side is broken. Assuming the card has checksum support correct? Which is why we have the SKBTX_SHARED_FRAG checked in skb_has_shared_frag() and the checksum helpers called by the drivers when they do not support the protocol being used. So probably OK assumption if using supported protocols and hardware? Perhaps in general folks just use normal protocols and hardware so it works. > > So this assumption you mention really does not hold. > OK. > There needs to be some feedback from the BPF program that parses the > packet. This way it can say, "I need at least X more bytes before I > can generate a verdict". And you keep copying more and more bytes > into a linear buffer and calling the parser over and over until it can > generate a full verdict or you run out of networking data. > So the "I need at least X more bytes" is the msg_cork_bytes() in patch 7. I could handle the sendpage case the same as I handle the sendmsg case and copy the data into the buffer until N bytes are received. I had planned to add this mode in a follow up series but could add it in this series so we have all the pieces in one submission. Although I used a scatterlist instead of a linear buffer. I was planning to add a helper to pull in next sg list item if needed rather than try to allocate a large linear block up front. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 7:06 ` John Fastabend @ 2018-03-06 15:47 ` David Miller 2018-03-06 18:18 ` John Fastabend 0 siblings, 1 reply; 31+ messages in thread From: David Miller @ 2018-03-06 15:47 UTC (permalink / raw) To: john.fastabend; +Cc: ast, daniel, netdev, davejwatson From: John Fastabend <john.fastabend@gmail.com> Date: Mon, 5 Mar 2018 23:06:01 -0800 > On 03/05/2018 10:42 PM, David Miller wrote: >> From: John Fastabend <john.fastabend@gmail.com> >> Date: Mon, 5 Mar 2018 22:22:21 -0800 >> >>> All I meant by this is if an application uses sendfile() call >>> there is no good way to know when/if the kernel side will copy or >>> xmit the data. So a reliable user space application will need to >>> only modify the data if it "knows" there are no outstanding sends >>> in-flight. So if we assume applications follow this then it >>> is OK to avoid the copy. Of course this is not good enough for >>> security, but for monitoring/statistics (my use case 1 it works). >> >> For an application implementing a networking file system, it's pretty >> legitimate for file contents to change before the page gets DMA's to >> the networking card. >> > > Still there are useful BPF programs that can tolerate this. So I > would prefer to allow BPF programs to operate in the no-copy mode > if wanted. It doesn't have to be the default though as it currently > is. A l7 load balancer is a good example of this. Maybe I'd be ok if it were not the default. But do you really want to expose a potential attack vector, even if the app gets to choose and say "I'm ok"? >> And that's perfectly fine, and we everything such that this will work >> properly. >> >> The card checksums what ends up being DMA'd so nothing from the >> networking side is broken. > > Assuming the card has checksum support correct? Which is why we have > the SKBTX_SHARED_FRAG checked in skb_has_shared_frag() and the checksum > helpers called by the drivers when they do not support the protocol > being used. So probably OK assumption if using supported protocols and > hardware? Perhaps in general folks just use normal protocols and > hardware so it works. If the hardware doesn't support the checksums, we linearize the SKB (therefore obtain a snapshot of the data), and checksum. Exactly what would happen if the hardware did the checksum. So OK in that case too. We always guarantee that you will always get a correct checksum on outgoing packets, even if you modify the page contents meanwhile. > So the "I need at least X more bytes" is the msg_cork_bytes() in patch > 7. I could handle the sendpage case the same as I handle the sendmsg > case and copy the data into the buffer until N bytes are received. I > had planned to add this mode in a follow up series but could add it in > this series so we have all the pieces in one submission. > > Although I used a scatterlist instead of a linear buffer. I was > planning to add a helper to pull in next sg list item if needed > rather than try to allocate a large linear block up front. For non-deep packet inspection cases this re-running of the parser case will probably not trigger at all. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 15:47 ` David Miller @ 2018-03-06 18:18 ` John Fastabend 2018-03-07 3:25 ` John Fastabend 0 siblings, 1 reply; 31+ messages in thread From: John Fastabend @ 2018-03-06 18:18 UTC (permalink / raw) To: David Miller; +Cc: ast, daniel, netdev, davejwatson On 03/06/2018 07:47 AM, David Miller wrote: > From: John Fastabend <john.fastabend@gmail.com> > Date: Mon, 5 Mar 2018 23:06:01 -0800 > >> On 03/05/2018 10:42 PM, David Miller wrote: >>> From: John Fastabend <john.fastabend@gmail.com> >>> Date: Mon, 5 Mar 2018 22:22:21 -0800 >>> >>>> All I meant by this is if an application uses sendfile() call >>>> there is no good way to know when/if the kernel side will copy or >>>> xmit the data. So a reliable user space application will need to >>>> only modify the data if it "knows" there are no outstanding sends >>>> in-flight. So if we assume applications follow this then it >>>> is OK to avoid the copy. Of course this is not good enough for >>>> security, but for monitoring/statistics (my use case 1 it works). >>> >>> For an application implementing a networking file system, it's pretty >>> legitimate for file contents to change before the page gets DMA's to >>> the networking card. >>> >> >> Still there are useful BPF programs that can tolerate this. So I >> would prefer to allow BPF programs to operate in the no-copy mode >> if wanted. It doesn't have to be the default though as it currently >> is. A l7 load balancer is a good example of this. > > Maybe I'd be ok if it were not the default. But do you really want to > expose a potential attack vector, even if the app gets to choose and > say "I'm ok"? > Yes, because I have use cases where I don't need to read the data, but have already "approved" the data. One example applications like nginx can serve static http data. Just reading over the code what they do, when sendfile is enabled, is a sendmsg call with the header. We want to enforce the policy on the header. Then we know the next N bytes are OK. Nginx will then send the payload over sendfile syscall. We already know the data is good from initial sendmsg call the next N bytes can get the verdict SK_PASS without even touching the data. If we do a copy in this case we see significant performance degradation. The other use case is the L7 load balancer mentioned above. If we are using RR policies or some other heuristic if the user modifies the payload after the BPF verdict that is also fine. A malicious user could rewrite the header and try to game the load balancer but the BPF program can always just dev/null (SK_DROP) the application when it detects this. This also assumes the load balancer is using the header for its heuristic some interesting heuristics may not use the header at all. >>> And that's perfectly fine, and we everything such that this will work >>> properly. >>> >>> The card checksums what ends up being DMA'd so nothing from the >>> networking side is broken. >> >> Assuming the card has checksum support correct? Which is why we have >> the SKBTX_SHARED_FRAG checked in skb_has_shared_frag() and the checksum >> helpers called by the drivers when they do not support the protocol >> being used. So probably OK assumption if using supported protocols and >> hardware? Perhaps in general folks just use normal protocols and >> hardware so it works. > > If the hardware doesn't support the checksums, we linearize the SKB > (therefore obtain a snapshot of the data), and checksum. Exactly what > would happen if the hardware did the checksum. > > So OK in that case too. > > We always guarantee that you will always get a correct checksum on > outgoing packets, even if you modify the page contents meanwhile. > Agreed the checksum is correct, but the user doesn't know if the linearize happened while it was modifying the data, potentially creating data with a partial update. Because the user modifying the data doesn't block the linearize operation in the kernel and vice versa the linearize operation can happen in parallel with the user side data modification. So maybe I'm still missing something but it seems the data can be in some unknown state on the wire. Either way though I think its fine to make the default sendpage hook do the copy. A flag to avoid the copy can be added later to resolve my use cases above. I'll code this up in a v2 today/tomorrow. >> So the "I need at least X more bytes" is the msg_cork_bytes() in patch >> 7. I could handle the sendpage case the same as I handle the sendmsg >> case and copy the data into the buffer until N bytes are received. I >> had planned to add this mode in a follow up series but could add it in >> this series so we have all the pieces in one submission. >> >> Although I used a scatterlist instead of a linear buffer. I was >> planning to add a helper to pull in next sg list item if needed >> rather than try to allocate a large linear block up front. > > For non-deep packet inspection cases this re-running of the parser case > will probably not trigger at all. > Agreed, its mostly there to handle cases where the sendmsg call only sent part of a application (kafka, http, etc) header. This can happen if user is sending multiple messages in a single sendmsg/sendfile call. But, yeah I see it rarely in practice its mostly there for completeness and to handle these edge cases. ^ permalink raw reply [flat|nested] 31+ messages in thread
* Re: [bpf-next PATCH 05/16] bpf: create tcp_bpf_ulp allowing BPF to monitor socket TX/RX data 2018-03-06 18:18 ` John Fastabend @ 2018-03-07 3:25 ` John Fastabend 2018-03-07 4:41 ` David Miller 2018-03-07 13:03 `