From: Ben Pfaff Date: Thu, 18 Dec 2008 22:00:59 +0000 (-0800) Subject: vswitchd: Basic working VLAN support. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f24cca57f1a55f95d571c46026ee60c34b5c23a2;p=openvswitch vswitchd: Basic working VLAN support. --- diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 775bbd7d..d6ce3ebb 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -717,80 +717,258 @@ queue_tx(struct bridge *br, struct ofpbuf *msg) } } +struct output { + uint16_t vlan; + uint16_t dp_ifidx; +}; + +static void +set_output(struct output *p, const struct flow *flow, + const struct port *in_port, const struct port *out_port) +{ + p->vlan = (out_port->vlan ? OFP_VLAN_NONE + : in_port->vlan ? in_port->vlan + : ntohs(flow->dl_vlan)); + p->dp_ifidx = out_port->ifaces[0]->dp_ifidx; +} + +static void * +add_action_header(struct ofpbuf *buf, uint16_t type) +{ + struct ofp_action_header *oah = ofpbuf_put_zeros(buf, sizeof *oah); + oah->type = htons(type); + oah->len = htons(sizeof *oah); + return oah; +} + +static void +add_output_action(struct ofpbuf *buf, uint16_t dp_ifidx) +{ + struct ofp_action_output *oao = add_action_header(buf, OFPAT_OUTPUT); + oao->port = htons(dp_ifidx); + oao->max_len = htons(0); +} + +static void +add_vlan_action(struct ofpbuf *buf, uint16_t old_vlan, uint16_t new_vlan) +{ + assert(old_vlan != new_vlan); + if (new_vlan == htons(OFP_VLAN_NONE)) { + add_action_header(buf, OFPAT_STRIP_VLAN); + } else { + struct ofp_action_vlan_vid *oavv + = add_action_header(buf, OFPAT_SET_VLAN_VID); + oavv->vlan_vid = htons(new_vlan); + } +} + +static void +put_actions(const struct bridge *br, const struct flow *flow, uint16_t vlan, + const struct port *in_port, const struct port *out_port, + struct ofpbuf *buf, void **actions, size_t *actions_len) +{ + struct output outs[DP_MAX_PORTS]; + struct output *p; + size_t n_outs; + uint16_t orig_vlan, cur_vlan; + size_t actions_ofs; + + if (*actions) { + ofpbuf_put(buf, *actions, *actions_len); + return; + } + + n_outs = 0; + if (out_port) { + /* Unicast. */ + set_output(&outs[n_outs++], flow, in_port, out_port); + } else { + /* Flood. */ + size_t i; + + for (i = 0; i < br->n_ports; i++) { + struct port *op = br->ports[i]; + if (op != in_port && (!op->vlan || vlan == op->vlan)) { + set_output(&outs[n_outs++], flow, in_port, op); + } + } + } + + actions_ofs = buf->size; + + orig_vlan = ntohs(flow->dl_vlan); + for (p = outs; p < &outs[n_outs]; p++) { + if (orig_vlan == p->vlan) { + add_output_action(buf, p->dp_ifidx); + } + } + cur_vlan = orig_vlan; + for (p = outs; p < &outs[n_outs]; p++) { + if (p->vlan != orig_vlan) { + if (p->vlan != cur_vlan) { + add_vlan_action(buf, cur_vlan, p->vlan); + cur_vlan = p->vlan; + } + add_output_action(buf, p->dp_ifidx); + } + } + + *actions = (char *) buf->data + actions_ofs; + *actions_len = buf->size - actions_ofs; +} + +static void +send_packets(struct bridge *br, const struct flow *flow, uint32_t buffer_id, + uint16_t vlan, uint16_t in_ifidx, + const void *pkt_data, size_t pkt_len, + const struct port *in_port, const struct port *out_port, + bool setup_flow) +{ + struct ofpbuf *fbuf = NULL; + struct ofpbuf *pbuf = NULL; + void *actions = NULL; + size_t actions_len = 0; + + if (setup_flow) { + fbuf = make_add_flow(flow, buffer_id, br->flow_idle_time, + sizeof(struct ofp_action_header) * 4); + put_actions(br, flow, vlan, in_port, out_port, fbuf, + &actions, &actions_len); + update_openflow_length(fbuf); + + /* Defer sending 'fbuf' to the end of the function: sending it can free + * it immediately, but we may need to reuse the cached actions for + * 'pbuf', below ('actions' points inside 'fbuf'). */ + } + + if (!setup_flow || buffer_id == UINT32_MAX) { + struct ofp_packet_out *opo; + + pbuf = ofpbuf_new(sizeof *opo + actions_len + pkt_len); + opo = put_openflow(sizeof *opo, OFPT_PACKET_OUT, pbuf); + opo->buffer_id = htonl(buffer_id); + opo->in_port = htons(in_ifidx); + opo->actions_len = htons(actions_len); + put_actions(br, flow, vlan, in_port, out_port, pbuf, + &actions, &actions_len); + if (buffer_id == UINT32_MAX) { + ofpbuf_put(pbuf, pkt_data, pkt_len); + } + update_openflow_length(pbuf); + queue_tx(br, pbuf); + } + + if (fbuf) { + queue_tx(br, fbuf); + } + if (pbuf) { + queue_tx(br, pbuf); + } +} + static void process_packet_in(struct bridge *br, void *opi_) { struct ofp_packet_in *opi = opi_; - uint16_t in_port = ntohs(opi->in_port); - uint16_t out_port = OFPP_FLOOD; + uint16_t in_ifidx = ntohs(opi->in_port); + uint16_t out_ifidx; - size_t pkt_len; struct ofpbuf pkt; struct flow flow; + struct iface *ifa; + struct port *in_port, *out_port; + int vlan; if (check_ofp_message_array(&opi->header, OFPT_PACKET_IN, offsetof(struct ofp_packet_in, data), - 1, &pkt_len)) { + 1, &pkt.size)) { return; } + /* Find the interface and port structure for the received packet. */ + if (in_ifidx < 0 || in_ifidx >= ARRAY_SIZE(br->ifaces) + || !br->ifaces[in_ifidx]) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "bridge %s: received packet on unknown " + "interface %"PRIu16, br->name, in_ifidx); + goto drop; + } + ifa = br->ifaces[in_ifidx]; + in_port = ifa->port; + /* Extract flow data from 'opi' into 'flow'. */ pkt.data = opi->data; - pkt.size = pkt_len; - flow_extract(&pkt, in_port, &flow); + flow_extract(&pkt, in_ifidx, &flow); + + /* Figure out what VLAN this packet belongs to. + * + * Note that dl_vlan of 0 and of OFP_VLAN_NONE both mean that the packet + * belongs to VLAN 0, so we should treat both cases identically. (In the + * former case, the packet has an 802.1Q header that specifies VLAN 0, + * presumably to allow a priority to be specified. In the latter case, the + * packet does not have any 802.1Q header.) */ + vlan = ntohs(flow.dl_vlan); + if (vlan == OFP_VLAN_NONE) { + vlan = 0; + } + if (in_port->vlan) { + if (vlan) { + /* XXX support double tagging? */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %"PRIu16" tagged " + "packet received on port %s configured with implicit " + "VLAN %"PRIu16, + br->name, ntohs(flow.dl_vlan), + in_port->name, in_port->vlan); + goto drop; + } + vlan = in_port->vlan; + } + /* MAC learning. */ + out_port = NULL; if (br->ml) { - if (mac_learning_learn(br->ml, flow.dl_src, in_port)) { + uint16_t out_port_idx; + /* XXX flush learning table entries when port indexes change due to + * reconfiguration */ + if (mac_learning_learn(br->ml, flow.dl_src, vlan, in_port->port_idx)) { /* The log messages here could actually be useful in debugging, so * keep the rate limit relatively high. */ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); VLOG_DBG_RL(&rl, "bridge %s: learned that "ETH_ADDR_FMT" is on " - "port %"PRIu16, - br->name, ETH_ADDR_ARGS(flow.dl_src), in_port); + "port %s in VLAN %d", + br->name, ETH_ADDR_ARGS(flow.dl_src), + in_port->name, vlan); + } + out_port_idx = mac_learning_lookup(br->ml, flow.dl_dst, vlan); + if (out_port_idx < br->n_ports) { + out_port = br->ports[out_port_idx]; } } - if (br->ml) { - out_port = mac_learning_lookup(br->ml, flow.dl_dst); - } - - if (in_port == out_port) { - /* Don't send out packets on their input ports. */ - goto drop_it; - } else if (br->flow_idle_time >= 0 - && (!br->ml || out_port != OFPP_FLOOD)) { - /* The output port is known, or we always flood everything, so add a - * new flow. */ - queue_tx(br, make_add_simple_flow(&flow, ntohl(opi->buffer_id), - out_port, br->flow_idle_time)); - - /* If the switch didn't buffer the packet, we need to send a copy. */ - if (ntohl(opi->buffer_id) == UINT32_MAX) { - queue_tx(br, - make_unbuffered_packet_out(&pkt, in_port, out_port)); - } + /* Send it out. */ + out_ifidx = out_port ? out_port->ifaces[0]->dp_ifidx : OFPP_FLOOD; + if (in_port != out_port) { + /* Add a new flow. */ + send_packets(br, &flow, ntohl(opi->buffer_id), vlan, + in_ifidx, pkt.data, pkt.size, in_port, out_port, + br->flow_idle_time >= 0); } else { - /* We don't know that MAC, or we don't set up flows. Send along the - * packet without setting up a flow. */ - struct ofpbuf *b; - if (ntohl(opi->buffer_id) == UINT32_MAX) { - b = make_unbuffered_packet_out(&pkt, in_port, out_port); - } else { - b = make_buffered_packet_out(ntohl(opi->buffer_id), - in_port, out_port); - } - queue_tx(br, b); + /* Don't send out packets on their input ports. */ + goto drop; } return; -drop_it: - /* Set up a flow to drop packets, or just drop the packet if we don't set - * up flows at all. */ +drop: if (br->flow_idle_time >= 0) { + /* Set up a flow to drop packets. */ queue_tx(br, make_add_flow(&flow, ntohl(opi->buffer_id), br->flow_idle_time, 0)); + } else { + /* Just drop the packet, since we don't set up flows at all. + * XXX we should send a packet_out with no actions if buffer_id != + * UINT32_MAX, to avoid clogging the kernel buffers. */ } - return; } static void @@ -825,6 +1003,7 @@ port_create(struct bridge *br, const char *name) static void port_reconfigure(struct port *port) { + bool bonded = cfg_has_section("bonding.%s", port->name); struct svec old_ifaces, new_ifaces; size_t i; @@ -833,7 +1012,7 @@ port_reconfigure(struct port *port) for (i = 0; i < port->n_ifaces; i++) { svec_add(&old_ifaces, port->ifaces[i]->name); } - if (cfg_has_section("bonding.%s", port->name)) { + if (bonded) { cfg_get_all_keys(&new_ifaces, "bonding.%s.slave", port->name); } else { svec_init(&new_ifaces); @@ -855,6 +1034,28 @@ port_reconfigure(struct port *port) iface_create(port, name); } } + + /* Get VLAN tag. */ + port->vlan = 0; + if (cfg_has("vlan.%s.tag", port->name)) { + if (!bonded) { + int vlan = cfg_get_int(0, "vlan.%s.tag", port->name); + if (port->vlan >= 0 && port->vlan <= 4095) { + VLOG_DBG("port %s: assigning VLAN tag %d", + port->name, port->vlan); + port->vlan = vlan; + } else { + VLOG_WARN("port %s: ignoring VLAN tag %d because it is not in " + "the valid range from 0 to 4095", + port->name, port->vlan); + } + } else { + /* It's possible that bonded, VLAN-tagged ports make sense but they + * are not worth implementing yet. */ + VLOG_WARN("port %s: VLAN tags not supported on bonded ports", + port->name); + } + } } static void