传统交换机的端口可以按照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; }
case OFPACT_OUTPUT: xlate_output_action(ctx, ofpact_get_OUTPUT(a)->port, //normal规则也是output的一种 ofpact_get_OUTPUT(a)->max_len, true); break;
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; } }
<pre name="code" class="cpp">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,<span style="font-family: Arial, Helvetica, sans-serif;">如果报文不包含VLAN,则接受该报文;如果包含VLAN,那么VLAN必须包含在端口的VLAN中。</span> 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(); } }
...... 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(); } }
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; }