[Openvpn-devel] [PATCH ovpn net v2 2/2] selftests: ovpn: add rtnl link mode check
Ralf Lici <[email protected]> Wed, 29 Jul 2026 15:41:31 +0200
| Newsgroups | net.sourceforge.lists.openvpn-devel |
|---|---|
| Message-ID | <256a0f1828ffa78a4c1c002b5561812eb1d4635e.1785330764.git.ralf@mandelbit.com> |
Add a get_iface command to ovpn-cli that queries RTM_GETLINK and parses the ovpn rtnl linkinfo nest, including IFLA_OVPN_MODE. When called without an interface name, make get_iface issue a filtered RTM_GETLINK dump for ovpn links. Use the dump form in the main ovpn selftest to verify that each namespace reports exactly one ovpn interface with the expected name and mode: MP for the server and P2P for the clients. This exercises the rtnl link-info dump path used by ovpn_fill_info. Signed-off-by: Ralf Lici <[email protected]> --- New patch added in v2. tools/testing/selftests/net/ovpn/ovpn-cli.c | 157 +++++++++++++++++++- tools/testing/selftests/net/ovpn/test.sh | 48 +++++- 2 files changed, 200 insertions(+), 5 deletions(-) diff --git a/tools/testing/selftests/net/ovpn/ovpn-cli.c b/tools/testing/selftests/net/ovpn/ovpn-cli.c index f4effa7580c0..f633dcea2833 100644 --- a/tools/testing/selftests/net/ovpn/ovpn-cli.c +++ b/tools/testing/selftests/net/ovpn/ovpn-cli.c @@ -78,6 +78,7 @@ struct nl_ctx { enum ovpn_cmd { CMD_INVALID, CMD_NEW_IFACE, + CMD_GET_IFACE, CMD_DEL_IFACE, CMD_LISTEN, CMD_CONNECT, @@ -1417,6 +1418,143 @@ static int ovpn_new_iface(struct ovpn_ctx *ovpn) return ret; } +static const char *ovpn_mode_str(enum ovpn_mode mode) +{ + switch (mode) { + case OVPN_MODE_P2P: + return "P2P"; + case OVPN_MODE_MP: + return "MP"; + } + + return "unknown"; +} + +static int ovpn_handle_iface(struct nlmsghdr *msg, void *arg) +{ + struct nlattr *linkinfo[IFLA_INFO_MAX + 1]; + struct nlattr *data[IFLA_OVPN_MAX + 1]; + struct nlattr *attrs[IFLA_MAX + 1]; + const struct ifinfomsg *ifinfo; + bool dump = *(bool *)arg; + enum ovpn_mode mode; + const char *kind; + int ret; + + if (msg->nlmsg_type != RTM_NEWLINK) { + fprintf(stderr, "unexpected rtnl message type: %u\n", + msg->nlmsg_type); + return -EINVAL; + } + + if (msg->nlmsg_len < NLMSG_LENGTH(sizeof(*ifinfo))) { + fprintf(stderr, "truncated rtnl link message\n"); + return -EINVAL; + } + + ifinfo = NLMSG_DATA(msg); + ret = nla_parse(attrs, IFLA_MAX, (struct nlattr *)IFLA_RTA(ifinfo), + IFLA_PAYLOAD(msg), NULL); + if (ret < 0) { + fprintf(stderr, "cannot parse rtnl link attributes: %d\n", ret); + return ret; + } + + if (!attrs[IFLA_LINKINFO]) { + fprintf(stderr, "missing linkinfo for ifindex %d\n", + ifinfo->ifi_index); + return -EINVAL; + } + + ret = nla_parse(linkinfo, IFLA_INFO_MAX, + nla_data(attrs[IFLA_LINKINFO]), + nla_len(attrs[IFLA_LINKINFO]), NULL); + if (ret < 0) { + fprintf(stderr, "cannot parse linkinfo attributes: %d\n", ret); + return ret; + } + + if (!linkinfo[IFLA_INFO_KIND]) { + fprintf(stderr, "missing link kind for ifindex %d\n", + ifinfo->ifi_index); + return -EINVAL; + } + + kind = nla_get_string(linkinfo[IFLA_INFO_KIND]); + if (strcmp(kind, OVPN_FAMILY_NAME)) { + fprintf(stderr, "unexpected link kind: %s\n", kind); + return -EINVAL; + } + + if (!linkinfo[IFLA_INFO_DATA]) { + fprintf(stderr, "missing ovpn link data for ifindex %d\n", + ifinfo->ifi_index); + return -EINVAL; + } + + ret = nla_parse(data, IFLA_OVPN_MAX, + nla_data(linkinfo[IFLA_INFO_DATA]), + nla_len(linkinfo[IFLA_INFO_DATA]), NULL); + if (ret < 0) { + fprintf(stderr, "cannot parse ovpn link data: %d\n", ret); + return ret; + } + + if (!data[IFLA_OVPN_MODE]) { + fprintf(stderr, "missing ovpn mode for ifindex %d\n", + ifinfo->ifi_index); + return -EINVAL; + } + + mode = nla_get_u8(data[IFLA_OVPN_MODE]); + fprintf(stdout, "ifindex %d\n", ifinfo->ifi_index); + if (attrs[IFLA_IFNAME]) + fprintf(stdout, "ifname %s\n", + nla_get_string(attrs[IFLA_IFNAME])); + fprintf(stdout, "kind %s\n", kind); + fprintf(stdout, "mode %s\n", ovpn_mode_str(mode)); + + return dump; +} + +static int ovpn_get_iface(struct ovpn_ctx *ovpn) +{ + uint32_t ext_filter_mask = RTEXT_FILTER_SKIP_STATS; + struct ovpn_link_req req = { 0 }; + bool dump = !ovpn->ifindex; + struct rtattr *linkinfo; + + req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.i)); + req.n.nlmsg_flags = NLM_F_REQUEST; + if (dump) + req.n.nlmsg_flags |= NLM_F_DUMP; + req.n.nlmsg_type = RTM_GETLINK; + + /* don't include stats */ + if (ovpn_addattr(&req.n, sizeof(req), IFLA_EXT_MASK, + &ext_filter_mask, sizeof(ext_filter_mask)) < 0) + return -1; + + /* if no iface was provided as argument, dump only the ovpn ifaces */ + if (dump) { + linkinfo = ovpn_nest_start(&req.n, sizeof(req), IFLA_LINKINFO); + if (!linkinfo) + return -1; + + if (ovpn_addattr(&req.n, sizeof(req), IFLA_INFO_KIND, + OVPN_FAMILY_NAME, + strlen(OVPN_FAMILY_NAME) + 1) < 0) + return -1; + + ovpn_nest_end(&req.n, linkinfo); + } + + req.i.ifi_family = AF_PACKET; + req.i.ifi_index = ovpn->ifindex; + + return ovpn_rt_send(&req.n, 0, 0, ovpn_handle_iface, &dump); +} + static int ovpn_del_iface(struct ovpn_ctx *ovpn) { struct ovpn_link_req req = { 0 }; @@ -1672,6 +1810,11 @@ static void usage(const char *cmd) fprintf(stderr, "\t\t- P2P for peer-to-peer mode (i.e. client)\n"); fprintf(stderr, "\t\t- MP for multi-peer mode (i.e. server)\n"); + fprintf(stderr, + "* get_iface [iface]: dump ovpn interface attributes\n"); + fprintf(stderr, + "\tiface: optional ovpn interface name; omit it to dump all ovpn interfaces in the current netns\n"); + fprintf(stderr, "* del_iface <iface>: delete ovpn interface\n"); fprintf(stderr, "\tiface: ovpn interface name\n"); @@ -1926,6 +2069,9 @@ static enum ovpn_cmd ovpn_parse_cmd(const char *cmd) if (!strcmp(cmd, "new_iface")) return CMD_NEW_IFACE; + if (!strcmp(cmd, "get_iface")) + return CMD_GET_IFACE; + if (!strcmp(cmd, "del_iface")) return CMD_DEL_IFACE; @@ -1994,6 +2140,9 @@ static int ovpn_run_cmd(struct ovpn_ctx *ovpn) case CMD_NEW_IFACE: ret = ovpn_new_iface(ovpn); break; + case CMD_GET_IFACE: + ret = ovpn_get_iface(ovpn); + break; case CMD_DEL_IFACE: ret = ovpn_del_iface(ovpn); break; @@ -2170,8 +2319,9 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) { int ret; - /* no args required for LISTEN_MCAST */ - if (ovpn->cmd == CMD_LISTEN_MCAST) + /* no args required for LISTEN_MCAST or GET_IFACE in dump mode */ + if (ovpn->cmd == CMD_LISTEN_MCAST || + (ovpn->cmd == CMD_GET_IFACE && argc == 2)) return 0; /* all commands need an ifname */ @@ -2181,7 +2331,7 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) strscpy(ovpn->ifname, argv[2], IFNAMSIZ - 1); ovpn->ifname[IFNAMSIZ - 1] = '\0'; - /* all commands, except NEW_IFNAME, needs an ifindex */ + /* all commands, except CMD_NEW_IFACE, needs an ifindex */ if (ovpn->cmd != CMD_NEW_IFACE) { ovpn->ifindex = if_nametoindex(ovpn->ifname); if (!ovpn->ifindex) { @@ -2207,6 +2357,7 @@ static int ovpn_parse_cmd_args(struct ovpn_ctx *ovpn, int argc, char *argv[]) } ovpn->mode_set = true; break; + case CMD_GET_IFACE: case CMD_DEL_IFACE: break; case CMD_LISTEN: diff --git a/tools/testing/selftests/net/ovpn/test.sh b/tools/testing/selftests/net/ovpn/test.sh index 9b5610837032..acd4aed0d254 100755 --- a/tools/testing/selftests/net/ovpn/test.sh +++ b/tools/testing/selftests/net/ovpn/test.sh @@ -56,6 +56,49 @@ ovpn_prepare_network() { done } +ovpn_check_iface() { + local expected + local peer_ns + local actual + local ifname + local count + local mode + local dump + local p + + for p in $(seq 0 ${OVPN_NUM_PEERS}); do + peer_ns="ovpn_peer${p}" + ifname="tun${p}" + mode="P2P" + [ "${p}" -eq 0 ] && mode="MP" + + if ! dump=$(ip netns exec "${peer_ns}" ${OVPN_CLI} get_iface); + then + printf 'failed to dump ovpn ifaces in %s\n' "${peer_ns}" + return 1 + fi + + # ensure exactly 1 iface is reported + count=$(printf '%s\n' "${dump}" | grep -c '^ifindex ' || true) + if [ "${count}" -ne 1 ]; then + printf 'unexpected iface count (%d) in %s\n' \ + "${count}" "${peer_ns}" + return 1 + fi + + # validate the stable interface attributes + printf -v expected 'ifname %s\nmode %s' "${ifname}" "${mode}" + actual=$(printf '%s\n' "${dump}" | + sed -n -e '/^ifname /p' -e '/^mode /p') + if [[ "${actual}" != "${expected}" ]]; then + printf 'unexpected ovpn interface in %s:\n' "${peer_ns}" + printf 'expected:\n%s\nactual:\n%s\n' \ + "${expected}" "${actual}" + return 1 + fi + done +} + ovpn_run_basic_traffic() { local p local header1 @@ -293,15 +336,16 @@ trap ovpn_stage_err ERR ktap_print_header if [ "${OVPN_FLOAT}" == "1" ]; then - ktap_set_plan 13 + ktap_set_plan 14 else - ktap_set_plan 12 + ktap_set_plan 13 fi ovpn_cleanup modprobe -q ovpn || true ovpn_run_stage "setup network topology" ovpn_prepare_network +ovpn_run_stage "validate iface modes" ovpn_check_iface ovpn_run_stage "run baseline data traffic" ovpn_run_basic_traffic ovpn_run_stage "run LAN traffic behind peer1" ovpn_run_lan_traffic [ "${OVPN_FLOAT}" == "1" ] && ovpn_run_stage "run floating peer checks" \ -- 2.55.0 _______________________________________________ Openvpn-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/openvpn-devel