Now, it is time to add the new hooks for BPF_SOCK_OPS_RCVQ_CB. Let's invoke the BPF SOCK_OPS prog when 1. TCP stack enqueues skb to sk->sk_receive_queue -> tcp_queue_rcv(), tcp_ofo_queue(), and tcp_fastopen_add_skb() 2. TCP recvmsg() completes -> __tcp_cleanup_rbuf() This will allow the BPF prog to parse each skb and dynamically adjust sk->sk_rcvlowat to suppress unnecessary EPOLLIN wakeups until sufficient data (e.g., a full RPC frame) is available in the receive queue. Note that the direct access to bpf_sock_ops.data is intentionally disabled by passing 0 as end_offset. Instead, the BPF prog is supposed to use bpf_skb_load_bytes() with bpf_sock_ops because payload is not in the linear area with TCP header/data split on and skb may contain a RPC descriptor in skb frag. This also simplifies the BPF prog. The placement of tcp_bpf_rcvlowat() in tcp_ofo_queue() and tcp_fastopen_add_skb() is chosen to provide the same snapshot with tcp_queue_rcv(). For example, if tcp_bpf_rcvlowat() were called before updating TCP_SKB_CB(skb)->seq in tcp_fastopen_add_skb(), BPF prog would need to implement an unlikely if branch to strip SYN. In addition, TCP stack can queue overlapping skb into recvq. Once rcv_nxt is updated with a new skb, BPF prog cannot infer the previous one from skb->len. Signed-off-by: Kuniyuki Iwashima --- v2: Add explanation of tcp_bpf_rcvlowat() placement. --- include/net/tcp.h | 12 ++++++++++++ net/ipv4/tcp.c | 2 ++ net/ipv4/tcp_fastopen.c | 2 ++ net/ipv4/tcp_input.c | 10 ++++++++++ 4 files changed, 26 insertions(+) diff --git a/include/net/tcp.h b/include/net/tcp.h index c6a6853909c4..2247937e385a 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2888,12 +2888,24 @@ static inline void bpf_skops_init_skb(struct bpf_sock_ops_kern *skops, skops->skb = skb; skops->skb_data_end = skb->data + end_offset; } + +void bpf_skops_rcvlowat(struct sock *sk, struct sk_buff *skb); + +static inline void tcp_bpf_rcvlowat(struct sock *sk, struct sk_buff *skb) +{ + if (BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk), BPF_SOCK_OPS_RCVQ_CB_FLAG)) + bpf_skops_rcvlowat(sk, skb); +} #else static inline void bpf_skops_init_skb(struct bpf_sock_ops_kern *skops, struct sk_buff *skb, unsigned int end_offset) { } + +static inline void tcp_bpf_rcvlowat(struct sock *sk, struct sk_buff *skb) +{ +} #endif /* Call BPF_SOCK_OPS program that returns an int. If the return value diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 3afeb69a547a..f7e32891bb4e 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -1602,6 +1602,8 @@ void __tcp_cleanup_rbuf(struct sock *sk, int copied) tcp_mstamp_refresh(tp); tcp_send_ack(sk); } + + tcp_bpf_rcvlowat(sk, NULL); } void tcp_cleanup_rbuf(struct sock *sk, int copied) diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c index 471c78be5513..91bf421fc5b6 100644 --- a/net/ipv4/tcp_fastopen.c +++ b/net/ipv4/tcp_fastopen.c @@ -281,6 +281,8 @@ void tcp_fastopen_add_skb(struct sock *sk, struct sk_buff *skb) TCP_SKB_CB(skb)->seq++; TCP_SKB_CB(skb)->tcp_flags &= ~TCPHDR_SYN; + tcp_bpf_rcvlowat(sk, skb); + tp->rcv_nxt = TCP_SKB_CB(skb)->end_seq; tcp_add_receive_queue(sk, skb); tp->syn_data_acked = 1; diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index c4ba4f1e9d9e..477bcf2ba89d 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -204,6 +204,12 @@ static void bpf_skops_established(struct sock *sk, int bpf_op, /* sk with TCP_REPAIR_ON does not have skb in tcp_finish_connect */ bpf_skops_common_locked(sk, bpf_op, skb, skb ? tcp_hdrlen(skb) : 0); } + +void bpf_skops_rcvlowat(struct sock *sk, struct sk_buff *skb) +{ + /* skb is NULL when called from __tcp_cleanup_rbuf(). */ + bpf_skops_common_locked(sk, BPF_SOCK_OPS_RCVQ_CB, skb, 0); +} #else static void bpf_skops_parse_hdr(struct sock *sk, struct sk_buff *skb) { @@ -5306,6 +5312,8 @@ static void tcp_ofo_queue(struct sock *sk) continue; } + tcp_bpf_rcvlowat(sk, skb); + tail = skb_peek_tail(&sk->sk_receive_queue); eaten = tail && tcp_try_coalesce(sk, tail, skb, &fragstolen); tcp_rcv_nxt_update(tp, TCP_SKB_CB(skb)->end_seq); @@ -5509,6 +5517,8 @@ static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int eaten; struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); + tcp_bpf_rcvlowat(sk, skb); + eaten = (tail && tcp_try_coalesce(sk, tail, skb, fragstolen)) ? 1 : 0; -- 2.54.0.746.g67dd491aae-goog