默认情况下,每个端口为trunk_mode,并且trunk的vlan为空,报文处理可参考下面的trunk模式。

ovs支持设置的vlan mode如下,可参考ovs-vswitchd.conf.db.5的VLAN Configuration部分。

access模式:
    收:
        报文带vlan,drop
        报文不带vlan,接收,并携带端口的vlan进行后续转发    
    发:
        发出去的报文不带vlan
        
trunk模式:
    收:
        报文带vlan,vlan必须在trunk vlan指定的范围或者trunk vlan为空(不用考虑报文携带的vlan)
        报文不带vlan,如果trunk vlan不为空,则drop。如果trunk vlan为空,则接收,此为默认情况下的处理。
    发:
        发出去的报文,带原始vlan
        
native tagged模式:
    收:
        报文带vlan,vlan必须在trunk vlan指定的范围或者trunk vlan为空(不用考虑报文携带的vlan)
        报文不带vlan,接收,并携带端口的native vlan进行后续转发
    发:
        发出去的报文,如果报文本来带vlan,则携带原始vlan。如果报文不带vlan,则携带native vlan
        
native untagged模式:
    收:
        报文带vlan,vlan必须在trunk vlan指定的范围或者trunk vlan为空(不用考虑报文携带的vlan)
        报文不带vlan,接收,并携带端口的native vlan进行后续转发
    发:
        发出去的报文,如果报文本来带vlan,则携带原始vlan。如果报文不带vlan,则报文也不带vlan

配置vlan流程

通过命令行配置端口的vlan模式和tag信息时,最终会调用port_configure,此函数会将从ovsdb-server接收的端口配置信息提取出来,再调用bundle_set将相关信息填充到struct ofbundle,并将struct ofbundle插入hash表ofproto->bundles。

static void
port_configure(struct port *port)
{
    const struct ovsrec_port *cfg = port->cfg;
    struct ofproto_bundle_settings s;
    struct iface *iface;

    /* Get name. */
    s.name = port->name;

    /* Get slaves. */
    s.n_slaves = 0;
    s.slaves = xmalloc(ovs_list_size(&port->ifaces) * sizeof *s.slaves);
    LIST_FOR_EACH (iface, port_elem, &port->ifaces) {
        s.slaves[s.n_slaves++] = iface->ofp_port;
    }

    /* Get VLAN tag. */
    s.vlan = -1;
    if (cfg->tag && *cfg->tag >= 0 && *cfg->tag <= 4095) {
        s.vlan = *cfg->tag;
    }

    /* Get VLAN trunks. */
    s.trunks = NULL;
    if (cfg->n_trunks) {
        s.trunks = vlan_bitmap_from_array(cfg->trunks, cfg->n_trunks);
    }

    s.cvlans = NULL;
    if (cfg->n_cvlans) {
        s.cvlans = vlan_bitmap_from_array(cfg->cvlans, cfg->n_cvlans);
    }

    /* Get VLAN mode. */
    if (cfg->vlan_mode) {
        if (!strcmp(cfg->vlan_mode, "access")) {
            s.vlan_mode = PORT_VLAN_ACCESS;
        } else if (!strcmp(cfg->vlan_mode, "trunk")) {
            s.vlan_mode = PORT_VLAN_TRUNK;
        } else if (!strcmp(cfg->vlan_mode, "native-tagged")) {
            s.vlan_mode = PORT_VLAN_NATIVE_TAGGED;
        } else if (!strcmp(cfg->vlan_mode, "native-untagged")) {
            s.vlan_mode = PORT_VLAN_NATIVE_UNTAGGED;
        } else if (!strcmp(cfg->vlan_mode, "dot1q-tunnel")) {
            s.vlan_mode = PORT_VLAN_DOT1Q_TUNNEL;
        } else {
            /* This "can't happen" because ovsdb-server should prevent it. */
            VLOG_WARN("port %s: unknown VLAN mode %s, falling "
                      "back to trunk mode", port->name, cfg->vlan_mode);
            s.vlan_mode = PORT_VLAN_TRUNK;
        }
    } else {
        if (s.vlan >= 0) {
            s.vlan_mode = PORT_VLAN_ACCESS;
            if (cfg->n_trunks || cfg->n_cvlans) {
                VLOG_WARN("port %s: ignoring trunks in favor of implicit vlan",
                          port->name);
            }
        } else {
            s.vlan_mode = PORT_VLAN_TRUNK;
        }
    }
    ...
    ...
    /* Register. */
    ofproto_bundle_register(port->bridge->ofproto, port, &s);
}

