From nobody Thu Sep 24 18:39:52 2026 Received: from mail-vk1-f178.google.com (mail-vk1-f178.google.com [209.85.221.178]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0C28D4E0B9D for ; Mon, 21 Sep 2026 20:57:15 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.178 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024238; cv=none; b=hjCSPPi8XKKGMc2SrmX+SK4bamNC/3QVOx4RDmGU7ni7wyFr/KcUjwaP5/1PZIm6OSuqLQimeBJ7AGTqZZpuLuIsE2KIRZYPmcKTzbxtmju3rMDamnDkuISDJR/6zH7pEpx+g061ND/EILPALHZY3J3Da9/ozMqjtYY0QIYRCHg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024238; c=relaxed/simple; bh=XwLRDqoGDPXb6K5PWgL1M7DdV69+VMAJJyWkcb0+NUo=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=U8qkMMw7rubpIDPzaT8fhrtnEe51wZFoiSyGsec8Xkcp1DBYkabPPORWLQKGZk26Dj/8OwtdiI2oVcNfrPfmZR/za3COqSfdlZMditkSg1jiDzjh4hpMZCw2DTauxNeEb4BafNKpD3R+td7UsUyBgTZl7FM+1EYHrptb4SDaO4Y= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=gctrSTTw; arc=none smtp.client-ip=209.85.221.178 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="gctrSTTw" Received: by mail-vk1-f178.google.com with SMTP id 71dfb90a1353d-5c675e64ffcso265225e0c.0 for ; Mon, 21 Sep 2026 13:57:15 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790024235; x=1790629035; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=83+plyF4HOp6Tc6yDaOIUjOh/yxbiFagCuYc/tYX0qs=; b=gctrSTTwh4KNldwSOKV4v9wn6xM3km2liI0bThL0e3MKs+L5sSxsYX6hiLihvTnXdC ku2HRaTnNhLChkKO6nZ19EJmAGOhmZVG7nv3aGERcadabr5OjfowsbVJJL2ZNL+IQdTA TZ4g4idRdhnJiCM6g3p0R/4z2WiO5RB8d+yp/M35TR7CcNQryP7AGd3W90riorE4KrtR YAUbyttQIbVzMSOSl6njNhujmrbYel19QJCVRf/n63I/vUlsrn5zrwB3LHnWF4d0ivff ldUJ8MFJd+qZmlerCs+7CyhKP9GCwN915N0WtyHfZQ1Yb03r02cY0BQs9S+JPSBWSrlW Xilw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790024235; x=1790629035; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=83+plyF4HOp6Tc6yDaOIUjOh/yxbiFagCuYc/tYX0qs=; b=ppIiXhuvmAagDeNtZFCPGok+izQNCyf3WqLzEjUo9Amtnsz7kneh+GhIr0z5DkOkbv vziIJJI/ZBWJiOmINJBBYFxyM8Z+pYKyMpX8MIdLNKnnWJ99Lzvyrpi+qgI1z5BjvMkY 8Ssfo+J5tP2Ymgnm+Qiy6x8MTBDAqqUTe6fDLXn441O0/9FX0yxgebmXVDKEons7/ndh rr2ySxGJ99INpVEfanPtzPfXLJhCdhBCQTOzdB3ZkMzGPtF1Pi4YrxNfFpiGUigRX09D DbueJ40krSjBgXvJg/NixbAr0xczbkX7DjXc1GUZJH29nvp003BI0RQlTSV5655RB+/9 /nDw== X-Forwarded-Encrypted: i=1; AKwUvBzJJAnMNvMg82BM5eQ+GULBrb4am1Caye2ziZ43XEgtQD9nWiEHUv83o+jNj21byfr7IBarMyNTZBAWqbg=@vger.kernel.org X-Gm-Message-State: AFuF++myXlMMYqXL2NCXApSB7rJv65InnLwThtYP7oHHPtUkUJP6tVKj +TaDnawKdg1Dv/v58uOCZ3+y1fIwzqEyucNLCyEF5lemp67/gQgYDG/p X-Gm-Gg: AYBFou32YhZ8PCSiEEW2N7Je/EzM4Q2OnVqR5TK8suYDydyvR+SsJRvx7RQ1D6YGVKO HVy/1VhFZp/DhFyE9WhSRMgu5DI1mMCJyknWthsbvO16lnYg54U3KbrJY0fk4jSTqIS5G86WxHS Ev/3qwjw3OET0xo+fTtNzzsvme5Oqr36tT3/aro4qDbH7lVueeVP/6J08tOFCg+Qh77ZbpKJ+gz kAoY4uwq8Pd8Z97XLL+80SGtTwhrWIoWyYoJIim2D7sMdBYfR+w8T+wv4kTM1xL8yCLh7XTLYqd 8u64zPUoNJh6ZyNOuwJyv2mpwz7G8Byqk26m3qYeD/JH+CrEBvx689VGn0dIXwT4mebCrHSCpIx 2Opby+CSGiVz+B4M/4ct1PzN2S2z2Ud1wrgP59gzLcEeI42seHbrhYTFpoixz+0KExec2r69zop ROncpZuwS79PDcTc1Sq1/iw7HTHB3GT1l2usSnkfDGkIi+FAnmL/rOPi+6Qx9gI+o= X-Received: by 2002:a67:ef8e:0:b0:7aa:d0a9:c5aa with SMTP id ada2fe7eead31-7aad0a9c60dmr280100137.18.1790024234730; Mon, 21 Sep 2026 13:57:14 -0700 (PDT) Received: from adriano ([190.215.95.120]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5c9e591d496sm286884e0c.2.2026.09.21.13.57.12 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Sep 2026 13:57:14 -0700 (PDT) From: Adriano Cordova To: Simon Horman , Julian Anastasov , Pablo Neira Ayuso Cc: Florian Westphal , netfilter-devel@vger.kernel.org, lvs-devel@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v5 nf-next 1/3] ipvs: add flags for per-service secure TCP state table Date: Mon, 21 Sep 2026 17:57:04 -0300 Message-ID: <20260921205706.1055288-2-adrianox@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260921205706.1055288-1-adrianox@gmail.com> References: <20260921205706.1055288-1-adrianox@gmail.com> 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" Add IP_VS_SVC_F_SECURE_TCP to mark a virtual service for the DoS-hardened TCP connection state table, and IP_VS_CONN_F_SECURE_TCP to carry that capability on a connection so the TCP state machine can select the hardened table for it. The service flag is 0x0100: bits 0x0008/0x0010/0x0020 are the scheduler bits, so leave 0x0040/0x0080 free for schedulers. Set IP_VS_CONN_F_SECURE_TCP on every connection bound to a destination whose service carries IP_VS_SVC_F_SECURE_TCP. Signed-off-by: Adriano Cordova --- Changes in v2: - Let IP_VS_SVC_F_SECURE_TCP be 0x0100, leaving 0x0040/0x0080 free for scheduler flags. - Let IP_VS_CONN_F_SECURE_TCP be (1 << 17) and drop it from IP_VS_CONN_F_BACKUP_MASK. - Set the flag in ip_vs_bind_dest() instead of at every ip_vs_conn_new() call site. Changes in v3: - Merge the uapi flag definition and the ip_vs_bind_dest() stamping into a single patch. (no changes since v3) include/uapi/linux/ip_vs.h | 2 ++ net/netfilter/ipvs/ip_vs_conn.c | 4 ++++ net/netfilter/ipvs/ip_vs_core.c | 3 +++ 3 files changed, 9 insertions(+) diff --git a/include/uapi/linux/ip_vs.h b/include/uapi/linux/ip_vs.h index 2c37c6ac7525..ade170109ff4 100644 --- a/include/uapi/linux/ip_vs.h +++ b/include/uapi/linux/ip_vs.h @@ -27,6 +27,7 @@ =20 #define IP_VS_SVC_F_SCHED_SH_FALLBACK IP_VS_SVC_F_SCHED1 /* SH fallback */ #define IP_VS_SVC_F_SCHED_SH_PORT IP_VS_SVC_F_SCHED2 /* SH use port */ +#define IP_VS_SVC_F_SECURE_TCP 0x0100 /* use the hardened TCP table */ =20 /* * IPVS sync daemon states @@ -105,6 +106,7 @@ =20 /* Flags that are not sent to backup server start from bit 16 */ #define IP_VS_CONN_F_NFCT (1 << 16) /* use netfilter conntrack */ +#define IP_VS_CONN_F_SECURE_TCP (1 << 17) /* use the hardened TCP table */ =20 /* Connection flags from destination that can be changed by user space */ #define IP_VS_CONN_F_DEST_MASK (IP_VS_CONN_F_FWD_MASK | \ diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_con= n.c index 6fa3e1dc534c..0b465298b64b 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1090,6 +1090,7 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_d= est *dest) { unsigned int conn_flags; __u32 flags; + struct ip_vs_service *svc; =20 /* if dest is NULL, then return directly */ if (!dest) @@ -1102,6 +1103,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_d= est *dest) if (cp->protocol !=3D IPPROTO_UDP) conn_flags &=3D ~IP_VS_CONN_F_ONE_PACKET; flags =3D cp->flags; + svc =3D rcu_dereference(dest->svc); + if (svc && (svc->flags & IP_VS_SVC_F_SECURE_TCP)) + flags |=3D IP_VS_CONN_F_SECURE_TCP; /* Bind with the destination and its corresponding transmitter */ if (flags & IP_VS_CONN_F_SYNC) { /* Synced conns are hashed, so they can not get this flag */ diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_cor= e.c index ba0957798bad..aa8a3a964ffd 100644 --- a/net/netfilter/ipvs/ip_vs_core.c +++ b/net/netfilter/ipvs/ip_vs_core.c @@ -784,6 +784,9 @@ int ip_vs_leave(struct ip_vs_service *svc, struct sk_bu= ff *skb, IP_VS_CONN_F_ONE_PACKET : 0; union nf_inet_addr daddr =3D { .all =3D { 0, 0, 0, 0 } }; =20 + if (svc->flags & IP_VS_SVC_F_SECURE_TCP) + flags |=3D IP_VS_CONN_F_SECURE_TCP; + /* create a new connection entry */ IP_VS_DBG(6, "%s(): create a cache_bypass entry\n", __func__); { --=20 2.51.0 From nobody Thu Sep 24 18:39:52 2026 Received: from mail-vs2-f12.google.com (mail-vs2-f12.google.com [74.125.227.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 584FE4E325C for ; Mon, 21 Sep 2026 20:57:19 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024241; cv=none; b=Fcx59LL+AZ84hAmtBr9vD4mupO7O/gC8AEswgoBi4st22KyU27hHl0+sGy8+/oT+J4+yG8p/2hueeZbkqA9c4f1jsLXqQ2tGAUYCDop8rJ4ad4sPzQqMD5alX6wO8EL7rLnLoxPokyBlN3l3uK2Ts4imC+xmFi7zLQlqQZykbas= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024241; c=relaxed/simple; bh=HvIdQDLYa1Lul0YF3dB9ewaICFXf/uHdEyk3gBqpQVA=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=SEpGDuGAbAfOaM8jGvJ8UGoL8myu48zfCsvf+e7kZ+8F/yDO2JKCIdL4AKZ9VlYaoy4TQ3xPtUc6d43ndvuQgDbhpCin32UBc2tpvvHHlvB8xoOpU/dMCZL/PuQSDVgQbEAUevY022BpwdLMR+xIc7vXYGGxSqqOBOjYUOPo05Q= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=EjBDGapi; arc=none smtp.client-ip=74.125.227.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="EjBDGapi" Received: by mail-vs2-f12.google.com with SMTP id 71dfb90a1353d-5c67e5292fdso1249039e0c.3 for ; Mon, 21 Sep 2026 13:57:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790024237; x=1790629037; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=AXTKrz4VGvcWU+Xa9prrE8tXzfiajVxX/35uOYLtN8Q=; b=EjBDGapilITtv4WiIJKuWO6iBAAOubOgrVtAinW7vEoX+PIvMQbvVHupQCWRNswyaZ d5U3948W/sM0IJ36TnZVzfq9BNhORAzmw/zHL7suJH+P0XuWVGhFARWMRDsocCl0Rrr/ UlLbmgOnAG8fLf0JtTERq0bYwZwHD927l5gUQ4r5y75xb72ft1BwPD55fOlmXfMVeNzs R1rpX7Kr5gxWGed4X41ShhcmkodOQOKCyE08a9uTaG6wb/yQ6q1aeV7aHcA0SvT8IkUA w+UZZKjij54a/D+FLjGNCXfq9Ri/1r6JHorlEBo4D2XyhO+m+DUwA/WsGNnWUx9HZPSY eUZg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790024237; x=1790629037; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=AXTKrz4VGvcWU+Xa9prrE8tXzfiajVxX/35uOYLtN8Q=; b=sgUTJ+FjCdVUO+aQLX4cbqIsLEvYem4HPO8rQAaPHGfDqmaXNVpwHp552Cr0foD75C dDbzYAa4hvy2bKcFwLYLGQlWSZKZgGmADLwlv3x46yLcQLlHQnCZ3N3dbzBAEK3RWkvs mj4kvV/hVbprHj9WewT6M4hxWcSGdKw2q7mxM0px5co2CrbJhZUQmA6otuhdctSp7LAP +O6oo1f6/JEf5e9hxHKAIB1yR48EsrUFil4+pkfGXkHpv2pnxmXS0eccg7evPpUJsM7w JIPx3QhjYU/eS+nt/meQK25lPQuupgfRx5Ykilb1t4Pq8QyNjSxjSPfr61161pcjEAmL N0xg== X-Forwarded-Encrypted: i=1; AKwUvBxlrtJ6ATxgVygNahaCJql4UckWbPZAxIO9EzZGna1m/VcR1kk/eNQHSjMljfpzcjfhE2Kj2RR3E/ENGCY=@vger.kernel.org X-Gm-Message-State: AFuF++kJRtIjWYNpw6RvE7vKDjFF8YBfBaK/2LuHBUCBWdzneEU5/vaE SyHe6oti+o1XKMugBBYy3aa7oa8M25OdBchgHA03KQSEOxjmI64LQRcp X-Gm-Gg: AYBFou3C6HnNoZhOS4/IcLwcOUnSFdIdgyJDw4RsNoR3K08V7zhXkf6mRd62+DOsvWD 0u8GwyBa7XdGWA9OPnuOBZc1JLB6RP4Dp+ICGyy33JOMGx68M/u8CL2r6+0vMUl82nSn8zeqIzX jn065lniujCjNJw/S/t94C+k6AtbuXr7QxEpJ/VDcaISMt2ijFMTs/w4U4dvovshCDRisau4Cs/ qfkydiBmSkv9MMMI3LpI6SMBhldS1ON5EQnaw/xNBRK5Sm6S67U0fELsx+RKNHkTCQNRJaYO6oU EQ9wpuY0tpNl71pHDSF13lULdxls+rTJtgeoOMVrkrOOUDHAi92YJNVdLvZVeAKw6MEpwDd/Mad C7fGr+9zHb9X4J92SJ/VXnfUsqFtur4hd1yhYOED20ivmOUkKeQ5T1U7pZgONpQ0+6EKy0Snh3c eh+s7KBTQlg012vY3bSMt7ivN/i18Xg3qtaoEXtHUTwz6FxIeKD5OVqTw5EWnP8WI= X-Received: by 2002:a05:6122:4b16:b0:5c9:a60b:e5d7 with SMTP id 71dfb90a1353d-5c9b5a2b125mr6458823e0c.17.1790024237459; Mon, 21 Sep 2026 13:57:17 -0700 (PDT) Received: from adriano ([190.215.95.120]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5c9e591d496sm286884e0c.2.2026.09.21.13.57.14 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Sep 2026 13:57:17 -0700 (PDT) From: Adriano Cordova To: Simon Horman , Julian Anastasov , Pablo Neira Ayuso Cc: Florian Westphal , netfilter-devel@vger.kernel.org, lvs-devel@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v5 nf-next 2/3] ipvs: tcp: enable per-connection secure_tcp in state machine Date: Mon, 21 Sep 2026 17:57:05 -0300 Message-ID: <20260921205706.1055288-3-adrianox@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260921205706.1055288-1-adrianox@gmail.com> References: <20260921205706.1055288-1-adrianox@gmail.com> 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" Let set_tcp_state select tcp_states_dos when the connection carries IP_VS_CONN_F_SECURE_TCP, otherwise keep using the global pd->tcp_state_table (the netns default / or if nomem). Signed-off-by: Adriano Cordova --- Changes in v5: - Document that secure_tcp is the netns-wide default and that a service can opt in to the hardened table with IP_VS_SVC_F_SECURE_TCP. Documentation/networking/ipvs-sysctl.rst | 4 ++++ net/netfilter/ipvs/ip_vs_proto_tcp.c | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/Documentation/networking/ipvs-sysctl.rst b/Documentation/netwo= rking/ipvs-sysctl.rst index fe36f4fcd3a0..5fc6afd2d39a 100644 --- a/Documentation/networking/ipvs-sysctl.rst +++ b/Documentation/networking/ipvs-sysctl.rst @@ -290,6 +290,10 @@ secure_tcp - INTEGER The value definition is the same as that of drop_entry and drop_packet. =20 + This is the network-namespace default. A virtual service can opt + in to the hardened table regardless of this setting by setting + IP_VS_SVC_F_SECURE_TCP (0x0100) in its service flags. + svc_lfactor - INTEGER Possible values: -8 (larger table) .. 8 (smaller table) =20 diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_v= s_proto_tcp.c index fec0e8b47b71..3b9a2c8e9a52 100644 --- a/net/netfilter/ipvs/ip_vs_proto_tcp.c +++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c @@ -451,11 +451,10 @@ static void tcp_timeout_change(struct ip_vs_proto_dat= a *pd, int flags) int on =3D (flags & 1); /* secure_tcp */ =20 /* - ** FIXME: change secure_tcp to independent sysctl var - ** or make it per-service or per-app because it is valid - ** for most if not for all of the applications. Something - ** like "capabilities" (flags) for each object. - */ + * This remains the netns-wide default / global floor (e.g. when + * memory pressure kicks in). Per-service hardening is now carried + * by IP_VS_CONN_F_SECURE_TCP on each connection (set_tcp_state). + */ pd->tcp_state_table =3D (on ? tcp_states_dos : tcp_states); } =20 @@ -479,6 +478,7 @@ set_tcp_state(struct ip_vs_proto_data *pd, struct ip_vs= _conn *cp, int state_idx; int new_state =3D IP_VS_TCP_S_CLOSE; int state_off =3D tcp_state_off[direction]; + const struct tcp_states_t *table; =20 /* * Update state offset to INPUT_ONLY if necessary @@ -496,8 +496,10 @@ set_tcp_state(struct ip_vs_proto_data *pd, struct ip_v= s_conn *cp, goto tcp_state_out; } =20 - new_state =3D - pd->tcp_state_table[state_off+state_idx].next_state[cp->state]; + table =3D pd->tcp_state_table; + if (cp->flags & IP_VS_CONN_F_SECURE_TCP) + table =3D tcp_states_dos; + new_state =3D table[state_off + state_idx].next_state[cp->state]; =20 tcp_state_out: if (new_state !=3D cp->state) { --=20 2.51.0 From nobody Thu Sep 24 18:39:52 2026 Received: from mail-vs2-f12.google.com (mail-vs2-f12.google.com [74.125.227.12]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E5A4A4E73BE for ; Mon, 21 Sep 2026 20:57:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.12 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024247; cv=none; b=OydIDoy/vgfe6AZW0mRVK3ot+43yov7Wh5wPD+I3L6glgfaYfOHxrEZ69iL/vcCx4e5OTAvqR6gdR5SKBEvYdsaE1f8LB1QRoLspOaRdWBJQHyOHPFdh8CCiir8eFcy81LFh0j2z7X5QW4dxALTKwhvsgqDj7Fs3dsv0fCwN0ww= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1790024247; c=relaxed/simple; bh=Ksghcm5X8LCN4vVxohqv4ljnWZLsedxMoepypuhBXFk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ox96Tsbfil6rEmUI2t36DeiXccW1pI6AaysKwAxPXrh8n9w92RE3H10vqStYiCPT02RSXNmbst0QkCN2F9+t+19CpGXz4krXVgDmeWwPkYejGVGO7YI4CIoHYYjC+V/hENkKYeQK9p7umcIDfidiw0liIoSmfYMNE4iuJ68lwTA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=JJ5AlytG; arc=none smtp.client-ip=74.125.227.12 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="JJ5AlytG" Received: by mail-vs2-f12.google.com with SMTP id 71dfb90a1353d-5c67e512ee6so1253031e0c.1 for ; Mon, 21 Sep 2026 13:57:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1790024241; x=1790629041; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=/HtUfIuegfaNudBUjGk4ZRNwRDJb7i8lilIXKpQlSYQ=; b=JJ5AlytGIuZCo/bCsA8+q/1HvKtuc73E4FVRN9bTObE6DIAnvoN7YZSnYCWCzbTBnz QgDU8LdYjMS9mB77hB2Sgw5xtwjlE2S1pxsYHQQE++lHXq4W3p5KpBsofWztq3RdbCs4 yb+dVan1Ir+bDosQIP/3eioLf5SSzWU7mBWU45oHj4P8fa/rQmcBHo3He2UNsR3r9sZD ET8p7bbfN0W9BVMa6O/Y81uuQx4yyhfuuzPvy23iDJ9jbJbpAtIQrFy2NzWeq8u0tcki YTHc9rQleN92R3IK3ZzsW2Fag4LLpkKG/xcqtKxEa9zA9M+2VVJgmy09cPnL9acPrmC+ 8wwQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1790024241; x=1790629041; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=/HtUfIuegfaNudBUjGk4ZRNwRDJb7i8lilIXKpQlSYQ=; b=Bj9JMTph3ThADKn4Wd+QAjcI+LHzOmbb9/wN+fL4napRODiMlIlr/NUy3D/Mu2htvY YRWl+zxs+C88daK9WYfRrHq3+VtFlQrn35QZcseZKo4+LZjYm5Y9B0ZOUJsbCCax46NB VN/k3cMabOncyzWK8yBLc/37WJUzJBEKPXONKh6Fn353Lc/n9iEAzOec4cDCgLcmZ6ef GTniFs9G5mU2b4oter+hj65mba5X0tplUrLiJj84mr9WsjkxIgFquZDJuOiUzYCYAcds PFe95lJyaR81AgtPbWenF4Rno8eP2AF4TWNxyRxTRrct6TxJdayTCTuokMLpfrSyyHlO RzqA== X-Forwarded-Encrypted: i=1; AKwUvByGv7f3+vZoDnlxdrmzwVhY36bIZMFIHRnHZJ2dp8IcmXytiie/U66HmA3U+7EQL2+VvJOc9xAAV7Ythvg=@vger.kernel.org X-Gm-Message-State: AFuF++nJJmW5kyNLVPAcFzsvgR9qbvthrTzZWnUMscl6k9e8acgb85u8 THDTdTvnf1LmFcuKqaHHvpPSAW/g55SFZdVCbag0/Pj6ZvMi9mk+ZvrA X-Gm-Gg: AYBFou20bKXc/Nn6Mh3mK8NzZoH5tS24jU28t/ItjxATW6AyzLvW/0MeLSYiySbChOC C3/yqIOTGVkLrGArn2lpuTC0nCyfauzP3R/DSlfmCpHGsjhkQnaeOqYn4/HSvZDSIhkJpgccwGo 0v0SR8c2tqa7u/sq1Fmxbb2Qbd3Y7wGoxWZEvz4tpWNOHEsbdOVSt2Bn36HKlhv5H2tx4Vp8TjO G8ubRK0XR3z1eu65hJBfdAvpGSQw6vo4Mo3obGROECQIU7GhrjqCyV3Cbl0rxfDwCwpy8n4NTlB tn2eNvx5qK/PeggquLj42YEQp4BTuGCmDWu86sKKKRRoZUK/fJQWwdSS/08TniID4n4qP6+ulO6 dqSKo7uio7xIlECHbVsivVNfZR94Suot/Xr3lu7fLBX+DrMdU+q6LM39d1sBXnXlCrP8R7Eevex ZJqFIzJaNoODvY2oMF1SP02UxAnkIVuK2Rk56KcwaapFfrg3OSxFmCd5B4iZcylFPrbw/svcl+t XE= X-Received: by 2002:a05:6122:4f99:b0:5c9:a60d:3277 with SMTP id 71dfb90a1353d-5c9b599e181mr7510938e0c.11.1790024240643; Mon, 21 Sep 2026 13:57:20 -0700 (PDT) Received: from adriano ([190.215.95.120]) by smtp.gmail.com with ESMTPSA id 71dfb90a1353d-5c9e591d496sm286884e0c.2.2026.09.21.13.57.17 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 21 Sep 2026 13:57:20 -0700 (PDT) From: Adriano Cordova To: Simon Horman , Julian Anastasov , Pablo Neira Ayuso Cc: Florian Westphal , netfilter-devel@vger.kernel.org, lvs-devel@vger.kernel.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v5 nf-next 3/3] selftests: netfilter: ipvs: add per-service secure_tcp test Date: Mon, 21 Sep 2026 17:57:06 -0300 Message-ID: <20260921205706.1055288-4-adrianox@gmail.com> X-Mailer: git-send-email 2.51.0 In-Reply-To: <20260921205706.1055288-1-adrianox@gmail.com> References: <20260921205706.1055288-1-adrianox@gmail.com> 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" Two services share a VIP, one carrying IP_VS_SVC_F_SECURE_TCP. A bare SYN followed by a bare ACK suffices to test the state machine: the normal service reaches ESTABLISHED, but the secure one stays in SYN_RECV. Assisted-by: opencode:deepseek-flash Signed-off-by: Adriano Cordova Reviewed-by: David Dull --- Changes in v2: - send probes with TTL=3D1 instead of an nft drop, and fix Sashiko comments. Changes in v4: - Check the return value of setsockopt(IP_HDRINCL), sendto() and mnl_socket_bind(), and fail do_add() when IPVS does not reply. - Check inet_pton(), initialize fam and the parsed addresses, and verify the flags attribute length before copying it (Sashiko). Changes in v5: - Reword the changelog: the probe sends a bare SYN followed by a bare ACK, not a single SYN+ACK. - Skip the test when the ip_vs or ip_vs_rr module is unavailable. - Check the probe exit status so a probe that dies after the SYN cannot leave the secure-side SYN_RECV assertion passing. .../testing/selftests/net/netfilter/Makefile | 6 + .../selftests/net/netfilter/gen_tcp_probe.c | 158 ++++++++ .../net/netfilter/ipvs_secure_tcp.sh | 169 +++++++++ .../net/netfilter/ipvs_secure_tcp_mln.c | 337 ++++++++++++++++++ 4 files changed, 670 insertions(+) create mode 100644 tools/testing/selftests/net/netfilter/gen_tcp_probe.c create mode 100755 tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh create mode 100644 tools/testing/selftests/net/netfilter/ipvs_secure_tcp_m= ln.c diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing= /selftests/net/netfilter/Makefile index df3c20c90f5d..f88bf69cc874 100644 --- a/tools/testing/selftests/net/netfilter/Makefile +++ b/tools/testing/selftests/net/netfilter/Makefile @@ -22,6 +22,7 @@ TEST_PROGS :=3D \ conntrack_tcp_unreplied.sh \ conntrack_vrf.sh \ ipvs.sh \ + ipvs_secure_tcp.sh \ nf_conntrack_packetdrill.sh \ nf_nat_edemux.sh \ nft_audit.sh \ @@ -52,6 +53,8 @@ TEST_GEN_FILES =3D \ connect_close \ conntrack_dump_flush \ conntrack_reverse_clash \ + gen_tcp_probe \ + ipvs_secure_tcp_mln \ nf_queue \ sctp_collision \ udpclash \ @@ -62,6 +65,9 @@ include ../../lib.mk $(OUTPUT)/nf_queue: CFLAGS +=3D $(MNL_CFLAGS) $(OUTPUT)/nf_queue: LDLIBS +=3D $(MNL_LDLIBS) =20 +$(OUTPUT)/ipvs_secure_tcp_mln: CFLAGS +=3D $(MNL_CFLAGS) +$(OUTPUT)/ipvs_secure_tcp_mln: LDLIBS +=3D $(MNL_LDLIBS) + $(OUTPUT)/conntrack_dump_flush: CFLAGS +=3D $(MNL_CFLAGS) $(OUTPUT)/conntrack_dump_flush: LDLIBS +=3D $(MNL_LDLIBS) $(OUTPUT)/udpclash: LDLIBS +=3D -lpthread diff --git a/tools/testing/selftests/net/netfilter/gen_tcp_probe.c b/tools/= testing/selftests/net/netfilter/gen_tcp_probe.c new file mode 100644 index 000000000000..185c3b1146dc --- /dev/null +++ b/tools/testing/selftests/net/netfilter/gen_tcp_probe.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Send a TCP SYN then a TCP ACK (no SYN-ACK, no data) to the VIP. + * IPVS's TCP state machine only inspects SYN/FIN/ACK/RST bits, so this + * exercises the INPUT-direction state transition: + * + * SYN: NONE -> SYN_RECV + * ACK: SYN_RECV -> ESTABLISHED (tcp_states, normal) + * SYN_RECV -> SYN_RECV (tcp_states_dos, secure_tcp) + * + * A TTL of 1 keeps the packets from reaching the real server: IPVS updates + * the connection state before forwarding, then the packet expires and an + * ICMP time-exceeded is sent back to us. + * + * Requires CAP_NET_RAW. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline uint16_t csump(const void *data, size_t len) +{ + const uint8_t *p =3D data; + uint32_t sum =3D 0; + + while (len > 1) { + uint16_t w; + + memcpy(&w, p, sizeof(w)); + sum +=3D w; + p +=3D 2; + len -=3D 2; + } + if (len) { + uint16_t w =3D 0; + + memcpy(&w, p, 1); + sum +=3D w; + } + while (sum >> 16) + sum =3D (sum & 0xffff) + (sum >> 16); + return ~sum; +} + +static int send_seg(int fd, const struct in_addr *sip, uint16_t sport, + const struct in_addr *dip, uint16_t dport, + uint32_t seq, int syn, int ack) +{ + struct { + struct iphdr ip; + struct tcphdr tcp; + } pkt =3D { }; + struct iphdr *ip =3D &pkt.ip; + struct tcphdr *tcp =3D &pkt.tcp; + struct sockaddr_in dst; + + ip->version =3D 4; + ip->ihl =3D 5; + ip->tot_len =3D htons(sizeof(pkt)); + ip->id =3D htons((uint16_t)(seq & 0xffff)); + ip->ttl =3D 1; + ip->protocol =3D IPPROTO_TCP; + ip->saddr =3D sip->s_addr; + ip->daddr =3D dip->s_addr; + + tcp->source =3D sport; + tcp->dest =3D dport; + tcp->seq =3D htonl(seq); + tcp->ack_seq =3D htonl(seq + 1); + tcp->doff =3D 5; + if (syn) + tcp->syn =3D 1; + if (ack) + tcp->ack =3D 1; + tcp->window =3D htons(1024); + + ip->check =3D csump(ip, sizeof(struct iphdr)); + /* pseudo header for TCP checksum */ + { + uint8_t ph[12]; + uint8_t tcpbuf[12 + sizeof(struct tcphdr)]; + + memcpy(ph, &ip->saddr, 4); + memcpy(ph + 4, &ip->daddr, 4); + ph[8] =3D 0; + ph[9] =3D IPPROTO_TCP; + ph[10] =3D (sizeof(struct tcphdr) >> 8) & 0xff; + ph[11] =3D sizeof(struct tcphdr) & 0xff; + + memcpy(tcpbuf, ph, 12); + memcpy(tcpbuf + 12, tcp, sizeof(struct tcphdr)); + tcp->check =3D csump(tcpbuf, sizeof(tcpbuf)); + } + + memset(&dst, 0, sizeof(dst)); + dst.sin_family =3D AF_INET; + dst.sin_addr =3D *dip; + dst.sin_port =3D dport; + if (sendto(fd, &pkt, sizeof(pkt), 0, (struct sockaddr *)&dst, + sizeof(dst)) < 0) { + perror("sendto"); + return -1; + } + return 0; +} + +int main(int argc, char *argv[]) +{ + struct in_addr sip =3D { }, dip =3D { }; + uint16_t sport, dport; + int fd, one =3D 1; + uint32_t seq =3D 0x12345678; + + if (argc !=3D 5) { + fprintf(stderr, "usage: %s \n", + argv[0]); + return 2; + } + if (inet_pton(AF_INET, argv[1], &sip) !=3D 1 || + inet_pton(AF_INET, argv[3], &dip) !=3D 1) { + fprintf(stderr, "bad address\n"); + return 2; + } + sport =3D htons((uint16_t)atoi(argv[2])); + dport =3D htons((uint16_t)atoi(argv[4])); + + fd =3D socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (fd < 0) { + perror("raw socket"); + return 1; + } + if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { + perror("setsockopt"); + close(fd); + return 1; + } + + if (send_seg(fd, &sip, sport, &dip, dport, seq, 1, 0) < 0) { + close(fd); + return 1; + } + usleep(100000); + if (send_seg(fd, &sip, sport, &dip, dport, seq + 1, 0, 1) < 0) { + close(fd); + return 1; + } + + close(fd); + return 0; +} diff --git a/tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh b/too= ls/testing/selftests/net/netfilter/ipvs_secure_tcp.sh new file mode 100755 index 000000000000..c1fda5a5a8a7 --- /dev/null +++ b/tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh @@ -0,0 +1,169 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Runtime test for per-service secure_tcp (IP_VS_SVC_F_SECURE_TCP). +# +# Sets up the same 3-namespace topology as ipvs.sh +# but checks the TCP state machine, not data forwarding. Two +# identical TCP services are added on the same VIP on different ports, +# one is marked secure_tcp, the other is not. For each a bare SYN is +# followed by a bare ACK (no SYN-ACK / no data). IPVS classifies the +# connection from the flag bits: +# * normal service: SYN -> SYN_RECV, ACK -> ESTABLISHED +# * secure_tcp service: SYN -> SYN_RECV, ACK -> SYN_RECV +# This test checks that this is the case via `ipvsadm -Lnc`. +# +# Requires root, netns, ipvsadm, and the built helpers +# ipvs_secure_tcp_mln and gen_tcp_probe. + +source lib.sh + +ret=3D0 +readonly vip=3D"207.175.44.110" +readonly gip=3D"10.0.0.1" +readonly dip=3D"172.16.0.1" +readonly rip=3D"172.16.0.2" +readonly cip=3D"10.0.0.2" +readonly sip=3D"10.0.0.3" +readonly port_secure=3D8081 +readonly port_plain=3D8080 + +GREEN=3D'\033[0;92m' +RED=3D'\033[0;31m' +NC=3D'\033[0m' + +checktool "ipvsadm -v" "run test without ipvsadm" + +if [ ! -d /proc/sys/net/ipv4/vs/ ]; then + if ! modprobe -q ip_vs; then + echo "skip: could not run test without ipvs module" + exit $ksft_skip + fi +fi +if ! modprobe -q ip_vs_rr; then + echo "skip: could not run test without ip_vs_rr module" + exit $ksft_skip +fi + +setup() { + setup_ns ns0 ns1 ns2 + + ip link add veth01 netns "${ns0}" type veth peer name veth10 netns "${ns1= }" + ip link add veth02 netns "${ns0}" type veth peer name veth20 netns "${ns2= }" + ip link add veth12 netns "${ns1}" type veth peer name veth21 netns "${ns2= }" + + ip netns exec "${ns0}" ip link set veth01 up + ip netns exec "${ns0}" ip link set veth02 up + ip netns exec "${ns0}" ip link add br0 type bridge + ip netns exec "${ns0}" ip link set veth01 master br0 + ip netns exec "${ns0}" ip link set veth02 master br0 + ip netns exec "${ns0}" ip link set br0 up + ip netns exec "${ns0}" ip addr add "${cip}/24" dev br0 + + ip netns exec "${ns1}" ip link set veth10 up + ip netns exec "${ns1}" ip addr add "${gip}/24" dev veth10 + ip netns exec "${ns1}" ip link set veth12 up + ip netns exec "${ns1}" ip addr add "${dip}/24" dev veth12 + ip netns exec "${ns1}" ip link set lo up + ip netns exec "${ns1}" ip addr add "${vip}/32" dev lo:1 + ip netns exec "${ns1}" sysctl -qw net.ipv4.ip_forward=3D1 + + ip netns exec "${ns2}" ip link set veth20 up + ip netns exec "${ns2}" ip addr add "${sip}/24" dev veth20 + ip netns exec "${ns2}" ip link set veth21 up + ip netns exec "${ns2}" ip addr add "${rip}/24" dev veth21 + + ip netns exec "${ns2}" ip addr add "${vip}/32" dev lo:1 + + ip netns exec "${ns0}" ip route add "${vip}/32" via "${gip}" dev br0 + + sleep 1 +} + +cleanup() { + cleanup_all_ns +} + +# State of the connection to the VIP:port, from `ipvsadm -Lnc`. +# Fields: pro expire state source virtual destination +conn_state() { + local vport=3D$1 + ip netns exec "${ns1}" ipvsadm -Lnc 2>/dev/null | + awk -v vt=3D"${vip}:${vport}" '$5=3D=3Dvt { print $3; exit }' +} + +assert_state() { + local port=3D$1 want=3D$2 + local got + got=3D"$(conn_state "$port")" + echo " vip ${vip}:${port}: state=3D${got:-?}" + if [ "${got:-}" !=3D "$want" ]; then + echo -e "${RED}FAIL${NC}: vip ${vip}:${port} expected state" \ + "${want}, got ${got:-none}" + ret=3D1 + fi +} + +test_secure() { + local bin probe + + # Register the two services (secure_tcp on the secure port) + bin=3D"$(pwd)/ipvs_secure_tcp_mln" + probe=3D"$(pwd)/gen_tcp_probe" + ip netns exec "${ns1}" "$bin" add "${vip}" "${port_secure}" secure + ip netns exec "${ns1}" "$bin" add "${vip}" "${port_plain}" plain + + # Add a real server to both services. Use NAT (-m): in DR the conn gets + # IP_VS_CONN_F_NOOUTPUT, which makes the client ACK an INPUT_ONLY event + # and even tcp_states_dos promotes to ESTABLISHED, hiding the difference. + ip netns exec "${ns1}" ipvsadm -a -m -t "${vip}:${port_secure}" -r "${rip= }:${port_secure}" + ip netns exec "${ns1}" ipvsadm -a -m -t "${vip}:${port_plain}" -r "${rip}= :${port_plain}" + + # verify the flag was actually set + local got + got=3D"$(ip netns exec "${ns1}" "$bin" get "${vip}" "${port_secure}")" + echo " secured service reports: ${got}" + echo "${got}" | grep -q "secure_tcp=3D1" || + { echo -e "${RED}FAIL${NC}: flag not set"; ret=3D1; } + got=3D"$(ip netns exec "${ns1}" "$bin" get "${vip}" "${port_plain}")" + echo "${got}" | grep -q "secure_tcp=3D0" || + { echo -e "${RED}FAIL${NC}: flag unexpectedly set"; ret=3D1; } + + # The probes carry TTL=3D1, so IPVS updates the connection state and + # then the packet expires before reaching the real server (which + # stays silent, no RST to interfere with the observation). + + # Push SYN then ACK to each service from the client. Both segments + # must be sent: a bare SYN already leaves the connection in SYN_RECV, + # so a probe that died after the SYN would not be caught otherwise. + if ! ip netns exec "${ns0}" "$probe" "${cip}" 40000 \ + "${vip}" "${port_secure}"; then + echo -e "${RED}FAIL${NC}: probe to ${vip}:${port_secure} failed" + ret=3D1 + fi + if ! ip netns exec "${ns0}" "$probe" "${cip}" 40001 \ + "${vip}" "${port_plain}"; then + echo -e "${RED}FAIL${NC}: probe to ${vip}:${port_plain} failed" + ret=3D1 + fi + sleep 1 + + echo "Testing per-service secure_tcp..." + echo " --- connection table (ipvsadm -Lnc) ---" + ip netns exec "${ns1}" ipvsadm -Lnc 2>/dev/null + echo " --- end connection table ---" + assert_state "${port_plain}" ESTABLISHED + assert_state "${port_secure}" SYN_RECV +} + +trap cleanup EXIT + +setup +test_secure + +if [ "$ret" -ne 0 ]; then + echo -e "$(basename $0): ${RED}FAIL${NC}" + exit 1 +fi +echo -e "$(basename $0): ${GREEN}PASS${NC}" +exit 0 diff --git a/tools/testing/selftests/net/netfilter/ipvs_secure_tcp_mln.c b/= tools/testing/selftests/net/netfilter/ipvs_secure_tcp_mln.c new file mode 100644 index 000000000000..54c6d860addf --- /dev/null +++ b/tools/testing/selftests/net/netfilter/ipvs_secure_tcp_mln.c @@ -0,0 +1,337 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * libmnl helper to set/query the per-service secure_tcp flag + * (IP_VS_SVC_F_SECURE_TCP), which ipvsadm does not expose. + * + * Usage: + * ipvs_secure_tcp_mln add + * Create a TCP virtual service (scheduler "rr") with the flag either + * set or not. Add real servers afterwards with: + * ipvsadm -a -t : -r : + * ipvs_secure_tcp_mln get + * Print "secure_tcp=3D<0|1>" for the service. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* Fallback in case the kernel's installed uapi header is older */ +#ifndef IP_VS_SVC_F_SECURE_TCP +#define IP_VS_SVC_F_SECURE_TCP 0x0100 +#endif + +/* 16-byte address storage, matching union nf_inet_addr for AF_INET */ +struct inet_addr16 { + uint8_t all[16]; +}; + +/* ---------------- family resolver ---------------- */ +static int ctrl_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb =3D data; + int type =3D mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0) + return MNL_CB_ERROR; + if (type =3D=3D CTRL_ATTR_FAMILY_ID) { + if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0) + return MNL_CB_ERROR; + tb[CTRL_ATTR_FAMILY_ID] =3D attr; + } + return MNL_CB_OK; +} + +static int ctrl_data_cb(const struct nlmsghdr *nlh, void *data) +{ + const struct nlattr *tb[CTRL_ATTR_MAX + 1] =3D { 0 }; + uint16_t *fam =3D data; + + if (nlh->nlmsg_type !=3D GENL_ID_CTRL) + return MNL_CB_OK; + mnl_attr_parse(nlh, sizeof(struct genlmsghdr), + (mnl_attr_cb_t)ctrl_attr_cb, tb); + if (tb[CTRL_ATTR_FAMILY_ID]) { + *fam =3D mnl_attr_get_u16(tb[CTRL_ATTR_FAMILY_ID]); + return MNL_CB_STOP; + } + return MNL_CB_OK; +} + +static int resolve_family(const char *name, uint16_t *fam) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + int ret; + + *fam =3D 0; + nl =3D mnl_socket_open(NETLINK_GENERIC); + if (!nl) + return -errno; + if (mnl_socket_bind(nl, 0, 0) < 0) { + mnl_socket_close(nl); + return -errno; + } + + nlh =3D mnl_nlmsg_put_header(buf); + genl =3D mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd =3D CTRL_CMD_GETFAMILY; + genl->version =3D 1; + nlh->nlmsg_type =3D GENL_ID_CTRL; + nlh->nlmsg_flags =3D NLM_F_REQUEST; + mnl_attr_put_strz(nlh, CTRL_ATTR_FAMILY_NAME, name); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + mnl_socket_close(nl); + return -errno; + } + do { + ret =3D mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret < 0) { + if (errno =3D=3D EAGAIN) + continue; + mnl_socket_close(nl); + return -errno; + } + ret =3D mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), + (mnl_cb_t)ctrl_data_cb, fam); + } while (ret > 0 && *fam =3D=3D 0); + + mnl_socket_close(nl); + return *fam ? 0 : -ENOENT; +} + +/* ---------------- fill service identifying attrs ---------------- */ +static int fill_service(struct nlmsghdr *nlh, const char *vip, + uint16_t port, int full, int secure) +{ + struct inet_addr16 vaddr =3D { 0 }; + struct nlattr *nest; + struct ip_vs_flags fl; + int af =3D AF_INET; + + if (inet_pton(af, vip, vaddr.all) !=3D 1) { + fprintf(stderr, "bad VIP %s\n", vip); + return -EINVAL; + } + + nest =3D mnl_attr_nest_start(nlh, IPVS_CMD_ATTR_SERVICE); + mnl_attr_put_u16(nlh, IPVS_SVC_ATTR_AF, af); + mnl_attr_put_u16(nlh, IPVS_SVC_ATTR_PROTOCOL, IPPROTO_TCP); + mnl_attr_put(nlh, IPVS_SVC_ATTR_ADDR, sizeof(vaddr), &vaddr); + /* port/be16: port is passed in network order from main() */ + mnl_attr_put_u16(nlh, IPVS_SVC_ATTR_PORT, port); + + if (full) { + mnl_attr_put_strz(nlh, IPVS_SVC_ATTR_SCHED_NAME, "rr"); + memset(&fl, 0, sizeof(fl)); + fl.mask =3D IP_VS_SVC_F_SECURE_TCP; + if (secure) + fl.flags =3D IP_VS_SVC_F_SECURE_TCP; + mnl_attr_put(nlh, IPVS_SVC_ATTR_FLAGS, sizeof(fl), &fl); + mnl_attr_put_u32(nlh, IPVS_SVC_ATTR_TIMEOUT, 0); + mnl_attr_put_u32(nlh, IPVS_SVC_ATTR_NETMASK, 0xffffffff); + } + mnl_attr_nest_end(nlh, nest); + return 0; +} + +static int send_cmd(struct mnl_socket *nl, struct nlmsghdr *nlh) +{ + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + perror("sendto"); + return -1; + } + return 0; +} + +/* ---------------- get secure flag ---------------- */ +static int svc_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb =3D data; + int type =3D mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, IPVS_SVC_ATTR_MAX) < 0) + return MNL_CB_ERROR; + tb[type] =3D attr; + return MNL_CB_OK; +} + +static int get_cb(const struct nlmsghdr *nlh, void *data) +{ + const struct nlattr *tb[IPVS_SVC_ATTR_MAX + 1] =3D { 0 }; + struct ip_vs_flags fl; + int *secure =3D data; + struct nlattr *nest; + + mnl_attr_for_each(nest, nlh, sizeof(struct genlmsghdr)) { + if (mnl_attr_get_type(nest) =3D=3D IPVS_CMD_ATTR_SERVICE) + mnl_attr_parse_nested(nest, (mnl_attr_cb_t)svc_attr_cb, tb); + } + if (tb[IPVS_SVC_ATTR_FLAGS] && + mnl_attr_get_payload_len(tb[IPVS_SVC_ATTR_FLAGS]) >=3D sizeof(fl)) { + memcpy(&fl, mnl_attr_get_payload(tb[IPVS_SVC_ATTR_FLAGS]), + sizeof(fl)); + *secure =3D !!(fl.flags & IP_VS_SVC_F_SECURE_TCP); + } + return MNL_CB_STOP; +} + +static int do_get(uint16_t fam, const char *vip, uint16_t port) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + int ret, secure =3D -1; + + nl =3D mnl_socket_open(NETLINK_GENERIC); + if (!nl) + return -errno; + if (mnl_socket_bind(nl, 0, 0) < 0) { + mnl_socket_close(nl); + return -errno; + } + nlh =3D mnl_nlmsg_put_header(buf); + genl =3D mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd =3D IPVS_CMD_GET_SERVICE; + genl->version =3D IPVS_GENL_VERSION; + nlh->nlmsg_type =3D fam; + nlh->nlmsg_flags =3D NLM_F_REQUEST; + ret =3D fill_service(nlh, vip, port, 0, 0); + if (ret < 0) { + mnl_socket_close(nl); + return ret; + } + if (send_cmd(nl, nlh) < 0) { + mnl_socket_close(nl); + return -1; + } + + ret =3D mnl_socket_recvfrom(nl, buf, sizeof(buf)); + while (ret >=3D 0) { + ret =3D mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), + (mnl_cb_t)get_cb, &secure); + if (ret <=3D MNL_CB_STOP || secure >=3D 0) + break; + ret =3D mnl_socket_recvfrom(nl, buf, sizeof(buf)); + } + mnl_socket_close(nl); + if (secure < 0) + return -ENOENT; + printf("secure_tcp=3D%d\n", secure); + return 0; +} + +/* ---------------- add service with flag ---------------- */ +static int do_add(uint16_t fam, const char *vip, uint16_t port, int secure) +{ + struct mnl_socket *nl; + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh; + struct genlmsghdr *genl; + int ret; + + /* NLM_F_EXCL: fail if the service already exists */ + nlh =3D mnl_nlmsg_put_header(buf); + genl =3D mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr)); + genl->cmd =3D IPVS_CMD_NEW_SERVICE; + genl->version =3D IPVS_GENL_VERSION; + nlh->nlmsg_type =3D fam; + nlh->nlmsg_flags =3D NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXC= L; + ret =3D fill_service(nlh, vip, port, 1, secure); + if (ret < 0) + return 1; + + nl =3D mnl_socket_open(NETLINK_GENERIC); + if (!nl) + return 1; + if (mnl_socket_bind(nl, 0, 0) < 0) { + mnl_socket_close(nl); + return 1; + } + if (send_cmd(nl, nlh) < 0) { + mnl_socket_close(nl); + return 1; + } + + /* Read the reply so we can report why a command may have failed */ + for (;;) { + ret =3D mnl_socket_recvfrom(nl, buf, sizeof(buf)); + if (ret <=3D 0) { + fprintf(stderr, "no reply from IPVS\n"); + mnl_socket_close(nl); + return 1; + } + ret =3D mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl), + NULL, NULL); + if (ret < 0) { + int e =3D errno; + + fprintf(stderr, "IPVS netlink error: ret=3D%d errno=3D%d (%s)\n", + ret, e, strerror(e)); + mnl_socket_close(nl); + return 1; + } + if (ret <=3D MNL_CB_STOP) + break; + } + mnl_socket_close(nl); + return 0; +} + +int main(int argc, char *argv[]) +{ + const char *cmd, *vip; + uint16_t fam =3D 0; + uint16_t port; + int ret, secure =3D 0; + + if (argc < 4) { + fprintf(stderr, + "usage: %s add \n" + " %s get \n", argv[0], argv[0]); + return 2; + } + cmd =3D argv[1]; + vip =3D argv[2]; + port =3D (uint16_t)atoi(argv[3]); + port =3D htons(port); + + ret =3D resolve_family(IPVS_GENL_NAME, &fam); + if (ret) { + fprintf(stderr, "cannot resolve IPVS genl family: %s\n", + strerror(-ret)); + return 1; + } + + if (strcmp(cmd, "add") =3D=3D 0) { + if (argc < 5) { + fprintf(stderr, "usage: %s add ... \n", + argv[0]); + return 2; + } + if (strcmp(argv[4], "secure") =3D=3D 0) { + secure =3D 1; + } else if (strcmp(argv[4], "plain") !=3D 0) { + fprintf(stderr, "unknown mode %s\n", argv[4]); + return 2; + } + return do_add(fam, vip, port, secure); + } else if (strcmp(cmd, "get") =3D=3D 0) { + return do_get(fam, vip, port); + } + + fprintf(stderr, "unknown command %s\n", cmd); + return 2; +} --=20 2.51.0