在VPP中,插件可以在程序启动的时候加载,一般我们会往里面加入node,实现一些功能。

首先介绍怎么直接重定义硬件接口RX到我们的node。

vnet_hw_interface_rx_redirect_to_node(vnet_main, hw_if_index,
        my_graph_node.index /* redirect to my_graph_node */);

这个接口主要是将指定的接口重定向到我们自己实现的node上。
具体的实现我们可以看sample-plugin,或者看以下简单的例子:
这是注册node的代码,里面主要是function、name和next_nodes。

//注册node
VLIB_REGISTER_NODE(my_node) = {
    .function = my_node_fn, //node功能函数
    .name = "zzx-test", //node名字,唯一
    .vector_size = 4,
    .format_trace = format_my_trace, //show trace显示
    .type = VLIB_NODE_TYPE_INTERNAL, //node类型

    .n_errors = ARRAY_LEN(my_error_strings),
    .error_strings = my_error_strings,

    .n_next_nodes = MY_N_NEXT,

    .next_nodes = {
        [MY_ETHERNET_INPUT] = "ethernet-input", //下一级node名字
    },
};

node功能如下,这里是拿到数据包,执行功能,设置下一级的node三个主要功能:

static uint64_t my_node_fn(vlib_main_t *vm,
        vlib_node_runtime_t *node, vlib_frame_t *frame)
{
    uint32_t *from = vlib_frame_vector_args(frame);
    uint32_t n_left_from = frame->n_vectors;
    my_next_t next_index = node->cached_next_index;
    uint32_t pkts_showed = 0;

    while(n_left_from > 0) {
        uint32_t *to_next;
        uint32_t n_left_to_next;
        vlib_get_next_frame(vm, node, next_index, to_next, n_left_to_next);

        /*
        * n_left_from表示vector拿到多少个数据包
        * n_left_to_next表示上次缓存的下一级node剩下的vector位置
        * 所以循环表示的是当前node还能拿到多少个包,
        * 下一级能接收多少包,以少的为准。有0的话就停止执行
        */
        while(n_left_from > 0 && n_left_to_next > 0) {
            uint32_t next0 = MY_ETHERNET_INPUT;
            uint32_t bi0 = from[0];
            to_next[0] = bi0;
            from += 1;
            to_next += 1;
            n_left_from -= 1;
            n_left_to_next -= 1;

            //拿到数据包
            vlib_buffer_t *b0 = vlib_get_buffer(vm, bi0);

            //当前node的主要功能是输出数据包内容到终端
            void *en0 = vlib_buffer_get_current(b0);
            show_packet(en0);

            //将数据包入队给下一级node
            pkts_showed += 1;
            vlib_validate_buffer_enqueue_x1(vm, node, next_index,
                    to_next, n_left_to_next, bi0, next0);
        }

        vlib_put_next_frame(vm, node, next_index, n_left_to_next);
    }

    //增加计数器,主要是加在show err命令了,vpp本身的node也是加在那
    vlib_node_increment_counter(vm, my_node.index, MY_ERROR_SHOW, pkts_showed);
    return frame->n_vectors;
}

以上是node.c的基本展示。

下面介绍下插件注册时需要的一些函数。
首先是注册初始化函数。

//我们没有什么特别的功能,所以初始化没有做任何事情
static clib_error_t *my_init(vlib_main_t *vm)
{
    return 0;
}

//此处是我们将初始化函数加载到vpp里面,以供程序运行的时候初始化
VLIB_INIT_FUNCTION(my_init);

接下来是必备函数,vpp在加载插件会调用这个函数,传递给我们一些必须的数据。

//vm里面记录了我们需要的大部分东西,好多接口里面都会调用
//vnet_main是我们连接其他node或者网络相关的数据
//这里因为运行的比较早,DPDK硬件初始化还没开始。
//所以我们在连接硬件接口RX的时候,这里不能做,这里最好就是获取一些基本的数据
clib_error_t *vlib_plugin_register(vlib_main_t *vm, vnet_plugin_handoff_t *h,
    int32_t from_early_init)
{
    my_main_t *mm = &my_main;

    mm->vlib_main = vm;
    mm->vnet_main = h->vnet_main;
    mm->ethernet_main = h->ethernet_main;

    return NULL;
}

