From: Adriano Cordova <adrianox@gmail.com>
To: Simon Horman <horms@verge.net.au>, Julian Anastasov <ja@ssi.bg>
Cc: Pablo Neira Ayuso <pablo@netfilter.org>,
Florian Westphal <fw@strlen.de>, Phil Sutter <phil@nwl.cc>,
"David S . Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
Shuah Khan <shuah@kernel.org>,
netdev@vger.kernel.org, lvs-devel@vger.kernel.org,
netfilter-devel@vger.kernel.org,
Adriano Cordova <adrianox@gmail.com>
Subject: [PATCH 4/4] selftests: netfilter: ipvs: add per-service secure_tcp test
Date: Fri, 11 Sep 2026 22:32:16 -0300 [thread overview]
Message-ID: <20260912013216.588300-5-adrianox@gmail.com> (raw)
In-Reply-To: <20260912013216.588300-1-adrianox@gmail.com>
Two services share a VIP, one carrying IP_VS_SVC_F_SECURE_TCP.
A bare SYN+ACK suffices to test the state machine: the normal
service reaches ESTABLISHED, but the secure one stays in SYN_RECV.
Assisted-by: opencode
Signed-off-by: Adriano Cordova <adrianox@gmail.com>
---
.../testing/selftests/net/netfilter/Makefile | 6 +
.../selftests/net/netfilter/gen_tcp_probe.c | 127 +++++++
.../net/netfilter/ipvs_secure_tcp.sh | 158 +++++++++
.../net/netfilter/ipvs_secure_tcp_mln.c | 310 ++++++++++++++++++
4 files changed, 601 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_mln.c
diff --git a/tools/testing/selftests/net/netfilter/Makefile b/tools/testing/selftests/net/netfilter/Makefile
index f88dd4ef8d26..fad05afadd41 100644
--- a/tools/testing/selftests/net/netfilter/Makefile
+++ b/tools/testing/selftests/net/netfilter/Makefile
@@ -20,6 +20,7 @@ TEST_PROGS := \
conntrack_tcp_unreplied.sh \
conntrack_vrf.sh \
ipvs.sh \
+ ipvs_secure_tcp.sh \
nf_conntrack_packetdrill.sh \
nf_nat_edemux.sh \
nft_audit.sh \
@@ -50,6 +51,8 @@ TEST_GEN_FILES = \
connect_close \
conntrack_dump_flush \
conntrack_reverse_clash \
+ gen_tcp_probe \
+ ipvs_secure_tcp_mln \
nf_queue \
sctp_collision \
udpclash \
@@ -60,6 +63,9 @@ include ../../lib.mk
$(OUTPUT)/nf_queue: CFLAGS += $(MNL_CFLAGS)
$(OUTPUT)/nf_queue: LDLIBS += $(MNL_LDLIBS)
+$(OUTPUT)/ipvs_secure_tcp_mln: CFLAGS += $(MNL_CFLAGS)
+$(OUTPUT)/ipvs_secure_tcp_mln: LDLIBS += $(MNL_LDLIBS)
+
$(OUTPUT)/conntrack_dump_flush: CFLAGS += $(MNL_CFLAGS)
$(OUTPUT)/conntrack_dump_flush: LDLIBS += $(MNL_LDLIBS)
$(OUTPUT)/udpclash: LDLIBS += -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..d62bfdce70c0
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/gen_tcp_probe.c
@@ -0,0 +1,127 @@
+// 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)
+ *
+ * Requires CAP_NET_RAW.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <linux/if_ether.h>
+
+static inline uint16_t csump(const void *data, size_t len)
+{
+ const uint16_t *p = data;
+ uint32_t sum = 0;
+
+ while (len > 1) {
+ sum += *p++;
+ len -= 2;
+ }
+ if (len)
+ sum += *(const uint8_t *)p;
+ while (sum >> 16)
+ sum = (sum & 0xffff) + (sum >> 16);
+ return ~sum;
+}
+
+static void 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)
+{
+ uint8_t pkt[sizeof(struct iphdr) + sizeof(struct tcphdr)] = { 0 };
+ struct iphdr *ip = (struct iphdr *)pkt;
+ struct tcphdr *tcp = (struct tcphdr *)(pkt + sizeof(struct iphdr));
+ struct sockaddr_in dst;
+
+ ip->version = 4;
+ ip->ihl = 5;
+ ip->tot_len = htons(sizeof(pkt));
+ ip->id = htons((uint16_t)(seq & 0xffff));
+ ip->ttl = 64;
+ ip->protocol = IPPROTO_TCP;
+ ip->saddr = sip->s_addr;
+ ip->daddr = dip->s_addr;
+
+ tcp->source = sport;
+ tcp->dest = dport;
+ tcp->seq = htonl(seq);
+ tcp->ack_seq = htonl(seq + 1);
+ tcp->doff = 5;
+ if (syn)
+ tcp->syn = 1;
+ if (ack)
+ tcp->ack = 1;
+ tcp->window = htons(1024);
+
+ ip->check = csump(ip, sizeof(struct iphdr));
+ /* pseudo header for TCP checksum */
+ {
+ uint8_t ph[12];
+
+ memcpy(ph, &ip->saddr, 4);
+ memcpy(ph + 4, &ip->daddr, 4);
+ ph[8] = 0;
+ ph[9] = IPPROTO_TCP;
+ ph[10] = (sizeof(struct tcphdr) >> 8) & 0xff;
+ ph[11] = sizeof(struct tcphdr) & 0xff;
+
+ uint8_t tcpbuf[12 + sizeof(struct tcphdr)];
+
+ memcpy(tcpbuf, ph, 12);
+ memcpy(tcpbuf + 12, tcp, sizeof(struct tcphdr));
+ tcp->check = csump(tcpbuf, sizeof(tcpbuf));
+ }
+
+ memset(&dst, 0, sizeof(dst));
+ dst.sin_family = AF_INET;
+ dst.sin_addr = *dip;
+ dst.sin_port = dport;
+ sendto(fd, pkt, sizeof(pkt), 0, (struct sockaddr *)&dst,
+ sizeof(dst));
+}
+
+int main(int argc, char *argv[])
+{
+ struct in_addr sip, dip;
+ uint16_t sport, dport;
+ int fd, one = 1;
+ uint32_t seq = 0x12345678;
+
+ if (argc != 5) {
+ fprintf(stderr, "usage: %s <src_ip> <src_port> <dst_ip> <dst_port>\n",
+ argv[0]);
+ return 2;
+ }
+ inet_pton(AF_INET, argv[1], &sip);
+ sport = htons((uint16_t)atoi(argv[2]));
+ inet_pton(AF_INET, argv[3], &dip);
+ dport = htons((uint16_t)atoi(argv[4]));
+
+ fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
+ if (fd < 0) {
+ perror("raw socket");
+ return 1;
+ }
+ setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
+
+ send_seg(fd, &sip, sport, &dip, dport, seq, 1, 0);
+ usleep(100000);
+ send_seg(fd, &sip, sport, &dip, dport, seq + 1, 0, 1);
+
+ close(fd);
+ return 0;
+}
diff --git a/tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh b/tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh
new file mode 100755
index 000000000000..b079c0fe6b79
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/ipvs_secure_tcp.sh
@@ -0,0 +1,158 @@
+#!/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, nft, and the built helpers
+# ipvs_secure_tcp_mln and gen_tcp_probe.
+
+source lib.sh
+
+ret=0
+readonly vip="207.175.44.110"
+readonly gip="10.0.0.1"
+readonly dip="172.16.0.1"
+readonly rip="172.16.0.2"
+readonly cip="10.0.0.2"
+readonly sip="10.0.0.3"
+readonly port_secure=8081
+readonly port_plain=8080
+
+GREEN='\033[0;92m'
+RED='\033[0;31m'
+NC='\033[0m'
+
+checktool "ipvsadm -v" "run test without ipvsadm"
+checktool "nft --version" "run test without nft"
+
+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=1
+
+ 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
+
+ # load ipvs, then the rr scheduler (separate calls: modprobe treats
+ # the second name as a module parameter, not a second module)
+ ip netns exec "${ns1}" modprobe ip_vs
+ ip netns exec "${ns1}" modprobe ip_vs_rr
+
+ 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=$1
+ ip netns exec "${ns1}" ipvsadm -Lnc 2>/dev/null |
+ awk -v vt="${vip}:${vport}" '$5==vt { print $3; exit }'
+}
+
+assert_state() {
+ local port=$1 want=$2
+ local got
+ got="$(conn_state "$port")"
+ echo " vip ${vip}:${port}: state=${got:-?}"
+ if [ "${got:-}" != "$want" ]; then
+ echo -e "${RED}FAIL${NC}: vip ${vip}:${port} expected state" \
+ "${want}, got ${got:-none}"
+ ret=1
+ fi
+}
+
+test_secure() {
+ local bin probe
+
+ # Register the two services (secure_tcp on the secure port)
+ bin="$(pwd)/ipvs_secure_tcp_mln"
+ probe="$(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="$(ip netns exec "${ns1}" "$bin" get "${vip}" "${port_secure}")"
+ echo " secured service reports: ${got}"
+ echo "${got}" | grep -q "secure_tcp=1" ||
+ { echo -e "${RED}FAIL${NC}: flag not set"; ret=1; }
+ got="$(ip netns exec "${ns1}" "$bin" get "${vip}" "${port_plain}")"
+ echo "${got}" | grep -q "secure_tcp=0" ||
+ { echo -e "${RED}FAIL${NC}: flag unexpectedly set"; ret=1; }
+
+ # Drop any SYN on the real server so it stays silent (no RST that
+ # would interfere with the state-machine observation).
+ ip netns exec "${ns2}" nft add table inet filter
+ ip netns exec "${ns2}" nft add chain inet filter probe \
+ '{ type filter hook input priority 0; }'
+ ip netns exec "${ns2}" nft add rule inet filter probe \
+ tcp dport '{ '"${port_secure}"', '"${port_plain}"' }' drop
+
+ # Push SYN then ACK to each service from the client
+ ip netns exec "${ns0}" "$probe" "${cip}" 40000 "${vip}" "${port_secure}"
+ ip netns exec "${ns0}" "$probe" "${cip}" 40001 "${vip}" "${port_plain}"
+ 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..c15a3c28e6fe
--- /dev/null
+++ b/tools/testing/selftests/net/netfilter/ipvs_secure_tcp_mln.c
@@ -0,0 +1,310 @@
+// 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 <vip> <port> <secure|plain>
+ * Create a TCP virtual service (scheduler "rr") with the flag either
+ * set or not. Add real servers afterwards with:
+ * ipvsadm -a -t <vip>:<port> -r <rs>:<port>
+ * ipvs_secure_tcp_mln get <vip> <port>
+ * Print "secure_tcp=<0|1>" for the service.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <arpa/inet.h>
+
+#include <linux/netlink.h>
+#include <linux/genetlink.h>
+#include <linux/ip_vs.h>
+
+#include <libmnl/libmnl.h>
+
+/* 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 0x0040
+#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 = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, CTRL_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+ if (type == CTRL_ATTR_FAMILY_ID) {
+ if (mnl_attr_validate(attr, MNL_TYPE_U16) < 0)
+ return MNL_CB_ERROR;
+ tb[CTRL_ATTR_FAMILY_ID] = attr;
+ }
+ return MNL_CB_OK;
+}
+
+static int ctrl_data_cb(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlattr *tb[CTRL_ATTR_MAX + 1] = { 0 };
+ uint16_t *fam = data;
+
+ if (nlh->nlmsg_type != 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 = 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;
+
+ nl = mnl_socket_open(NETLINK_GENERIC);
+ if (!nl)
+ return -errno;
+ mnl_socket_bind(nl, 0, 0);
+
+ nlh = mnl_nlmsg_put_header(buf);
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = CTRL_CMD_GETFAMILY;
+ genl->version = 1;
+ nlh->nlmsg_type = GENL_ID_CTRL;
+ nlh->nlmsg_flags = 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 = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret < 0) {
+ if (errno == EAGAIN)
+ continue;
+ mnl_socket_close(nl);
+ return -errno;
+ }
+ ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl),
+ (mnl_cb_t)ctrl_data_cb, fam);
+ } while (ret > 0 && *fam == 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 = { 0 };
+ struct nlattr *nest;
+ struct ip_vs_flags fl;
+ int af = AF_INET;
+
+ if (inet_pton(af, vip, vaddr.all) != 1) {
+ fprintf(stderr, "bad VIP %s\n", vip);
+ return -EINVAL;
+ }
+
+ nest = 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 = IP_VS_SVC_F_SECURE_TCP;
+ if (secure)
+ fl.flags = 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 = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, IPVS_SVC_ATTR_MAX) < 0)
+ return MNL_CB_ERROR;
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static int get_cb(const struct nlmsghdr *nlh, void *data)
+{
+ const struct nlattr *tb[IPVS_SVC_ATTR_MAX + 1] = { 0 };
+ struct ip_vs_flags fl;
+ int *secure = data;
+ struct nlattr *nest;
+
+ mnl_attr_for_each(nest, nlh, sizeof(struct genlmsghdr)) {
+ if (mnl_attr_get_type(nest) == IPVS_CMD_ATTR_SERVICE)
+ mnl_attr_parse_nested(nest, (mnl_attr_cb_t)svc_attr_cb, tb);
+ }
+ if (tb[IPVS_SVC_ATTR_FLAGS]) {
+ memcpy(&fl, mnl_attr_get_payload(tb[IPVS_SVC_ATTR_FLAGS]),
+ sizeof(fl));
+ *secure = !!(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 = -1;
+
+ nl = mnl_socket_open(NETLINK_GENERIC);
+ mnl_socket_bind(nl, 0, 0);
+ nlh = mnl_nlmsg_put_header(buf);
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = IPVS_CMD_GET_SERVICE;
+ genl->version = IPVS_GENL_VERSION;
+ nlh->nlmsg_type = fam;
+ nlh->nlmsg_flags = NLM_F_REQUEST;
+ fill_service(nlh, vip, port, 0, 0);
+ send_cmd(nl, nlh);
+
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ while (ret >= 0) {
+ ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl),
+ (mnl_cb_t)get_cb, &secure);
+ if (ret <= MNL_CB_STOP || secure >= 0)
+ break;
+ ret = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ }
+ mnl_socket_close(nl);
+ if (secure < 0)
+ return -ENOENT;
+ printf("secure_tcp=%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 = mnl_nlmsg_put_header(buf);
+ genl = mnl_nlmsg_put_extra_header(nlh, sizeof(struct genlmsghdr));
+ genl->cmd = IPVS_CMD_NEW_SERVICE;
+ genl->version = IPVS_GENL_VERSION;
+ nlh->nlmsg_type = fam;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
+ fill_service(nlh, vip, port, 1, secure);
+
+ nl = mnl_socket_open(NETLINK_GENERIC);
+ mnl_socket_bind(nl, 0, 0);
+ 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 = mnl_socket_recvfrom(nl, buf, sizeof(buf));
+ if (ret <= 0)
+ break;
+ ret = mnl_cb_run(buf, ret, 0, mnl_socket_get_portid(nl),
+ NULL, NULL);
+ if (ret < 0) {
+ int e = errno;
+
+ fprintf(stderr, "IPVS netlink error: ret=%d errno=%d (%s)\n",
+ ret, e, strerror(e));
+ mnl_socket_close(nl);
+ return 1;
+ }
+ if (ret <= MNL_CB_STOP)
+ break;
+ }
+ mnl_socket_close(nl);
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ const char *cmd, *vip;
+ uint16_t fam;
+ uint16_t port;
+ int ret, secure = 0;
+
+ if (argc < 4) {
+ fprintf(stderr,
+ "usage: %s add <vip> <port> <secure|plain>\n"
+ " %s get <vip> <port>\n", argv[0], argv[0]);
+ return 2;
+ }
+ cmd = argv[1];
+ vip = argv[2];
+ port = (uint16_t)atoi(argv[3]);
+ port = htons(port);
+
+ ret = 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") == 0) {
+ if (argc < 5) {
+ fprintf(stderr, "usage: %s add ... <secure|plain>\n",
+ argv[0]);
+ return 2;
+ }
+ if (strcmp(argv[4], "secure") == 0) {
+ secure = 1;
+ } else if (strcmp(argv[4], "plain") != 0) {
+ fprintf(stderr, "unknown mode %s\n", argv[4]);
+ return 2;
+ }
+ return do_add(fam, vip, port, secure);
+ } else if (strcmp(cmd, "get") == 0) {
+ return do_get(fam, vip, port);
+ }
+
+ fprintf(stderr, "unknown command %s\n", cmd);
+ return 2;
+}
--
2.51.0
prev parent reply other threads:[~2026-09-12 1:32 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-12 1:32 [PATCH 0/4] ipvs: add per-service secure_tcp Adriano Cordova
2026-09-12 1:32 ` [PATCH 1/4] ipvs: add flags for per-service secure TCP state table Adriano Cordova
2026-09-12 1:32 ` [PATCH 2/4] ipvs: stamp per-service secure_tcp on new connections Adriano Cordova
2026-09-12 1:32 ` [PATCH 3/4] ipvs: tcp: enable per-connection secure_tcp in state machine Adriano Cordova
2026-09-12 1:32 ` Adriano Cordova [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260912013216.588300-5-adrianox@gmail.com \
--to=adrianox@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=fw@strlen.de \
--cc=horms@verge.net.au \
--cc=ja@ssi.bg \
--cc=kuba@kernel.org \
--cc=lvs-devel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=netfilter-devel@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=pablo@netfilter.org \
--cc=phil@nwl.cc \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox