51学通信论坛2017新版

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 1572|回复: 0
打印 上一主题 下一主题

OpenvSwitch系列之flow

[复制链接]

 成长值: 16435

  • TA的每日心情
    开心
    2022-7-17 17:50
  • 2444

    主题

    2544

    帖子

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    74104
    跳转到指定楼层
    楼主
    发表于 2017-9-17 12:29:23 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    最近两周一直在研究flow_mod这个消息,flow_mod这个消息是openflow中最重要的消息,没有之一,所以花在它的时间上比较多,而且里面涉及的内容也比较复杂。社区有一篇博文对我帮助还是很大。因此这边可能和他的文章有一部分冲突,但是对于学习和总结无所谓啦!!
    我们在上一篇中有介绍了,OpenvSwitch是如何进行不同openflow协议版本的控制的,也知道了入口函数是handle_openflow__,本文的主题是flow_mod消息,因此我们直接进入flow_mod处理函数。
    众所周知,flow_mod是用于下发流表的。可以简单理解为一个flow_mod就是就是一个流表项。如果有谁不是很了解flow_mod,请自行阅读openflow协议。
    通过入口函数openflow_handle__可以知道flow_mod处理函数是,为了描述相对清晰,我会在代码中直接进行注释,除了需要特别指出或者着重描写才在博文中单独写出来。
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    static enum ofperr
    handle_flow_mod(struct ofconn *ofconn, const struct ofp_header *oh)
    OVS_EXCLUDED(ofproto_mutex)
    {
    /* 每一个ofproto代表一个交换机,并且挂在ofconn连接中,
    * 如果有谁不知道其中关联关系,请参考我的第三篇文章。
    * 因此第一步就需要获取openflow交换机对象,即ofproto
    */
    struct ofproto *ofproto = ofconn_get_ofproto(ofconn);
    struct ofputil_flow_mod fm; /* flow mod 消息 */
    uint64_t ofpacts_stub[1024 / 8];
    struct ofpbuf ofpacts;
    enum ofperr error;
    error = reject_slave_controller(ofconn); /* 角色权限验证我们可以忽略。 */
    if (error) {
    goto exit;
    }
    /* 将stub挂在ofpbuf中这个地方经常见到不再详细分析有不明白的可参考前面几篇*/
    ofpbuf_use_stub(&ofpacts, ofpacts_stub, sizeof ofpacts_stub);
    /* flow_mod解码解码过程其实很简单,只要按照标准协议格式一个字段一个字段
    * 解析就可以啦!!这里我们按照某一种报文进行讲解,这样对于代码理解帮助非常有
    * 意义,也不至于会有这样的疑问,为什么这样写??哈哈哈
    */
    error = ofputil_decode_flow_mod(&fm, oh, ofconn_get_protocol(ofconn),
    &ofpacts,
    u16_to_ofp(ofproto->max_ports),
    ofproto->n_tables);
    /* 校验action内容 */
    if (!error) {
    error = ofproto_check_ofpacts(ofproto, fm.ofpacts, fm.ofpacts_len);
    }
    if (!error) {
    error = handle_flow_mod__(ofproto, ofconn, &fm, oh); /* 真正处理flow_mod函数 */
    }
    if (error) {
    goto exit_free_ofpacts;
    }
    /*
    * 修改统计值大概是为了控制器下发统计请求用的吧!!
    */
    ofconn_report_flow_mod(ofconn, fm.command); /* 释放内存,这里不会真正调用free函数的 */
    exit_free_ofpacts:
    ofpbuf_uninit(&ofpacts);
    exit:
    return error;
    }

    上面是flow_mod函数处理框架,下面我们进行逐个分析,我们进行分析时候会结构flow_mod消息,因此有具体的消息内容,我们在分析代码的时候不至于那么抽象。
    下面报文就是我抓取的,比较有针对性,控制器下发的流表是从host1到host2,出端口是交换机的2。具体报文和网络拓扑如下:



    注:红色线就是打通的通道。
    由于函数ofputil_decode_flow_mod相对较长,因此采用分段描述,层层深入方式进行剖析。
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29

    /* Converts an OFPT_FLOW_MOD or NXT_FLOW_MOD message 'oh' into an abstract
    * flow_mod in 'fm'. Returns 0 if successful, otherwise an OpenFlow error
    * code.
    *
    * Uses 'ofpacts' to store the abstract OFPACT_* version of 'oh''s actions.
    * The caller must initialize 'ofpacts' and retains ownership of it.
    * 'fm->ofpacts' will point into the 'ofpacts' buffer.
    *
    * Does not validate the flow_mod actions. The caller should do that, with
    * ofpacts_check.
    * OpenvSwitch支持两种flow格式,一种标准flow_mod,一种NXT flow_mod.
    * 对于OpenvSwitch来说无论是哪种flow_mod,经过ovs抽象之后都会转成
    * struct ofputil_flow_mod进行存储。
    */
    enum ofperr
    ofputil_decode_flow_mod(struct ofputil_flow_mod *fm,
    const struct ofp_header *oh,
    enum ofputil_protocol protocol,
    struct ofpbuf *ofpacts,
    ofp_port_t max_port, uint8_t max_table)
    {
    ovs_be16 raw_flags;
    enum ofperr error;
    struct ofpbuf b;
    enum ofpraw raw;
    /* 将flow_mod消息挂载在ofpbuf的b中 */
    ofpbuf_use_const(&b, oh, ntohs(oh->length));
    /* 解析消息头前面有一篇介绍OpenvSwitch是如何进行版本兼容。*/
    raw = ofpraw_pull_assert(&b);

    上面内容相对简单,也不用多说些。下面是我们进行着重分析的。由于我们基于openflow1.3进行连接的,因此raw实际值是OFPRAW_OFPT11_FLOW_MOD。(opeflow1.1之后版本flow_mod没有变化。)在这里就有if-else判断,所以我们会着重分析if分支。 一般情况下,flow_mod消息主要是由openflow固定字段,match字段,instruction字段(可以没有),然而match字段中可以包含多个oxm字段(可以没有oxm),instruction字段中可以包含多个action字段。其中oxm格式是TLV(Type-Length-Value)。针对上面的报文来说,oxm有两个,action只有一个。所以我们能够预测if分支中主要是针对这些字段进行解析。
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93

    if (raw == OFPRAW_OFPT11_FLOW_MOD) {
    /* Standard OpenFlow 1.1+ flow_mod. Openflow1.1之后版本flow_mod无变化*/
    const struct ofp11_flow_mod *ofm;
    /* 获取flow_mod固定长度字段头其实是通过指针偏移指向buf,并非申请内存
    * 主要是标准flow_mod, 在下面会把ofm相关值赋值到fm中。
    */
    ofm = ofpbuf_pull(&b, sizeof *ofm); /* 从b中移出大小为sizeof(*ofm) */
    /* 获取flow_mod中的match数据赋值到fm->match中
    * 如果存在多个oxm的话,也会同时解析出来的,而且所有的
    * oxm做为一个flow流结构
    */
    error = ofputil_pull_ofp11_match(&b, &fm->match, NULL);
    if (error) {
    return error;
    }
    /* 解析instruction数据主要是解析action,将报文中action保存在ofpacts中*/
    error = ofpacts_pull_openflow_instructions(&b, ofpbuf_size(&b), oh->version,
    ofpacts);
    if (error) {
    return error;
    }
    /* Translate the message. Flow_mod固定字段解析。这个地方其实没有什么需要讲解,纯粹赋值。*/
    fm->priority = ntohs(ofm->priority);
    if (ofm->command == OFPFC_ADD
    || (oh->version == OFP11_VERSION
    && (ofm->command == OFPFC_MODIFY ||
    ofm->command == OFPFC_MODIFY_STRICT)
    && ofm->cookie_mask == htonll(0))) {
    /* In OpenFlow 1.1 only, a "modify" or "modify-strict" that does
    * not match on the cookie is treated as an "add" if there is no
    * match. */
    fm->cookie = htonll(0);
    fm->cookie_mask = htonll(0);
    fm->new_cookie = ofm->cookie;
    } else {
    fm->cookie = ofm->cookie;
    fm->cookie_mask = ofm->cookie_mask;
    fm->new_cookie = OVS_BE64_MAX;
    }
    fm->modify_cookie = false;
    fm->command = ofm->command;
    /* Get table ID.
    *
    * OF1.1 entirely forbids table_id == OFPTT_ALL.
    * OF1.2+ allows table_id == OFPTT_ALL only for deletes. */
    fm->table_id = ofm->table_id;
    if (fm->table_id == OFPTT_ALL
    && (oh->version == OFP11_VERSION
    || (ofm->command != OFPFC_DELETE &&
    ofm->command != OFPFC_DELETE_STRICT))) {
    return OFPERR_OFPFMFC_BAD_TABLE_ID;
    }
    fm->idle_timeout = ntohs(ofm->idle_timeout);
    fm->hard_timeout = ntohs(ofm->hard_timeout);
    fm->buffer_id = ntohl(ofm->buffer_id);
    error = ofputil_port_from_ofp11(ofm->out_port, &fm->out_port);
    if (error) {
    return error;
    }
    fm->out_group = (ofm->command == OFPFC_DELETE ||
    ofm->command == OFPFC_DELETE_STRICT
    ? ntohl(ofm->out_group)
    : OFPG11_ANY);
    raw_flags = ofm->flags;
    } else {
    /* 这个else是针对openflow1.0的flow_mod消息进行解析的,我们可以忽略掉 */
    }
    最后一部分代码,主要内容就是常规校验,我也没有太深入阅读,这里为了保证一致性还是顺便提一下。
    /*
    * 保存actions。因为fm是抽象层flow_mod,在最后生成流表项的时候需要这个。
    */
    fm->ofpacts = ofpbuf_data(ofpacts);
    fm->ofpacts_len = ofpbuf_size(ofpacts);
    error=ofputil_decode_flow_mod_flags(raw_flags,fm->command,oh->version, &fm->flags);
    if (error) {
    return error;
    }
    if (fm->flags & OFPUTIL_FF_EMERG) {
    /* We do not support the OpenFlow 1.0 emergency flow cache, which
    * is not required in OpenFlow 1.0.1 and removed from OpenFlow 1.1.
    *
    * OpenFlow 1.0 specifies the error code to use when idle_timeout
    * or hard_timeout is nonzero. Otherwise, there is no good error
    * code, so just state that the flow table is full. */
    return (fm->hard_timeout || fm->idle_timeout
    ? OFPERR_OFPFMFC_BAD_EMERG_TIMEOUT
    : OFPERR_OFPFMFC_TABLE_FULL);
    }
    /* 校验action */
    return ofpacts_check_consistency(fm->ofpacts, fm->ofpacts_len,
    &fm->match.flow, max_port,
    fm->table_id, max_table, protocol);
    } /* 整个函数结束 */

    以上就是flow_mod解析函数大体框架,下面我们来看一下match、instruction这两个解析函数。首先我们来看一下函数调用关系图:


    根据关系图,从函数3、函数4内容都比较短小,函数也比较容易理解,我们从函数5开始进行分析,分析如下:
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

    /*
    * strict – 是否需要严格检查
    * b – 保存match的原始报文buf
    * match – 保存解析后的match 输出参数
    */
    static enum ofperr
    oxm_pull_match__(struct ofpbuf *b, bool strict, struct match *match)
    {
    struct ofp11_match_header *omh = ofpbuf_data(b); /* 获取match head指针 */
    uint8_t *p;
    uint16_t match_len;
    if (ofpbuf_size(b) < sizeof *omh) {
    return OFPERR_OFPBMC_BAD_LEN;
    }
    match_len = ntohs(omh->length);
    if (match_len < sizeof *omh) {
    return OFPERR_OFPBMC_BAD_LEN;
    }
    if (omh->type != htons(OFPMT_OXM)) {
    return OFPERR_OFPBMC_BAD_TYPE;
    }
    /* 尝试将缓冲区b中移除大小为match_len数据
    * 经过下面这个函数处理之后,缓冲区b里面已经没有match相关数据,
    * 剩下就是instruction域
    */
    p = ofpbuf_try_pull(b, ROUND_UP(match_len, 8));
    if (!p) {
    VLOG_DBG_RL(&rl, "oxm length %u, rounded up to a "
    "multiple of 8, is longer than space in message (max "
    "length %"PRIu32")", match_len, ofpbuf_size(b));
    return OFPERR_OFPBMC_BAD_LEN;
    }
    /*
    * 解析一下这个函数实参:
    * p + sizeof *omh == 跳过match头部
    * match_len - sizeof *omh == 总长度减去match头部长度,即实际报文内容长度。
    */
    return nx_pull_raw(p + sizeof *omh, match_len - sizeof *omh,
    strict, match, NULL, NULL);
    }

    在介绍nx_pull_raw函数之前,我们先看来看一下这个全局数组const struct mf_field mf_fields[MFF_N_IDS](在文件meta-flow.c),这个数组在下面的函数会用到mf_from_nxm_header。所先把分析一下。
    这个虽然数组很长,但是里面的注释对于理解还是有帮助的,数组中是按照网络层次进行划分的,主要是metadata, L2, L2 .5,L3, L4, L5。也就是说根据报文中match字段可得出位于数组的哪层。例如上面报文,是针对物理地址(源/宿mac)那么我们可以推断出是位于数组的L2,如下面代码所示:
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68

    const struct mf_field mf_fields[MFF_N_IDS] = {
    /* ## -------- ## */
    /* ## metadata ## */
    /* ## -------- ## */
    {
    MFF_DP_HASH, "dp_hash", NULL,
    MF_FIELD_SIZES(be32),
    MFM_FULLY,
    MFS_HEXADECIMAL,
    MFP_NONE,
    false,
    NXM_NX_DP_HASH, "NXM_NX_DP_HASH",
    NXM_NX_DP_HASH, "NXM_NX_DP_HASH", 0,
    OFPUTIL_P_NXM_OXM_ANY,
    OFPUTIL_P_NXM_OXM_ANY,
    -1,
    }, {…
    }
    /* ## -- ## */
    /* ## L2层以太网层 ## */
    /* ## -- ## */
    {/*源mac地址 */
    MFF_ETH_SRC, "eth_src", "dl_src",
    MF_FIELD_SIZES(mac),
    MFM_FULLY,
    MFS_ETHERNET,
    MFP_NONE,
    true,
    NXM_OF_ETH_SRC, "NXM_OF_ETH_SRC",
    OXM_OF_ETH_SRC, "OXM_OF_ETH_SRC", OFP12_VERSION,
    OFPUTIL_P_ANY,
    OFPUTIL_P_NXM_OF11_UP, /* Bitwise masking only with NXM and OF11+! */
    -1,
    }, {/* 目的mac地址 */
    MFF_ETH_DST, "eth_dst", "dl_dst",
    MF_FIELD_SIZES(mac),
    MFM_FULLY,
    MFS_ETHERNET,
    MFP_NONE,
    true,
    NXM_OF_ETH_DST, "NXM_OF_ETH_DST",
    OXM_OF_ETH_DST, "OXM_OF_ETH_DST", OFP12_VERSION,
    OFPUTIL_P_ANY,
    OFPUTIL_P_NXM_OF11_UP, /* Bitwise masking only with NXM and OF11+! */
    -1,
    }, {/*以太网类型*/
    MFF_ETH_TYPE, "eth_type", "dl_type",
    MF_FIELD_SIZES(be16),
    MFM_NONE,
    MFS_HEXADECIMAL,
    MFP_NONE,
    false,
    NXM_OF_ETH_TYPE, "NXM_OF_ETH_TYPE",
    OXM_OF_ETH_TYPE, "OXM_OF_ETH_TYPE", OFP12_VERSION,
    OFPUTIL_P_ANY,
    OFPUTIL_P_NONE,
    -1,
    },{…}
    /* ## ---- ## */
    /* ## L2.5层 ## */
    /* ## ---- ## */
    {…}
    /* ## ---- ## */
    /* ## L3层 ## */
    /* ## ---- ## */
    {…}
    } /* 数组结束 */

    我们来看一下蓝色字体,这个宏定义 枚举:
    C
    OFPXMT12_OFB_ETH_SRC = 4
    OFPXMT12_OFB_ETH_DST = 3
    OFPXMC12_OPENFLOW_BASIC = 0x8000
    #define OXM_OF_ETH_SRC OXM_HEADER (OFPXMT12_OFB_ETH_SRC, 6)
    #define OXM_OF_ETH_DST OXM_HEADER (OFPXMT12_OFB_ETH_DST, 6)
    #define OXM_HEADER(FIELD, LENGTH) \
    NXM_HEADER(OFPXMC12_OPENFLOW_BASIC, FIELD, LENGTH)
    通过上面代码罗列可得知,OXM_HEADER是用于构造报文OXM头部的宏,而OXM是以TLV格式进行封装的。针对上面报文和宏定义就有如下对应(图片蓝色下划线):


    经过函数nxm_do_init处理,会把这个数组中成员挂在全局变量all_fields,这个变量是一个hmap结构,至于这里具体存储关系是怎么关联的,这里不进行详细解析(可以参考函数nxm_do_init)。如果有不理解hmap的网友可以去看一下我之前文章。下面我们继续分析nx_pull_raw函数。
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97

    /*
    * p -- 指向第一个oxm字段起始内存位置
    * match_len = 整个match字段-match头部长度即所有oxm字段长度,不包括padding
    * strict 是否需要严格检查
    * match 存储match数据(出参)
    */
    static enum ofperr
    nx_pull_raw(const uint8_t *p, unsigned int match_len, bool strict,
    struct match *match, ovs_be64 *cookie, ovs_be64 *cookie_mask)
    {
    uint32_t header;
    ovs_assert((cookie != NULL) == (cookie_mask != NULL));
    match_init_catchall(match); /* init match. */
    if (cookie) {
    *cookie = *cookie_mask = htonll(0);
    }
    if (!match_len) {
    return 0;
    }
    /* 我们分析一下for的条件
    * 1、可能有多个oxm字段因此需要一个一个遍历
    * 标准里面oxm头部只能说类tlv格式,为什么说是类tlv呢?
    * 标准tlv格式 t和l是两个字节(有的是4个字节),而oxm中tlv
    * t = 3个字节,l = 1个字节其中t中前两个字节是表示类型,
    * 最后一个字节是又被分成两部分,高7位表示子类型,最后一位代表是否存在mask (根据上面报文可以知道)
    *
    * 2、for循环中魔鬼数字4表示tlv中header字段长度,也就是说header是固长度
    * 3、函数nx_entry_ok 其实是拷贝oxm header部分(4个字节)到变量uint32 header中,对于上面报文中第一个oxm,这个header=0x80000606。
    * 4、宏NXM_LENGTH是用于获取tlv中length值。就是header&0xff。针对上面header是0x80000606,与oxff与操作后正好是6。通过报文查看length的确是6。
    * 5、每解析一次oxm,p就要指向下一个oxm起始位置,match_len就要减去对应长度。
    */
    for (;
    (header = nx_entry_ok(p, match_len)) != 0;
    p += 4 + NXM_LENGTH(header), match_len -= 4 + NXM_LENGTH(header)) {
    const struct mf_field *mf;
    enum ofperr error;
    mf = mf_from_nxm_header(header); /* 从hmap中获取mf,mf这个数组在上面已经介绍过!!这里就不详细介绍了*/
    if (!mf) {
    if (strict) {
    error = OFPERR_OFPBMC_BAD_FIELD;
    } else {
    continue;
    }
    } else if (!mf_are_prereqs_ok(mf, &match->flow)) {
    error = OFPERR_OFPBMC_BAD_PREREQ;
    } else if (!mf_is_all_wild(mf, &match->wc)) {
    error = OFPERR_OFPBMC_DUP_FIELD;
    } else {
    unsigned int width = mf->n_bytes;
    union mf_value value; /* 这个地方的value是一个联合,设计非常巧妙,可以同时满足各种类型。 */
    memcpy(&value, p + 4, width); /* 将tlv(match)中的value字段赋值到value变量*/
    if (!mf_is_value_valid(mf, &value)) {
    error = OFPERR_OFPBMC_BAD_VALUE;
    } else if (!NXM_HASMASK(header)) {/* 没有设置mask标志会进入此分支针对上面报文,会进入这个分支的!!*/
    error = 0;
    mf_set_value(mf, &value, match); /* 上面已经说过了value是一个联合类型,因此在mf赋值的时候需要进行类型判断,才能正确赋值。 */
    } else {/* 设置了mask标志才会进入else分支 */
    union mf_value mask;
    memcpy(&mask, p + 4 + width, width);
    if (!mf_is_mask_valid(mf, &mask)) {
    error = OFPERR_OFPBMC_BAD_MASK;
    } else {
    error = 0;
    check_mask_consistency(p, mf);
    mf_set(mf, &value, &mask, match);
    }
    }
    }
    /* 下面都一些基本校验,对于业务逻辑没有影响我们不进行深入分析 */
    /* Check if the match is for a cookie rather than a classifier rule. */
    if ((header == NXM_NX_COOKIE || header == NXM_NX_COOKIE_W) && cookie) {
    if (*cookie_mask) {
    error = OFPERR_OFPBMC_DUP_FIELD;
    } else {
    unsigned int width = sizeof *cookie;
    memcpy(cookie, p + 4, width);
    if (NXM_HASMASK(header)) {
    memcpy(cookie_mask, p + 4 + width, width);
    } else {
    *cookie_mask = OVS_BE64_MAX;
    }
    error = 0;
    }
    }
    if (error) {
    VLOG_DBG_RL(&rl, "bad nxm_entry %#08"PRIx32" (vendor=%"PRIu32", "
    "field=%"PRIu32", hasmask=%"PRIu32", len=%"PRIu32"), "
    "(%s)", header,
    NXM_VENDOR(header), NXM_FIELD(header),
    NXM_HASMASK(header), NXM_LENGTH(header),
    ofperr_to_string(error));
    return error;
    }
    }
    return match_len ? OFPERR_OFPBMC_BAD_LEN : 0;
    }

    上面分析比较混乱,这里进行一下总结:
    1、通过上面这个函数处理(核心代码是for循环),就会把报文中两个oxm字段解析成功并且保存在入参match里面。
    2、我们知道oxm表现形式是一个tlv格式,OpenvSwitch会把value字段赋值到match结构体中的,struct flow(flow.h)结构体。flow这个结构体和mf_fields是有关联关系的。
    3、通过上面解析后,flow中字段数据时这样的(实际内存中没有冒号):
    flow.dl_dst[6] = 00:00:00:00:00:02
    flow.dl_src[6] = 00:00:00:00:00:01
    其他字段是默认值,大部分是0。
    通过上面的一系列分析,openflow中的match字段就结束了。现在我们返回到函数ofputil_decode_flow_mod中,下面应该解析instructions。
    基于目前OpenvSwitch实现主要支持这6中actions,分别是:openflow1.3中meter表,openflow1.1中apply_action,openflow1.1中clear_action,openflow1.1中write_action,openflow1.1中write_metadata,openflow1.1中goto_table。针对当前的flowmod中action是apply_action,如下图所示:


    在介绍函数之前,我们先来看一下函数中即将用到指针数组,const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS],通过查看宏定义可知, N_OVS_INSTRUCTIONS=6
    默认指针数组中保存地址是null,经过处理后会把其中某个数组存储单元修改为有效地址。针对上面那个报文,instructions的类型是apply_actions,因此经过函数decode_openflow11_instructions会把数组下标为OVSINST_OFPIT11_APPLY_ACTIONS的值修改为有效地址(即上图中红色字体)。
    现在我们来看一下这个函数代码,下面红色字体就是那6种action:
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109

    enum ofperr
    ofpacts_pull_openflow_instructions(struct ofpbuf *openflow,
    unsigned int instructions_len,
    enum ofp_version version,
    struct ofpbuf *ofpacts)
    {
    const struct ofp11_instruction *instructions;
    /* 通过查看宏定义可知, N_OVS_INSTRUCTIONS=6 */
    const struct ofp11_instruction *insts[N_OVS_INSTRUCTIONS];
    enum ofperr error;
    ofpbuf_clear(ofpacts); /* 清空缓冲区输出参数保留action */
    if (instructions_len % OFP11_INSTRUCTION_ALIGN != 0) {
    VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u is not a "
    "multiple of %d",
    instructions_len, OFP11_INSTRUCTION_ALIGN);
    error = OFPERR_OFPBIC_BAD_LEN;
    goto exit;
    }
    /* 移除instructions_len大小内存数据instructions 保存数据起始位置*/
    instructions = ofpbuf_try_pull(openflow, instructions_len);
    if (instructions == NULL) {
    VLOG_WARN_RL(&rl, "OpenFlow message instructions length %u exceeds "
    "remaining message length (%"PRIu32")",
    instructions_len, ofpbuf_size(openflow));
    error = OFPERR_OFPBIC_BAD_LEN;
    goto exit;
    }
    /*
    * 解析openflow中instructions,并保存在指针数组out中
    * 这个函数介绍重点。我们放到后面进行详细解说!!
    * 这里说一下insts这个指针数组,即每个数组元素都是一个指针,指针类型就是
    * 标准instruction头部,其大小固定是6。这里insts就是上面表格,经过这个函数
    * 处理之后就会把某一个数组单元设置成有效地址。
    */
    error = decode_openflow11_instructions(
    instructions, instructions_len / OFP11_INSTRUCTION_ALIGN,
    insts);
    if (error) {
    goto exit;
    }
    /*
    * 针对上面的解析结构即对out指针数组进行action解析
    * 解析meter表
    */
    if (insts[OVSINST_OFPIT13_METER]) {…
    }
    /*
    * 应用action--解析,当前flowmod就这个!着重说一下这个,
    *其他无关代码已经删除
    */
    if (insts[OVSINST_OFPIT11_APPLY_ACTIONS]) {
    const union ofp_action *actions;
    size_t max_actions;
    /*
    * 可以有多个action
    * actions变量保存报文actions指针,这是一个联合结构。
    * max_actions变量保存action个数
    */
    get_actions_from_instruction(insts[OVSINST_OFPIT11_APPLY_ACTIONS],
    &actions, &max_actions);
    /*
    * 解析action 保存在ofpacts
    * 通过不同类型判断,是openflow1.0还是openflow1.1。对于上面报文来说,
    * 最终调用解析函数是openflow1.1的函数即ofpact_from_openflow11。
    * 注:actions这个变量时一个联合,这个地方涉及非常巧妙。
    */
    error = ofpacts_from_openflow(actions, max_actions, version, ofpacts);
    if (error) {
    goto exit;
    }
    }
    /*
    * 清空action--解析
    */
    if (insts[OVSINST_OFPIT11_CLEAR_ACTIONS]) {…
    }
    /*
    * 重写action--解析
    */
    if (insts[OVSINST_OFPIT11_WRITE_ACTIONS]) {…
    }
    /*
    * 重写元数据--解析
    */
    if (insts[OVSINST_OFPIT11_WRITE_METADATA]) {…
    }
    /*
    * 跳转流表--解析
    */
    if (insts[OVSINST_OFPIT11_GOTO_TABLE]) {…
    }
    error = ofpacts_verify(ofpbuf_data(ofpacts), ofpbuf_size(ofpacts));
    exit:
    if (error) {
    ofpbuf_clear(ofpacts);
    }
    return error;
    }

    我们现在来分析一下这两个函数:decode_openflow11_instructions(解析instructions函数)和ofpacts_from_openflow(解析actions函数)。
    函数decode_openflow11_instructions主要是循环遍历,不断解析instructions,内部最终处理函数是decode_openflow11_instruction,对于这个函数,比较难看懂,主要是因为里面switch-case子句是通过宏定义的,这样不便于理解。然而我们可以借助编译器(GCC),让它帮我们展开宏定义。由于展开后代码变得非常庞大,因此我只相关代码展示出来:
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25

    static enum ofperr
    decode_openflow11_instruction(const struct ofp11_instruction *inst,
    enum ovs_instruction_type *type)
    {
    uint16_t len = ntohs(inst->len);
    switch (inst->type) {
    case CONSTANT_HTONS(OFPIT11_EXPERIMENTER):
    return OFPERR_OFPBIC_BAD_EXPERIMENTER;
    /* 该报文相关的case子句—apply_actions */
    case CONSTANT_HTONS( OFPIT11_APPLY_ACTIONS ):
    if ( true ? len >= sizeof(struct ofp11_instruction_actions) : len == sizeof(struct ofp11_instruction_actions) )
    {
    *type = OVSINST_OFPIT11_APPLY_ACTIONS;/* 返回枚举定义*/
    return(0);
    }
    else
    {
    return(OFPERR_OFPBIC_BAD_LEN);
    }
    default:
    return OFPERR_OFPBIC_UNKNOWN_INST;
    }
    }

    我们再来看一下actions解析函数,上面已经说过了,对于openflow1.3的actions报文,最终会进入这个ofpact_from_openflow11函数,函数主体代码如下:
    C

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    static enum ofperr
    ofpact_from_openflow11(const union ofp_action *a, enum ofp_version version,
    struct ofpbuf *out)
    {
    enum ofputil_action_code code;
    enum ofperr error;
    struct ofpact_vlan_vid *vlan_vid;
    struct ofpact_vlan_pcp *vlan_pcp;
    error = decode_openflow11_action(a, &code); /* 这个函数和上面那个函数类似,也是通过宏定义构建的,也可以通过同样方式获取宏展开后代码。 */
    if (error) {
    return error;
    }
    /*
    * openflow1.2+ 中部分消息将废弃掉
    */
    if (version >= OFP12_VERSION) {

    }
    }
    switch (code) {
    case OFPUTIL_ACTION_INVALID:
    #define OFPAT10_ACTION(ENUM, STRUCT, NAME) case OFPUTIL_##ENUM:
    #define OFPAT13_ACTION(ENUM, STRUCT, EXTENSIBLE, NAME) case OFPUTIL_##ENUM:
    #include "ofp-util.def"
    OVS_NOT_REACHED;
    case OFPUTIL_OFPAT11_OUTPUT: /* 针对上面报来说,最终会进入这个case子句 */
    return output_from_openflow11(&a->ofp11_output, out); /* 将ofp11_output中端口信息在保存out中。 */

    }//end switch 函数结束
    }
    static enum ofperr
    output_from_openflow11(const struct ofp11_action_output *oao,
    struct ofpbuf *out)
    {
    struct ofpact_output *output;
    enum ofperr error;
    /* 这个函数是宏定义函数,最终调用是调用ofpact_put。宏定义位置:ofp_actions.h,689行 */
    output = ofpact_put_OUTPUT(out);
    output->max_len = ntohs(oao->max_len);
    error = ofputil_port_from_ofp11(oao->port, &output->port); /* 将端口保存在output->port中。 */
    if (error) {
    return error;
    }
    return ofpact_check_output_port(output->port, OFPP_MAX); /* 验证端口 */
    }

    至此,基本上flow_mod消息就解析完成了,那么我们接下来做什么呢?下发到数据平面(datapath),通过handle_flow_mod__进行下发。请参考下一篇。
    作者简介:
    徐小冰:毕业于河北大学,主要从事嵌入式软件开发,虚拟化,SDN。目前基于ODL和Open vSwitch进行二次开发,希望与广大网友一起探讨学习。作者系OpenDaylihgt群(194240432)资深活跃用户,@IT难人。
    声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。
    扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    Archiver|手机版|小黑屋|51学通信技术论坛

    GMT+8, 2025-6-17 06:14 , Processed in 0.100131 second(s), 32 queries .

    Powered by Discuz! X3

    © 2001-2013 Comsenz Inc.

    快速回复 返回顶部 返回列表