传统交换机的端口可以按照vlan可以划分为access、trunk和hybrid三类接口。 首先,我们先看OVS的VLAN实现原理,最后对比OVS与传统交换机的差异。
OVS中,数据面的转发流表都是从用户态下发的,所以流表生成的入口是upcall_actions函数(该函数不是upcall的总入口,由于层次比较多,以该函数作为分析的入口是合适的)。
1、xlate_actions函数
mirror_ingress_packet(&ctx);
do_xlate_actions(ofpacts, ofpacts_len, &ctx); //openflow流表转化为精确流表
if (ctx.error) {
goto exit;
}
2、do_xlate_actons函数
case OFPACT_OUTPUT:
xlate_output_action(ctx, ofpact_get_OUTPUT(a)->port, //normal规则也是output的一种
ofpact_get_OUTPUT(a)->max_len, true);
break;
3、xlate_output_action函数
static void
xlate_output_action(struct xlate_ctx *ctx,
ofp_port_t port, uint16_t max_len, bool may_packet_in)
{
ofp_port_t prev_nf_output_iface = ctx->nf_output_iface;
ctx->nf_output_iface = NF_OUT_DROP;
switch (port) {
case OFPP_IN_PORT:
compose_output_action(ctx, ctx->xin->flow.in_port.ofp_port, NULL);
break;
case OFPP_TABLE:
xlate_table_action(ctx, ctx->xin->flow.in_port.ofp_port,
0, may_packet_in, true);
break;
case OFPP_NORMAL:
xlate_normal(ctx); //normal规则流表转化为精确流表
break;
case OFPP_FLOOD:
flood_packets(ctx, false);
break;
case OFPP_ALL:
flood_packets(ctx, true);
break;
case OFPP_CONTROLLER:
execute_controller_action(ctx, max_len,
(ctx->in_group ? OFPR_GROUP
: ctx->in_action_set ? OFPR_ACTION_SET
: OFPR_ACTION),
0);
break;
case OFPP_NONE:
break;
case OFPP_LOCAL:
default:
if (port != ctx->xin->flow.in_port.ofp_port) {
compose_output_action(ctx, port, NULL);
} else {
xlate_report(ctx, "skipping output to input port");
}
break;
}
if (prev_nf_output_iface == NF_OUT_FLOOD) {
ctx->nf_output_iface = NF_OUT_FLOOD;
} else if (ctx->nf_output_iface == NF_OUT_DROP) {
ctx->nf_output_iface = prev_nf_output_iface;
} else if (prev_nf_output_iface != NF_OUT_DROP &&
ctx->nf_output_iface != NF_OUT_FLOOD) {
ctx->nf_output_iface = NF_OUT_MULTI;
}
}
4、xlate_normal函数
xlate_normal(struct xlate_ctx *ctx)
{
.......
/* Check VLAN. */
vid = vlan_tci_to_vid(flow->vlan_tci); //计算报文的vlan值
if (!input_vid_is_valid(vid, in_xbundle, ctx->xin->packet != NULL)) { //判断报文是否满足vlan要求,如果不满足则丢球
xlate_report(ctx, "disallowed VLAN VID for this input port, dropping");
return;
}
vlan = input_vid_to_vlan(in_xbundle, vid); //计算报文进入OVS桥之后的VLAN值,该VLAN会贯穿报文在OVS内处理的全流程
......
/* Determine output bundle. */ if (mcast_snooping_enabled(ctx->xbridge->ms) ...... } else { ovs_rwlock_rdlock(&ctx->xbridge->ml->rwlock); mac = mac_learning_lookup(ctx->xbridge->ml, flow->dl_dst, vlan); //根据目标mac和vlan值寻找目的端口 mac_port = mac ? mac_entry_get_port(ctx->xbridge->ml, mac) : NULL; ovs_rwlock_unlock(&ctx->xbridge->ml->rwlock); if (mac_port) { struct xlate_cfg *xcfg = ovsrcu_get(struct xlate_cfg *, &xcfgp); struct xbundle *mac_xbundle = xbundle_lookup(xcfg, mac_port); if (mac_xbundle && mac_xbundle != in_xbundle) { xlate_report(ctx, "forwarding to learned port"); output_normal(ctx, mac_xbundle, vlan); //找到目的端口,从该端口发送报文 } else if (!mac_xbundle) { xlate_report(ctx, "learned port is unknown, dropping"); } else { xlate_report(ctx, "learned port is input port, dropping"); } } else { xlate_report(ctx, "no learned MAC for destination, flooding"); xlate_normal_flood(ctx, in_xbundle, vlan); //没找到目的端口,flood报文 } }}
static bool
input_vid_is_valid(uint16_t vid, struct xbundle *in_xbundle, bool warn)
{
/* Allow any VID on the OFPP_NONE port. */
if (in_xbundle == &ofpp_none_bundle) {
return true;
}
switch (in_xbundle->vlan_mode) {
case PORT_VLAN_ACCESS:
if (vid) { //如果入端口为ACCESS口,且报文包含VLAN,那么丢弃该报文
if (warn) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
VLOG_WARN_RL(&rl, "dropping VLAN %"PRIu16" tagged "
"packet received on port %s configured as VLAN "
"%"PRIu16" access port", vid, in_xbundle->name,
in_xbundle->vlan);
}
return false;
}
return true;
case PORT_VLAN_NATIVE_UNTAGGED:
case PORT_VLAN_NATIVE_TAGGED:
if (!vid) { //如果端口类型为native-untagged和native-tagged,如果报文不包含VLAN,则接受该报文;如果包含VLAN,那么VLAN必须包含在端口的VLAN中。
/* Port must always carry its native VLAN. */
return true;
}
/* Fall through. */
case PORT_VLAN_TRUNK:
if (!xbundle_includes_vlan(in_xbundle, vid)) { //如果端口类型为trunk,如果报文不包含VLAN,则接受该报文;如果包含VLAN,那么VLAN必须包含在端口的VLAN中。
if (warn) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
VLOG_WARN_RL(&rl, "dropping VLAN %"PRIu16" packet "
"received on port %s not configured for trunking "
"VLAN %"PRIu16, vid, in_xbundle->name, vid);
}
return false;
}
return true;
default:
OVS_NOT_REACHED();
}
}
static uint16_t
input_vid_to_vlan(const struct xbundle *in_xbundle, uint16_t vid) //计算报文进入OVS交换机之后,报文的VLAN值
{
switch (in_xbundle->vlan_mode) {
case PORT_VLAN_ACCESS:
return in_xbundle->vlan; //如果端口是ACCESS口,则报文进入OVS交换机后,VLAN值=端口的VLAN值;
break;
case PORT_VLAN_TRUNK: //如果端口是trunk口,则报文的VLAN值不变;
return vid;
case PORT_VLAN_NATIVE_UNTAGGED:
case PORT_VLAN_NATIVE_TAGGED:
return vid ? vid : in_xbundle->vlan; //如果端口是native-tagged和native-untagged,当报文没有vlan时,报文的VLAN等于端口vlan,否则不变;
default:
OVS_NOT_REACHED();
}
}
5、output_normal函数
......
vid = output_vlan_to_vid(out_xbundle, vlan); //计算报文发出OVS交换机之后的VLAN值
......
old_tci = *flow_tci;
tci = htons(vid);
if (tci || out_xbundle->use_priority_tags) {
tci |= *flow_tci & htons(VLAN_PCP_MASK);
if (tci) {
tci |= htons(VLAN_CFI);
}
}
*flow_tci = tci;
compose_output_action(ctx, xport->ofp_port, use_recirc ? &xr : NULL); //生成流表
static uint16_t
output_vlan_to_vid(const struct xbundle *out_xbundle, uint16_t vlan)
{
switch (out_xbundle->vlan_mode) {
case PORT_VLAN_ACCESS: //access端口,报文没有vlan
return 0;
case PORT_VLAN_TRUNK:
case PORT_VLAN_NATIVE_TAGGED:
return vlan; //trunk和native-tagged端口,出口报文的VLAN等于过程中的VLAN值
case PORT_VLAN_NATIVE_UNTAGGED: //native-untagged端口,如果vlan值等于端口的vlan值,那么剥掉vlan值,否则保留vlan值
return vlan == out_xbundle->vlan ? 0 : vlan;
default:
OVS_NOT_REACHED();
}
}
6、xlate_normal_flood函数
static void
xlate_normal_flood(struct xlate_ctx *ctx, struct xbundle *in_xbundle,
uint16_t vlan)
{
struct xbundle *xbundle;
LIST_FOR_EACH (xbundle, list_node, &ctx->xbridge->xbundles) {
if (xbundle != in_xbundle
&& xbundle_includes_vlan(xbundle, vlan) //端口包含该vlan值
&& xbundle->floodable
&& !xbundle_mirror_out(ctx->xbridge, xbundle)) {
output_normal(ctx, xbundle, vlan);
}
}
ctx->nf_output_iface = NF_OUT_FLOOD;
}
下一篇将系统总结下,OVS交换机的总体行为,并和标准交换机进行对比。