#include "dirs.h"
#include "dpif.h"
#include "flow.h"
+#include "flowtrack.h"
#include "hash.h"
#include "list.h"
#include "mac-learning.h"
int txqlen; /* # of messages queued to send on 'rconn'. */
struct mac_learning *ml; /* MAC learning table, or null not to learn. */
int flow_idle_time; /* Idle time for flows we set up. */
+ bool sent_config; /* Successfully sent config request? */
/* Kernel datapath information. */
int dp_idx; /* Kernel datapath index. */
/* Bridge ports. */
struct port **ports;
size_t n_ports, allocated_ports;
+
+ /* Flow tracking. */
+ struct ft *ft;
+ struct tag_set revalidate_set;
};
/* List of all bridges. */
static int bridge_fetch_dp_ifaces(struct bridge *, struct svec *iface_names);
static void bridge_process_msg(struct bridge *, struct ofpbuf *);
+static void revalidate_flow(struct bridge *br, struct ft_flow *f);
static void port_create(struct bridge *, const char *name);
static void port_reconfigure(struct port *);
br->name = xstrdup(name);
br->ml = mac_learning_create();
br->flow_idle_time = 5;
+ br->sent_config = false;
+ br->ft = ft_create();
/* Create kernel datapath. */
for (;;) {
for (i = 0; i < br->n_ports; i++) {
port_destroy(br->ports[i]);
}
+ ft_destroy(br->ft);
mac_learning_destroy(br->ml);
free(br->ports);
free(br);
int iteration;
rconn_run(br->rconn);
+ if (rconn_is_connected(br->rconn) && !br->sent_config) {
+ struct ofp_switch_config *osc;
+ struct ofpbuf *msg;
+ int retval;
+
+ osc = make_openflow(sizeof *osc, OFPT_SET_CONFIG, &msg);
+ osc->flags = htons(OFPC_SEND_FLOW_EXP | OFPC_FRAG_NORMAL);
+ osc->miss_send_len = htons(OFP_DEFAULT_MISS_SEND_LEN);
+ retval = rconn_send(br->rconn, msg, &br->txqlen);
+ if (retval) {
+ ofpbuf_delete(msg);
+ } else {
+ br->sent_config = true;
+ }
+ }
+
+ tag_set_init(&br->revalidate_set);
for (iteration = 0; iteration < 50 && !bridge_is_backlogged(br);
iteration++) {
struct ofpbuf *msg = rconn_recv(br->rconn);
bridge_process_msg(br, msg);
ofpbuf_delete(msg);
}
+ hmap_shrink(&br->ft->flows);
if (br->ml) {
mac_learning_run(br->ml, &br->revalidate_set);
}
+ if (!tag_set_is_empty(&br->revalidate_set)) {
+ struct ft_flow *f, *next;
+
+ HMAP_FOR_EACH_SAFE (f, next, struct ft_flow, node, &br->ft->flows) {
+ if (tag_set_intersects(&br->revalidate_set, f->tags)) {
+ revalidate_flow(br, f);
+ }
+ }
+ }
+
if (process_exited(br->secchan)) {
int status = process_status(br->secchan);
VLOG_ERR("%s: secchan subprocess with pid %ld died unexpectedly (%s)",
typedef void packet_handler_func(struct bridge *, void *);
static packet_handler_func process_echo_request;
static packet_handler_func process_packet_in;
+static packet_handler_func process_flow_expired;
static void
bridge_process_msg(struct bridge *br, struct ofpbuf *msg)
},
{
OFPT_FLOW_EXPIRED,
- NULL
+ process_flow_expired
},
};
const size_t n_processors = ARRAY_SIZE(processors);
static void
queue_tx(struct bridge *br, struct ofpbuf *msg)
{
- int retval = rconn_send(br->rconn, msg, &br->txqlen);
+ int retval;
+ update_openflow_length(msg);
+ retval = rconn_send(br->rconn, msg, &br->txqlen);
if (retval) {
ofpbuf_delete(msg);
/* No point in logging: rconn_send() only fails due to disconnection,
}
}
-struct output {
- uint16_t vlan;
- uint16_t dp_ifidx;
-};
-
static struct iface *
choose_output_iface(const struct port *port, const struct flow *flow)
{
}
static void
-set_output(struct output *p, const struct flow *flow,
- const struct port *in_port, const struct port *out_port)
+set_dst(struct ft_dst *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
p->dp_ifidx = choose_output_iface(out_port, flow)->dp_ifidx;
}
+static void
+swap_dst(struct ft_dst *p, struct ft_dst *q)
+{
+ struct ft_dst tmp = *p;
+ *p = *q;
+ *q = tmp;
+}
+
static void *
add_action_header(struct ofpbuf *buf, uint16_t type)
{
}
}
-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)
+static size_t
+compose_dsts(const struct bridge *br, const struct flow *flow, uint16_t vlan,
+ const struct port *in_port, const struct port *out_port,
+ struct ft_dst dsts[])
{
- 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 == FLOOD_PORT) {
/* Flood. */
+ struct ft_dst *dst; /* Next element in 'dsts'. */
+ struct ft_dst *vlan_dsts; /* First 'dsts' with vlan != flow->dl_vlan */
size_t i;
+ dst = vlan_dsts = dsts;
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);
+ struct port *port = br->ports[i];
+ if (port != in_port && (!port->vlan || vlan == port->vlan)) {
+ /* Put destinations for original VLAN at the front, so that we
+ * don't have to add actions to set the VLAN tag for those. */
+ set_dst(dst, flow, in_port, port);
+ if (dst->vlan == ntohs(flow->dl_vlan)) {
+ swap_dst(dst, vlan_dsts++);
+ }
+ dst++;
}
}
+
+ /* XXX Sort 'vlan_dsts' through 'dst' by VLAN, to reduce number of
+ * VLAN-setting actions to minimum. */
+ return dst - dsts;
} else if (out_port) {
/* Unicast. */
- set_output(&outs[n_outs++], flow, in_port, out_port);
+ set_dst(dsts, flow, in_port, out_port);
+ return 1;
+ } else {
+ /* Drop. */
+ return 0;
}
+}
- 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);
+static void
+put_actions(const struct ft_dst dsts[], size_t n_dsts, uint16_t flow_vlan,
+ struct ofpbuf *buf)
+{
+ const struct ft_dst *p;
+ uint16_t vlan = flow_vlan;
+ for (p = dsts; p < &dsts[n_dsts]; p++) {
+ if (p->vlan != vlan) {
+ add_vlan_action(buf, vlan, p->vlan);
+ vlan = p->vlan;
}
+ add_output_action(buf, p->dp_ifidx);
}
-
- *actions = (char *) buf->data + actions_ofs;
- *actions_len = buf->size - actions_ofs;
}
+struct received_packet {
+ struct ofpbuf *buf; /* NULL if no packet data. */
+ uint32_t buffer_id; /* Host byte order; UINT32_MAX if not buffered. */
+};
+
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,
+send_packets(struct bridge *br, const struct flow *flow,
+ const struct received_packet *pkt, uint16_t vlan,
const struct port *in_port, const struct port *out_port,
- bool setup_flow)
+ tag_type tags, bool setup_flow)
{
- struct ofpbuf *fbuf = NULL;
- struct ofpbuf *pbuf = NULL;
+ struct ft_dst dsts[DP_MAX_PORTS];
+ size_t actions_len; /* Estimated length of actions, in bytes. */
+ size_t n_dsts;
- void *actions = NULL;
- size_t actions_len = sizeof(struct ofp_action_header) * 4; /* Estimated. */
+ n_dsts = compose_dsts(br, flow, vlan, in_port, out_port, dsts);
+ actions_len = (sizeof(struct ofp_action_header) + 2) * n_dsts;
if (setup_flow) {
- fbuf = make_add_flow(flow, buffer_id, br->flow_idle_time, actions_len);
- put_actions(br, flow, vlan, in_port, out_port, fbuf,
- &actions, &actions_len);
- update_openflow_length(fbuf);
+ struct ft_flow *f;
+ bool queue;
+
+ f = ft_lookup(br->ft, flow);
+ if (f) {
+ if (!ftd_equal(dsts, n_dsts, f->dsts, f->n_dsts)) {
+ ftf_set_dsts(f, dsts, n_dsts);
+ queue = true;
+ } else {
+ /* Correct flow is already in the flow table, nothing to do.
+ * This should only happen on revalidate, since
+ * process_packet_in() will delete any flow entry for a
+ * no-match packet-in message. (If it could happen in other
+ * circumstances then we'd want to arrange to send a packet-out
+ * below, but there's no need.) */
+ queue = false;
+ }
+ f->tags = tags;
+ } else {
+ ft_insert(br->ft, ftf_create(flow, dsts, n_dsts, tags));
+ queue = true;
+ }
- /* 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 (queue) {
+ struct ofpbuf *fbuf = make_add_flow(flow, pkt->buffer_id,
+ br->flow_idle_time,
+ actions_len);
+ put_actions(dsts, n_dsts, ntohs(flow->dl_vlan), fbuf);
+ queue_tx(br, fbuf);
+ }
+ } else {
+ struct ft_flow *f = ft_lookup(br->ft, flow);
+ if (f) {
+ /* XXX delete flow from ft, queue delete-flow openflow message */
+ }
}
- if (!setup_flow || buffer_id == UINT32_MAX) {
+ if (pkt->buffer_id == UINT32_MAX ? pkt->buf != NULL : !setup_flow) {
+ size_t pkt_size = pkt->buf ? pkt->buf->size : 0;
struct ofp_packet_out *opo;
+ struct ofpbuf *pbuf;
- pbuf = ofpbuf_new(sizeof *opo + actions_len + pkt_len);
+ pbuf = ofpbuf_new(sizeof *opo + actions_len + pkt_size);
opo = put_openflow(sizeof *opo, OFPT_PACKET_OUT, pbuf);
- opo->buffer_id = htonl(buffer_id);
- opo->in_port = htons(in_ifidx);
- put_actions(br, flow, vlan, in_port, out_port, pbuf,
- &actions, &actions_len);
+ opo->buffer_id = htonl(pkt->buffer_id);
+ opo->in_port = flow->in_port;
+ put_actions(dsts, n_dsts, ntohs(flow->dl_vlan), pbuf);
opo = pbuf->data;
opo->actions_len = htons(actions_len);
- if (buffer_id == UINT32_MAX) {
- ofpbuf_put(pbuf, pkt_data, pkt_len);
+ if (pkt->buffer_id == UINT32_MAX) {
+ ofpbuf_put(pbuf, pkt->buf->data, pkt->buf->size);
}
- update_openflow_length(pbuf);
- }
-
- if (fbuf) {
- queue_tx(br, fbuf);
- }
- if (pbuf) {
queue_tx(br, pbuf);
}
}
}
static void
-process_packet_in(struct bridge *br, void *opi_)
+process_flow(struct bridge *br, const struct flow *flow,
+ struct received_packet *pkt)
{
- struct ofp_packet_in *opi = opi_;
- uint16_t in_ifidx = ntohs(opi->in_port);
-
- struct ofpbuf pkt;
- struct flow flow;
+ uint16_t in_ifidx = ntohs(flow->in_port);
struct iface *in_iface;
struct port *in_port;
struct port *out_port = NULL; /* By default, drop the packet/flow. */
+ tag_type tags = 0;
int vlan;
- /* Validate Openflow message. */
- if (check_ofp_message_array(&opi->header, OFPT_PACKET_IN,
- offsetof(struct ofp_packet_in, data),
- 1, &pkt.size)) {
- return;
- }
+ /* Find the interface and port structure for the received packet. */
+ if (in_ifidx >= ARRAY_SIZE(br->ifaces) || !br->ifaces[in_ifidx]) {
+ struct ft_flow *f;
- /* Extract flow data from 'opi' into 'flow'. */
- pkt.data = opi->data;
- flow_extract(&pkt, in_ifidx, &flow);
+ if (pkt->buf) {
+ 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);
+ }
- /* 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);
- queue_tx(br, make_add_flow(&flow, ntohl(opi->buffer_id),
+ queue_tx(br, make_add_flow(flow, pkt->buffer_id,
br->flow_idle_time, 0));
+
+ f = ft_lookup(br->ft, flow);
+ if (f) {
+ ftf_set_dsts(f, NULL, 0);
+ f->tags = tags;
+ } else {
+ ft_insert(br->ft, ftf_create(flow, NULL, 0, tags));
+ }
+
return;
}
in_iface = br->ifaces[in_ifidx];
* 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);
+ 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);
+ if (pkt->buf) {
+ 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 done;
}
vlan = in_port->vlan;
* avoid receiving duplicates. */
if (in_port->n_ifaces > 0
&& in_port->active_iface != in_iface->port_ifidx
- && eth_addr_is_multicast(flow.dl_dst)) {
+ && eth_addr_is_multicast(flow->dl_dst)) {
+ //tags |= in_iface->inactive_iface_tag;
goto done;
}
/* XXX flush learning table entries when port indexes change due to
* reconfiguration */
- /* If the packet arrived on a bonded port, don't learn from it unless
- * we haven't learned any port at all for that address (because we
- * probably sent the packet on one bonded interface and got it back on
- * the other). */
- if (in_port->n_ifaces > 1) {
- uint16_t src_idx = mac_learning_lookup(br->ml, flow.dl_src, vlan);
+ if (!pkt->buf) {
+ /* Don't try to learn from revalidation. */
+ may_learn = false;
+ } else if (in_port->n_ifaces > 1) {
+ /* If the packet arrived on a bonded port, don't learn from it
+ * unless we haven't learned any port at all for that address
+ * (because we probably sent the packet on one bonded interface and
+ * got it back on the other). */
+ /* XXX invalidation? */
+ uint16_t src_idx = mac_learning_lookup(br->ml, flow->dl_src, vlan);
may_learn = src_idx == OFPP_FLOOD || src_idx == in_port->port_idx;
/* Broadcast ARP replies are an exception to this rule: the host
* has moved to another switch. */
- if (!may_learn && is_bcast_arp_reply(&flow, &pkt)) {
+ if (!may_learn && is_bcast_arp_reply(flow, pkt->buf)) {
may_learn = true;
}
} else {
}
/* Learn source MAC. */
- if (may_learn &&
- 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 %s in VLAN %d",
- br->name, ETH_ADDR_ARGS(flow.dl_src),
- in_port->name, vlan);
+ if (may_learn) {
+ tag_type rev_tag = mac_learning_learn(br->ml, flow->dl_src,
+ vlan, in_port->port_idx);
+ if (rev_tag) {
+ /* 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 %s in VLAN %d",
+ br->name, ETH_ADDR_ARGS(flow->dl_src),
+ in_port->name, vlan);
+ tag_set_add(&br->revalidate_set, rev_tag);
+ }
}
/* Determine output port. */
- out_port_idx = mac_learning_lookup(br->ml, flow.dl_dst, vlan);
+ out_port_idx = mac_learning_lookup_tag(br->ml, flow->dl_dst, vlan,
+ &tags);
if (out_port_idx < br->n_ports) {
out_port = br->ports[out_port_idx];
}
* them.
*/
done:
- send_packets(br, &flow, ntohl(opi->buffer_id), vlan,
- in_ifidx, pkt.data, pkt.size, in_port, out_port,
+ send_packets(br, flow, pkt, vlan, in_port, out_port, tags,
(br->flow_idle_time >= 0
&& (in_port->n_ifaces < 2
- || flow.dl_type != htons(ETH_TYPE_ARP)
- || !eth_addr_is_broadcast(flow.dl_dst))));
+ || flow->dl_type != htons(ETH_TYPE_ARP)
+ || !eth_addr_is_broadcast(flow->dl_dst))));
+}
+
+static void
+revalidate_flow(struct bridge *br, struct ft_flow *f)
+{
+ struct received_packet pkt;
+ pkt.buf = NULL;
+ pkt.buffer_id = UINT32_MAX;
+ process_flow(br, &f->flow, &pkt);
+}
+
+static void
+process_packet_in(struct bridge *br, void *opi_)
+{
+ struct ofp_packet_in *opi = opi_;
+ struct received_packet pkt;
+ struct ofpbuf buf;
+ struct flow flow;
+
+ if (check_ofp_message_array(&opi->header, OFPT_PACKET_IN,
+ offsetof(struct ofp_packet_in, data),
+ 1, &buf.size)) {
+ return;
+ }
+ buf.data = opi->data;
+ pkt.buf = &buf;
+ pkt.buffer_id = ntohl(opi->buffer_id);
+ flow_extract(&buf, ntohs(opi->in_port), &flow);
+
+ if (opi->reason == OFPR_NO_MATCH) {
+ /* Delete any existing flow from the flow table. It must not really be
+ * there (otherwise we wouldn't be getting a packet-in). */
+ struct ft_flow *f = ft_lookup(br->ft, &flow);
+ if (f) {
+ ft_remove(br->ft, f);
+ ftf_destroy(f);
+ }
+ }
+
+ process_flow(br, &flow, &pkt);
}
static void
struct ofp_header *rq = rq_;
queue_tx(br, make_echo_reply(rq));
}
+
+static void
+process_flow_expired(struct bridge *br, void *ofe_)
+{
+ struct ofp_flow_expired *ofe = ofe_;
+ struct ft_flow *f;
+ struct flow flow;
+
+ if (check_ofp_message(&ofe->header, OFPT_FLOW_EXPIRED, sizeof *ofe)) {
+ return;
+ }
+
+ if (ofe->match.wildcards != htonl(0)) {
+ /* We don't use flows with wildcards, so there's nothing to do. */
+ return;
+ }
+ flow.nw_src = ofe->match.nw_src;
+ flow.nw_dst = ofe->match.nw_dst;
+ flow.in_port = ofe->match.in_port;
+ flow.dl_vlan = ofe->match.dl_vlan;
+ flow.dl_type = ofe->match.dl_type;
+ flow.tp_src = ofe->match.tp_src;
+ flow.tp_dst = ofe->match.tp_dst;
+ memcpy(flow.dl_src, ofe->match.dl_src, ETH_ADDR_LEN);
+ memcpy(flow.dl_dst, ofe->match.dl_dst, ETH_ADDR_LEN);
+ flow.nw_proto = ofe->match.nw_proto;
+ flow.reserved = 0;
+
+ f = ft_lookup(br->ft, &flow);
+ if (f) {
+ ft_remove(br->ft, f);
+ ftf_destroy(f);
+ } else if (VLOG_IS_DBG_ENABLED()) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+ VLOG_DBG_RL(&rl, "received flow expiration for flow not in table");
+ }
+}
\f
/* Port functions. */
}
if (bonded) {
cfg_get_all_keys(&new_ifaces, "bonding.%s.slave", port->name);
+ if (!new_ifaces.n) {
+ VLOG_ERR("port %s: no interfaces specified for bonded port",
+ port->name);
+ } else if (new_ifaces.n == 1) {
+ VLOG_WARN("port %s: only 1 interface specified for bonded port",
+ port->name);
+ }
} else {
svec_init(&new_ifaces);
svec_add(&new_ifaces, port->name);
port->name, port->vlan);
}
} else {
- /* It's possible that bonded, VLAN-tagged ports make sense but they
- * are not worth implementing yet. */
+ /* It's possible that bonded, VLAN-tagged ports make sense. Maybe
+ * they even work as-is. But they have not been tested. */
VLOG_WARN("port %s: VLAN tags not supported on bonded ports",
port->name);
}