root/tools/net/ynl/tests/rt-link.c
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <string.h>

#include <ynl.h>

#include <arpa/inet.h>
#include <net/if.h>

#include <kselftest_harness.h>

#include "rt-link-user.h"

static void rt_link_print(struct __test_metadata *_metadata,
                          struct rt_link_getlink_rsp *r)
{
        unsigned int i;

        EXPECT_TRUE((bool)r->_hdr.ifi_index);
        ksft_print_msg("%3d: ", r->_hdr.ifi_index);

        EXPECT_TRUE((bool)r->_len.ifname);
        if (r->_len.ifname)
                printf("%6s: ", r->ifname);

        if (r->_present.mtu)
                printf("mtu %5d  ", r->mtu);

        if (r->linkinfo._len.kind)
                printf("kind %-8s  ", r->linkinfo.kind);
        else
                printf("     %8s  ", "");

        if (r->prop_list._count.alt_ifname) {
                printf("altname ");
                for (i = 0; i < r->prop_list._count.alt_ifname; i++)
                        printf("%s ", r->prop_list.alt_ifname[i]->str);
                printf(" ");
        }

        if (r->linkinfo._present.data && r->linkinfo.data._present.netkit) {
                struct rt_link_linkinfo_netkit_attrs *netkit;
                const char *name;

                netkit = &r->linkinfo.data.netkit;
                printf("primary %d  ", netkit->primary);

                name = NULL;
                if (netkit->_present.policy)
                        name = rt_link_netkit_policy_str(netkit->policy);
                if (name)
                        printf("policy %s  ", name);
        }

        printf("\n");
}

static int netkit_create(struct ynl_sock *ys)
{
        struct rt_link_getlink_ntf *ntf_gl;
        struct rt_link_newlink_req *req;
        struct ynl_ntf_base_type *ntf;
        int ret;

        req = rt_link_newlink_req_alloc();
        if (!req)
                return -1;

        rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE | NLM_F_ECHO);
        rt_link_newlink_req_set_linkinfo_kind(req, "netkit");
        rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, NETKIT_DROP);

        ret = rt_link_newlink(ys, req);
        rt_link_newlink_req_free(req);
        if (ret)
                return -1;

        if (!ynl_has_ntf(ys))
                return 0;

        ntf = ynl_ntf_dequeue(ys);
        if (!ntf || ntf->cmd != RTM_NEWLINK) {
                ynl_ntf_free(ntf);
                return 0;
        }
        ntf_gl = (void *)ntf;
        ret = ntf_gl->obj._hdr.ifi_index;
        ynl_ntf_free(ntf);

        return ret;
}

static void netkit_delete(struct __test_metadata *_metadata,
                          struct ynl_sock *ys, int ifindex)
{
        struct rt_link_dellink_req *req;

        req = rt_link_dellink_req_alloc();
        ASSERT_NE(NULL, req);

        req->_hdr.ifi_index = ifindex;
        EXPECT_EQ(0, rt_link_dellink(ys, req));
        rt_link_dellink_req_free(req);
}

FIXTURE(rt_link)
{
        struct ynl_sock *ys;
};

FIXTURE_SETUP(rt_link)
{
        struct ynl_error yerr;

        self->ys = ynl_sock_create(&ynl_rt_link_family, &yerr);
        ASSERT_NE(NULL, self->ys) {
                TH_LOG("failed to create rt-link socket: %s", yerr.msg);
        }
}

FIXTURE_TEARDOWN(rt_link)
{
        ynl_sock_destroy(self->ys);
}

TEST_F(rt_link, dump)
{
        struct rt_link_getlink_req_dump *req;
        struct rt_link_getlink_list *rsp;

        req = rt_link_getlink_req_dump_alloc();
        ASSERT_NE(NULL, req);
        rsp = rt_link_getlink_dump(self->ys, req);
        rt_link_getlink_req_dump_free(req);
        ASSERT_NE(NULL, rsp) {
                TH_LOG("dump failed: %s", self->ys->err.msg);
        }
        ASSERT_FALSE(ynl_dump_empty(rsp));

        ynl_dump_foreach(rsp, link)
                rt_link_print(_metadata, link);

        rt_link_getlink_list_free(rsp);
}

TEST_F(rt_link, netkit)
{
        struct rt_link_getlink_req_dump *dreq;
        struct rt_link_getlink_list *rsp;
        bool found = false;
        int netkit_ifindex;

        /* Create netkit with valid policy */
        netkit_ifindex = netkit_create(self->ys);
        ASSERT_GT(netkit_ifindex, 0)
                TH_LOG("failed to create netkit: %s", self->ys->err.msg);

        /* Verify it appears in a dump */
        dreq = rt_link_getlink_req_dump_alloc();
        ASSERT_NE(NULL, dreq);
        rsp = rt_link_getlink_dump(self->ys, dreq);
        rt_link_getlink_req_dump_free(dreq);
        ASSERT_NE(NULL, rsp) {
                TH_LOG("dump failed: %s", self->ys->err.msg);
        }

        ynl_dump_foreach(rsp, link) {
                if (link->_hdr.ifi_index == netkit_ifindex) {
                        rt_link_print(_metadata, link);
                        found = true;
                }
        }
        rt_link_getlink_list_free(rsp);
        EXPECT_TRUE(found);

        netkit_delete(_metadata, self->ys, netkit_ifindex);
}

TEST_F(rt_link, netkit_err_msg)
{
        struct rt_link_newlink_req *req;
        int ret;

        /* Test creating netkit with bad policy - should fail */
        req = rt_link_newlink_req_alloc();
        ASSERT_NE(NULL, req);
        rt_link_newlink_req_set_nlflags(req, NLM_F_CREATE);
        rt_link_newlink_req_set_linkinfo_kind(req, "netkit");
        rt_link_newlink_req_set_linkinfo_data_netkit_policy(req, 10);

        ret = rt_link_newlink(self->ys, req);
        rt_link_newlink_req_free(req);
        EXPECT_NE(0, ret) {
                TH_LOG("creating netkit with bad policy should fail");
        }

        /* Expect:
         * Kernel error: 'Provided default xmit policy not supported' (bad attribute: .linkinfo.data(netkit).policy)
         */
        EXPECT_NE(NULL, strstr(self->ys->err.msg, "bad attribute: .linkinfo.data(netkit).policy")) {
                TH_LOG("expected extack msg not found: %s",
                       self->ys->err.msg);
        }
}

TEST_HARNESS_MAIN