默认情况下,每个端口为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。
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 - 简书