From nobody Sat Feb 7 23:10:54 2026 Received: from cvsmtppost03.nmdf.navercorp.com (cvsmtppost03.nmdf.navercorp.com [114.111.35.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 19F691E1DE5 for ; Tue, 6 Jan 2026 09:26:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=114.111.35.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767691577; cv=none; b=WdHo6JrtwFojJ4r10tsJnRgc1tcD6+Rbh4EvKDApu7emsM5sxgby20s0cKswCbFjZhGAsLSrl+T+V/g0c3o1NtuT9Re0C3+zJ1YY3dnt066oejsBTnTdpTqPKmtlA8nQA6rs/AxKNMECsXT7WEKxxe+Lr10N0/ee1U04Vr1s+BU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1767691577; c=relaxed/simple; bh=KYkpwmu9D4exjDep8uFp3ZmzOeScxgUISmJMrTeTv1E=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=J/IvAEHRe/62hLz57fTsA5QKJ8mbxcr8neCZ6xK7IJF93ucSohQe3ekVrmfb4qRxlfUXaJ7wTX24pracheIqyZp4coqmO+/fGr9eMfYXaqIYAnfI52mFxP7ex3i10BmNFsOTPlEUhn/yCw+cHpwG6asF+ZZg1DcTCyzB/cezUCU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=navercorp.com; spf=pass smtp.mailfrom=navercorp.com; dkim=pass (2048-bit key) header.d=navercorp.com header.i=@navercorp.com header.b=IwvcCzSV; arc=none smtp.client-ip=114.111.35.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=navercorp.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=navercorp.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=navercorp.com header.i=@navercorp.com header.b="IwvcCzSV" Received: from cvsendbo04.nmdf ([10.112.20.54]) by cvsmtppost03.nmdf.navercorp.com with ESMTP id nstW3FqXTaaLAB4AzP6WXQ for ; Tue, 06 Jan 2026 08:55:50 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=navercorp.com; s=s20171120; t=1767689750; bh=KYkpwmu9D4exjDep8uFp3ZmzOeScxgUISmJMrTeTv1E=; h=From:To:Subject:Date:Message-Id:From:Subject:Feedback-ID: X-Works-Security; b=IwvcCzSVCY6oOQcusuNLM2RrMBY+0iTMdFz6vurQ+CxInbwPLJdMSd0COuFjItfUd z7FShhFAycyN3dGTGqBBpzHxPjgsl7w7/wXvvVtDUDkrGlz71EOIOd+PH9PRQPT0sC YC5riUDxD8W5rbL41CBzl0iJGSwBZW/CUqrj3hjDf1uT91ulHjb8W4jXKnuhE+GO+8 P8top89amzIDlkFJtHkEqDsK0I+LxAd+ZnnJEOQTFqkfv5fCczmyLmRs0NbUCmfWL5 XPswNeRFoOKp1ZZSU8Pan1cnnK6ETxl75ZdQxV+N0j02kuXAnQfmaYZ2fhQ+Q09/pe 5XhxDNXU4N34w== X-Session-ID: gcC4629NSVussM3wz1c+gQ X-Works-Send-Opt: xQbwjAiYjHm2KHwYjHmlUVg= X-Works-Smtp-Source: gZY9FobrFqJZ+HmmFxKX+6E= Received: from localhost.localdomain ([10.25.143.76]) by cvnsmtp02.nmdf.navercorp.com with ESMTP id gcC4629NSVussM3wz1c+gQ for (version=TLSv1.3 cipher=TLS_CHACHA20_POLY1305_SHA256); Tue, 06 Jan 2026 08:55:50 -0000 From: gyutae.opensource@navercorp.com To: Quentin Monnet , bpf@vger.kernel.org Cc: linux-kernel@vger.kernel.org, Alexei Starovoitov , Daniel Borkmann , Andrii Nakryiko , Martin KaFai Lau , Eduard Zingerman , Song Liu , Yonghong Song , John Fastabend , KP Singh , Stanislav Fomichev , Hao Luo , Jiri Olsa , Gyutae Bae , Siwan Kim , Daniel Xu , Jiayuan Chen , Tao Chen , Kumar Kartikeya Dwivedi Subject: [PATCH v2] bpftool: Add 'prepend' option for tcx attach to insert at chain start Date: Tue, 6 Jan 2026 17:55:27 +0900 Message-Id: <20260106085527.4774-1-gyutae.opensource@navercorp.com> X-Mailer: git-send-email 2.39.5 (Apple Git-154) In-Reply-To: <43c23468-530b-45f3-af22-f03484e5148c@kernel.org> References: <43c23468-530b-45f3-af22-f03484e5148c@kernel.org> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: Gyutae Bae Add support for the 'prepend' option when attaching tcx_ingress and tcx_egress programs. This option allows inserting a BPF program at the beginning of the TCX chain instead of appending it at the end. The implementation queries the first program ID in the chain and uses BPF_F_BEFORE flag with the relative_id to insert the new program before the existing first program. If the chain is empty, the program is simply attached normally. This change includes: - Add get_first_tcx_prog_id() helper to retrieve the first program ID - Modify do_attach_tcx() to support prepend insertion using BPF_F_BEFORE - Update documentation to describe the new 'prepend' option - Add bash completion support for the 'prepend' option on tcx attach types - Add example usage in the documentation The 'prepend' option is only valid for tcx_ingress and tcx_egress attach types. For XDP attach types, the existing 'overwrite' option remains available. Example usage: # bpftool net attach tcx_ingress name tc_prog dev lo prepend This feature is useful when the order of program execution in the TCX chain matters and users need to ensure certain programs run first. Co-developed-by: Siwan Kim Signed-off-by: Siwan Kim Signed-off-by: Gyutae Bae --- Hi Quentin. Apologies for the delay in getting back to you. I had to sort out some configuration issues with my mail system to ensure proper delivery. I really appreciate your detailed review and feedback on the first version. I have incorporated your suggestions in this v2 patch. Changes in v2: - Renamed 'head' to 'prepend' for consistency with 'overwrite' (Quentin) - Moved relative_id variable to relevant scope inside if block (Quentin) - Changed condition style from '=3D=3D 0' to '!' (Quentin) - Updated documentation to clarify 'overwrite' is XDP-only (Quentin) - Removed outdated "only XDP-related modes are supported" note (Quentin) - Removed extra help text from do_help() for consistency (Quentin) --- .../bpf/bpftool/Documentation/bpftool-net.rst | 30 +++++++++--- tools/bpf/bpftool/bash-completion/bpftool | 9 +++- tools/bpf/bpftool/net.c | 47 +++++++++++++++++-- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/tools/bpf/bpftool/Documentation/bpftool-net.rst b/tools/bpf/bp= ftool/Documentation/bpftool-net.rst index a9ed8992800f..22da07087e42 100644 --- a/tools/bpf/bpftool/Documentation/bpftool-net.rst +++ b/tools/bpf/bpftool/Documentation/bpftool-net.rst @@ -24,7 +24,7 @@ NET COMMANDS =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D | **bpftool** **net** { **show** | **list** } [ **dev** *NAME* ] -| **bpftool** **net attach** *ATTACH_TYPE* *PROG* **dev** *NAME* [ **overw= rite** ] +| **bpftool** **net attach** *ATTACH_TYPE* *PROG* **dev** *NAME* [ **overw= rite** | **prepend** ] | **bpftool** **net detach** *ATTACH_TYPE* **dev** *NAME* | **bpftool** **net help** | @@ -58,11 +58,9 @@ bpftool net { show | list } [ dev *NAME* ] then all bpf programs attached to non clsact qdiscs, and finally all b= pf programs attached to root and clsact qdisc. -bpftool net attach *ATTACH_TYPE* *PROG* dev *NAME* [ overwrite ] +bpftool net attach *ATTACH_TYPE* *PROG* dev *NAME* [ overwrite | prepend ] Attach bpf program *PROG* to network interface *NAME* with type specif= ied - by *ATTACH_TYPE*. Previously attached bpf program can be replaced by t= he - command used with **overwrite** option. Currently, only XDP-related mo= des - are supported for *ATTACH_TYPE*. + by *ATTACH_TYPE*. *ATTACH_TYPE* can be of: **xdp** - try native XDP and fallback to generic XDP if NIC driver doe= s not support it; @@ -72,11 +70,18 @@ bpftool net attach *ATTACH_TYPE* *PROG* dev *NAME* [ ov= erwrite ] **tcx_ingress** - Ingress TCX. runs on ingress net traffic; **tcx_egress** - Egress TCX. runs on egress net traffic; + For XDP-related attach types (**xdp**, **xdpgeneric**, **xdpdrv**, + **xdpoffload**), the **overwrite** option can be used to replace a + previously attached bpf program. + + For **tcx_ingress** and **tcx_egress** attach types, the **prepend** o= ption + can be used to attach the program at the beginning of the chain instea= d of + at the end. + bpftool net detach *ATTACH_TYPE* dev *NAME* Detach bpf program attached to network interface *NAME* with type spec= ified by *ATTACH_TYPE*. To detach bpf program, same *ATTACH_TYPE* previously= used - for attach must be specified. Currently, only XDP-related modes are - supported for *ATTACH_TYPE*. + for attach must be specified. bpftool net help Print short help message. @@ -191,6 +196,17 @@ EXAMPLES tc: lo(1) tcx/ingress tc_prog prog_id 29 +| +| **# bpftool net attach tcx_ingress name tc_prog2 dev lo prepend** +| **# bpftool net** +| + +:: + + tc: + lo(1) tcx/ingress tc_prog2 prog_id 30 + lo(1) tcx/ingress tc_prog prog_id 29 + | | **# bpftool net attach tcx_ingress name tc_prog dev lo** | **# bpftool net detach tcx_ingress dev lo** diff --git a/tools/bpf/bpftool/bash-completion/bpftool b/tools/bpf/bpftool/= bash-completion/bpftool index 53bcfeb1a76e..a28f0cc522e4 100644 --- a/tools/bpf/bpftool/bash-completion/bpftool +++ b/tools/bpf/bpftool/bash-completion/bpftool @@ -1142,7 +1142,14 @@ _bpftool() return 0 ;; 8) - _bpftool_once_attr 'overwrite' + case ${words[3]} in + tcx_ingress|tcx_egress) + _bpftool_once_attr 'prepend' + ;; + *) + _bpftool_once_attr 'overwrite' + ;; + esac return 0 ;; esac diff --git a/tools/bpf/bpftool/net.c b/tools/bpf/bpftool/net.c index cfc6f944f7c3..7935a6ba1491 100644 --- a/tools/bpf/bpftool/net.c +++ b/tools/bpf/bpftool/net.c @@ -637,6 +637,25 @@ static int net_parse_dev(int *argc, char ***argv) return ifindex; } +static int get_first_tcx_prog_id(int ifindex, enum bpf_attach_type type, _= _u32 *first_id) +{ + int ret; + + LIBBPF_OPTS(bpf_prog_query_opts, optq); + __u32 prog_ids[1] =3D {}; + + optq.prog_ids =3D prog_ids; + optq.count =3D ARRAY_SIZE(prog_ids); + + ret =3D bpf_prog_query_opts(ifindex, type, &optq); + + if (ret =3D=3D 0 && optq.count > 0) { + *first_id =3D prog_ids[0]; + return 0; + } + return -1; +} + static int do_attach_detach_xdp(int progfd, enum net_attach_type attach_ty= pe, int ifindex, bool overwrite) { @@ -666,10 +685,21 @@ static int get_tcx_type(enum net_attach_type attach_t= ype) } } -static int do_attach_tcx(int progfd, enum net_attach_type attach_type, int= ifindex) +static int do_attach_tcx(int progfd, enum net_attach_type attach_type, int= ifindex, bool prepend) { int type =3D get_tcx_type(attach_type); + if (prepend) { + __u32 relative_id; + + if (!get_first_tcx_prog_id(ifindex, type, &relative_id)) { + LIBBPF_OPTS(bpf_prog_attach_opts, opts, + .flags =3D BPF_F_BEFORE | BPF_F_ID, + .relative_id =3D relative_id + ); + return bpf_prog_attach_opts(progfd, ifindex, type, &opts); + } + } return bpf_prog_attach(progfd, ifindex, type, 0); } @@ -685,6 +715,7 @@ static int do_attach(int argc, char **argv) enum net_attach_type attach_type; int progfd, ifindex, err =3D 0; bool overwrite =3D false; + bool prepend =3D false; /* parse attach args */ if (!REQ_ARGS(5)) @@ -710,8 +741,16 @@ static int do_attach(int argc, char **argv) if (argc) { if (is_prefix(*argv, "overwrite")) { overwrite =3D true; + } else if (is_prefix(*argv, "prepend")) { + if (attach_type !=3D NET_ATTACH_TYPE_TCX_INGRESS && + attach_type !=3D NET_ATTACH_TYPE_TCX_EGRESS) { + p_err("'prepend' is only supported for tcx_ingress/tcx_egress"); + err =3D -EINVAL; + goto cleanup; + } + prepend =3D true; } else { - p_err("expected 'overwrite', got: '%s'?", *argv); + p_err("expected 'overwrite' or 'prepend', got: '%s'?", *argv); err =3D -EINVAL; goto cleanup; } @@ -728,7 +767,7 @@ static int do_attach(int argc, char **argv) /* attach tcx prog */ case NET_ATTACH_TYPE_TCX_INGRESS: case NET_ATTACH_TYPE_TCX_EGRESS: - err =3D do_attach_tcx(progfd, attach_type, ifindex); + err =3D do_attach_tcx(progfd, attach_type, ifindex, prepend); break; default: break; @@ -985,7 +1024,7 @@ static int do_help(int argc, char **argv) fprintf(stderr, "Usage: %1$s %2$s { show | list } [dev ]\n" - " %1$s %2$s attach ATTACH_TYPE PROG dev [ overwrite ]\n" + " %1$s %2$s attach ATTACH_TYPE PROG dev [ overwrite | pr= epend ]\n" " %1$s %2$s detach ATTACH_TYPE dev \n" " %1$s %2$s help\n" "\n" -- 2.39.5 (Apple Git-154)