鲲鹏社区首页
中文
注册
开发者
我要评分
获取效率
正确性
完整性
易理解
在线提单
论坛求助

控制面开发详细步骤

开发前准备

  1. 开发者可以在自定义代码工程下创建OVS控制面开发目录。
    示例如下,详情请参见自定义目录定制代码
    ovs_project/
    ├── CMakeLists.txt                   # 顶层CMake配置文件
    ├── src_control_plane                # 用户OVS控制面源代码目录
        ├── CMakeLists.txt               # OVS控制面CMake配置文件
        └── ...                          # OVS控制面源代码
    └── src_dsl                          # 用户数据面源代码子目录
        ├── CMakeLists.txt               # 数据面的子目录CMake配置文件
        └── ovs_project.hdr              # 数据面Hydra源文件
  2. 复制固件编译生成的hydra_info.h头文件到 src_control_plane下。
    详情请参见执行固件编译
    cd {$project_path}/ovs_project
    cp src_dsl/build/hydra_info.h src_control_plane/
  3. 在代码中引入编程框架提供的OVS控制面头文件,包括hook.h和libapi.h。
    复制“flexda_sdk/include/control_plane/”中的hook.h和libapi.h到src_control_plane下。
    cd {$sdk_path}/flexda_sdk/
    cp include/control_plane/* {$project_path}/ovs_project/src_control_plane/

步骤一:开发OVS控制面代码

进行OVS控制面源代码的开发,通过代码和libapi.h中提供的接口,实现hook.h中定义的hook函数。

全局初始化和反初始化hook函数为可选实现,其他hook函数为必选实现。源代码中如未提供实现其他hook函数,编译安装产物后会导致OVS启动不成功。

  1. 开发代码前根据业务场景以及Hydra数据面代码,识别需要下发硬件流表的报文类型以及对应的识别特征。接着从Hydra数据面代码和hydra_info.h中提取每种报文类型应该下发流表到哪张表(table)、在表中匹配哪些字段(key)、需要对流表行为(action)做哪些修改。

    以处理入向VLAN流量,执行VLAN POP和内层RSS action的场景为例:

    Hydra数据面代码:

    #include "hydra_model.hdr"
    #include "hydra_parser.hdr"
    #include "hydra_externs.hdr"
    #include "hydra_builtin.hdr"
    #define VPORT_TYPE_BOND 3
     
    control MyMainPipe(in hdr_fix_headers hdr) {
        rte_vlan_hdr_t vlan_key;
        bit<16> inner_eth;
        @main
        table vlan_exact {
            key = {
                vlan_key         : exact;
                inner_eth        : exact;
            }
            actions = {
                rte_of_pop_vlan;
                rte_port_id;
                flexda_inner_rss;
            }
            size = 0x100000;
            default_action = flexda_no_action;
            aging_time = 0;
        }
        apply {
            if ((hdr_input_port_id_get() >> 12) == VPORT_TYPE_BOND) { // 判断报文为RX
                if (hdr.vlan.isValid()) { // 判断报文为VLAN RX
                    if (hdr.ethernet.isValid()) {
                        inner_eth = hdr.ethernet.eth_type; // 提取VLAN类型
                    }
                    vlan_key.vlan_tci = (bit<16>)((((bit<16>)hdr.vlan.pri) << 13) | ((bit<16>)hdr.vlan.vid)); // 提取VLAN id
                    if (vlan_exact.apply().miss) { // 匹配流表
                        hdr_ovs_pipeline_action_upcall();
                    }
                } 
            }
        }
    }
     
    control MyPostPipe(in hdr_fix_headers hdr) {
        apply {}
    }
     
    Switch(
            TX_OVS(hdr_fix_parser(),MyMainPipe(),MyPostPipe()),
            RX_OVS(hdr_fix_parser(),MyMainPipe(),MyPostPipe())
    ) main;

    Hydra数据面生成的hydra_info.h:

    ... //一些hydra结构体定义
    enum hydra_group_type { // table id
      HYDRA_TABLE_INVALID = 0x0000,
      HYDRA_TABLE_MYMAINPIPE_VLAN_EXACT,
    };
    enum hydra_default_action_type { // hydra默认action类型枚举
      DEFAULT_FLOW_ACTION_TYPE_UPCALL = 359,
      DEFAULT_FLOW_ACTION_TYPE_CT = 357,
      DEFAULT_FLOW_ACTION_TYPE_INNER_RSS = 364,
    };
    enum hydra_action_type { // hydra自定义action类型枚举
      HYDRA_ACTION_INVALID = 0x1000,
    };
    enum hydra_item_type { // hydra自定义key类型枚举
      HYDRA_ITEM_INVALID = 0x2000,
      HYDRA_ITEM_MYMAINPIPE_VLAN_EXACT_INNER_ETH,
    };
    int table_patterns_mymainpipe_vlan_exact [2] = { //table包含的key类型
      RTE_FLOW_ITEM_TYPE_VLAN,
      HYDRA_ITEM_MYMAINPIPE_VLAN_EXACT_INNER_ETH,
    };
    int table_actions_mymainpipe_vlan_exact [4] = { //table包含的action类型
      RTE_FLOW_ACTION_TYPE_COUNT,
      RTE_FLOW_ACTION_TYPE_OF_POP_VLAN,
      RTE_FLOW_ACTION_TYPE_PORT_ID,
      DEFAULT_FLOW_ACTION_TYPE_INNER_RSS,
    };
    ... //

    Hydra数据面对VLAN RX流量处理时从报文头中提取VLAN类型(inner_eth)以及VLAN ID作为key,在vlan_exact精确表中匹配,可执行action包括rte_of_pop_vlan、rte_port_id和flexda_inner_rss。

    hydra_info.h中定义了:

    • vlan_exact 表枚举值:HYDRA_TABLE_MYMAINPIPE_VLAN_EXACT
    • inner_eth key枚举值:HYDRA_ITEM_MYMAINPIPE_INNER_ETH_TYPE
    • vlan_key key枚举值:RTE_FLOW_ITEM_TYPE_VLAN

    VLAN RX流量会先命中OVS带有RTE_FLOW_ACTION_TYPE_OF_POP_VLAN & RTE_FLOW_ACTION_TYPE_PORT_ID action的软件流表,组装硬件流表时需添加软件流表不包含的DEFAULT_FLOW_ACTION_TYPE_INNER_RSS action。

    可将以上分析归纳为下表:
    表1 控制面代码开发前分析示例

    报文类型

    识别特征

    table

    key

    action

    VLAN RX

    OVS流表action包含VLAN POP

    HYDRA_TABLE_MYMAINPIPE_VLAN_EXACT

    RTE_FLOW_ITEM_TYPE_VLAN

    添加DEFAULT_FLOW_ACTION_TYPE_INNER_RSS

    HYDRA_ITEM_MYMAINPIPE_INNER_ETH_TYPE

  2. 根据需要,定义OVS运行时全局变量。并在初始化/反初始化hook函数的实现中处理全局变量的初始化/反初始化。
    flexda_custom_api_t g_api = {0};
    /*... 其他全局变量 ...*/
    int flexda_custom_init(flexda_custom_api_t api)
    {
        g_api = api;
        /*... 其他全局变量初始化 ...*/
        return 0;
    }
    int flexda_custom_deinit(void)
    {
        /*... 其他全局变量反初始化 ...*/
        return 0;
    }
  3. 根据业务场景需要,定义OVS控制面自定义报文上下文(同报文在不同流表卸载流程中拥有同一份报文上下文),并实现自定义报文上下文初始化/反初始化hook函数。
    自定义报文上下文中最少应保存:报文类型标记、各类型报文的原始报文头信息。
    // 参考以下示例代码,展示处理单层VLAN RX场景时如何自定义报文上下文。
    struct custom_packet_ctx {
        // 报文场景标志位  
        uint8_t vlan_poped;  // VLAN RX报文的标志位
        /* ... 其他标志位 ...*/
    
        // 报文头信息
        struct flexda_sub_hdr out_header;  // 该报文头信息保存外层的l4\l3\l2报文头信息
        /* ... 其他报文头信息 ...*/
    
        // 其他业务需要的报文上下文
        /* ... 其他上下文 ... */ 
    };
    
    int flexda_custom_init_ctx(void **ctx_addr)
    {
        // 为自定义报文上下文申请内存
        struct custom_packet_ctx *ctx = (struct custom_packet_ctx *)flexda_calloc(1, sizeof(struct custom_packet_ctx));
        if (ctx == NULL) {
            return -EINVAL;
        }
        *ctx_addr = (void *)ctx;
        ctx->vlan_poped = 0;
        /* ... 其他上下文初始化赋值 ...*/
        return 0;
    }
    
    int flexda_custom_deinit_ctx(void **ctx_addr)
    {
        if (ctx_addr == NULL || *ctx_addr == NULL) {
            return -EINVAL;
        }
        struct custom_packet_ctx *ctx = (struct custom_packet_ctx *)*ctx_addr;
        // 为自定义报文上下文释放内存
        flexda_free(ctx);
        *ctx_addr = NULL;
        return 0;
    }
  4. 根据业务场景,在更新上下文和提取报文头的hook函数实现中,用代码指示更新上下文和提取报文头的逻辑。
    • flexda_custom_update_ctx最少应实现:读取入参info判断报文类型,并将报文类型写入ctx。
    • flexda_custom_extract_hdr最少应实现:读取入参ctx判断报文类型,并该类型报文的原始报文头信息解析并写入在ctx。
      // 参考以下示例代码,当前卸载的OVS流表action中包含VLAN_POP时判定报文场景为VLAN RX,vlan_poped置为1
      int flexda_custom_update_ctx(void *ctx, flexda_custom_info_t info)
      {
          struct custom_packet_ctx *custom_ctx = (struct custom_packet_ctx *)ctx;
          struct rte_flow_action *actions = info.src_actions;
          for (int i = 0; i < 15; i++) {
              switch (actions[i]->type) {
                  case RTE_FLOW_ACTION_TYPE_OF_POP_VLAN:
                      custom_ctx->vlan_poped = 1;
                      break;
                  /*** 其他场景更新上下文标志位逻辑 ***/
                  default:
                      break;
              }
          }
          /*** 其他场景更新上下文逻辑 ***/
          return 0;
      }
      
      // 参考以下示例代码,提取报文头时若vlan_poped为1则提取VLAN RX报文组key需要的报文头信息。
      int flexda_custom_extract_hdr(void *ctx, flexda_custom_info_t info)
      {
          struct custom_packet_ctx *custom_ctx = (struct custom_packet_ctx *)ctx;
          struct flexda_sub_hdr *out_header = &custom_ctx->out_header;  // 框架提供的默认报文头信息结构体,搭配报文解析api使用
          struct dp_packet *pkt = info.pkt;
          struct flexda_extract_ctx pkt_parser;  // 框架提供的默认报文解析结构体,搭配报文解析api使用
          pkt_parser.pkt_itr = (uint8_t *)flexda_dp_packet_data(pkt);
          pkt_parser.pkt_end = (uint8_t *)flexda_dp_packet_data(pkt) + flexda_dp_packet_size(pkt);
          if (custom_ctx->vlan_poped == 1) { 
              // 调用框架提供的默认报文头解析API,将报文头信息解析保存在报文自定义上下文中
              if (!flexda_extract_hdr(&pkt_parser, out_header)) { // 解析不成功
                      return -EINVAL;
              }
              return 0; // 解析成功
          }
          /*** 其他场景提取报文头信息逻辑 ***/
          return false;// 标志位不为1
      }
  5. 根据业务场景和Hydra代码实现的数据面逻辑,实现对应匹配的key/action/attribute构造hook函数。
    • flexda_custom_construct_attr最少应实现:读取入参ctx判断报文类型以及对应的原始报文头信息,将报文类型对应的表id(参考1识别结果)写入attr->group。
    • flexda_custom_construct_key最少应实现:读取入参ctx判断报文类型以及对应的原始报文头信息,调用接口将报文类型所有匹配字段(参考1识别结果)写入参数hw_key。
    • flexda_custom_construct_action最少应实现:读取入参ctx判断报文类型以及对应的原始报文头信息,调用接口将对参数hw_action中的流表行为做添加删除或转换(参考1识别结果)。
    • flexda_custom_construct_attr中指定table时,使用hydra_info.h中的枚举值enum hydra_group_type,见Group、Action、Item枚举
    • flexda_custom_construct_key中插入key时,在hydra_info.h中找到对应table的key,见表Key信息;根据Hydra代码定义的匹配逻辑,调用对应接口插入需要的key,key类型使用表Key信息中的枚举值。
    • flexda_custom_construct_action中插入action时,在hydra_info.h中找到对应table支持的action列表,见表Action讯息;按业务需要插入或转换流表hydra action,action类型使用表Action讯息中的枚举值,action data参考HydraAction讯息中对应的结构体进行组装。
    //注:此处调用时attr的group值默认为1
    // 参考以下示例代码:当处理VLAN RX报文时(vlan_poped为1),下发到Hydra数据面定义的VLAN精确流表(HYDRA_TABLE_MYMAINPIPE_VLAN_EXACT)。
    int flexda_custom_construct_attr(void *ctx, flexda_custom_info_t info, struct rte_flow_attr *attr)
    {
        struct custom_packet_ctx *custom_ctx = (struct custom_packet_ctx *)ctx;
        // 根据自定义上下文,将Hydra table的枚举值赋给attr中的group来指定下发的table
        if (custom_ctx->vlan_poped == 1) {
          attr->group = HYDRA_TABLE_MYMAINPIPE_VLAN_EXACT;
        }
        /*** 其他场景指定下发table ***/
        return 0;
    }
    
    // 注:此处调用时hw_key为空,需要用户将Hydra数据面需要的key全部实现插入
    // 参考以下示例代码:当处理VLAN RX报文时(vlan_poped为1),从外层报文头out_header提取vlan和encap,调用接口插入hw_key中。(例如Hydra数据面代码处理VLAN RX报文时仅做vlan和encap的匹配)
    int flexda_custom_construct_key(void *ctx, flexda_custom_info_t info, struct rte_flow_item *hw_key)
    {
        struct custom_packet_ctx *custom_ctx = (struct custom_packet_ctx *)ctx;
        struct flexda_sub_hdr *out_header = &custom_ctx->out_header;
        // 根据上下文条件控制组key流程,需要与Hydra里为table定义的key匹配
        if (custom_ctx->vlan_poped == 1) { // vlan pop
            
            struct rte_flow_item_vlan vlan = {0};
    vlan.tci = custom_ctx->out_header.l2.vlan.vlan_id;
            flexda_insert_rte_key(hw_key, -1, &vlan, sizeof(rte_flow_item_vlan ), RTE_FLOW_ITEM_TYPE_VLAN);
            // 下发vlan eth_type key,Hydra自定义key类型使用匹配的插入key api,提供与Hydra匹配的长度和类型枚举值
            uint16_t 
    encap = custom_ctx->out_header.l2.vlan.encap;
            flexda_insert_hydra_key(hw_key, -1, &encap, sizeof(uint16_t), HYDRA_ITEM_MYMAINPIPE_INNER_ETH_TYPE);
            /*** 匹配Hydra数据面vlan pop场景硬件流表的其他组key代码 ***/
            return 0;
        }
        /*** 其他场景卸载硬件流表组key代码 ***/
        return -EINVAL;
    }
    
    //注:此处调用时hw_action已为流表归一拼接后的action,
    // 参考以下示例代码:当处理VLAN RX报文时(vlan_poped为1),调用接口在hw_action中加入Hydra支持的RSS action(HWOFF_FLOW_ACT_DEF_INNER_RSS)。
    int flexda_custom_construct_action(void *ctx, flexda_custom_info_t info, struct rte_flow_action *hw_action)
    {
        struct custom_packet_ctx *custom_ctx = (struct custom_packet_ctx *)ctx;
        // 根据上下文条件控制组action流程,需要保证下发Hydra里table支持的action
        
        if (custom_ctx->vlan_poped == 1) {
             
            uint8_t rss = 1;
            
            // Hydra自定义action类型使用匹配的插入key api,提供与Hydra匹配的长度和类型枚举值
            flexda_insert_hydra_action(hw_action, -1, &rss, sizeof(uint8_t), DEFAULT_FLOW_ACTION_TYPE_INNER_RSS);
        }
        /*** 其他action编辑代码 ***/
        return 0;
    }
  6. 可实现对OVS隧道端口的卸载请求进行放通。
    int flexda_custom_check_tunnel(const char *devname)
    {
        // 当OVS卸载请求来自名称为gre的隧道端口时,可以卸载
        if (strcmp(devname, "gre") == 0) {
            return 0;
        }
        // 来自其他(非gre/vxlan/geneve/tap的端口)时不卸载
        return -EINVAL;
    }

