vswitchd: Basic working VLAN support.
authorBen Pfaff <blp@nicira.com>
Thu, 18 Dec 2008 22:00:59 +0000 (14:00 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 18 Dec 2008 22:01:39 +0000 (14:01 -0800)
vswitchd/bridge.c

index 775bbd7dae7c590ccf29a78d38c1d4efd7077133..d6ce3ebb54df7a5f21ef60f71d42ee82bf665b84 100644 (file)
@@ -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