最后在函数type_run中,将ofproto->bundles转换到struct xbundle中,并插入new_xcfg->xbundles。

static int
type_run(const char *type)
{
    HMAP_FOR_EACH (ofproto, all_ofproto_dpifs_node, &all_ofproto_dpifs) {
        HMAP_FOR_EACH (bundle, hmap_node, &ofproto->bundles) {
                xlate_bundle_set(ofproto, bundle, bundle->name,
                                 bundle->vlan_mode, bundle->qinq_ethtype,
                                 bundle->vlan, bundle->trunks, bundle->cvlans,
                                 bundle->use_priority_tags,
                                 bundle->bond, bundle->lacp,
                                 bundle->floodable, bundle->protected);
            }
    }
}

报文入方向vlan处理

网桥端口接收到报文后,如果快速路径查找不到相关流表,就会开始慢速路径的处理,最终会根据fdb表转发,调用xlate_normal。此函数中调用input_vid_is_valid根据入端口vlan模式配置,判断报文的去留。

//vid为报文携带的vlan id,如果报文不带vlan,则为0
//in_xbundle为报文入端口,其中包括端口上vlan相关的设置
static bool
input_vid_is_valid(const struct xlate_ctx *ctx,
                   uint16_t vid, struct xbundle *in_xbundle)
{
    /* Allow any VID on the OFPP_NONE port. */
    if (in_xbundle == &ofpp_none_bundle) {
        return true;
    }

    switch (in_xbundle->vlan_mode) {
     //access模式,如果报文带vlan,则drop
    case PORT_VLAN_ACCESS:
        if (vid) {
            xlate_report_error(ctx, "dropping VLAN %"PRIu16" tagged "
                               "packet received on port %s configured as VLAN "
                               "%d access port", vid, in_xbundle->name,
                               in_xbundle->vlan);
            return false;
        }
        return true;
    //native tagged和native untagged模式下,如果报文不带vlan,则接收此报文。
    //如果报文携带vlan,则就要判断此vlan是否在trunk vlan中。
    //trunk vlan中会自动加入native vlan
    case PORT_VLAN_NATIVE_UNTAGGED:
    case PORT_VLAN_NATIVE_TAGGED:
        if (!vid) {
            /* Port must always carry its native VLAN. */
            return true;
        }
        /* Fall through. */
    //native tagged,native untagged和trunk模式都会走此流程,判断
    //报文携带vlan是否在trunk vlan中
    case PORT_VLAN_TRUNK:
        if (!xbundle_trunks_vlan(in_xbundle, vid)) {
            xlate_report_error(ctx, "dropping VLAN %"PRIu16" packet "
                               "received on port %s not configured for "
                               "trunking VLAN %"PRIu16,
                               vid, in_xbundle->name, vid);
            return false;
        }
        return true;

    case PORT_VLAN_DOT1Q_TUNNEL:
        if (!xbundle_allows_cvlan(in_xbundle, vid)) {
            xlate_report_error(ctx, "dropping VLAN %"PRIu16" packet received "
                               "on dot1q-tunnel port %s that excludes this "
                               "VLAN", vid, in_xbundle->name);
            return false;
        }
        return true;

    default:
        OVS_NOT_REACHED();
    }

}

如果报文通过了input_vid_is_valid的检查,则调用xvlan_input_translate获取vlan信息,以便报文使用此vlan信息在后续流程中进行转发。

