51学通信论坛2017新版

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

Open vSwitch系列之openflow版本兼容

[复制链接]

 成长值: 15613

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

    主题

    2544

    帖子

    7万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    74104
    跳转到指定楼层
    楼主
    发表于 2017-9-17 12:37:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    众所周知Open vSwitch支持的openflow版本从1.0到1.5版本(当前Open vSwitch版本是2.3.2)通过阅读代码,处理openflow协议的入口函数是openflow_handle__(一看命名就知道这个是老外写的,因为老外比较喜欢用handle这个单词)。当我看到这个函数后,和我想象中处理不太一样,我个人想法应该会有各种版本号判断,然而实际上却没有而只有switch-case,那么Open vSwitch是如何做到不同协议版本的兼容呢?
    为了弄清楚这个问题,我大概花了三四天时间,终于揭开了版本兼容面纱—raw这个结构。因此今天来分析一下raw这个结构体。
    在介绍之前,说明一下Open vSwitch代码风格,函数名字凡是以两个下划线结尾(__),都是静态函数,并且是真正逻辑处理函数。
    我们开始步入正题吧,还是以往的形式,所有解释说明都在代码注释中,除非特别需要强调的才会单独摘出来进行补充说明。
    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

    /*
    * openflow协议处理入口函数 为了节省篇幅删除一些case语句。
    */
    static enum ofperr
    handle_openflow__(struct ofconn *ofconn, const struct ofpbuf *msg)
    OVS_EXCLUDED(ofproto_mutex)
    {
    const struct ofp_header *oh = msg->data; /* 从ofpbuf获取数据 */
    enum ofptype type;
    enum ofperr error;
    /* openflow协议头解码 这个函数是我们介绍重点 */
    error = ofptype_decode(&type, oh);
    if (error) {
    return error;
    }
    if (oh->version >= OFP13_VERSION && ofpmsg_is_stat_request(oh)
    && ofpmp_more(oh)) {
    /* We have no buffer implementation for multipart requests.
    * Report overflow for requests which consists of multiple
    * messages. */
    return OFPERR_OFPBRC_MULTIPART_BUFFER_OVERFLOW;
    }
    /*
    * 根据不同type进行不同处理消息
    */
    switch (type) {
    /* OpenFlow requests. */
    case OFPTYPE_ECHO_REQUEST:
    return handle_echo_request(ofconn, oh);
    case OFPTYPE_PACKET_OUT: /* packet out消息 */
    return handle_packet_out(ofconn, oh);
    case OFPTYPE_PORT_MOD:
    return handle_port_mod(ofconn, oh);
    case OFPTYPE_FLOW_MOD:
    return handle_flow_mod(ofconn, oh);
    ...
    case OFPTYPE_BARRIER_REQUEST: /* barrier消息 没有做什么啊!! */
    return handle_barrier_request(ofconn, oh);
    case OFPTYPE_ROLE_REQUEST:
    return handle_role_request(ofconn, oh);
    case OFPTYPE_TABLE_STATS_REQUEST:
    return handle_table_stats_request(ofconn, oh);
    case OFPTYPE_TABLE_FEATURES_STATS_REQUEST:
    return handle_table_features_request(ofconn, oh);
    ....
    case OFPTYPE_HELLO:
    case OFPTYPE_ERROR:
    case OFPTYPE_FEATURES_REPLY:
    case OFPTYPE_GET_CONFIG_REPLY:
    case OFPTYPE_PACKET_IN: /* PACKET_IN消息 */
    ...
    case OFPTYPE_METER_FEATURES_STATS_REPLY:
    case OFPTYPE_TABLE_FEATURES_STATS_REPLY:
    case OFPTYPE_ROLE_STATUS:
    default:
    if (ofpmsg_is_stat_request(oh)) {
    return OFPERR_OFPBRC_BAD_STAT;
    } else {
    return OFPERR_OFPBRC_BAD_TYPE;
    }
    }
    }

    在进行函数分析之前,我觉得理解枚举定义是重点,只有充分理解这个枚举,才能理解下面解析流程。因此在这里我强烈建议读者:阅读一下代码中注释。下面对于枚举解释只是我个人理解,不保证是正确的。由于这个枚举定义很长,为了节省篇幅,这里只摘取典型类型。
    由于openflow版本较多并且有些版本差异化比较大(我经常说openflow还很年轻!!),OpenvSwitch为了支持各个版本的差异化,的确花费了很多心思。所以OpenvSwitch定义了一系列的枚举,枚举格式是以OFPRAW_开始。在介绍各个枚举之前,我们先说一下注释内容,因为RAW注释对于我们理解代码非常有帮助。 我们在阅读代码的时候,会发现每个枚举类型的上方有类似的注释
    C
    /* OFPT <all> (0): uint8_t. */,
    /* OFPT 1.0 (6): struct ofp_switch_features, struct ofp10_phy_port. */,
    根据注释内容我知道,这些注释格式大体是这样的:type versions (number): arguments
    就那上面例子来说吧,就会出现下面的表格:
    那么我们来看一下type,version,arguments分别代表什么意思?这部分主要是翻译源代码中注释,网友可以自行查阅。
    Type:有下面几种OFPT(标准openflow协议消息),OFPST(标准openflow统计消息),NXT(Nicira扩展消息),NXST(Nicira扩展统计消息)
    Version:对应openflow协议版本号,如果是
    Number:理解不是很深入,只能通过代码注释得知是类型值。然而当我自己去匹配的时候,没有匹配成功。这个字段需要在再仔细分析一下。
    Arguments:表示消息体具体类型。如果是void表示无消息体,如果uint8表示消息体是变长的。Argument存在多个时候,表示消息体可能里面类型它们中之一,这就要依据openflow版本号啦。 例如: /* OFPT 1.1-1.3 (12): struct ofp_port_status, struct ofp11_port. */表示消息体既可以是ofp_port_status,又可以是ofp11_port.
    通过上面分析,可以得出下面两种枚举定义格式:
    1)标准协议
    OFPRAW_OFPT_ (OFPRAW_OFPT10_, OFPRAW_OFPT11_等)
    OFPRAW_OFPST_ (OFPRAW_OFPST10_, OFPRAW_OFPST11_等)
    2)Nicira扩展协议
    OFPRAW_NXT_
    OFPRAW_NXST_
    通过上面类型的抽象,完成对openflow各个版本兼容,这个抽象过程非常重要,只有充分理解这个抽象过程,才能更好的理解处理流程。
    C
    enum ofperr
    ofptype_decode(enum ofptype *typep, const struct ofp_header *oh)
    {
    enum ofperr error;
    enum ofpraw raw; /* 枚举类型 */
    error = ofpraw_decode(&raw, oh); /* 解析openflow消息头并保存在raw中 */
    *typep = error ? 0 : ofptype_from_ofpraw(raw); /* 根据raw中返回类型 */
    return error;
    }
    上面这个两个函数都很重要,下面我会对它们进行详细说明的。我们现在调整一下介绍顺序,这样能够方便理解:


    ofpraw_pull函数中会调用右侧三个函数,所我们先来介绍这三个函数,这三个函数搞清楚了ofpraw_pull函数也就清楚了。 在介绍之前,我抓取两个报文,方便解释代码:


    </p>图1:feature_reply消息



    图2:multipart_request消息


    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

    /*
    * 解析openflow头部,如果存在子类型则会进一步解析,针对报文图2就会进一步解码。
    * 参数1:出参
    */
    static enum ofperr
    ofphdrs_decode(struct ofphdrs *hdrs, const struct ofp_header *oh, size_t length)
    {
    memset(hdrs, 0, sizeof *hdrs);
    if (length < sizeof *oh) {
    return OFPERR_OFPBRC_BAD_LEN;
    }
    /* 获取基本消息版本号和类型,如果当前消息存在子类型就会进入if-else分支进一步解析子类型,反之直接退出。*/
    hdrs->version = oh->version;
    hdrs->type = oh->type;
    /* 如果存在子类型就会进入if-else分支进一步解析,针对上面两种报文,图1不会进入任何一个分支,而图2则会进入else分支。为了节省篇幅,我们只针对常见消息进行解析—openflow1.3消息。 */
    if (hdrs->type == OFPT_VENDOR) {//获取设备厂商信息
    ...
    } else if (hdrs->version == OFP10_VERSION
    && (hdrs->type == OFPT10_STATS_REQUEST ||
    hdrs->type == OFPT10_STATS_REPLY)) {//openflow1.0的消息,我们不关系
    ...
    } else if (hdrs->version != OFP10_VERSION
    && (hdrs->type == OFPT11_STATS_REQUEST ||
    hdrs->type == OFPT11_STATS_REPLY)) {/* 通过报文和代码中枚举类型可知,图2中报文会进入此分支。 */
    const struct ofp11_stats_msg *osm;
    /* Get statistic type (OFPST_*). */
    if (length < sizeof *osm) {
    return OFPERR_OFPBRC_BAD_LEN;
    }
    osm = (const struct ofp11_stats_msg *) oh;
    hdrs->stat = ntohs(osm->type);/* 将子类型赋值到出参的stat中 */
    if (hdrs->stat == OFPST_VENDOR) {/* 这个分支也是获取厂商信息 对于上面消息而言不会进入,我们这里忽略掉。 */
    }
    }
    return 0;
    }
    这个函数我们就分析完了,我们来看一下这个函数:ofpraw_from_ofphdrs。
    /*
    * 通过上面函数得到hdrs(参数2)传入到这个函数中,以便获取raw。
    */
    static enum ofperr
    ofpraw_from_ofphdrs(enum ofpraw *raw, const struct ofphdrs *hdrs)
    {
    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
    struct raw_instance *raw_hdrs;
    uint32_t hash;
    ofpmsgs_init;
    hash = ofphdrs_hash(hdrs);/* 对参数2进行hash运算,然后在hash-map中查找 */
    HMAP_FOR_EACH_WITH_HASH (raw_hdrs, hmap_node, hash, &raw_instance_map) {
    if (ofphdrs_equal(hdrs, &raw_hdrs->hdrs)) {
    *raw = raw_hdrs->raw;/* 返回找到raw类型 */
    return 0;
    }
    }
    /* 如果没有找则记录日志并且返回错误 */
    if (!VLOG_DROP_WARN(&rl)) {
    struct ds s;
    ds_init(&s);
    ds_put_format(&s, "version %"PRIu8", type %"PRIu8,
    hdrs->version, hdrs->type);
    if (ofphdrs_is_stat(hdrs)) {
    ds_put_format(&s, ", stat %"PRIu16, hdrs->stat);
    }
    if (hdrs->vendor) {
    ds_put_format(&s, ", vendor 0x%"PRIx32", subtype %"PRIu32,
    hdrs->vendor, hdrs->subtype);
    }
    VLOG_WARN("unknown OpenFlow message (%s)", ds_cstr(&s));
    ds_destroy(&s);
    }
    return (hdrs->vendor ? OFPERR_OFPBRC_BAD_SUBTYPE
    : ofphdrs_is_stat(hdrs) ? OFPERR_OFPBRC_BAD_STAT
    : OFPERR_OFPBRC_BAD_TYPE);
    }

    上面这个函数逻辑相对简单,我们主要看一下raw_instance_map组成结构,ofpmsgs_init这个函数是用于初始化raw_instance_map的。通过查看这个函数源代码又可知,这个hashmap的输入是这个结构raw_infos(ofp-msg.inc文件)。我们看一下这个定义,这是一个结构体数组:static struct raw_info raw_infos = {...}。我们分析一下这个结构体定义:
    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

    /* Information about a particular 'enum ofpraw'. */
    struct raw_info {
    /* All possible instantiations of this OFPRAW_* into OpenFlow headers. */
    struct raw_instance *instances; /* 这个是一个数组,其数组大小是min_version - max_version + 1. */
    uint8_t min_version; /* 当前实例支持的最小版本号 最小是1*/
    uint8_t max_version; /* 当前实例支持的最大版本号 最大是255*/
    unsigned int min_body; /* 消息体最小大小 */
    unsigned int extra_multiple;/* 这个字段不是很明白是什么意思 */
    enum ofptype type;/* openflow消息类型,例如OFPTYPE_HELLO,OFPTYPE_PACKET_IN等 */
    const char *name;/* 字符描述 */
    };
    /* A mapping from OpenFlow headers to OFPRAW_*. */
    struct raw_instance {
    struct hmap_node hmap_node; /* In 'raw_instance_map'. Hash节点*/
    struct ofphdrs hdrs; /* Key. Hash-key*/
    enum ofpraw raw; /* Value. */
    unsigned int hdrs_len; /* ofphdrs_len(hdrs). */
    };
    我们来分析两个两个消息,一个是hello消息,一个是flow_mod消息。
    static struct raw_info raw_infos = {
    {
    ofpraw_ofpt_hello_instances, /* 实例定义 */
    1, 255,/* 最小版本号1说明hello消息是从openflow1.0开始, 最大版本号是255说明,这个hello消息在各个版本中都支持。*/
    #line 109 "./lib/ofp-msgs.h"
    0,/* 最小消息体长度是0,表明没有消息体 */
    #line 109 "./lib/ofp-msgs.h"
    sizeof(uint8_t),
    #line 1620 "lib/ofp-msgs.inc"
    OFPTYPE_HELLO,/* openflow消息类型,消息类型是hello消息 */
    "OFPT_HELLO", /* 字符描述 */
    },
    ...
    {
    ofpraw_ofpt10_flow_mod_instances,/* openflow1.0中flow_mod */
    1, 1, /* 最小和最大版本号都是1,表明这个flow_mod消息支持1.0 */
    #line 175 "./lib/ofp-msgs.h"
    sizeof(struct ofp10_flow_mod),
    #line 175 "./lib/ofp-msgs.h"
    sizeof(uint8_t[8]),
    #line 1884 "lib/ofp-msgs.inc"
    OFPTYPE_FLOW_MOD,
    "OFPT_FLOW_MOD",
    },
    {
    ofpraw_ofpt11_flow_mod_instances, /* openflow1.1协议中flow_mod */
    2, 6, /* 最小版本号是2,最大版本号是6,表明这个flow_mod支持openflow1.1到openflow1.5。 */
    #line 177 "./lib/ofp-msgs.h"
    sizeof(struct ofp11_flow_mod),/* 标准openflow中flow_mod结构大小 */
    #line 177 "./lib/ofp-msgs.h"
    sizeof(struct ofp11_instruction), /*扩展结构,即instruction结构*/
    #line 1895 "lib/ofp-msgs.inc"
    OFPTYPE_FLOW_MOD,
    "OFPT_FLOW_MOD",
    },
    }

    我们再来看一下ofpraw_ofpt11_flow_mod_instances的定义:
    C
    static struct raw_instance ofpraw_ofpt11_flow_mod_instances = {
    { {0, NULL}, {2, 14, 0, 0x0, 0}, OFPRAW_OFPT11_FLOW_MOD, 0 },
    { {0, NULL}, {3, 14, 0, 0x0, 0}, OFPRAW_OFPT11_FLOW_MOD, 0 },
    { {0, NULL}, {4, 14, 0, 0x0, 0}, OFPRAW_OFPT11_FLOW_MOD, 0 },
    { {0, NULL}, {5, 14, 0, 0x0, 0}, OFPRAW_OFPT11_FLOW_MOD, 0 },
    { {0, NULL}, {6, 14, 0, 0x0, 0}, OFPRAW_OFPT11_FLOW_MOD, 0 },
    };
    其中红色字体就是函数ofpraw_from_ofphdrs最终返回的raw类型,数组大小是6-2+1=5。 下面这个两个函数就是根据返回的raw,获取对应实例raw_instance,在定义的时候成员变量hdrs_len,定义成0。当在初始化hashmap的时候,会对这个成员进行初始化,可以参考函数ofpmsgs_init。
    C
    static const struct raw_info *
    raw_info_get(enum ofpraw raw)
    {
    ofpmsgs_init;/* 这个函数只会被调用一次。 */
    ovs_assert(raw < ARRAY_SIZE(raw_infos));
    return &raw_infos[raw]; /* 返回raw_info */
    }
    static struct raw_instance *
    raw_instance_get(const struct raw_info *info, uint8_t version)
    {
    ovs_assert(version >= info->min_version && version <= info->max_version);
    return &info->instances[version - info->min_version];/* 返回raw_instance */
    }
    有了这个上面分析基础,我们再回过头来看一下这个最外部函数ofpraw_pull。
    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

    enum ofperr
    ofpraw_pull(enum ofpraw *rawp, struct ofpbuf *msg)
    {
    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
    const struct raw_instance *instance;
    const struct raw_info *info;
    struct ofphdrs hdrs;
    unsigned int min_len;
    unsigned int len;
    enum ofperr error;
    enum ofpraw raw;
    /* Set default outputs. */
    msg->header = msg->data;
    msg->msg = msg->header;
    *rawp = 0;
    len = msg->size;
    error = ofphdrs_decode(&hdrs, msg->data, len);/* 解析openflow头部消息 保存在hdrs中 */
    if (error) {
    return error;
    }
    error = ofpraw_from_ofphdrs(&raw, &hdrs); /* 根据hdrs,从hmap中获取raw */
    if (error) {
    return error;
    }
    /* 获取实例对象 */
    info = raw_info_get(raw);
    instance = raw_instance_get(info, hdrs.version);
    /* 根据实例对象配置,进行数据分割。 */
    msg->header = ofpbuf_pull(msg, instance->hdrs_len);
    msg->msg = msg->data;
    min_len = instance->hdrs_len + info->min_body;
    switch (info->extra_multiple) {
    case 0:
    if (len != min_len) {
    VLOG_WARN_RL(&rl, "received %s with incorrect length %u (expected "
    "length %u)", info->name, len, min_len);
    return OFPERR_OFPBRC_BAD_LEN;
    }
    break;
    case 1:
    if (len < min_len) {
    VLOG_WARN_RL(&rl, "received %s with incorrect length %u (expected "
    "length at least %u bytes)",
    info->name, len, min_len);
    return OFPERR_OFPBRC_BAD_LEN;
    }
    break;
    default:
    if (len < min_len || (len - min_len) % info->extra_multiple) {
    VLOG_WARN_RL(&rl, "received %s with incorrect length %u (must be "
    "exactly %u bytes or longer by an integer multiple "
    "of %u bytes)",
    info->name, len, min_len, info->extra_multiple);
    return OFPERR_OFPBRC_BAD_LEN;
    }
    break;
    }
    *rawp = raw; /* 返回从实例中获取raw类型。 */
    return 0;
    }

    通过上面这一系列的分析,我们梳理清楚raw是怎么操作的,下面我们返回到这个最开始的函数:
    我们现在看一下函数ofptype_from_ofpraw,这个函数也非常简单,我们跟踪代码可以得知,返回来的类型,其实就是文件ofp-msg.inc中定义的数组结构体struct raw_info raw_infos,里面某个成员(raw是数组下标)。
    这样我们就比较清楚的梳理出,openvswitch是如何进行各个版本兼容的。我们总结一下: 1、OpenvSwitch版本兼容核心技巧,在于这个数组定义,raw_info raw_infos,其中min_version,max_version是关键,通过这个两个变量做到了版本兼容。
    2、虽然版本能做到兼容,但是各个版本还有差异,那么Open vSwitch是如何做到呢?是通过raw_instance数组定义,也是定义在文件Ofp-msgs.inc中。
    只有充分理解上面这个两个数组,我们就能清楚知道Open vSwitch是何如做到版本兼容,当我们基于Open vSwitch进行二次开发的时候,就能够知道在哪些地方增加对应的消息啦。
    作者简介:
    徐小冰:毕业于河北大学,主要从事嵌入式软件开发,虚拟化,SDN。目前基于ODL和Open vSwitch进行二次开发,希望与广大网友一起探讨学习。作者系OpenDaylihgt群(194240432)资深活跃用户,@IT难人。
    声明:本文转载自网络。版权归原作者所有,如有侵权请联系删除。
    扫描并关注51学通信微信公众号,获取更多精彩通信课程分享。

    本帖子中包含更多资源

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

    x
    回复

    使用道具 举报

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

    本版积分规则

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

    GMT+8, 2025-1-31 18:09 , Processed in 0.069971 second(s), 32 queries .

    Powered by Discuz! X3

    © 2001-2013 Comsenz Inc.

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