因为如上说到硬件还没初始化,所以我们不能在这里直接去重定向硬件RX,但是其他地方也没有合适的接口被调用,所以我们考虑采用命令行去配置需要重定向的接口,命令行的支持如下:

//命令行注册,path表示的是命令,function是执行函数
VLIB_CLI_COMMAND(show_packet_command, static) = {
    .path = "show packet",
    .short_help = "show packet <interface name> [disable]",
    .function = my_command_enable_disable_fn,
};
static clib_error_t *my_command_enable_disable_fn(vlib_main_t *vm,
        unformat_input_t *input, vlib_cli_command_t *cmd)
{
    my_main_t *mm = &my_main;
    int32_t enable_disable = 1;
    uint32_t sw_if_index = UINT32_MAX;

    while(unformat_check_input(input) != UNFORMAT_END_OF_INPUT) {
        //判定是开还是关
        if(unformat(input, "disable"))
            enable_disable = 0;
        //获取需要重定向的RX
        else if(unformat(input, "%U", unformat_vnet_sw_interface,
                mm->vnet_main, &sw_if_index))
        ;
        else
            break;
    }

    if(sw_if_index == UINT32_MAX)
        return clib_error_return(0, "Please specify an interface...");

    int32_t rv = my_show_packet_enable_disable(mm, sw_if_index, enable_disable);

    switch(rv) {
        case 0:
            break;

        case VNET_API_ERROR_INVALID_SW_IF_INDEX:
            return clib_error_return(0, "Invalid interface, only works on physical ports");
            break;

        case VNET_API_ERROR_UNIMPLEMENTED:
            return clib_error_return(0, "Device driver doesn't support redirection");
            break;

        default:
            return clib_error_return(0, "%s returned %d", __FUNCTION__, rv);
    }
    return NULL;
}
int32_t my_show_packet_enable_disable(my_main_t *mm,
        uint32_t sw_if_index, int32_t enable_disable)
{
    //释放sw index
    if(pool_is_free_index(mm->vnet_main->interface_main.sw_interfaces, sw_if_index))
        return VNET_API_ERROR_INVALID_SW_IF_INDEX;

    //获取RX的结构
    vnet_sw_interface_t *sw = vnet_get_sw_interface(mm->vnet_main, sw_if_index);
    if(sw->type != VNET_SW_INTERFACE_TYPE_HARDWARE) {
        return VNET_API_ERROR_INVALID_SW_IF_INDEX;
    }

    //node_index是my_node.index时是重定向到我的node,否则~0表示关闭该RX的重定向
    uint32_t node_index = enable_disable ? my_node.index : UINT32_MAX;
    int32_t rv = vnet_hw_interface_rx_redirect_to_node(mm->vnet_main,
            sw_if_index, node_index);
    return rv;
}

以上是直接重定向RX的操作。

另外还有需要插入node连接其他node时候,VPP提供了几个常用接口:

ethernet_register_input_type (vm, ETHERNET_TYPE_ARP, arp_input_node.index);
//这个是ARP从ethernet-input node注册ARP协议的下一级node,
//还有ipv4等等也是这样注册,我们可以注册其他的协议
ip4_register_protocol (IP_PROTOCOL_UDP, udp4_input_node.index);
//这个是从ip-local node注册4层协议的下一级node,其他协议比如tcp也可以这样注册。
ip6_register_protocol (IP_PROTOCOL_L2TP, l2t_decap_node.index);
udp_register_dst_port (vm, UDP_DST_PORT_vxlan,
        vxlan_input_node.index, 1 /* is_ip4 */);
最后修改:2021 年 08 月 18 日
如果觉得我的文章对你有用,请随意赞赏