步骤二:编译构建

  1. 如有需要,打包OVS控制面开发目录并发送至编译构建环境。
  2. 编写OVS控制面CMake配置文件,引入src_control_plane目录下的头文件以及源文件,并设置产物名为custom_openvswitch_plugin.so。
  3. 确认编译环境DPDK版本是否正确。
    DPDK版本约束请参见表2
    pkg-config --modversion libdpdk
  4. 确认编译环境操作系统和架构是否正确。
    操作系统约束请参见表1
    uname -r
  5. 执行编译构建。
    cd {project_path}
    mkdir build; cd build; cmake ..; make;
  6. build目录下获取编译产物custom_openvswitch_plugin.so。

步骤三:安装编译产物

  1. 将获取的产物custom_openvswitch_plugin.so上传并复制到运行环境的“/usr/lib64”目录下。
  2. 修改执行权限为550。
  • 安装或更新custom_openvswitch_plugin.so后需要重启OVS软件生效。
  • 用户需要将custom_openvswitch_plugin.so设置为root权限,否则libhiflexda_ovs_adapter.so在加载该动态库时可能由于权限校验不通过,导致加载失败。
  • 推荐用户将custom_openvswitch_plugin.so的权限设置为550。权限高于550会增加被篡改的风险。
  • 不建议在custom_openvswitch_plugin.so使用软链接,以免引入权限风险或访问受限。