//in_xbundle为入端口
//in_xvlan为报文携带的vlan,如果报文不带vlan,则为空
//xvlan输出参数
static void
xvlan_input_translate(const struct xbundle *in_xbundle,
                      const struct xvlan *in_xvlan, struct xvlan *xvlan)
{

    switch (in_xbundle->vlan_mode) {
    //access模式下,使用in_xbundle->vlan,此vlan为用户配置。
    case PORT_VLAN_ACCESS:
        memset(xvlan, 0, sizeof(*xvlan));
        xvlan->v[0].tpid = in_xvlan->v[0].tpid ? in_xvlan->v[0].tpid :
                                                 ETH_TYPE_VLAN_8021Q;
        xvlan->v[0].vid = in_xbundle->vlan;
        xvlan->v[0].pcp = in_xvlan->v[0].pcp;
        break;
    //trunk模式下,使用in_xvlan即可
    case PORT_VLAN_TRUNK:
        xvlan_copy(xvlan, in_xvlan);
        break;
    //native_tagged和native_untagged模式下,也使用in_xvlan,
    //如果in_xvlan为空,则说明报文不带vlan,使用native vlan
    case PORT_VLAN_NATIVE_UNTAGGED:
    case PORT_VLAN_NATIVE_TAGGED:
        xvlan_copy(xvlan, in_xvlan);
        if (!in_xvlan->v[0].vid) {
            xvlan->v[0].tpid = in_xvlan->v[0].tpid ? in_xvlan->v[0].tpid :
                                                     ETH_TYPE_VLAN_8021Q;
            xvlan->v[0].vid = in_xbundle->vlan;
            xvlan->v[0].pcp = in_xvlan->v[0].pcp;
        }
        break;

    case PORT_VLAN_DOT1Q_TUNNEL:
        xvlan_copy(xvlan, in_xvlan);
        xvlan_push_uninit(xvlan);
        xvlan->v[0].tpid = in_xbundle->qinq_ethtype;
        xvlan->v[0].vid = in_xbundle->vlan;
        xvlan->v[0].pcp = 0;
        break;

    default:
        OVS_NOT_REACHED();
    }
}

报文出方向vlan处理

经过fdb表后,如果找到出端口,则调用output_normal将报文从出端口发出,如果没有找到出端口,则将报文发送到所有的端口,最终也会调用output_normal。此函数中调用xvlan_output_translate根据出端口vlan模式选择最终的vlan信息。

//out_xbundle出端口
//xvlan在xvlan_input_translate输出的vlan信息
//out_xvlan最终使用的vlan,报文会携带此vlan
static void
xvlan_output_translate(const struct xbundle *out_xbundle,
                       const struct xvlan *xvlan, struct xvlan *out_xvlan)
{
    switch (out_xbundle->vlan_mode) {
    //access模式,则设置out_xvlan为0,即报文发出去时不携带vlan
    case PORT_VLAN_ACCESS:
        memset(out_xvlan, 0, sizeof(*out_xvlan));
        break;
    //trunk和native_tagged模式下,报文发出去时携带vlan
    case PORT_VLAN_TRUNK:
    case PORT_VLAN_NATIVE_TAGGED:
        xvlan_copy(out_xvlan, xvlan);
        break;
    //native_untagged模式下,如果报文out_xvlan中包含native vlan, 
    //则需要将此vlan去掉,即报文发出去时不携带vlan
    case PORT_VLAN_NATIVE_UNTAGGED:
        xvlan_copy(out_xvlan, xvlan);
        if (xvlan->v[0].vid == out_xbundle->vlan) {
            xvlan_pop(out_xvlan);
        }
        break;

    case PORT_VLAN_DOT1Q_TUNNEL:
        xvlan_copy(out_xvlan, xvlan);
        xvlan_pop(out_xvlan);
        break;

    default:
        OVS_NOT_REACHED();
    }
}

配置

和vlan相关的配置有如下几个参数,每种vlan mode需要的参数不一样,下面分别实践。
vlan_mode 为 access时,只需要tag参数。
vlan_mode 为trunk时,只需要trunks参数。
vlan_mode为native−tagged或者native−untagged时,同时需要tag和trunks。

vlanid是4095的报文 vlan报文格式_access

image.png

具体的命令如下

ovs-vsctl set port vetha tag=101
ovs-vsctl set port vetha vlan_mode=trunk
ovs-vsctl set port vetha trunks=101-102

也可参考:ovs vlan - 简书