summaryrefslogtreecommitdiff
path: root/net
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 13:08:49 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-23 13:08:49 -1000
commit55fbedcec622a943cb09a969aef1ddcba87ee40a (patch)
tree99bf57398ef32396f831f31efffb5d917ce22e0c /net
parent90b754d40117b2889bbe847ce7bb4dcf968e7cb8 (diff)
parentb116ac6087541d3d98031e786adbe9c733cd6df3 (diff)
Merge tag 'bpf-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next
Pull bpf updates from Alexei Starovoitov: "Major changes: - Introduce scalar evolution (SCEV) analysis in the verifier. It infers how registers and stack slots change in a loop and lets the verifier check bounded loops with induction variables as ranges instead of walking every iteration. The number of processed insns drops ten fold in programs with loops (Eduard Zingerman) - Add 'callx' instruction: indirect call of a static BPF subprogram. The address is taken with ld_imm64 or loaded from read-only data. Supported by x86-64, arm64 and riscv64 JITs (Alexei Starovoitov, Pu Lehui) - Add unwind_goto and unwind_exit instructions, so that Rust panic or C++ exception can run destructors of every frame it passes through. The normal path is free of unwind code. Supported by x86-64 and arm64 JITs (Alexei Starovoitov) - Support programs compiled by rust-bpf: treat load and store through a number as arena access with BPF_F_ARENA_SCALAR, accept rust names in BTF (Alexei Starovoitov) - Raise BPF program stack size from 512 bytes to 2 KiB on x86-64 and arm64 (Kumar Kartikeya Dwivedi) - Support struct, union and __int128 up to 16 bytes passed and returned by value in BPF subprograms and kfuncs, and fix x86-64 and arm64 trampolines for such arguments (Yonghong Song) - Introduce a common way to attach struct_ops to a cgroup via bpf_link. bpf_tcp_ops is the first user and mirrors TCP sock_ops callbacks (Martin KaFai Lau, Amery Hung) - Describe inlined functions in BTF. New kinds tell where the inline sites are and how to fetch the arguments there. CONFIG_DEBUG_INFO_BTF_INLINE keeps it apart from the main BTF in .BTF.inline section or in btf_vmlinux_inline.ko (Alan Maguire) - Fix many memory safety, lifetime and locking bugs in BPF core, verifier, iterators, local storage, dynptr, devmap, cpumap and seg6local (Josef Bacik, Alexei Starovoitov) - Diff stat is dominated by selftests and by removal of lib/test_bpf.c. Its 16k lines of instruction tests were converted to BPF selftests that BPF CI runs on every architecture it covers (Daniel Borkmann) Other features and fixes: - Add KASAN checks to memory accesses of JITed programs on x86-64 (Alexis Lothoré) - Complete unification of helper, kfunc and subprog argument checks in the verifier (Amery Hung) - Add bpf_copy_from_user_mm[_str]() kfuncs (Anastasios Papagiannis) - Various features and fixes (Daniel Borkmann): - Add .bpf keyring for program signature verification, modelled after dm-verity keyring, and support ML-DSA in signing tools - Emit shorter and faster code in x86-64 JIT for patterns that LLVM generates - Fix various issues in gotox handling - Let BPF LSM set xattrs at inode creation with bpf_inode_init_xattr() (David Windsor, Daniel Borkmann) - Use sleepable page allocation in sleepable arena paths. Fix races between arena free, allocation and user page fault (Emil Tsalapatis) - Switch BPF crypto from lskcipher to AES-CBC and AES-ECB libraries (Eric Biggers) - Add bpf_proactive_reclaim() kfunc (Hui Zhu) - Render decl_tags in libbpf btf_dump and add tests for bpftool C dump (Ihor Solodrai) - Speed up tracing_multi attach on x86 (Jiri Olsa) - Various features and fixes (Kumar Kartikeya Dwivedi): - Add BPF_PROG_STREAM_OPEN command to read BPF streams through a file descriptor with poll support - Let kernel structs publish arena pointers with __arena type tag - Fix __uninit kfunc output buffers and fastcall rewrite with indirect stack accesses - Refuse CO-RE relocations for signed programs - Add enqueue_rcvq and dequeue_rcvq hooks to bpf_tcp_ops to adjust sk_rcvlowat and avoid unnecessary wakeups (Kuniyuki Iwashima) - Add bpf_ksock_sendto() kfunc and allow ksock connect and send from bpf_wq (Mahe Tardy) - Fix racy plt target update in arm64 bpf_arch_text_poke() (Matthew Wood) - Restructure BPF selftests Makefile as a layered build (Mykola Lysenko) - Speed up hash map lookups for u32 and u64 keys (Mykyta Yatsenko) - Add signed div/mod, movsx, bswap and gotol to MIPS JITs, signed div/mod to parisc JITs (Nicholas Dudar, Johan Almbladh) - Fix stack overflow in build ID parser on 32-bit (Nickolai Zeldovich) - Add stack arguments, signed arena loads and gotox to riscv64 JIT. Pu Lehui is now co-maintainer of riscv BPF JITs (Pu Lehui, Chen Pei, Feng Jiang) - Add bpf_call_rcu() and bpf_call_rcu_tasks_trace() kfuncs. Support fetching and/or/xor atomics in arena on x86-64 (Puranjay Mohan) - Fix flaky selftests, selftests with clang 23 and bpf-gcc (Qiliang Yuan, KaFai Wan, Vineet Gupta) - Fix builds broken by fatal resolve_btfids warnings, BTF generation on ARC, and define arch_cmpxchg_local() on arc, hexagon and openrisc (Sasha Levin) - Reduce memory use of hash maps by up to 50% per element (T.J. Mercier) - Mark pointer fields of sched_ext ops arguments as trusted (Tejun Heo) - Remove legacy bpf_asm, bpf_dbg and bpf_jit_disasm tools (Xia Yan) - Fix index overflow in queue and stack maps, arena range tree after allocation failure, tail call loop after freplace detach, lost signal in bpf_send_signal() (Yuan Chen) - Many more fixes and cleanups" * tag 'bpf-next-7.4' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (660 commits) selftests/bpf: Time tc_edt from its first packet with data selftests/bpf: Test unwind_exit of an exception callback selftests/bpf: Tests for 32-bit scalar evolution bounds computation bpf: Reject unwind_goto and unwind_exit in offloaded progs bpf: Support 32-bit linear scalar evolution bpf: Make unwind_exit of an exception callback exit in JITs selftests/bpf: Test forbidden bpf_ksock_sendto() LSM attach selftests/bpf: Test ksock sendto() bpf: Add bpf_ksock_sendto() kfunc selftests/bpf: Test unwinding from R0: R2 subprog selftests/bpf: Add a C++ prog with exceptions selftests/bpf: Add tests for unwind_goto and unwind_exit bpf, arm64: Support unwind_goto and unwind_exit bpf, x86: Support unwind_goto and unwind_exit bpf: Add unwind_goto and unwind_exit insns tools/bpf: Remove legacy BPF tools bpf: Annotate data races on bpf_lru_node type selftests/bpf: Add arena allocation race tests bpf: Fix arena race between page free and alloc leading to incoherency bpf: Revalidate faulted arena pages before installing user mappings ...
Diffstat (limited to 'net')
-rw-r--r--net/bpf/test_run.c27
-rw-r--r--net/core/bpf_ksock.c121
-rw-r--r--net/core/bpf_sk_storage.c96
-rw-r--r--net/core/filter.c98
-rw-r--r--net/core/ptp_classifier.c8
-rw-r--r--net/core/sock_map.c17
-rw-r--r--net/ipv4/Makefile1
-rw-r--r--net/ipv4/af_inet.c3
-rw-r--r--net/ipv4/bpf_tcp_ca.c16
-rw-r--r--net/ipv4/bpf_tcp_ops.c453
-rw-r--r--net/ipv4/tcp.c21
-rw-r--r--net/ipv4/tcp_bpf.c1
-rw-r--r--net/ipv4/tcp_fastopen.c2
-rw-r--r--net/ipv4/tcp_input.c52
-rw-r--r--net/ipv4/tcp_nv.c2
-rw-r--r--net/ipv4/tcp_output.c147
-rw-r--r--net/ipv4/tcp_timer.c7
-rw-r--r--net/sched/bpf_qdisc.c2
18 files changed, 908 insertions, 166 deletions
diff --git a/net/bpf/test_run.c b/net/bpf/test_run.c
index 513354e928cb..3043a7393c29 100644
--- a/net/bpf/test_run.c
+++ b/net/bpf/test_run.c
@@ -1237,7 +1237,13 @@ int bpf_prog_test_run_skb(struct bpf_prog *prog, const union bpf_attr *kattr,
memset(__skb_push(skb, hh_len), 0, hh_len);
}
- if (kattr->test.flags & BPF_F_TEST_SKB_CHECKSUM_COMPLETE) {
+ /*
+ * A helper such as bpf_skb_change_tail() may have downgraded the skb
+ * from CHECKSUM_COMPLETE. skb->csum is then unused by the stack and
+ * there is nothing to validate.
+ */
+ if ((kattr->test.flags & BPF_F_TEST_SKB_CHECKSUM_COMPLETE) &&
+ skb->ip_summed == CHECKSUM_COMPLETE) {
const int off = skb_network_offset(skb);
int len = skb->len - off;
__wsum csum;
@@ -1650,20 +1656,19 @@ int bpf_prog_test_run_sk_lookup(struct bpf_prog *prog, const union bpf_attr *kat
ctx.selected_sk = NULL;
retval = BPF_PROG_SK_LOOKUP_RUN_ARRAY(progs, ctx, bpf_prog_run);
} while (bpf_test_timer_continue(&t, 1, repeat, &ret, &duration));
- bpf_test_timer_leave(&t);
-
- if (ret < 0)
- goto out;
+ /* Nothing but RCU keeps the selected socket */
user_ctx->cookie = 0;
- if (ctx.selected_sk) {
- if (ctx.selected_sk->sk_reuseport && !ctx.no_reuseport) {
+ if (!ret && ctx.selected_sk) {
+ if (ctx.selected_sk->sk_reuseport && !ctx.no_reuseport)
ret = -EOPNOTSUPP;
- goto out;
- }
-
- user_ctx->cookie = sock_gen_cookie(ctx.selected_sk);
+ else
+ user_ctx->cookie = sock_gen_cookie(ctx.selected_sk);
}
+ bpf_test_timer_leave(&t);
+
+ if (ret < 0)
+ goto out;
ret = bpf_test_finish(kattr, uattr, NULL, NULL, 0, 0, retval, duration);
if (!ret)
diff --git a/net/core/bpf_ksock.c b/net/core/bpf_ksock.c
index e9943aeeadd3..37168f43c16c 100644
--- a/net/core/bpf_ksock.c
+++ b/net/core/bpf_ksock.c
@@ -37,13 +37,47 @@ static void ksock_release_work_fn(struct work_struct *work)
kfree(ks);
}
-static bool bpf_ksock_has_user_task_context(void)
+static int bpf_ksock_addr_to_sockaddr(const union bpf_ksock_addr *addr,
+ u32 addr_sz, struct sockaddr_storage *sa)
{
- /*
- * Task work can run from do_exit() after exit_nsproxy_namespaces()
- * cleared current->nsproxy, while current is still not a kthread.
- */
- return !(current->flags & PF_KTHREAD) && current->nsproxy;
+ if (!addr || addr_sz != sizeof(*addr))
+ return -EINVAL;
+
+ /* Kfunc memory arguments may be unaligned. */
+ memcpy(sa, addr, sizeof(*addr));
+
+ switch (sa->ss_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+static int ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data_sz,
+ const union bpf_ksock_addr *addr, u32 addr_sz)
+{
+ struct sockaddr_storage sa;
+ struct msghdr msg = {
+ .msg_flags = MSG_DONTWAIT,
+ };
+ struct kvec iov = {
+ .iov_base = (void *)data,
+ .iov_len = data_sz,
+ };
+ int addrlen;
+
+ if (addr) {
+ addrlen = bpf_ksock_addr_to_sockaddr(addr, addr_sz, &sa);
+ if (addrlen < 0)
+ return addrlen;
+ msg.msg_name = &sa;
+ msg.msg_namelen = addrlen;
+ }
+
+ return kernel_sendmsg(ks->sock, &msg, &iov, 1, data_sz);
}
__bpf_kfunc_start_defs();
@@ -59,7 +93,7 @@ __bpf_kfunc_start_defs();
* This function may sleep (sock_create), so it can only be used
* in sleepable BPF programs (SYSCALL).
* It cannot be called from a BPF workqueue callback because that callback
- * does not retain the invoking task's namespace or security context.
+ * does not retain the invoking task's namespace.
*
* @opts: Pointer to struct bpf_ksock_create_opts with socket parameters.
* @opts__sz: Size of the opts struct.
@@ -74,11 +108,10 @@ bpf_ksock_create(const struct bpf_ksock_create_opts *opts, u32 opts__sz,
int err;
/*
- * sock_create() derives the network namespace, credentials, and cgroup
- * from current. Kernel threads, including BPF workqueue callbacks, do
- * not carry the context of the task that invoked the BPF program.
+ * sock_create() derives the network namespace from current and task
+ * work can run from do_exit() after exit_nsproxy_namespaces()
*/
- if (!bpf_ksock_has_user_task_context()) {
+ if ((current->flags & PF_KTHREAD) || !current->nsproxy) {
err = -EOPNOTSUPP;
goto err_out;
}
@@ -165,25 +198,9 @@ __bpf_kfunc int bpf_ksock_connect(struct bpf_ksock *ks,
struct sockaddr_storage sa;
int addrlen;
- if (!bpf_ksock_has_user_task_context())
- return -EOPNOTSUPP;
-
- if (!addr || addr__sz != sizeof(*addr))
- return -EINVAL;
-
- /* Kfunc memory arguments may be unaligned. */
- memcpy(&sa, addr, sizeof(*addr));
-
- switch (sa.ss_family) {
- case AF_INET:
- addrlen = sizeof(struct sockaddr_in);
- break;
- case AF_INET6:
- addrlen = sizeof(struct sockaddr_in6);
- break;
- default:
- return -EAFNOSUPPORT;
- }
+ addrlen = bpf_ksock_addr_to_sockaddr(addr, addr__sz, &sa);
+ if (addrlen < 0)
+ return addrlen;
return connect_socket(ks->sock, &sa, addrlen, 0);
}
@@ -238,21 +255,29 @@ CFI_NOSEAL(bpf_ksock_release_dtor);
__bpf_kfunc int bpf_ksock_send(struct bpf_ksock *ks, const void *data,
u32 data__sz)
{
- struct msghdr msg = {
- .msg_flags = MSG_DONTWAIT,
- };
- struct kvec iov = {
- .iov_base = (void *)data,
- .iov_len = data__sz,
- };
- int ret;
-
- if (!bpf_ksock_has_user_task_context())
- return -EOPNOTSUPP;
-
- ret = kernel_sendmsg(ks->sock, &msg, &iov, 1, data__sz);
+ return ksock_sendto(ks, data, data__sz, NULL, 0);
+}
- return ret;
+/**
+ * bpf_ksock_sendto() - Send data through a BPF kernel socket to a remote address.
+ * @ks: The BPF kernel socket context. Must be an acquired reference.
+ * @data: Pointer to the data to send.
+ * @data__sz: Size of the data to send.
+ * @addr__nullable: Pointer to an IPv4 or IPv6 socket address, or NULL to use
+ * the connected destination.
+ * @addr__sz: Size of the address union, ignored when the address is NULL.
+ *
+ * Sends data to the specified address, or to the connected destination when
+ * the address is NULL, best-effort and nonblocking. This may sleep
+ * (kernel_sendmsg), so it can only be called from sleepable BPF programs.
+ *
+ * Return: Number of bytes sent on success, negative errno on error.
+ */
+__bpf_kfunc int bpf_ksock_sendto(struct bpf_ksock *ks, const void *data, u32 data__sz,
+ const union bpf_ksock_addr *addr__nullable,
+ u32 addr__sz)
+{
+ return ksock_sendto(ks, data, data__sz, addr__nullable, addr__sz);
}
__bpf_kfunc_end_defs();
@@ -271,10 +296,16 @@ BTF_KFUNCS_START(ksock_kfunc_btf_ids)
BTF_ID_FLAGS(func, bpf_ksock_release, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_ksock_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_ksock_send, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_ksock_sendto, KF_SLEEPABLE)
BTF_KFUNCS_END(ksock_kfunc_btf_ids)
#ifdef CONFIG_BPF_LSM
-BTF_ID_LIST_SINGLE(bpf_lsm_socket_sendmsg_id, func, bpf_lsm_socket_sendmsg)
+BTF_ID_LIST(bpf_lsm_socket_sendmsg_id)
+#ifdef CONFIG_SECURITY_NETWORK
+BTF_ID(func, bpf_lsm_socket_sendmsg)
+#else
+BTF_ID_UNUSED
+#endif
#endif
static int bpf_ksock_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
diff --git a/net/core/bpf_sk_storage.c b/net/core/bpf_sk_storage.c
index 1d295a8769fa..1c59300af05c 100644
--- a/net/core/bpf_sk_storage.c
+++ b/net/core/bpf_sk_storage.c
@@ -164,6 +164,13 @@ int bpf_sk_storage_clone(const struct sock *sk, struct sock *newsk)
if (!sk_storage || hlist_empty(&sk_storage->list))
goto out;
+ /*
+ * The maps lead to newsk as soon as an element is linked, but
+ * sk_clone() sets sk_refcnt only later. Until then it holds whatever
+ * the slab object held before: no reference can be had yet.
+ */
+ refcount_set(&newsk->sk_refcnt, 0);
+
hlist_for_each_entry_rcu(selem, &sk_storage->list, snode) {
struct bpf_local_storage_elem *copy_selem;
struct bpf_local_storage_map *smap;
@@ -692,8 +699,37 @@ struct bpf_iter_seq_sk_storage_map_info {
struct bpf_map *map;
unsigned int bucket_id;
unsigned skip_elems;
+ struct rcu_head *dead_sks;
};
+/*
+ * sock_put() in two halves. The end of a socket may want process context:
+ * rfcomm_sock_destruct() takes a mutex. If the iterator drops the last
+ * reference, it keeps the socket until it has left the RCU read side critical
+ * section. Nobody uses sk_rcu before sk_destruct().
+ */
+static void
+bpf_sk_storage_map_seq_put_sk(struct bpf_iter_seq_sk_storage_map_info *info,
+ struct sock *sk)
+{
+ if (refcount_dec_and_test(&sk->sk_refcnt)) {
+ sk->sk_rcu.next = info->dead_sks;
+ info->dead_sks = &sk->sk_rcu;
+ }
+}
+
+static void
+bpf_sk_storage_map_seq_free_sks(struct bpf_iter_seq_sk_storage_map_info *info)
+{
+ struct sock *sk;
+
+ while (info->dead_sks) {
+ sk = container_of(info->dead_sks, struct sock, sk_rcu);
+ info->dead_sks = sk->sk_rcu.next;
+ sk_free(sk);
+ }
+}
+
static struct bpf_local_storage_elem *
bpf_sk_storage_map_seq_find_next(struct bpf_iter_seq_sk_storage_map_info *info,
struct bpf_local_storage_elem *prev_selem)
@@ -722,6 +758,7 @@ bpf_sk_storage_map_seq_find_next(struct bpf_iter_seq_sk_storage_map_info *info,
/* not found, unlock and go to the next bucket */
b = &smap->buckets[bucket_id++];
rcu_read_unlock();
+ bpf_sk_storage_map_seq_free_sks(info);
skip_elems = 0;
break;
}
@@ -789,12 +826,44 @@ DEFINE_BPF_ITER_FUNC(bpf_sk_storage_map, struct bpf_iter_meta *meta,
struct bpf_map *map, struct sock *sk,
void *value)
+/*
+ * Take a reference on the socket which owns @selem. RCU keeps the element, not
+ * its owner. As long as the element is linked under the lock of the storage,
+ * the owner has not got past bpf_sk_storage_free(), unless that has given up
+ * on the lock. Then it still waits for owner_refcnt.
+ */
+static struct sock *
+bpf_sk_storage_selem_get_sk(struct bpf_local_storage_elem *selem)
+{
+ struct bpf_local_storage *sk_storage;
+ struct sock *sk = NULL;
+ unsigned long flags;
+
+ sk_storage = rcu_dereference(selem->local_storage);
+ /* reset since ->next() looked: the socket goes away */
+ if (!sk_storage)
+ return NULL;
+
+ if (raw_res_spin_lock_irqsave(&sk_storage->lock, flags))
+ return NULL;
+
+ if (!hlist_unhashed(&selem->snode) &&
+ refcount_inc_not_zero(&sk_storage->owner_refcnt)) {
+ sk = sk_storage->owner;
+ if (!refcount_inc_not_zero(&sk->sk_refcnt))
+ sk = NULL;
+ bpf_local_storage_unpin_owner(sk_storage);
+ }
+ raw_res_spin_unlock_irqrestore(&sk_storage->lock, flags);
+
+ return sk;
+}
+
static int __bpf_sk_storage_map_seq_show(struct seq_file *seq,
struct bpf_local_storage_elem *selem)
{
struct bpf_iter_seq_sk_storage_map_info *info = seq->private;
struct bpf_iter__bpf_sk_storage_map ctx = {};
- struct bpf_local_storage *sk_storage;
struct bpf_iter_meta meta;
struct bpf_prog *prog;
int ret = 0;
@@ -805,11 +874,14 @@ static int __bpf_sk_storage_map_seq_show(struct seq_file *seq,
ctx.meta = &meta;
ctx.map = info->map;
if (selem) {
- sk_storage = rcu_dereference(selem->local_storage);
- ctx.sk = sk_storage->owner;
+ ctx.sk = bpf_sk_storage_selem_get_sk(selem);
+ if (!ctx.sk)
+ return SEQ_SKIP;
ctx.value = SDATA(selem)->data;
}
ret = bpf_iter_run_prog(prog, &ctx);
+ if (ctx.sk)
+ bpf_sk_storage_map_seq_put_sk(info, ctx.sk);
}
return ret;
@@ -827,6 +899,7 @@ static void bpf_sk_storage_map_seq_stop(struct seq_file *seq, void *v)
(void)__bpf_sk_storage_map_seq_show(seq, v);
else
rcu_read_unlock();
+ bpf_sk_storage_map_seq_free_sks(seq->private);
}
static int bpf_iter_init_sk_storage_map(void *priv_data,
@@ -846,6 +919,15 @@ static void bpf_iter_fini_sk_storage_map(void *priv_data)
bpf_map_put_with_uref(seq_info->map);
}
+static int bpf_iter_map_check_prog(const struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ if (prog->aux->max_rdwr_access > aux->map->value_size)
+ return -EACCES;
+
+ return bpf_iter_map_check_value_write(prog, aux->map);
+}
+
static int bpf_iter_attach_map(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux)
@@ -863,12 +945,11 @@ static int bpf_iter_attach_map(struct bpf_prog *prog,
if (map->map_type != BPF_MAP_TYPE_SK_STORAGE)
goto put_map;
- if (prog->aux->max_rdwr_access > map->value_size) {
- err = -EACCES;
+ aux->map = map;
+ err = bpf_iter_map_check_prog(prog, aux);
+ if (err)
goto put_map;
- }
- aux->map = map;
return 0;
put_map:
@@ -899,6 +980,7 @@ static struct bpf_iter_reg bpf_sk_storage_map_reg_info = {
.target = "bpf_sk_storage_map",
.attach_target = bpf_iter_attach_map,
.detach_target = bpf_iter_detach_map,
+ .check_prog = bpf_iter_map_check_prog,
.show_fdinfo = bpf_iter_map_show_fdinfo,
.fill_link_info = bpf_iter_map_fill_link_info,
.ctx_arg_info_size = 2,
diff --git a/net/core/filter.c b/net/core/filter.c
index 70dc621672f2..b7ac145b68b1 100644
--- a/net/core/filter.c
+++ b/net/core/filter.c
@@ -1715,6 +1715,24 @@ static inline void bpf_pull_mac_rcsum(struct sk_buff *skb)
skb_postpull_rcsum(skb, skb_mac_header(skb), skb->mac_len);
}
+/*
+ * The source of a write through a dynptr may be a slice of the same skb.
+ * Making the skb writable can move its head: follow the move.
+ */
+static int bpf_try_make_writable_from(struct sk_buff *skb, u32 write_len,
+ const void **from)
+{
+ const void *head = skb->head;
+ bool in_head = *from >= head &&
+ *from < (const void *)skb_end_pointer(skb);
+ int err;
+
+ err = bpf_try_make_writable(skb, write_len);
+ if (!err && in_head)
+ *from = skb->head + (*from - head);
+ return err;
+}
+
BPF_CALL_5(bpf_skb_store_bytes, struct sk_buff *, skb, u32, offset,
const void *, from, u32, len, u64, flags)
{
@@ -1724,14 +1742,15 @@ BPF_CALL_5(bpf_skb_store_bytes, struct sk_buff *, skb, u32, offset,
return -EINVAL;
if (unlikely(offset > INT_MAX))
return -EFAULT;
- if (unlikely(bpf_try_make_writable(skb, offset + len)))
+ if (unlikely(bpf_try_make_writable_from(skb, offset + len, &from)))
return -EFAULT;
ptr = skb->data + offset;
if (flags & BPF_F_RECOMPUTE_CSUM)
__skb_postpull_rcsum(skb, ptr, len, offset);
- memcpy(ptr, from, len);
+ /* through a dynptr the source may be an overlapping slice of the skb */
+ memmove(ptr, from, len);
if (flags & BPF_F_RECOMPUTE_CSUM)
__skb_postpush_rcsum(skb, ptr, len, offset);
@@ -3612,6 +3631,14 @@ static int bpf_skb_net_grow(struct sk_buff *skb, u32 off, u32 len_diff,
return -ENOTSUPP;
}
+ /*
+ * The MSS is lowered by len_diff. Leave the least that the stack lets a
+ * TCP GSO packet have, or gso_segs can overflow.
+ */
+ if (skb_is_gso(skb) && !(flags & BPF_F_ADJ_ROOM_FIXED_GSO) &&
+ skb_shinfo(skb)->gso_size < len_diff + TCP_MIN_GSO_SIZE)
+ return -ENOTSUPP;
+
ret = skb_cow_head(skb, meta_len + len_diff);
if (unlikely(ret < 0))
return ret;
@@ -4303,7 +4330,8 @@ BPF_CALL_4(bpf_xdp_store_bytes, struct xdp_buff *, xdp, u32, offset,
if (!ptr)
bpf_xdp_copy_buf(xdp, offset, buf, len, true);
else
- memcpy(ptr, buf, len);
+ /* through a dynptr the source may be an overlapping slice */
+ memmove(ptr, buf, len);
return 0;
}
@@ -7017,15 +7045,15 @@ static void bpf_update_srh_state(struct sk_buff *skb)
{
struct seg6_bpf_srh_state *srh_state =
this_cpu_ptr(&seg6_bpf_srh_states);
- int srhoff = 0;
- if (ipv6_find_hdr(skb, &srhoff, IPPROTO_ROUTING, NULL, NULL) < 0) {
- srh_state->srh = NULL;
- } else {
- srh_state->srh = (struct ipv6_sr_hdr *)(skb->data + srhoff);
+ /* Behind a decapsulation this is a header that nobody has looked at */
+ srh_state->srh = seg6_get_srh(skb, 0);
+ if (srh_state->srh) {
srh_state->hdrlen = srh_state->srh->hdrlen << 3;
srh_state->valid = true;
}
+ /* it may have been pulled into the head */
+ bpf_compute_data_pointers(skb);
}
BPF_CALL_4(bpf_lwt_seg6_action, struct sk_buff *, skb,
@@ -7058,9 +7086,14 @@ BPF_CALL_4(bpf_lwt_seg6_action, struct sk_buff *, skb,
if (ipv6_find_hdr(skb, &hdroff, IPPROTO_IPV6, NULL, NULL) < 0)
return -EBADMSG;
- if (!pskb_pull(skb, hdroff))
+ /* As long as nothing has been changed, the program goes on */
+ if (!pskb_may_pull(skb, hdroff + sizeof(struct ipv6hdr))) {
+ /* but the head may have moved */
+ bpf_update_srh_state(skb);
return -EBADMSG;
+ }
+ __skb_pull(skb, hdroff);
skb_postpull_rcsum(skb, skb_network_header(skb), hdroff);
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
@@ -7074,8 +7107,8 @@ BPF_CALL_4(bpf_lwt_seg6_action, struct sk_buff *, skb,
return -EBADMSG;
err = bpf_push_seg6_encap(skb, BPF_LWT_ENCAP_SEG6_INLINE,
param, param_len);
- if (!err)
- bpf_update_srh_state(skb);
+ /* The header can be in place although err says otherwise */
+ bpf_update_srh_state(skb);
return err;
case SEG6_LOCAL_ACTION_END_B6_ENCAP:
@@ -7083,8 +7116,8 @@ BPF_CALL_4(bpf_lwt_seg6_action, struct sk_buff *, skb,
return -EBADMSG;
err = bpf_push_seg6_encap(skb, BPF_LWT_ENCAP_SEG6,
param, param_len);
- if (!err)
- bpf_update_srh_state(skb);
+ /* The header can be in place although err says otherwise */
+ bpf_update_srh_state(skb);
return err;
default:
@@ -7600,8 +7633,8 @@ bool bpf_tcp_sock_is_valid_access(int off, int size, enum bpf_access_type type,
return false;
switch (off) {
- case offsetof(struct bpf_tcp_sock, bytes_received):
- case offsetof(struct bpf_tcp_sock, bytes_acked):
+ case bpf_ctx_range(struct bpf_tcp_sock, bytes_received):
+ case bpf_ctx_range(struct bpf_tcp_sock, bytes_acked):
return size == sizeof(__u64);
default:
return size == sizeof(__u32);
@@ -8052,17 +8085,14 @@ static const u8 *bpf_search_tcp_opt(const u8 *op, const u8 *opend,
return ERR_PTR(-ENOMSG);
}
-BPF_CALL_4(bpf_sock_ops_load_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
- void *, search_res, u32, len, u64, flags)
+int __bpf_sock_ops_load_hdr_opt(struct bpf_sock_ops_kern *bpf_sock,
+ void *search_res, u32 len, u64 flags)
{
bool eol, load_syn = flags & BPF_LOAD_HDR_OPT_TCP_SYN;
const u8 *op, *opend, *magic, *search = search_res;
u8 search_kind, search_len, copy_len, magic_len;
int ret;
- if (!is_locked_tcp_sock_ops(bpf_sock))
- return -EOPNOTSUPP;
-
/* 2 byte is the minimal option len except TCPOPT_NOP and
* TCPOPT_EOL which are useless for the bpf prog to learn
* and this helper disallow loading them also.
@@ -8123,6 +8153,15 @@ BPF_CALL_4(bpf_sock_ops_load_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
return ret;
}
+BPF_CALL_4(bpf_sock_ops_load_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
+ void *, search_res, u32, len, u64, flags)
+{
+ if (!is_locked_tcp_sock_ops(bpf_sock))
+ return -EOPNOTSUPP;
+
+ return __bpf_sock_ops_load_hdr_opt(bpf_sock, search_res, len, flags);
+}
+
static const struct bpf_func_proto bpf_sock_ops_load_hdr_opt_proto = {
.func = bpf_sock_ops_load_hdr_opt,
.gpl_only = false,
@@ -8133,17 +8172,14 @@ static const struct bpf_func_proto bpf_sock_ops_load_hdr_opt_proto = {
.arg4_type = ARG_ANYTHING,
};
-BPF_CALL_4(bpf_sock_ops_store_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
- const void *, from, u32, len, u64, flags)
+int __bpf_sock_ops_store_hdr_opt(struct bpf_sock_ops_kern *bpf_sock,
+ const void *from, u32 len, u64 flags)
{
u8 new_kind, new_kind_len, magic_len = 0, *opend;
const u8 *op, *new_op, *magic = NULL;
struct sk_buff *skb;
bool eol;
- if (bpf_sock->op != BPF_SOCK_OPS_WRITE_HDR_OPT_CB)
- return -EPERM;
-
if (len < 2 || flags)
return -EINVAL;
@@ -8201,6 +8237,15 @@ BPF_CALL_4(bpf_sock_ops_store_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
return 0;
}
+BPF_CALL_4(bpf_sock_ops_store_hdr_opt, struct bpf_sock_ops_kern *, bpf_sock,
+ const void *, from, u32, len, u64, flags)
+{
+ if (bpf_sock->op != BPF_SOCK_OPS_WRITE_HDR_OPT_CB)
+ return -EPERM;
+
+ return __bpf_sock_ops_store_hdr_opt(bpf_sock, from, len, flags);
+}
+
static const struct bpf_func_proto bpf_sock_ops_store_hdr_opt_proto = {
.func = bpf_sock_ops_store_hdr_opt,
.gpl_only = false,
@@ -12402,7 +12447,7 @@ int __bpf_skb_meta_store_bytes(struct sk_buff *skb, u32 offset,
{
if (unlikely(flags))
return -EINVAL;
- if (unlikely(bpf_try_make_writable(skb, 0)))
+ if (unlikely(bpf_try_make_writable_from(skb, 0, &from)))
return -EFAULT;
memmove(bpf_skb_meta_pointer(skb, offset), from, len);
@@ -12878,6 +12923,7 @@ static int __init bpf_kfunc_init(void)
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_LWT_SEG6LOCAL, &bpf_kfunc_set_skb);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_NETFILTER, &bpf_kfunc_set_skb);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &bpf_kfunc_set_skb);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &bpf_kfunc_set_skb);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &bpf_kfunc_set_skb_meta);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_ACT, &bpf_kfunc_set_skb_meta);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &bpf_kfunc_set_xdp);
diff --git a/net/core/ptp_classifier.c b/net/core/ptp_classifier.c
index 598041b0499e..939104e25c75 100644
--- a/net/core/ptp_classifier.c
+++ b/net/core/ptp_classifier.c
@@ -2,12 +2,8 @@
/* PTP classifier
*/
-/* The below program is the bpf_asm (tools/net/) representation of
- * the opcode array in the ptp_filter structure.
- *
- * For convenience, this can easily be altered and reviewed with
- * bpf_asm and bpf_dbg, e.g. `./bpf_asm -c prog` where prog is a
- * simple file containing the below program:
+/* The below program is the classic BPF assembly representation of
+ * the opcode array in the ptp_filter structure:
*
* ldh [12] ; load ethertype
*
diff --git a/net/core/sock_map.c b/net/core/sock_map.c
index 38df84284328..057e2f3c0265 100644
--- a/net/core/sock_map.c
+++ b/net/core/sock_map.c
@@ -1933,6 +1933,15 @@ out:
return ret;
}
+static int sock_map_iter_check_prog(const struct bpf_prog *prog,
+ const struct bpf_iter_aux_info *aux)
+{
+ if (prog->aux->max_rdonly_access > aux->map->key_size)
+ return -EACCES;
+
+ return 0;
+}
+
static int sock_map_iter_attach_target(struct bpf_prog *prog,
union bpf_iter_link_info *linfo,
struct bpf_iter_aux_info *aux)
@@ -1951,12 +1960,11 @@ static int sock_map_iter_attach_target(struct bpf_prog *prog,
map->map_type != BPF_MAP_TYPE_SOCKHASH)
goto put_map;
- if (prog->aux->max_rdonly_access > map->key_size) {
- err = -EACCES;
+ aux->map = map;
+ err = sock_map_iter_check_prog(prog, aux);
+ if (err)
goto put_map;
- }
- aux->map = map;
return 0;
put_map:
@@ -1973,6 +1981,7 @@ static struct bpf_iter_reg sock_map_iter_reg = {
.target = "sockmap",
.attach_target = sock_map_iter_attach_target,
.detach_target = sock_map_iter_detach_target,
+ .check_prog = sock_map_iter_check_prog,
.show_fdinfo = bpf_iter_map_show_fdinfo,
.fill_link_info = bpf_iter_map_fill_link_info,
.ctx_arg_info_size = 2,
diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile
index 69e4baaed122..e8a0e9605b90 100644
--- a/net/ipv4/Makefile
+++ b/net/ipv4/Makefile
@@ -71,6 +71,7 @@ obj-$(CONFIG_TCP_AO) += tcp_ao.o
ifeq ($(CONFIG_BPF_JIT),y)
obj-$(CONFIG_BPF_SYSCALL) += bpf_tcp_ca.o
+obj-$(CONFIG_CGROUP_BPF) += bpf_tcp_ops.o
endif
ifdef CONFIG_GCOV_PROFILE_NETFILTER
diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c
index c012e2afc6dc..7677b0343abd 100644
--- a/net/ipv4/af_inet.c
+++ b/net/ipv4/af_inet.c
@@ -226,7 +226,8 @@ int __inet_listen_sk(struct sock *sk, int backlog)
if (err)
return err;
- tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_LISTEN_CB, 0, NULL);
+ tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_LISTEN_CB);
+ bpf_tcp_ops_call(listen, sk);
}
return 0;
}
diff --git a/net/ipv4/bpf_tcp_ca.c b/net/ipv4/bpf_tcp_ca.c
index 9deed2244c2d..2a71dc81562f 100644
--- a/net/ipv4/bpf_tcp_ca.c
+++ b/net/ipv4/bpf_tcp_ca.c
@@ -340,6 +340,22 @@ static struct bpf_struct_ops bpf_tcp_congestion_ops = {
.validate = bpf_tcp_ca_validate,
.name = "tcp_congestion_ops",
.cfi_stubs = &__bpf_ops_tcp_congestion_ops,
+ /* The struct_ops's function may switch to another struct_ops.
+ *
+ * For example, bpf_tcp_cc_x->init() may switch to
+ * another tcp_cc_y by calling
+ * setsockopt(TCP_CONGESTION, "tcp_cc_y").
+ * During the switch, bpf_struct_ops_put(tcp_cc_x) is called
+ * and its refcount may reach 0 which then free its
+ * trampoline image while tcp_cc_x is still running.
+ *
+ * A vanilla rcu gp is to wait for all bpf-tcp-cc prog
+ * to finish. bpf-tcp-cc prog is non sleepable.
+ * A rcu_tasks gp is to wait for the last few insn
+ * in the tramopline image to finish before releasing
+ * the trampoline image.
+ */
+ .free_after_tasks_rcu_gp = true,
.owner = THIS_MODULE,
};
diff --git a/net/ipv4/bpf_tcp_ops.c b/net/ipv4/bpf_tcp_ops.c
new file mode 100644
index 000000000000..2ba73dd6c52c
--- /dev/null
+++ b/net/ipv4/bpf_tcp_ops.c
@@ -0,0 +1,453 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
+
+#include <linux/bpf.h>
+#include <linux/btf_ids.h>
+#include <linux/bpf_verifier.h>
+#include <linux/filter.h>
+#include <net/bpf_sk_storage.h>
+#include <net/tcp.h>
+
+static int timeout_init_stub(struct sock *sk, struct request_sock *req__nullable)
+{
+ struct bpf_tramp_run_ctx *ctx =
+ container_of(current->bpf_ctx, struct bpf_tramp_run_ctx, run_ctx);
+
+ return ctx->retval;
+}
+
+static int rwnd_init_stub(struct sock *sk, struct request_sock *req__nullable)
+{
+ struct bpf_tramp_run_ctx *ctx =
+ container_of(current->bpf_ctx, struct bpf_tramp_run_ctx, run_ctx);
+
+ return ctx->retval;
+}
+
+static void active_established_stub(struct sock *sk, struct sk_buff *skb__nullable)
+{
+}
+
+static void passive_established_stub(struct sock *sk, struct sk_buff *skb)
+{
+}
+
+static void rto_stub(struct sock *sk)
+{
+}
+
+static void rtt_stub(struct sock *sk, long mrtt, u32 srtt)
+{
+}
+
+static void set_state_stub(struct sock *sk, int state)
+{
+}
+
+static void retrans_stub(struct sock *sk, struct sk_buff *skb, int err)
+{
+}
+
+static void connect_stub(struct sock *sk)
+{
+}
+
+static void listen_stub(struct sock *sk)
+{
+}
+
+static void parse_hdr_stub(struct sock *sk, struct sk_buff *skb)
+{
+}
+
+static void hdr_opt_len_stub(struct sock *sk, struct sk_buff *skb__nullable,
+ struct request_sock *req__nullable,
+ struct sk_buff *syn_skb__nullable,
+ enum tcp_synack_type synack_type,
+ unsigned int *remaining)
+{
+}
+
+static void write_hdr_opt_stub(struct sock *sk, struct sk_buff *skb,
+ struct request_sock *req__nullable,
+ struct sk_buff *syn_skb__nullable,
+ enum tcp_synack_type synack_type,
+ u32 opt_off)
+{
+}
+
+static void enqueue_rcvq_stub(struct sock *sk, struct sk_buff *skb)
+{
+}
+
+static void dequeue_rcvq_stub(struct sock *sk)
+{
+}
+
+static struct bpf_tcp_ops __bpf_tcp_ops = {
+ .timeout_init = timeout_init_stub,
+ .rwnd_init = rwnd_init_stub,
+ .active_established = active_established_stub,
+ .passive_established = passive_established_stub,
+ .rto = rto_stub,
+ .rtt = rtt_stub,
+ .set_state = set_state_stub,
+ .retrans = retrans_stub,
+ .connect = connect_stub,
+ .listen = listen_stub,
+ .parse_hdr = parse_hdr_stub,
+ .hdr_opt_len = hdr_opt_len_stub,
+ .write_hdr_opt = write_hdr_opt_stub,
+ .enqueue_rcvq = enqueue_rcvq_stub,
+ .dequeue_rcvq = dequeue_rcvq_stub,
+};
+
+BPF_CALL_4(bpf_tcp_ops_store_hdr_opt, void *, ctx, const void *, from,
+ u32, len, u64, flags)
+{
+ u64 *args = ctx;
+ struct sk_buff *skb = (void *)(unsigned long)args[1];
+ struct bpf_sock_ops_kern sock_ops = {};
+ u32 opt_off = args[5];
+ u8 *op, *opend;
+
+ /*
+ * bpf_tcp_ops does not keep track of the end of the written TCP header
+ * options, so search for it every time the helper is called. The free
+ * space is NOP-filled, so a TCPOPT_NOP ends the search rather than being
+ * skipped as in a normal option walk in sockops.
+ */
+ op = skb->data + opt_off;
+ opend = skb->data + tcp_hdrlen(skb);
+ while (op < opend && *op != TCPOPT_NOP) {
+ if (*op == TCPOPT_EOL || op + 1 >= opend || op[1] < 2)
+ break;
+ op += op[1];
+ }
+
+ sock_ops.skb = skb;
+ sock_ops.skb_data_end = op;
+ sock_ops.remaining_opt_len = opend - op;
+
+ return __bpf_sock_ops_store_hdr_opt(&sock_ops, from, len, flags);
+}
+
+static const struct bpf_func_proto bpf_tcp_ops_store_hdr_opt_proto = {
+ .func = bpf_tcp_ops_store_hdr_opt,
+ .gpl_only = false,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_RDONLY,
+ .arg3_type = ARG_MEM_SIZE,
+ .arg4_type = ARG_ANYTHING,
+};
+
+BPF_CALL_4(bpf_tcp_ops_load_hdr_opt, void *, ctx, void *, search_res,
+ u32, len, u64, flags)
+{
+ u64 *args = ctx;
+ struct sk_buff *skb = (void *)(unsigned long)args[1];
+ struct bpf_sock_ops_kern sock_ops = {};
+
+ /*
+ * No flags supported. In particular BPF_LOAD_HDR_OPT_TCP_SYN, which
+ * loads from the saved SYN, is not available because bpf_tcp_ops has no
+ * carrier to track the SYN source across the hooks.
+ */
+ if (flags)
+ return -EINVAL;
+
+ sock_ops.skb = skb;
+ sock_ops.skb_data_end = skb->data + tcp_hdrlen(skb);
+
+ return __bpf_sock_ops_load_hdr_opt(&sock_ops, search_res, len, flags);
+}
+
+static const struct bpf_func_proto bpf_tcp_ops_load_hdr_opt_proto = {
+ .func = bpf_tcp_ops_load_hdr_opt,
+ .gpl_only = false,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg2_type = ARG_PTR_TO_MEM | MEM_WRITE,
+ .arg3_type = ARG_MEM_SIZE,
+ .arg4_type = ARG_ANYTHING,
+};
+
+BPF_CALL_3(bpf_tcp_ops_reserve_hdr_opt, void *, ctx, u32, len, u64, flags)
+{
+ u64 *args = ctx;
+ unsigned int *remaining = (void *)(unsigned long)args[5];
+
+ if (flags || len < 2)
+ return -EINVAL;
+
+ if (len > *remaining)
+ return -ENOSPC;
+
+ *remaining -= len;
+ return 0;
+}
+
+static const struct bpf_func_proto bpf_tcp_ops_reserve_hdr_opt_proto = {
+ .func = bpf_tcp_ops_reserve_hdr_opt,
+ .gpl_only = false,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg2_type = ARG_ANYTHING,
+ .arg3_type = ARG_ANYTHING,
+};
+
+BPF_CALL_0(bpf_tcp_ops_get_retval)
+{
+ struct bpf_tramp_run_ctx *ctx =
+ container_of(current->bpf_ctx, struct bpf_tramp_run_ctx, run_ctx);
+
+ /* bpf_get_retval() is only exposed to timeout_init/rwnd_init, which
+ * always run via bpf_tcp_ops_call_int(). Its run_ctx carries the int
+ * return value chained across the bpf_tcp_ops attached to the cgroup
+ * and is this program's saved_run_ctx.
+ */
+ if (WARN_ON_ONCE(!ctx->saved_run_ctx))
+ return 0;
+
+ return container_of(ctx->saved_run_ctx, struct bpf_tramp_run_ctx,
+ run_ctx)->retval;
+}
+
+const struct bpf_func_proto bpf_tcp_ops_get_retval_proto = {
+ .func = bpf_tcp_ops_get_retval,
+ .gpl_only = false,
+ .ret_type = RET_INTEGER,
+};
+
+static bool is_sockopt_supported(u32 moff)
+{
+ switch (moff) {
+ case offsetof(struct bpf_tcp_ops, active_established):
+ case offsetof(struct bpf_tcp_ops, passive_established):
+ case offsetof(struct bpf_tcp_ops, rto):
+ case offsetof(struct bpf_tcp_ops, rtt):
+ case offsetof(struct bpf_tcp_ops, set_state):
+ case offsetof(struct bpf_tcp_ops, retrans):
+ case offsetof(struct bpf_tcp_ops, connect):
+ case offsetof(struct bpf_tcp_ops, listen):
+ case offsetof(struct bpf_tcp_ops, parse_hdr):
+ return true;
+ }
+
+ return false;
+}
+
+static const struct bpf_func_proto *
+get_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog)
+{
+ u32 moff = prog->aux->attach_st_ops_member_off;
+
+ switch (func_id) {
+ case BPF_FUNC_sk_storage_get:
+ return &bpf_sk_storage_get_proto;
+ case BPF_FUNC_sk_storage_delete:
+ return &bpf_sk_storage_delete_proto;
+ case BPF_FUNC_setsockopt:
+ if (is_sockopt_supported(moff))
+ return &bpf_sk_setsockopt_proto;
+ return NULL;
+ case BPF_FUNC_getsockopt:
+ if (is_sockopt_supported(moff))
+ return &bpf_sk_getsockopt_proto;
+ return NULL;
+ case BPF_FUNC_get_retval:
+ if (moff == offsetof(struct bpf_tcp_ops, timeout_init) ||
+ moff == offsetof(struct bpf_tcp_ops, rwnd_init))
+ return &bpf_tcp_ops_get_retval_proto;
+ return NULL;
+ case BPF_FUNC_reserve_hdr_opt:
+ if (moff == offsetof(struct bpf_tcp_ops, hdr_opt_len))
+ return &bpf_tcp_ops_reserve_hdr_opt_proto;
+ return NULL;
+ case BPF_FUNC_load_hdr_opt:
+ if (moff == offsetof(struct bpf_tcp_ops, parse_hdr) ||
+ moff == offsetof(struct bpf_tcp_ops, write_hdr_opt))
+ return &bpf_tcp_ops_load_hdr_opt_proto;
+ return NULL;
+ case BPF_FUNC_store_hdr_opt:
+ if (moff == offsetof(struct bpf_tcp_ops, write_hdr_opt))
+ return &bpf_tcp_ops_store_hdr_opt_proto;
+ return NULL;
+ default:
+ return bpf_base_func_proto(func_id, prog);
+ }
+}
+
+static bool is_valid_access(int off, int size, enum bpf_access_type type,
+ const struct bpf_prog *prog, struct bpf_insn_access_aux *info)
+{
+ if (!bpf_tracing_btf_ctx_access(off, size, type, prog, info))
+ return false;
+
+ if (base_type(info->reg_type) == PTR_TO_BTF_ID &&
+ !bpf_type_has_unsafe_modifiers(info->reg_type) &&
+ info->btf_id == btf_sock_ids[BTF_SOCK_TYPE_SOCK])
+ /* promote it to tcp_sock */
+ info->btf_id = btf_sock_ids[BTF_SOCK_TYPE_TCP];
+
+ return true;
+}
+
+static int bpf_tcp_ops_init_member(const struct btf_type *t,
+ const struct btf_member *member,
+ void *kdata, const void *udata)
+{
+ return 0;
+}
+
+static int bpf_tcp_ops_check_member(const struct btf_type *t,
+ const struct btf_member *member,
+ const struct bpf_prog *prog)
+{
+ if (prog->sleepable)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int bpf_tcp_ops_init(struct btf *btf)
+{
+ return 0;
+}
+
+static int bpf_tcp_ops_validate(void *kdata)
+{
+ return 0;
+}
+
+static const struct bpf_verifier_ops bpf_tcp_ops_verifier = {
+ .get_func_proto = get_func_proto,
+ .is_valid_access = is_valid_access,
+};
+
+static struct bpf_struct_ops bpf_tcp_ops = {
+ .verifier_ops = &bpf_tcp_ops_verifier,
+ .init_member = bpf_tcp_ops_init_member,
+ .check_member = bpf_tcp_ops_check_member,
+ .init = bpf_tcp_ops_init,
+ .validate = bpf_tcp_ops_validate,
+ .name = "bpf_tcp_ops",
+ .cgroup_atype = CGROUP_TCP_SOCK_OPS,
+ .cfi_stubs = &__bpf_tcp_ops,
+ .owner = THIS_MODULE,
+};
+
+__bpf_kfunc_start_defs();
+
+__bpf_kfunc int bpf_tcp_ops_set_flags(struct tcp_sock *tp, u32 enable, u32 disable)
+{
+ u32 old, new;
+
+ if ((enable & disable) || (enable | disable) & ~BPF_TCP_OPS_FLAG_ALL)
+ return -EINVAL;
+
+ if (sk_is_mptcp((struct sock *)tp) && (enable & BPF_TCP_OPS_FLAG_RCVQ))
+ return -EOPNOTSUPP;
+
+ old = READ_ONCE(tp->bpf_tcp_ops_flags);
+
+ do {
+ new = (old | enable) & ~disable;
+ if (new == old)
+ break;
+ } while (!try_cmpxchg(&tp->bpf_tcp_ops_flags, &old, new));
+
+ return 0;
+}
+
+__bpf_kfunc int bpf_tcp_ops_set_rcvlowat(struct sock *sk, int rcvlowat,
+ const struct bpf_prog_aux *aux)
+{
+ u32 moff = aux->attach_st_ops_member_off;
+ bool wakeup = false;
+
+ if (moff == offsetof(struct bpf_tcp_ops, dequeue_rcvq))
+ wakeup = true;
+
+ if (rcvlowat < 0)
+ rcvlowat = INT_MAX;
+
+ return __tcp_set_rcvlowat(sk, rcvlowat, wakeup);
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(bpf_tcp_ops_set_flags_kfunc_set)
+BTF_ID_FLAGS(func, bpf_tcp_ops_set_flags)
+BTF_KFUNCS_END(bpf_tcp_ops_set_flags_kfunc_set)
+
+BTF_KFUNCS_START(bpf_tcp_ops_set_rcvlowat_kfunc_set)
+BTF_ID_FLAGS(func, bpf_tcp_ops_set_rcvlowat, KF_IMPLICIT_ARGS)
+BTF_KFUNCS_END(bpf_tcp_ops_set_rcvlowat_kfunc_set)
+
+static int bpf_tcp_ops_set_flags_kfunc_filter(const struct bpf_prog *prog,
+ u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&bpf_tcp_ops_set_flags_kfunc_set, kfunc_id))
+ return 0;
+
+ if (prog->type == BPF_PROG_TYPE_STRUCT_OPS &&
+ prog->aux->st_ops == &bpf_tcp_ops)
+ return 0;
+
+ if (prog->type == BPF_PROG_TYPE_CGROUP_SOCKOPT ||
+ prog->type == BPF_PROG_TYPE_CGROUP_SKB)
+ return 0;
+
+ return -EACCES;
+}
+
+static const struct btf_kfunc_id_set bpf_tcp_ops_set_flags_kfunc_id_set = {
+ .owner = THIS_MODULE,
+ .set = &bpf_tcp_ops_set_flags_kfunc_set,
+ .filter = bpf_tcp_ops_set_flags_kfunc_filter,
+};
+
+static int bpf_tcp_ops_set_rcvlowat_kfunc_filter(const struct bpf_prog *prog,
+ u32 kfunc_id)
+{
+ u32 moff;
+
+ if (!btf_id_set8_contains(&bpf_tcp_ops_set_rcvlowat_kfunc_set, kfunc_id))
+ return 0;
+
+ if (prog->aux->st_ops != &bpf_tcp_ops)
+ return -EACCES;
+
+ moff = prog->aux->attach_st_ops_member_off;
+ if (moff != offsetof(struct bpf_tcp_ops, enqueue_rcvq) &&
+ moff != offsetof(struct bpf_tcp_ops, dequeue_rcvq))
+ return -EACCES;
+
+ return 0;
+}
+
+static const struct btf_kfunc_id_set bpf_tcp_ops_set_rcvlowat_kfunc_id_set = {
+ .owner = THIS_MODULE,
+ .set = &bpf_tcp_ops_set_rcvlowat_kfunc_set,
+ .filter = bpf_tcp_ops_set_rcvlowat_kfunc_filter,
+};
+
+static int __init __bpf_tcp_ops_init(void)
+{
+ int ret;
+
+ ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bpf_tcp_ops_set_flags_kfunc_id_set);
+ /* BPF_PROG_TYPE_CGROUP_{SOCKOPT,SKB} share BTF_KFUNC_HOOK_CGROUP. */
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_CGROUP_SOCKOPT,
+ &bpf_tcp_ops_set_flags_kfunc_id_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS,
+ &bpf_tcp_ops_set_rcvlowat_kfunc_id_set);
+ ret = ret ?: register_bpf_struct_ops(&bpf_tcp_ops, bpf_tcp_ops);
+
+ return ret;
+}
+
+late_initcall(__bpf_tcp_ops_init);
diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c
index 3fd477605704..59e52161c577 100644
--- a/net/ipv4/tcp.c
+++ b/net/ipv4/tcp.c
@@ -1612,6 +1612,8 @@ void tcp_cleanup_rbuf(struct sock *sk, int copied)
"cleanup rbuf bug: copied %X seq %X rcvnxt %X\n",
tp->copied_seq, TCP_SKB_CB(skb)->end_seq, tp->rcv_nxt);
__tcp_cleanup_rbuf(sk, copied);
+
+ bpf_tcp_ops_call_flag(dequeue_rcvq, RCVQ, sk);
}
static void tcp_eat_recv_skb(struct sock *sk, struct sk_buff *skb)
@@ -1828,8 +1830,7 @@ int tcp_peek_len(struct socket *sock)
return tcp_inq(sock->sk);
}
-/* Make sure sk_rcvbuf is big enough to satisfy SO_RCVLOWAT hint */
-int tcp_set_rcvlowat(struct sock *sk, int val)
+int __tcp_set_rcvlowat(struct sock *sk, int val, bool wakeup)
{
struct tcp_sock *tp = tcp_sk(sk);
int space, cap;
@@ -1842,7 +1843,8 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
WRITE_ONCE(sk->sk_rcvlowat, val ? : 1);
/* Check if we need to signal EPOLLIN right now */
- tcp_data_ready(sk);
+ if (wakeup)
+ tcp_data_ready(sk);
if (sk->sk_userlocks & SOCK_RCVBUF_LOCK)
return 0;
@@ -1857,6 +1859,12 @@ int tcp_set_rcvlowat(struct sock *sk, int val)
return 0;
}
+/* Make sure sk_rcvbuf is big enough to satisfy SO_RCVLOWAT hint */
+int tcp_set_rcvlowat(struct sock *sk, int val)
+{
+ return __tcp_set_rcvlowat(sk, val, true);
+}
+
void tcp_set_rcvbuf(struct sock *sk, int val)
{
tcp_set_window_clamp(sk, tcp_win_from_space(sk, val));
@@ -2986,8 +2994,8 @@ void tcp_set_state(struct sock *sk, int state)
*/
BTF_TYPE_EMIT_ENUM(BPF_TCP_ESTABLISHED);
- if (BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk), BPF_SOCK_OPS_STATE_CB_FLAG))
- tcp_call_bpf_2arg(sk, BPF_SOCK_OPS_STATE_CB, oldstate, state);
+ tcp_call_bpf_flag(sk, BPF_SOCK_OPS_STATE_CB, oldstate, state);
+ bpf_tcp_ops_call(set_state, sk, state);
switch (state) {
case TCP_ESTABLISHED:
@@ -5208,6 +5216,9 @@ static void __init tcp_struct_check(void)
CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_txrx, lost_out);
CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_txrx, sacked_out);
CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_txrx, scaling_ratio);
+#ifdef CONFIG_BPF
+ CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_txrx, bpf_tcp_ops_flags);
+#endif
/* RX read-mostly hotpath cache lines */
CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_rx, copied_seq);
diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
index c2b024a90bbd..8cf63a9e7fed 100644
--- a/net/ipv4/tcp_bpf.c
+++ b/net/ipv4/tcp_bpf.c
@@ -108,7 +108,6 @@ static int tcp_bpf_push(struct sock *sk, struct sk_msg *msg, u32 apply_bytes,
off = sge->offset;
page = sg_page(sge);
- tcp_rate_check_app_limited(sk);
retry:
msghdr.msg_flags = flags | MSG_SPLICE_PAGES;
has_tx_ulp = tls_sw_has_ctx_tx(sk);
diff --git a/net/ipv4/tcp_fastopen.c b/net/ipv4/tcp_fastopen.c
index 471c78be5513..6dfe40322fb5 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;
+ bpf_tcp_ops_call_flag(enqueue_rcvq, RCVQ, 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 1ddce9c4b7e8..e1f2d9765f81 100644
--- a/net/ipv4/tcp_input.c
+++ b/net/ipv4/tcp_input.c
@@ -146,14 +146,16 @@ EXPORT_SYMBOL_GPL(clean_acked_data_flush);
#ifdef CONFIG_CGROUP_BPF
static void bpf_skops_parse_hdr(struct sock *sk, struct sk_buff *skb)
{
- bool unknown_opt = tcp_sk(sk)->rx_opt.saw_unknown &&
- BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk),
- BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG);
- bool parse_all_opt = BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk),
- BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG);
struct bpf_sock_ops_kern sock_ops;
+ const struct tcp_sock *tp;
- if (likely(!unknown_opt && !parse_all_opt))
+ if (!cgroup_bpf_enabled(CGROUP_SOCK_OPS))
+ return;
+
+ tp = tcp_sk(sk);
+ if (!(tp->rx_opt.saw_unknown &&
+ BPF_SOCK_OPS_TEST_FLAG(tp, BPF_SOCK_OPS_PARSE_UNKNOWN_HDR_OPT_CB_FLAG)) &&
+ !BPF_SOCK_OPS_TEST_FLAG(tp, BPF_SOCK_OPS_PARSE_ALL_HDR_OPT_CB_FLAG))
return;
/* The skb will be handled in the
@@ -176,7 +178,7 @@ static void bpf_skops_parse_hdr(struct sock *sk, struct sk_buff *skb)
sock_ops.sk = sk;
bpf_skops_init_skb(&sock_ops, skb, tcp_hdrlen(skb));
- BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
+ __BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
}
static void bpf_skops_established(struct sock *sk, int bpf_op,
@@ -184,6 +186,9 @@ static void bpf_skops_established(struct sock *sk, int bpf_op,
{
struct bpf_sock_ops_kern sock_ops;
+ if (!cgroup_bpf_enabled(CGROUP_SOCK_OPS))
+ return;
+
sock_owned_by_me(sk);
memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
@@ -195,7 +200,7 @@ static void bpf_skops_established(struct sock *sk, int bpf_op,
if (skb)
bpf_skops_init_skb(&sock_ops, skb, tcp_hdrlen(skb));
- BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
+ __BPF_CGROUP_RUN_PROG_SOCK_OPS(&sock_ops);
}
#else
static void bpf_skops_parse_hdr(struct sock *sk, struct sk_buff *skb)
@@ -208,6 +213,28 @@ static void bpf_skops_established(struct sock *sk, int bpf_op,
}
#endif
+static void bpf_tcp_ops_parse_hdr(struct sock *sk, struct sk_buff *skb)
+{
+ const struct tcp_sock *tp;
+
+ if (!cgroup_bpf_enabled(CGROUP_TCP_SOCK_OPS))
+ return;
+
+ switch (sk->sk_state) {
+ case TCP_SYN_RECV:
+ case TCP_SYN_SENT:
+ case TCP_LISTEN:
+ return;
+ }
+
+ tp = tcp_sk(sk);
+
+ if ((tp->rx_opt.saw_unknown &&
+ BPF_TCP_OPS_TEST_FLAG(tp, PARSE_HDR_OPT_UNKNOWN)) ||
+ BPF_TCP_OPS_TEST_FLAG(tp, PARSE_HDR_OPT_ALL))
+ __bpf_tcp_ops_call(parse_hdr, sk, skb);
+}
+
static __cold void tcp_gro_dev_warn(const struct sock *sk, const struct sk_buff *skb,
unsigned int len)
{
@@ -5338,6 +5365,8 @@ static void tcp_ofo_queue(struct sock *sk)
continue;
}
+ bpf_tcp_ops_call_flag(enqueue_rcvq, RCVQ, 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);
@@ -5541,6 +5570,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);
+ bpf_tcp_ops_call_flag(enqueue_rcvq, RCVQ, sk, skb);
+
eaten = (tail &&
tcp_try_coalesce(sk, tail,
skb, fragstolen)) ? 1 : 0;
@@ -6480,6 +6511,7 @@ syn_challenge:
pass:
bpf_skops_parse_hdr(sk, skb);
+ bpf_tcp_ops_parse_hdr(sk, skb);
return true;
@@ -6744,6 +6776,10 @@ void tcp_init_transfer(struct sock *sk, int bpf_op, struct sk_buff *skb)
tp->snd_cwnd_stamp = tcp_jiffies32;
bpf_skops_established(sk, bpf_op, skb);
+ if (bpf_op == BPF_SOCK_OPS_ACTIVE_ESTABLISHED_CB)
+ bpf_tcp_ops_call(active_established, sk, skb);
+ else
+ bpf_tcp_ops_call(passive_established, sk, skb);
/* Initialize congestion control unless BPF initialized it already: */
if (!icsk->icsk_ca_initialized)
tcp_init_congestion_control(sk);
diff --git a/net/ipv4/tcp_nv.c b/net/ipv4/tcp_nv.c
index f345897a68df..7b0dae23d9aa 100644
--- a/net/ipv4/tcp_nv.c
+++ b/net/ipv4/tcp_nv.c
@@ -146,7 +146,7 @@ static void tcpnv_init(struct sock *sk)
* within a datacenter, where we have reasonable estimates of
* RTTs
*/
- base_rtt = tcp_call_bpf(sk, BPF_SOCK_OPS_BASE_RTT, 0, NULL);
+ base_rtt = tcp_call_bpf(sk, BPF_SOCK_OPS_BASE_RTT);
if (base_rtt > 0) {
ca->nv_base_rtt = base_rtt;
ca->nv_lower_bound_rtt = (base_rtt * 205) >> 8; /* 80% */
diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c
index c8865205cdc8..df3aaba8574f 100644
--- a/net/ipv4/tcp_output.c
+++ b/net/ipv4/tcp_output.c
@@ -476,8 +476,8 @@ static u32 bpf_skops_hdr_opt_len(struct sock *sk, struct sk_buff *skb,
struct bpf_sock_ops_kern sock_ops;
int err;
- if (likely(!BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk),
- BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG)) ||
+ if (!BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk),
+ BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG) ||
!remaining)
return remaining;
@@ -518,7 +518,7 @@ static u32 bpf_skops_hdr_opt_len(struct sock *sk, struct sk_buff *skb,
if (skb)
bpf_skops_init_skb(&sock_ops, skb, 0);
- err = BPF_CGROUP_RUN_PROG_SOCK_OPS_SK(&sock_ops, sk);
+ err = __BPF_CGROUP_RUN_PROG_SOCK_OPS_SK(&sock_ops, sk);
if (err || sock_ops.remaining_opt_len == remaining)
return remaining;
@@ -536,43 +536,54 @@ static void bpf_skops_write_hdr_opt(struct sock *sk, struct sk_buff *skb,
enum tcp_synack_type synack_type,
struct tcp_out_options *opts)
{
- u8 first_opt_off, nr_written, max_opt_len = opts->bpf_opt_len;
- struct bpf_sock_ops_kern sock_ops;
- int err;
+ u8 first_opt_off, nr_written = 0, max_opt_len = opts->bpf_opt_len;
if (likely(!max_opt_len))
return;
- memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
+ first_opt_off = tcp_hdrlen(skb) - max_opt_len;
- sock_ops.op = BPF_SOCK_OPS_WRITE_HDR_OPT_CB;
+ if (cgroup_bpf_enabled(CGROUP_SOCK_OPS) &&
+ BPF_SOCK_OPS_TEST_FLAG(tcp_sk(sk),
+ BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG)) {
+ struct bpf_sock_ops_kern sock_ops;
+ int err;
- if (req) {
- sock_ops.sk = (struct sock *)req;
- sock_ops.syn_skb = syn_skb;
- } else {
- sock_owned_by_me(sk);
+ memset(&sock_ops, 0, offsetof(struct bpf_sock_ops_kern, temp));
- sock_ops.is_fullsock = 1;
- sock_ops.is_locked_tcp_sock = 1;
- sock_ops.sk = sk;
- }
+ sock_ops.op = BPF_SOCK_OPS_WRITE_HDR_OPT_CB;
- sock_ops.args[0] = bpf_skops_write_hdr_opt_arg0(skb, synack_type);
- sock_ops.remaining_opt_len = max_opt_len;
- first_opt_off = tcp_hdrlen(skb) - max_opt_len;
- bpf_skops_init_skb(&sock_ops, skb, first_opt_off);
+ if (req) {
+ sock_ops.sk = (struct sock *)req;
+ sock_ops.syn_skb = syn_skb;
+ } else {
+ sock_owned_by_me(sk);
- err = BPF_CGROUP_RUN_PROG_SOCK_OPS_SK(&sock_ops, sk);
+ sock_ops.is_fullsock = 1;
+ sock_ops.is_locked_tcp_sock = 1;
+ sock_ops.sk = sk;
+ }
- if (err)
- nr_written = 0;
- else
- nr_written = max_opt_len - sock_ops.remaining_opt_len;
+ sock_ops.args[0] = bpf_skops_write_hdr_opt_arg0(skb, synack_type);
+ sock_ops.remaining_opt_len = max_opt_len;
+ bpf_skops_init_skb(&sock_ops, skb, first_opt_off);
+
+ err = __BPF_CGROUP_RUN_PROG_SOCK_OPS_SK(&sock_ops, sk);
+ if (!err)
+ nr_written = max_opt_len - sock_ops.remaining_opt_len;
+ }
if (nr_written < max_opt_len)
memset(skb->data + first_opt_off + nr_written, TCPOPT_NOP,
max_opt_len - nr_written);
+
+ /*
+ * bpf_tcp_ops portion is NOP-filled (everything past the sockops
+ * writer's bytes). The writer finds the append point by scanning from
+ * first_opt_off + nr_written to the first NOP.
+ */
+ bpf_tcp_ops_call_flag(write_hdr_opt, WRITE_HDR_OPT, sk, skb, req,
+ syn_skb, synack_type, first_opt_off + nr_written);
}
#else
static u32 bpf_skops_hdr_opt_len(struct sock *sk, struct sk_buff *skb,
@@ -594,6 +605,53 @@ static void bpf_skops_write_hdr_opt(struct sock *sk, struct sk_buff *skb,
}
#endif
+static u32 bpf_tcp_ops_hdr_opt_len(struct sock *sk, struct sk_buff *skb,
+ struct request_sock *req,
+ struct sk_buff *syn_skb,
+ enum tcp_synack_type synack_type,
+ struct tcp_out_options *opts,
+ u32 remaining)
+{
+ unsigned int remaining_out = remaining, reserved;
+
+ if (!BPF_TCP_OPS_TEST_FLAG(tcp_sk(sk), WRITE_HDR_OPT) ||
+ !remaining)
+ return remaining;
+
+ /* bpf_tcp_ops_reserve_hdr_opt() reserves space via remaining_out */
+ __bpf_tcp_ops_call(hdr_opt_len, sk, skb, req, syn_skb, synack_type, &remaining_out);
+
+ reserved = remaining - remaining_out;
+ if (!reserved)
+ return remaining;
+
+ /* round up to 4 bytes */
+ reserved = (reserved + 3) & ~3;
+
+ opts->bpf_opt_len += reserved;
+ return remaining - reserved;
+}
+
+static __always_inline u32 tcp_bpf_hdr_opt_len(struct sock *sk, struct sk_buff *skb,
+ struct request_sock *req,
+ struct sk_buff *syn_skb,
+ enum tcp_synack_type synack_type,
+ struct tcp_out_options *opts,
+ u32 remaining)
+{
+ if (cgroup_bpf_enabled(CGROUP_SOCK_OPS))
+ remaining = bpf_skops_hdr_opt_len(sk, skb, req, syn_skb,
+ synack_type, opts,
+ remaining);
+
+ if (cgroup_bpf_enabled(CGROUP_TCP_SOCK_OPS))
+ remaining = bpf_tcp_ops_hdr_opt_len(sk, skb, req, syn_skb,
+ synack_type, opts,
+ remaining);
+
+ return remaining;
+}
+
static __be32 *process_tcp_ao_options(struct tcp_sock *tp,
const struct tcp_request_sock *tcprsk,
struct tcp_out_options *opts,
@@ -1051,8 +1109,8 @@ static unsigned int tcp_syn_options(struct sock *sk, struct sk_buff *skb,
remaining -= tcp_options_fit_accecn(opts, 0, remaining);
}
- remaining = bpf_skops_hdr_opt_len(sk, skb, NULL, NULL, 0, opts,
- remaining);
+ remaining = tcp_bpf_hdr_opt_len(sk, skb, NULL, NULL, 0, opts,
+ remaining);
return MAX_TCP_OPTION_SPACE - remaining;
}
@@ -1139,8 +1197,8 @@ static unsigned int tcp_synack_options(const struct sock *sk,
remaining -= tcp_options_fit_accecn(opts, 0, remaining);
}
- remaining = bpf_skops_hdr_opt_len((struct sock *)sk, skb, req, syn_skb,
- synack_type, opts, remaining);
+ remaining = tcp_bpf_hdr_opt_len((struct sock *)sk, skb, req, syn_skb,
+ synack_type, opts, remaining);
return MAX_TCP_OPTION_SPACE - remaining;
}
@@ -1153,10 +1211,12 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
struct tcp_key *key)
{
struct tcp_sock *tp = tcp_sk(sk);
- unsigned int size = 0;
unsigned int eff_sacks;
+ unsigned int remaining;
+ unsigned int size = 0;
opts->options = 0;
+ opts->bpf_opt_len = 0;
/* Better than switch (key.type) as it has static branches */
if (tcp_key_is_md5(key)) {
@@ -1183,10 +1243,10 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
* left.
*/
if (sk_is_mptcp(sk)) {
- unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
bool has_ts = opts->options & OPTION_TS;
int opt_size;
+ remaining = MAX_TCP_OPTION_SPACE - size;
opts->mptcp.drop_ts = 0;
opt_size = mptcp_established_options(sk, skb, remaining, has_ts,
@@ -1203,7 +1263,8 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
eff_sacks = tp->rx_opt.num_sacks + tp->rx_opt.dsack;
if (unlikely(eff_sacks)) {
- const unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
+ remaining = MAX_TCP_OPTION_SPACE - size;
+
if (likely(remaining >= TCPOLEN_SACK_BASE_ALIGNED +
TCPOLEN_SACK_PERBLOCK)) {
opts->num_sack_blocks =
@@ -1234,15 +1295,10 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb
}
}
- if (unlikely(BPF_SOCK_OPS_TEST_FLAG(tp,
- BPF_SOCK_OPS_WRITE_HDR_OPT_CB_FLAG))) {
- unsigned int remaining = MAX_TCP_OPTION_SPACE - size;
+ remaining = tcp_bpf_hdr_opt_len(sk, skb, NULL, NULL, 0, opts,
+ MAX_TCP_OPTION_SPACE - size);
- remaining = bpf_skops_hdr_opt_len(sk, skb, NULL, NULL, 0, opts,
- remaining);
-
- size = MAX_TCP_OPTION_SPACE - remaining;
- }
+ size = MAX_TCP_OPTION_SPACE - remaining;
return size;
}
@@ -3683,9 +3739,9 @@ start:
err = tcp_transmit_skb(sk, skb, 1, GFP_ATOMIC);
}
- if (BPF_SOCK_OPS_TEST_FLAG(tp, BPF_SOCK_OPS_RETRANS_CB_FLAG))
- tcp_call_bpf_3arg(sk, BPF_SOCK_OPS_RETRANS_CB,
- TCP_SKB_CB(skb)->seq, segs, err);
+ tcp_call_bpf_flag(sk, BPF_SOCK_OPS_RETRANS_CB,
+ TCP_SKB_CB(skb)->seq, segs, err);
+ bpf_tcp_ops_call(retrans, sk, skb, err);
if (unlikely(err) && err != -EBUSY)
NET_ADD_STATS(sock_net(sk), LINUX_MIB_TCPRETRANSFAIL, segs);
@@ -4312,7 +4368,8 @@ int tcp_connect(struct sock *sk)
struct sk_buff *buff;
int err;
- tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB, 0, NULL);
+ tcp_call_bpf(sk, BPF_SOCK_OPS_TCP_CONNECT_CB);
+ bpf_tcp_ops_call(connect, sk);
#if defined(CONFIG_TCP_MD5SIG) && defined(CONFIG_TCP_AO)
/* Has to be checked late, after setting daddr/saddr/ops.
diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c
index e56eae4bc341..00debcba122b 100644
--- a/net/ipv4/tcp_timer.c
+++ b/net/ipv4/tcp_timer.c
@@ -286,10 +286,9 @@ static int tcp_write_timeout(struct sock *sk)
tcp_fastopen_active_detect_blackhole(sk, expired);
mptcp_active_detect_blackhole(sk, expired);
- if (BPF_SOCK_OPS_TEST_FLAG(tp, BPF_SOCK_OPS_RTO_CB_FLAG))
- tcp_call_bpf_3arg(sk, BPF_SOCK_OPS_RTO_CB,
- icsk->icsk_retransmits,
- icsk->icsk_rto, (int)expired);
+ tcp_call_bpf_flag(sk, BPF_SOCK_OPS_RTO_CB,
+ icsk->icsk_retransmits, icsk->icsk_rto, (int)expired);
+ bpf_tcp_ops_call(rto, sk);
if (expired) {
/* Has it gone just too far? */
diff --git a/net/sched/bpf_qdisc.c b/net/sched/bpf_qdisc.c
index 098ca02aed89..5691c13781a8 100644
--- a/net/sched/bpf_qdisc.c
+++ b/net/sched/bpf_qdisc.c
@@ -280,7 +280,6 @@ BTF_KFUNCS_START(qdisc_kfunc_ids)
BTF_ID_FLAGS(func, bpf_skb_get_hash)
BTF_ID_FLAGS(func, bpf_kfree_skb, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_qdisc_skb_drop, KF_RELEASE)
-BTF_ID_FLAGS(func, bpf_dynptr_from_skb)
BTF_ID_FLAGS(func, bpf_qdisc_watchdog_schedule)
BTF_ID_FLAGS(func, bpf_qdisc_init_prologue)
BTF_ID_FLAGS(func, bpf_qdisc_reset_destroy_epilogue)
@@ -290,7 +289,6 @@ BTF_KFUNCS_END(qdisc_kfunc_ids)
BTF_SET_START(qdisc_common_kfunc_set)
BTF_ID(func, bpf_skb_get_hash)
BTF_ID(func, bpf_kfree_skb)
-BTF_ID(func, bpf_dynptr_from_skb)
BTF_SET_END(qdisc_common_kfunc_set)
BTF_SET_START(qdisc_enqueue_kfunc_set)