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

Reply via email to