特殊场景说明

以示例代码ovs_example.zip为例(用户需自行准备)。请根据以下操作执行控制面开发。

  1. 在flexda_sdk目录下解压示例代码。
    目录结构如下所示,此处以geneve_single为例。
    flexda_sdk/example/geneve_single/
    ├── CMakeLists.txt                   # 顶层CMake配置文件
    └── src_control_plane                # 用户控制面源代码子目录
        ├── CMakeLists.txt               # 控制面子目录CMake配置文件
        ├── context.c                    # 控制面C语言源代码
        └── context.h                    # 控制面C语言头文件
    └── src_dsl                          # 用户数据面源代码子目录
        ├── CMakeLists.txt               # 数据面的子目录CMake配置文件
        └── geneve_ovs.hdr               # 数据面Hydra源文件
  2. 参考特殊场景说明执行固件编译。
  3. 复制固件编译生成的hydra_info.h头文件到src_control_plane下。
    cd {$sdk_path}/flexda_sdk/example/geneve_single/
    cp src_dsl/build/hydra_info.h src_control_plane/
  4. (可选)当前环境(固件编译环境)与OVS控制面编译环境不同时,将flexda_sdk打包,从当前环境发送至OVS控制面编译环境。
    #当前环境执行:
    zip -r flexda_sdk.zip {$sdk_path}/flexda_sdk
    scp flexda_sdk.zip {$username}@{$cplane_compile_env_address}:{$sdk_path}/
    #发送成功后登录至OVS控制面编译环境执行:
    cd {$sdk_path}
    unzip flexda_sdk.zip
    cd flexda_sdk/example/geneve_single/
  5. 进入OVS控制面项目目录,执行编译构建。
    cd src_control_plane
    # 保证cmake/make生成的目录和文件权限不会过大
    umask 077
    # 生成构建文件;
    mkdir build; cd build; cmake ..; 
    # 执行make命令,进行动态库编译
    make
    打印示例如下所示,表示编译成功。
    [100%] Built target custom_openvswitch_plugin
  6. 将编译生成的动态链接库安装至运行环境。
    scp custom_openvswitch_plugin.so {$username}@{$run_env_address}:{$path}/
    #登录至运行环境,安装动态链接库:
    chmod 550 custom_openvswitch_plugin.so
    cp {$path}/custom_openvswitch_plugin.so /usr/lib64