X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=vswitchd%2Fbridge.c;h=dbcf31250f804fc9840d933f64f7340327eb71f1;hb=e4bfff8f0d2c01009759acb8adcab070327b5747;hp=69c50901afbb4ac09e02b3bbdf114c3c4a2100c6;hpb=0efaf4b50c99bfdc255a27bb55312744feb34cc6;p=sliver-openvswitch.git diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 69c50901a..dbcf31250 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -43,12 +43,14 @@ #include "odp-util.h" #include "ofp-print.h" #include "ofpbuf.h" +#include "ofproto/netflow.h" #include "ofproto/ofproto.h" #include "packets.h" #include "poll-loop.h" #include "port-array.h" #include "proc-net-compat.h" #include "process.h" +#include "shash.h" #include "socket-util.h" #include "stp.h" #include "svec.h" @@ -71,17 +73,18 @@ struct dst { extern uint64_t mgmt_id; struct iface { + /* These members are always valid. */ struct port *port; /* Containing port. */ size_t port_ifidx; /* Index within containing port. */ - char *name; /* Host network device name. */ - int dp_ifidx; /* Index within kernel datapath. */ - - uint8_t mac[ETH_ADDR_LEN]; /* Ethernet address (all zeros if unknowns). */ - tag_type tag; /* Tag associated with this interface. */ - bool enabled; /* May be chosen for flows? */ long long delay_expires; /* Time after which 'enabled' may change. */ + + /* These members are valid only after bridge_reconfigure() causes them to + * be initialized.*/ + int dp_ifidx; /* Index within kernel datapath. */ + struct netdev *netdev; /* Network device. */ + bool enabled; /* May be chosen for flows? */ }; #define BOND_MASK 0xff @@ -130,6 +133,7 @@ struct port { tag_type active_iface_tag; /* Tag for bcast flows. */ tag_type no_ifaces_tag; /* Tag for flows when all ifaces disabled. */ int updelay, downdelay; /* Delay before iface goes up/down, in ms. */ + bool bond_compat_is_stale; /* Need to call port_update_bond_compat()? */ /* Port mirroring info. */ mirror_mask_t src_mirrors; /* Mirrors triggered when packet received. */ @@ -145,7 +149,7 @@ struct port { struct bridge { struct list node; /* Node in global list of bridges. */ char *name; /* User-specified arbitrary name. */ - struct mac_learning *ml; /* MAC learning table, or null not to learn. */ + struct mac_learning *ml; /* MAC learning table. */ bool sent_config_request; /* Successfully sent config request? */ uint8_t default_ea[ETH_ADDR_LEN]; /* Default MAC. */ @@ -192,6 +196,7 @@ enum { DP_MAX = 256 }; static struct bridge *bridge_create(const char *name); static void bridge_destroy(struct bridge *); static struct bridge *bridge_lookup(const char *name); +static void bridge_unixctl_dump_flows(struct unixctl_conn *, const char *); static int bridge_run_one(struct bridge *); static void bridge_reconfigure_one(struct bridge *); static void bridge_reconfigure_controller(struct bridge *); @@ -200,10 +205,11 @@ static void bridge_fetch_dp_ifaces(struct bridge *); static void bridge_flush(struct bridge *); static void bridge_pick_local_hw_addr(struct bridge *, uint8_t ea[ETH_ADDR_LEN], - const char **devname); + struct iface **hw_addr_iface); static uint64_t bridge_pick_datapath_id(struct bridge *, const uint8_t bridge_ea[ETH_ADDR_LEN], - const char *devname); + struct iface *hw_addr_iface); +static struct iface *bridge_get_local_iface(struct bridge *); static uint64_t dpid_from_hash(const void *, size_t nbytes); static void bridge_unixctl_fdb_show(struct unixctl_conn *, const char *args); @@ -213,6 +219,7 @@ static void bond_run(struct bridge *); static void bond_wait(struct bridge *); static void bond_rebalance_port(struct port *); static void bond_send_learning_packets(struct port *); +static void bond_enable_slave(struct iface *iface, bool enable); static void port_create(struct bridge *, const char *name); static void port_reconfigure(struct port *); @@ -223,6 +230,7 @@ static struct port *port_from_dp_ifidx(const struct bridge *, uint16_t dp_ifidx); static void port_update_bond_compat(struct port *); static void port_update_vlan_compat(struct port *); +static void port_update_bonding(struct port *); static void mirror_create(struct bridge *, const char *name); static void mirror_destroy(struct mirror *); @@ -240,6 +248,8 @@ static void iface_destroy(struct iface *); static struct iface *iface_lookup(const struct bridge *, const char *name); static struct iface *iface_from_dp_ifidx(const struct bridge *, uint16_t dp_ifidx); +static bool iface_is_internal(const struct bridge *, const char *name); +static void iface_set_mac(struct iface *); /* Hooks into ofproto processing. */ static struct ofhooks bridge_ofhooks; @@ -282,6 +292,7 @@ bridge_init(void) unixctl_command_register("fdb/show", bridge_unixctl_fdb_show); + svec_init(&dpif_names); dp_enumerate(&dpif_names); for (i = 0; i < dpif_names.n; i++) { const char *dpif_name = dpif_names.names[i]; @@ -306,6 +317,9 @@ bridge_init(void) dpif_close(dpif); } } + svec_destroy(&dpif_names); + + unixctl_command_register("bridge/dump-flows", bridge_unixctl_dump_flows); bond_init(); bridge_reconfigure(); @@ -356,12 +370,163 @@ bridge_configure_ssl(void) } #endif +/* Attempt to create the network device 'iface_name' through the netdev + * library. */ +static int +set_up_iface(const char *iface_name, bool create) +{ + const char *type; + const char *arg; + struct svec arg_svec; + struct shash args; + int error; + size_t i; + + /* If a type is not explicitly declared, then assume it's an existing + * "system" device. */ + type = cfg_get_string(0, "iface.%s.type", iface_name); + if (!type || !strcmp(type, "system")) { + return 0; + } + + svec_init(&arg_svec); + cfg_get_subsections(&arg_svec, "iface.%s.args", iface_name); + + shash_init(&args); + SVEC_FOR_EACH (i, arg, &arg_svec) { + const char *value; + + value = cfg_get_string(0, "iface.%s.args.%s", iface_name, arg); + if (value) { + shash_add(&args, arg, xstrdup(value)); + } + } + + if (create) { + error = netdev_create(iface_name, type, &args); + } else { + /* xxx Check to make sure that the type hasn't changed. */ + error = netdev_reconfigure(iface_name, &args); + } + + svec_destroy(&arg_svec); + shash_destroy(&args); + + return error; +} + +static int +create_iface(const char *iface_name) +{ + return set_up_iface(iface_name, true); +} + +static int +reconfigure_iface(const char *iface_name) +{ + return set_up_iface(iface_name, false); +} + +static void +destroy_iface(const char *iface_name) +{ + netdev_destroy(iface_name); +} + + +/* iterate_and_prune_ifaces() callback function that opens the network device + * for 'iface', if it is not already open, and retrieves the interface's MAC + * address and carrier status. */ +static bool +init_iface_netdev(struct bridge *br UNUSED, struct iface *iface, + void *aux UNUSED) +{ + if (iface->netdev) { + return true; + } else if (!netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, + &iface->netdev)) { + netdev_get_carrier(iface->netdev, &iface->enabled); + return true; + } else { + /* If the network device can't be opened, then we're not going to try + * to do anything with this interface. */ + return false; + } +} + +static bool +check_iface_dp_ifidx(struct bridge *br, struct iface *iface, void *aux UNUSED) +{ + if (iface->dp_ifidx >= 0) { + VLOG_DBG("%s has interface %s on port %d", + dpif_name(br->dpif), + iface->name, iface->dp_ifidx); + return true; + } else { + VLOG_ERR("%s interface not in %s, dropping", + iface->name, dpif_name(br->dpif)); + return false; + } +} + +static bool +set_iface_properties(struct bridge *br UNUSED, struct iface *iface, + void *aux UNUSED) +{ + int rate, burst; + + /* Set policing attributes. */ + rate = cfg_get_int(0, "port.%s.ingress.policing-rate", iface->name); + burst = cfg_get_int(0, "port.%s.ingress.policing-burst", iface->name); + netdev_set_policing(iface->netdev, rate, burst); + + /* Set MAC address of internal interfaces other than the local + * interface. */ + if (iface->dp_ifidx != ODPP_LOCAL + && iface_is_internal(br, iface->name)) { + iface_set_mac(iface); + } + + return true; +} + +/* Calls 'cb' for each interfaces in 'br', passing along the 'aux' argument. + * Deletes from 'br' all the interfaces for which 'cb' returns false, and then + * deletes from 'br' any ports that no longer have any interfaces. */ +static void +iterate_and_prune_ifaces(struct bridge *br, + bool (*cb)(struct bridge *, struct iface *, + void *aux), + void *aux) +{ + size_t i, j; + + for (i = 0; i < br->n_ports; ) { + struct port *port = br->ports[i]; + for (j = 0; j < port->n_ifaces; ) { + struct iface *iface = port->ifaces[j]; + if (cb(br, iface, aux)) { + j++; + } else { + iface_destroy(iface); + } + } + + if (port->n_ifaces) { + i++; + } else { + VLOG_ERR("%s port has no interfaces, dropping", port->name); + port_destroy(port); + } + } +} + void bridge_reconfigure(void) { struct svec old_br, new_br; struct bridge *br, *next; - size_t i, j; + size_t i; COVERAGE_INC(bridge_reconfigure); @@ -424,6 +589,7 @@ bridge_reconfigure(void) p->devname, dpif_name(br->dpif), strerror(retval)); } + destroy_iface(p->devname); } } svec_destroy(&want_ifaces); @@ -444,12 +610,30 @@ bridge_reconfigure(void) bridge_get_all_ifaces(br, &want_ifaces); svec_diff(&want_ifaces, &cur_ifaces, &add_ifaces, NULL, NULL); + for (i = 0; i < cur_ifaces.n; i++) { + const char *if_name = cur_ifaces.names[i]; + reconfigure_iface(if_name); + } + for (i = 0; i < add_ifaces.n; i++) { const char *if_name = add_ifaces.names[i]; - int internal = cfg_get_bool(0, "iface.%s.internal", if_name); - int flags = internal ? ODP_PORT_INTERNAL : 0; - int error = dpif_port_add(br->dpif, if_name, flags, NULL); - if (error == EXFULL) { + bool internal; + int error; + + /* Attempt to create the network interface in case it + * doesn't exist yet. */ + error = create_iface(if_name); + if (error) { + VLOG_WARN("could not create iface %s: %s\n", if_name, + strerror(error)); + continue; + } + + /* Add to datapath. */ + internal = iface_is_internal(br, if_name); + error = dpif_port_add(br->dpif, if_name, + internal ? ODP_PORT_INTERNAL : 0, NULL); + if (error == EFBIG) { VLOG_ERR("ran out of valid port numbers on %s", dpif_name(br->dpif)); break; @@ -465,44 +649,20 @@ bridge_reconfigure(void) LIST_FOR_EACH (br, struct bridge, node, &all_bridges) { uint8_t ea[8]; uint64_t dpid; - struct iface *local_iface = NULL; - const char *devname; - uint8_t engine_type, engine_id; - bool add_id_to_iface = false; - struct svec nf_hosts; + struct iface *local_iface; + struct iface *hw_addr_iface; + struct netflow_options nf_options; bridge_fetch_dp_ifaces(br); - for (i = 0; i < br->n_ports; ) { - struct port *port = br->ports[i]; + iterate_and_prune_ifaces(br, init_iface_netdev, NULL); - for (j = 0; j < port->n_ifaces; ) { - struct iface *iface = port->ifaces[j]; - if (iface->dp_ifidx < 0) { - VLOG_ERR("%s interface not in %s, dropping", - iface->name, dpif_name(br->dpif)); - iface_destroy(iface); - } else { - if (iface->dp_ifidx == ODPP_LOCAL) { - local_iface = iface; - } - VLOG_DBG("%s has interface %s on port %d", - dpif_name(br->dpif), - iface->name, iface->dp_ifidx); - j++; - } - } - if (!port->n_ifaces) { - VLOG_ERR("%s port has no interfaces, dropping", port->name); - port_destroy(port); - continue; - } - i++; - } + iterate_and_prune_ifaces(br, check_iface_dp_ifidx, NULL); /* Pick local port hardware address, datapath ID. */ - bridge_pick_local_hw_addr(br, ea, &devname); + bridge_pick_local_hw_addr(br, ea, &hw_addr_iface); + local_iface = bridge_get_local_iface(br); if (local_iface) { - int error = netdev_nodev_set_etheraddr(local_iface->name, ea); + int error = netdev_set_etheraddr(local_iface->netdev, ea); if (error) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_ERR_RL(&rl, "bridge %s: failed to set bridge " @@ -511,39 +671,50 @@ bridge_reconfigure(void) } } - dpid = bridge_pick_datapath_id(br, ea, devname); + dpid = bridge_pick_datapath_id(br, ea, hw_addr_iface); ofproto_set_datapath_id(br->ofproto, dpid); /* Set NetFlow configuration on this bridge. */ - dpif_get_netflow_ids(br->dpif, &engine_type, &engine_id); + memset(&nf_options, 0, sizeof nf_options); + dpif_get_netflow_ids(br->dpif, &nf_options.engine_type, + &nf_options.engine_id); + nf_options.active_timeout = -1; + if (cfg_has("netflow.%s.engine-type", br->name)) { - engine_type = cfg_get_int(0, "netflow.%s.engine-type", + nf_options.engine_type = cfg_get_int(0, "netflow.%s.engine-type", br->name); } if (cfg_has("netflow.%s.engine-id", br->name)) { - engine_id = cfg_get_int(0, "netflow.%s.engine-id", br->name); + nf_options.engine_id = cfg_get_int(0, "netflow.%s.engine-id", + br->name); + } + if (cfg_has("netflow.%s.active-timeout", br->name)) { + nf_options.active_timeout = cfg_get_int(0, + "netflow.%s.active-timeout", + br->name); } if (cfg_has("netflow.%s.add-id-to-iface", br->name)) { - add_id_to_iface = cfg_get_bool(0, "netflow.%s.add-id-to-iface", - br->name); + nf_options.add_id_to_iface = cfg_get_bool(0, + "netflow.%s.add-id-to-iface", + br->name); } - if (add_id_to_iface && engine_id > 0x7f) { + if (nf_options.add_id_to_iface && nf_options.engine_id > 0x7f) { VLOG_WARN("bridge %s: netflow port mangling may conflict with " "another vswitch, choose an engine id less than 128", br->name); } - if (add_id_to_iface && br->n_ports > 0x1ff) { + if (nf_options.add_id_to_iface && br->n_ports > 508) { VLOG_WARN("bridge %s: netflow port mangling will conflict with " - "another port when 512 or more ports are used", + "another port when more than 508 ports are used", br->name); } - svec_init(&nf_hosts); - cfg_get_all_keys(&nf_hosts, "netflow.%s.host", br->name); - if (ofproto_set_netflow(br->ofproto, &nf_hosts, engine_type, - engine_id, add_id_to_iface)) { + svec_init(&nf_options.collectors); + cfg_get_all_keys(&nf_options.collectors, "netflow.%s.host", br->name); + if (ofproto_set_netflow(br->ofproto, &nf_options)) { VLOG_ERR("bridge %s: problem setting netflow collectors", br->name); } + svec_destroy(&nf_options.collectors); /* Update the controller and related settings. It would be more * straightforward to call this from bridge_reconfigure_one(), but we @@ -559,23 +730,26 @@ bridge_reconfigure(void) LIST_FOR_EACH (br, struct bridge, node, &all_bridges) { for (i = 0; i < br->n_ports; i++) { struct port *port = br->ports[i]; + port_update_vlan_compat(port); + port_update_bonding(port); } } LIST_FOR_EACH (br, struct bridge, node, &all_bridges) { brstp_reconfigure(br); + iterate_and_prune_ifaces(br, set_iface_properties, NULL); } } static void bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN], - const char **devname) + struct iface **hw_addr_iface) { uint64_t requested_ea; size_t i, j; int error; - *devname = NULL; + *hw_addr_iface = NULL; /* Did the user request a particular MAC? */ requested_ea = cfg_get_mac(0, "bridge.%s.mac", br->name); @@ -597,35 +771,79 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN], memset(ea, 0xff, sizeof ea); for (i = 0; i < br->n_ports; i++) { struct port *port = br->ports[i]; + uint8_t iface_ea[ETH_ADDR_LEN]; + uint64_t iface_ea_u64; + struct iface *iface; + + /* Mirror output ports don't participate. */ if (port->is_mirror_output_port) { continue; } - for (j = 0; j < port->n_ifaces; j++) { - struct iface *iface = port->ifaces[j]; - uint8_t iface_ea[ETH_ADDR_LEN]; + + /* Choose the MAC address to represent the port. */ + iface_ea_u64 = cfg_get_mac(0, "port.%s.mac", port->name); + if (iface_ea_u64) { + /* User specified explicitly. */ + eth_addr_from_uint64(iface_ea_u64, iface_ea); + + /* Find the interface with this Ethernet address (if any) so that + * we can provide the correct devname to the caller. */ + iface = NULL; + for (j = 0; j < port->n_ifaces; j++) { + struct iface *candidate = port->ifaces[j]; + uint8_t candidate_ea[ETH_ADDR_LEN]; + if (!netdev_get_etheraddr(candidate->netdev, candidate_ea) + && eth_addr_equals(iface_ea, candidate_ea)) { + iface = candidate; + } + } + } else { + /* Choose the interface whose MAC address will represent the port. + * The Linux kernel bonding code always chooses the MAC address of + * the first slave added to a bond, and the Fedora networking + * scripts always add slaves to a bond in alphabetical order, so + * for compatibility we choose the interface with the name that is + * first in alphabetical order. */ + iface = port->ifaces[0]; + for (j = 1; j < port->n_ifaces; j++) { + struct iface *candidate = port->ifaces[j]; + if (strcmp(candidate->name, iface->name) < 0) { + iface = candidate; + } + } + + /* The local port doesn't count (since we're trying to choose its + * MAC address anyway). Other internal ports don't count because + * we really want a physical MAC if we can get it, and internal + * ports typically have randomly generated MACs. */ if (iface->dp_ifidx == ODPP_LOCAL || cfg_get_bool(0, "iface.%s.internal", iface->name)) { continue; } - error = netdev_nodev_get_etheraddr(iface->name, iface_ea); - if (!error) { - if (!eth_addr_is_multicast(iface_ea) && - !eth_addr_is_reserved(iface_ea) && - !eth_addr_is_zero(iface_ea) && - memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0) { - memcpy(ea, iface_ea, ETH_ADDR_LEN); - *devname = iface->name; - } - } else { + + /* Grab MAC. */ + error = netdev_get_etheraddr(iface->netdev, iface_ea); + if (error) { static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); VLOG_ERR_RL(&rl, "failed to obtain Ethernet address of %s: %s", iface->name, strerror(error)); + continue; } } + + /* Compare against our current choice. */ + if (!eth_addr_is_multicast(iface_ea) && + !eth_addr_is_reserved(iface_ea) && + !eth_addr_is_zero(iface_ea) && + memcmp(iface_ea, ea, ETH_ADDR_LEN) < 0) + { + memcpy(ea, iface_ea, ETH_ADDR_LEN); + *hw_addr_iface = iface; + } } if (eth_addr_is_multicast(ea) || eth_addr_is_vif(ea)) { memcpy(ea, br->default_ea, ETH_ADDR_LEN); - *devname = NULL; + *hw_addr_iface = NULL; VLOG_WARN("bridge %s: using default bridge Ethernet " "address "ETH_ADDR_FMT, br->name, ETH_ADDR_ARGS(ea)); } else { @@ -636,13 +854,13 @@ bridge_pick_local_hw_addr(struct bridge *br, uint8_t ea[ETH_ADDR_LEN], /* Choose and returns the datapath ID for bridge 'br' given that the bridge * Ethernet address is 'bridge_ea'. If 'bridge_ea' is the Ethernet address of - * a network device, then that network device's name must be passed in as - * 'devname'; if 'bridge_ea' was derived some other way, then 'devname' must be - * passed in as a null pointer. */ + * an interface on 'br', then that interface must be passed in as + * 'hw_addr_iface'; if 'bridge_ea' was derived some other way, then + * 'hw_addr_iface' must be passed in as a null pointer. */ static uint64_t bridge_pick_datapath_id(struct bridge *br, const uint8_t bridge_ea[ETH_ADDR_LEN], - const char *devname) + struct iface *hw_addr_iface) { /* * The procedure for choosing a bridge MAC address will, in the most @@ -663,9 +881,9 @@ bridge_pick_datapath_id(struct bridge *br, return dpid; } - if (devname) { + if (hw_addr_iface) { int vlan; - if (!netdev_get_vlan_vid(devname, &vlan)) { + if (!netdev_get_vlan_vid(hw_addr_iface->netdev, &vlan)) { /* * A bridge whose MAC address is taken from a VLAN network device * (that is, a network device created with vconfig(8) or similar @@ -756,9 +974,7 @@ bridge_wait(void) continue; } - if (br->ml) { - mac_learning_wait(br->ml); - } + mac_learning_wait(br->ml); bond_wait(br); brstp_wait(br); } @@ -771,9 +987,27 @@ bridge_flush(struct bridge *br) { COVERAGE_INC(bridge_flush); br->flush = true; - if (br->ml) { - mac_learning_flush(br->ml); + mac_learning_flush(br->ml); +} + +/* Returns the 'br' interface for the ODPP_LOCAL port, or null if 'br' has no + * such interface. */ +static struct iface * +bridge_get_local_iface(struct bridge *br) +{ + size_t i, j; + + for (i = 0; i < br->n_ports; i++) { + struct port *port = br->ports[i]; + for (j = 0; j < port->n_ifaces; j++) { + struct iface *iface = port->ifaces[j]; + if (iface->dp_ifidx == ODPP_LOCAL) { + return iface; + } + } } + + return NULL; } /* Bridge unixctl user interface functions. */ @@ -782,6 +1016,7 @@ bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args) { struct ds ds = DS_EMPTY_INITIALIZER; const struct bridge *br; + const struct mac_entry *e; br = bridge_lookup(args); if (!br) { @@ -790,16 +1025,13 @@ bridge_unixctl_fdb_show(struct unixctl_conn *conn, const char *args) } ds_put_cstr(&ds, " port VLAN MAC Age\n"); - if (br->ml) { - const struct mac_entry *e; - LIST_FOR_EACH (e, struct mac_entry, lru_node, &br->ml->lrus) { - if (e->port < 0 || e->port >= br->n_ports) { - continue; - } - ds_put_format(&ds, "%5d %4d "ETH_ADDR_FMT" %3d\n", - br->ports[e->port]->ifaces[0]->dp_ifidx, - e->vlan, ETH_ADDR_ARGS(e->mac), mac_entry_age(e)); + LIST_FOR_EACH (e, struct mac_entry, lru_node, &br->ml->lrus) { + if (e->port < 0 || e->port >= br->n_ports) { + continue; } + ds_put_format(&ds, "%5d %4d "ETH_ADDR_FMT" %3d\n", + br->ports[e->port]->ifaces[0]->dp_ifidx, + e->vlan, ETH_ADDR_ARGS(e->mac), mac_entry_age(e)); } unixctl_command_reply(conn, 200, ds_cstr(&ds)); ds_destroy(&ds); @@ -816,21 +1048,12 @@ bridge_create(const char *name) assert(!bridge_lookup(name)); br = xcalloc(1, sizeof *br); - error = dpif_create(name, &br->dpif); - if (error == EEXIST || error == EBUSY) { - error = dpif_open(name, &br->dpif); - if (error) { - VLOG_ERR("datapath %s already exists but cannot be opened: %s", - name, strerror(error)); - free(br); - return NULL; - } - dpif_flow_flush(br->dpif); - } else if (error) { - VLOG_ERR("failed to create datapath %s: %s", name, strerror(error)); + error = dpif_create_and_open(name, &br->dpif); + if (error) { free(br); return NULL; } + dpif_flow_flush(br->dpif); error = ofproto_create(name, &bridge_ofhooks, br, &br->ofproto); if (error) { @@ -910,6 +1133,27 @@ bridge_get_datapathid(const char *name) return br ? ofproto_get_datapath_id(br->ofproto) : 0; } +/* Handle requests for a listing of all flows known by the OpenFlow + * stack, including those normally hidden. */ +static void +bridge_unixctl_dump_flows(struct unixctl_conn *conn, const char *args) +{ + struct bridge *br; + struct ds results; + + br = bridge_lookup(args); + if (!br) { + unixctl_command_reply(conn, 501, "Unknown bridge"); + return; + } + + ds_init(&results); + ofproto_get_all_flows(br->ofproto, &results); + + unixctl_command_reply(conn, 200, ds_cstr(&results)); + ds_destroy(&results); +} + static int bridge_run_one(struct bridge *br) { @@ -920,9 +1164,7 @@ bridge_run_one(struct bridge *br) return error; } - if (br->ml) { - mac_learning_run(br->ml, ofproto_get_revalidate_set(br->ofproto)); - } + mac_learning_run(br->ml, ofproto_get_revalidate_set(br->ofproto)); bond_run(br); brstp_run(br); @@ -944,13 +1186,29 @@ bridge_get_controller(const struct bridge *br) return controller && controller[0] ? controller : NULL; } +static bool +check_duplicate_ifaces(struct bridge *br, struct iface *iface, void *ifaces_) +{ + struct svec *ifaces = ifaces_; + if (!svec_contains(ifaces, iface->name)) { + svec_add(ifaces, iface->name); + svec_sort(ifaces); + return true; + } else { + VLOG_ERR("bridge %s: %s interface is on multiple ports, " + "removing from %s", + br->name, iface->name, iface->port->name); + return false; + } +} + static void bridge_reconfigure_one(struct bridge *br) { struct svec old_ports, new_ports, ifaces; struct svec listeners, old_listeners; struct svec snoops, old_snoops; - size_t i, j; + size_t i; /* Collect old ports. */ svec_init(&old_ports); @@ -1008,28 +1266,7 @@ bridge_reconfigure_one(struct bridge *br) /* Check and delete duplicate interfaces. */ svec_init(&ifaces); - for (i = 0; i < br->n_ports; ) { - struct port *port = br->ports[i]; - for (j = 0; j < port->n_ifaces; ) { - struct iface *iface = port->ifaces[j]; - if (svec_contains(&ifaces, iface->name)) { - VLOG_ERR("bridge %s: %s interface is on multiple ports, " - "removing from %s", - br->name, iface->name, port->name); - iface_destroy(iface); - } else { - svec_add(&ifaces, iface->name); - svec_sort(&ifaces); - j++; - } - } - if (!port->n_ifaces) { - VLOG_ERR("%s port has no interfaces, dropping", port->name); - port_destroy(port); - } else { - i++; - } - } + iterate_and_prune_ifaces(br, check_duplicate_ifaces, &ifaces); svec_destroy(&ifaces); /* Delete all flows if we're switching from connected to standalone or vice @@ -1110,10 +1347,8 @@ bridge_reconfigure_controller(struct bridge *br) cfg_get_string(0, "%s.accept-regex", pfx), update_resolv_conf); } else { - char local_name[IF_NAMESIZE]; - struct netdev *netdev; + struct iface *local_iface; bool in_band; - int error; in_band = (!cfg_is_valid(CFG_BOOL | CFG_REQUIRED, "%s.in-band", pfx) @@ -1121,37 +1356,32 @@ bridge_reconfigure_controller(struct bridge *br) ofproto_set_discovery(br->ofproto, false, NULL, NULL); ofproto_set_in_band(br->ofproto, in_band); - error = dpif_port_get_name(br->dpif, ODPP_LOCAL, - local_name, sizeof local_name); - if (!error) { - error = netdev_open(local_name, NETDEV_ETH_TYPE_NONE, &netdev); - } - if (!error) { - if (cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) { - struct in_addr ip, mask, gateway; - ip.s_addr = cfg_get_ip(0, "%s.ip", pfx); - mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx); - gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx); - - netdev_turn_flags_on(netdev, NETDEV_UP, true); - if (!mask.s_addr) { - mask.s_addr = guess_netmask(ip.s_addr); - } - if (!netdev_set_in4(netdev, ip, mask)) { - VLOG_INFO("bridge %s: configured IP address "IP_FMT", " - "netmask "IP_FMT, - br->name, IP_ARGS(&ip.s_addr), - IP_ARGS(&mask.s_addr)); - } + local_iface = bridge_get_local_iface(br); + if (local_iface + && cfg_is_valid(CFG_IP | CFG_REQUIRED, "%s.ip", pfx)) { + struct netdev *netdev = local_iface->netdev; + struct in_addr ip, mask, gateway; + ip.s_addr = cfg_get_ip(0, "%s.ip", pfx); + mask.s_addr = cfg_get_ip(0, "%s.netmask", pfx); + gateway.s_addr = cfg_get_ip(0, "%s.gateway", pfx); + + netdev_turn_flags_on(netdev, NETDEV_UP, true); + if (!mask.s_addr) { + mask.s_addr = guess_netmask(ip.s_addr); + } + if (!netdev_set_in4(netdev, ip, mask)) { + VLOG_INFO("bridge %s: configured IP address "IP_FMT", " + "netmask "IP_FMT, + br->name, IP_ARGS(&ip.s_addr), + IP_ARGS(&mask.s_addr)); + } - if (gateway.s_addr) { - if (!netdev_add_router(netdev, gateway)) { - VLOG_INFO("bridge %s: configured gateway "IP_FMT, - br->name, IP_ARGS(&gateway.s_addr)); - } + if (gateway.s_addr) { + if (!netdev_add_router(netdev, gateway)) { + VLOG_INFO("bridge %s: configured gateway "IP_FMT, + br->name, IP_ARGS(&gateway.s_addr)); } } - netdev_close(netdev); } } @@ -1168,7 +1398,7 @@ bridge_reconfigure_controller(struct bridge *br) if (probe < 5) { probe = cfg_get_int(0, "mgmt.inactivity-probe"); if (probe < 5) { - probe = 15; + probe = 5; } } ofproto_set_probe_interval(br->ofproto, probe); @@ -1177,7 +1407,7 @@ bridge_reconfigure_controller(struct bridge *br) if (!max_backoff) { max_backoff = cfg_get_int(0, "mgmt.max-backoff"); if (!max_backoff) { - max_backoff = 15; + max_backoff = 8; } } ofproto_set_max_backoff(br->ofproto, max_backoff); @@ -1248,9 +1478,12 @@ bridge_get_all_ifaces(const struct bridge *br, struct svec *ifaces) struct iface *iface = port->ifaces[j]; svec_add(ifaces, iface->name); } + if (port->n_ifaces > 1 + && cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) { + svec_add(ifaces, port->name); + } } - svec_sort(ifaces); - assert(svec_is_unique(ifaces)); + svec_sort_unique(ifaces); } /* For robustness, in case the administrator moves around datapath ports behind @@ -1315,13 +1548,31 @@ lookup_bond_entry(const struct port *port, const uint8_t mac[ETH_ADDR_LEN]) static int bond_choose_iface(const struct port *port) { - size_t i; + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20); + size_t i, best_down_slave = -1; + long long next_delay_expiration = LLONG_MAX; + for (i = 0; i < port->n_ifaces; i++) { - if (port->ifaces[i]->enabled) { + struct iface *iface = port->ifaces[i]; + + if (iface->enabled) { return i; + } else if (iface->delay_expires < next_delay_expiration) { + best_down_slave = i; + next_delay_expiration = iface->delay_expires; } } - return -1; + + if (best_down_slave != -1) { + struct iface *iface = port->ifaces[best_down_slave]; + + VLOG_INFO_RL(&rl, "interface %s: skipping remaining %lli ms updelay " + "since no other interface is up", iface->name, + iface->delay_expires - time_msec()); + bond_enable_slave(iface, true); + } + + return best_down_slave; } static bool @@ -1345,6 +1596,7 @@ choose_output_iface(const struct port *port, const uint8_t *dl_src, return false; } e->iface_tag = tag_create_random(); + ((struct port *) port)->bond_compat_is_stale = true; } *tags |= e->iface_tag; iface = port->ifaces[e->iface_idx]; @@ -1370,10 +1622,12 @@ bond_link_status_update(struct iface *iface, bool carrier) iface->delay_expires = LLONG_MAX; VLOG_INFO_RL(&rl, "interface %s: will not be %s", iface->name, carrier ? "disabled" : "enabled"); - } else if (carrier && port->updelay && port->active_iface < 0) { - iface->delay_expires = time_msec(); - VLOG_INFO_RL(&rl, "interface %s: skipping %d ms updelay since no " - "other interface is up", iface->name, port->updelay); + } else if (carrier && port->active_iface < 0) { + bond_enable_slave(iface, true); + if (port->updelay) { + VLOG_INFO_RL(&rl, "interface %s: skipping %d ms updelay since no " + "other interface is up", iface->name, port->updelay); + } } else { int delay = carrier ? port->updelay : port->downdelay; iface->delay_expires = time_msec() + delay; @@ -1410,6 +1664,12 @@ bond_enable_slave(struct iface *iface, bool enable) struct port *port = iface->port; struct bridge *br = port->bridge; + /* This acts as a recursion check. If the act of disabling a slave + * causes a different slave to be enabled, the flag will allow us to + * skip redundant work when we reenter this function. It must be + * cleared on exit to keep things safe with multiple bonds. */ + static bool moving_active_iface = false; + iface->delay_expires = LLONG_MAX; if (enable == iface->enabled) { return; @@ -1422,18 +1682,29 @@ bond_enable_slave(struct iface *iface, bool enable) if (iface->port_ifidx == port->active_iface) { ofproto_revalidate(br->ofproto, port->active_iface_tag); + + /* Disabling a slave can lead to another slave being immediately + * enabled if there will be no active slaves but one is waiting + * on an updelay. In this case we do not need to run most of the + * code for the newly enabled slave since there was no period + * without an active slave and it is redundant with the disabling + * path. */ + moving_active_iface = true; bond_choose_active_iface(port); } bond_send_learning_packets(port); } else { VLOG_WARN("interface %s: enabled", iface->name); - if (port->active_iface < 0) { + if (port->active_iface < 0 && !moving_active_iface) { ofproto_revalidate(br->ofproto, port->no_ifaces_tag); bond_choose_active_iface(port); bond_send_learning_packets(port); } iface->tag = tag_create_random(); } + + moving_active_iface = false; + port->bond_compat_is_stale = true; } static void @@ -1443,15 +1714,20 @@ bond_run(struct bridge *br) for (i = 0; i < br->n_ports; i++) { struct port *port = br->ports[i]; - if (port->n_ifaces < 2) { - continue; - } - for (j = 0; j < port->n_ifaces; j++) { - struct iface *iface = port->ifaces[j]; - if (time_msec() >= iface->delay_expires) { - bond_enable_slave(iface, !iface->enabled); + + if (port->n_ifaces >= 2) { + for (j = 0; j < port->n_ifaces; j++) { + struct iface *iface = port->ifaces[j]; + if (time_msec() >= iface->delay_expires) { + bond_enable_slave(iface, !iface->enabled); + } } } + + if (port->bond_compat_is_stale) { + port->bond_compat_is_stale = false; + port_update_bond_compat(port); + } } } @@ -1577,7 +1853,7 @@ port_includes_vlan(const struct port *port, uint16_t vlan) static size_t compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan, const struct port *in_port, const struct port *out_port, - struct dst dsts[], tag_type *tags) + struct dst dsts[], tag_type *tags, uint16_t *nf_output_iface) { mirror_mask_t mirrors = in_port->src_mirrors; struct dst *dst = dsts; @@ -1596,7 +1872,9 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan, dst++; } } + *nf_output_iface = NF_OUT_FLOOD; } else if (out_port && set_dst(dst, flow, in_port, out_port, tags)) { + *nf_output_iface = dst->dp_ifidx; mirrors |= out_port->dst_mirrors; dst++; } @@ -1613,14 +1891,28 @@ compose_dsts(const struct bridge *br, const flow_t *flow, uint16_t vlan, for (i = 0; i < br->n_ports; i++) { struct port *port = br->ports[i]; if (port_includes_vlan(port, m->out_vlan) - && set_dst(dst, flow, in_port, port, tags) - && !dst_is_duplicate(dsts, dst - dsts, dst)) + && set_dst(dst, flow, in_port, port, tags)) { + int flow_vlan; + if (port->vlan < 0) { dst->vlan = m->out_vlan; } - if (dst->dp_ifidx == flow->in_port - && dst->vlan == vlan) { + if (dst_is_duplicate(dsts, dst - dsts, dst)) { + continue; + } + + /* Use the vlan tag on the original flow instead of + * the one passed in the vlan parameter. This ensures + * that we compare the vlan from before any implicit + * tagging tags place. This is necessary because + * dst->vlan is the final vlan, after removing implicit + * tags. */ + flow_vlan = ntohs(flow->dl_vlan); + if (flow_vlan == 0) { + flow_vlan = OFP_VLAN_NONE; + } + if (port == in_port && dst->vlan == flow_vlan) { /* Don't send out input port on same VLAN. */ continue; } @@ -1650,14 +1942,16 @@ print_dsts(const struct dst *dsts, size_t n) static void compose_actions(struct bridge *br, const flow_t *flow, uint16_t vlan, const struct port *in_port, const struct port *out_port, - tag_type *tags, struct odp_actions *actions) + tag_type *tags, struct odp_actions *actions, + uint16_t *nf_output_iface) { struct dst dsts[DP_MAX_PORTS * (MAX_MIRRORS + 1)]; size_t n_dsts; const struct dst *p; uint16_t cur_vlan; - n_dsts = compose_dsts(br, flow, vlan, in_port, out_port, dsts, tags); + n_dsts = compose_dsts(br, flow, vlan, in_port, out_port, dsts, tags, + nf_output_iface); cur_vlan = ntohs(flow->dl_vlan); for (p = dsts; p < &dsts[n_dsts]; p++) { @@ -1676,14 +1970,77 @@ compose_actions(struct bridge *br, const flow_t *flow, uint16_t vlan, } } +/* Returns the effective vlan of a packet, taking into account both the + * 802.1Q header and implicitly tagged ports. A value of 0 indicates that + * the packet is untagged and -1 indicates it has an invalid header and + * should be dropped. */ +static int flow_get_vlan(struct bridge *br, const flow_t *flow, + struct port *in_port, bool have_packet) +{ + /* 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.) */ + int vlan = ntohs(flow->dl_vlan); + if (vlan == OFP_VLAN_NONE) { + vlan = 0; + } + if (in_port->vlan >= 0) { + if (vlan) { + /* XXX support double tagging? */ + if (have_packet) { + 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); + } + return -1; + } + vlan = in_port->vlan; + } else { + if (!port_includes_vlan(in_port, vlan)) { + if (have_packet) { + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged " + "packet received on port %s not configured for " + "trunking VLAN %d", + br->name, vlan, in_port->name, vlan); + } + return -1; + } + } + + return vlan; +} + +static void +update_learning_table(struct bridge *br, const flow_t *flow, int vlan, + struct port *in_port) +{ + 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); + ofproto_revalidate(br->ofproto, rev_tag); + } +} + static bool -is_bcast_arp_reply(const flow_t *flow, const struct ofpbuf *packet) +is_bcast_arp_reply(const flow_t *flow) { - struct arp_eth_header *arp = (struct arp_eth_header *) packet->data; return (flow->dl_type == htons(ETH_TYPE_ARP) - && eth_addr_is_broadcast(flow->dl_dst) - && packet->size >= sizeof(struct arp_eth_header) - && arp->ar_op == ARP_OP_REQUEST); + && flow->nw_proto == ARP_OP_REPLY + && eth_addr_is_broadcast(flow->dl_dst)); } /* If the composed actions may be applied to any packet in the given 'flow', @@ -1692,12 +2049,13 @@ is_bcast_arp_reply(const flow_t *flow, const struct ofpbuf *packet) static bool process_flow(struct bridge *br, const flow_t *flow, const struct ofpbuf *packet, struct odp_actions *actions, - tag_type *tags) + tag_type *tags, uint16_t *nf_output_iface) { struct iface *in_iface; struct port *in_port; struct port *out_port = NULL; /* By default, drop the packet/flow. */ int vlan; + int out_port_idx; /* Find the interface and port structure for the received packet. */ in_iface = iface_from_dp_ifidx(br, flow->in_port); @@ -1725,41 +2083,9 @@ process_flow(struct bridge *br, const flow_t *flow, return true; } in_port = in_iface->port; - - /* 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 >= 0) { - if (vlan) { - /* XXX support double tagging? */ - if (packet != NULL) { - 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; - } else { - if (!port_includes_vlan(in_port, vlan)) { - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged " - "packet received on port %s not configured for " - "trunking VLAN %d", - br->name, vlan, in_port->name, vlan); - goto done; - } + vlan = flow_get_vlan(br, flow, in_port, !!packet); + if (vlan < 0) { + goto done; } /* Drop frames for ports that STP wants entirely killed (both for @@ -1783,63 +2109,48 @@ process_flow(struct bridge *br, const flow_t *flow, goto done; } - /* Multicast (and broadcast) packets on bonds need special attention, to - * avoid receiving duplicates. */ - if (in_port->n_ifaces > 1 && eth_addr_is_multicast(flow->dl_dst)) { - *tags |= in_port->active_iface_tag; - if (in_port->active_iface != in_iface->port_ifidx) { - /* Drop all multicast packets on inactive slaves. */ - goto done; - } else { - /* Drop all multicast packets for which we have learned a different - * input port, because we probably sent the packet on one slaves - * and got it back on the active slave. Broadcast ARP replies are - * an exception to this rule: the host has moved to another - * switch. */ - int src_idx = mac_learning_lookup(br->ml, flow->dl_src, vlan); - if (src_idx != -1 && src_idx != in_port->port_idx) { - if (packet) { - if (!is_bcast_arp_reply(flow, packet)) { - goto done; - } - } else { - /* No way to know whether it's an ARP reply, because the - * flow entry doesn't include enough information and we - * don't have a packet. Punt. */ - return false; - } + /* Packets received on bonds need special attention to avoid duplicates. */ + if (in_port->n_ifaces > 1) { + int src_idx; + + if (eth_addr_is_multicast(flow->dl_dst)) { + *tags |= in_port->active_iface_tag; + if (in_port->active_iface != in_iface->port_ifidx) { + /* Drop all multicast packets on inactive slaves. */ + goto done; } } + + /* Drop all packets for which we have learned a different input + * port, because we probably sent the packet on one slave and got + * it back on the other. Broadcast ARP replies are an exception + * to this rule: the host has moved to another switch. */ + src_idx = mac_learning_lookup(br->ml, flow->dl_src, vlan); + if (src_idx != -1 && src_idx != in_port->port_idx && + !is_bcast_arp_reply(flow)) { + goto done; + } } /* MAC learning. */ out_port = FLOOD_PORT; - if (br->ml) { - int out_port_idx; - - /* Learn source MAC (but don't try to learn from revalidation). */ - if (packet) { - 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); - ofproto_revalidate(br->ofproto, rev_tag); - } - } - - /* Determine output port. */ - out_port_idx = mac_learning_lookup_tag(br->ml, flow->dl_dst, vlan, - tags); - if (out_port_idx >= 0 && out_port_idx < br->n_ports) { - out_port = br->ports[out_port_idx]; - } + /* Learn source MAC (but don't try to learn from revalidation). */ + if (packet) { + update_learning_table(br, flow, vlan, in_port); + } + + /* Determine output port. */ + out_port_idx = mac_learning_lookup_tag(br->ml, flow->dl_dst, vlan, + tags); + if (out_port_idx >= 0 && out_port_idx < br->n_ports) { + out_port = br->ports[out_port_idx]; + } else if (!packet && !eth_addr_is_multicast(flow->dl_dst)) { + /* If we are revalidating but don't have a learning entry then + * eject the flow. Installing a flow that floods packets opens + * up a window of time where we could learn from a packet reflected + * on a bond and blackhole packets before the learning table is + * updated to reflect the correct port. */ + return false; } /* Don't send packets out their input ports. Don't forward frames that STP @@ -1849,17 +2160,10 @@ process_flow(struct bridge *br, const flow_t *flow, } done: - compose_actions(br, flow, vlan, in_port, out_port, tags, actions); + compose_actions(br, flow, vlan, in_port, out_port, tags, actions, + nf_output_iface); - /* - * We send out only a single packet, instead of setting up a flow, if the - * packet is an ARP directed to broadcast that arrived on a bonded - * interface. In such a situation ARP requests and replies must be handled - * differently, but OpenFlow unfortunately can't distinguish them. - */ - return (in_port->n_ifaces < 2 - || flow->dl_type != htons(ETH_TYPE_ARP) - || !eth_addr_is_broadcast(flow->dl_dst)); + return true; } /* Careful: 'opp' is in host byte order and opp->port_no is an OFP port @@ -1891,7 +2195,6 @@ bridge_port_changed_ofhook_cb(enum ofp_port_reason reason, bridge_flush(br); } else { - memcpy(iface->mac, opp->hw_addr, ETH_ADDR_LEN); if (port->n_ifaces > 1) { bool up = !(opp->state & OFPPS_LINK_DOWN); bond_link_status_update(iface, up); @@ -1902,7 +2205,8 @@ bridge_port_changed_ofhook_cb(enum ofp_port_reason reason, static bool bridge_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet, - struct odp_actions *actions, tag_type *tags, void *br_) + struct odp_actions *actions, tag_type *tags, + uint16_t *nf_output_iface, void *br_) { struct bridge *br = br_; @@ -1915,7 +2219,7 @@ bridge_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet, #endif COVERAGE_INC(bridge_process_flow); - return process_flow(br, flow, packet, actions, tags); + return process_flow(br, flow, packet, actions, tags, nf_output_iface); } static void @@ -1925,17 +2229,30 @@ bridge_account_flow_ofhook_cb(const flow_t *flow, void *br_) { struct bridge *br = br_; + struct port *in_port; const union odp_action *a; + /* Feed information from the active flows back into the learning table + * to ensure that table is always in sync with what is actually flowing + * through the datapath. */ + in_port = port_from_dp_ifidx(br, flow->in_port); + if (in_port) { + int vlan = flow_get_vlan(br, flow, in_port, false); + if (vlan >= 0) { + update_learning_table(br, flow, vlan, in_port); + } + } + if (!br->has_bonded_ports) { return; } for (a = actions; a < &actions[n_actions]; a++) { if (a->type == ODPAT_OUTPUT) { - struct port *port = port_from_dp_ifidx(br, a->output.port); - if (port && port->n_ifaces >= 2) { - struct bond_entry *e = lookup_bond_entry(port, flow->dl_src); + struct port *out_port = port_from_dp_ifidx(br, a->output.port); + if (out_port && out_port->n_ifaces >= 2) { + struct bond_entry *e = lookup_bond_entry(out_port, + flow->dl_src); e->tx_bytes += n_bytes; } } @@ -2090,8 +2407,9 @@ log_bals(const struct slave_balance *bals, size_t n_bals, struct port *port) /* Shifts 'hash' from 'from' to 'to' within 'port'. */ static void bond_shift_load(struct slave_balance *from, struct slave_balance *to, - struct bond_entry *hash) + int hash_idx) { + struct bond_entry *hash = from->hashes[hash_idx]; struct port *port = from->iface->port; uint64_t delta = hash->tx_bytes; @@ -2109,12 +2427,11 @@ bond_shift_load(struct slave_balance *from, struct slave_balance *to, * it require more work, the only purpose it would be to allow that hash to * be migrated to another slave in this rebalancing run, and there is no * point in doing that. */ - if (from->hashes[0] == hash) { + if (hash_idx == 0) { from->hashes++; } else { - int i = hash - from->hashes[0]; - memmove(from->hashes + i, from->hashes + i + 1, - (from->n_hashes - (i + 1)) * sizeof *from->hashes); + memmove(from->hashes + hash_idx, from->hashes + hash_idx + 1, + (from->n_hashes - (hash_idx + 1)) * sizeof *from->hashes); } from->n_hashes--; @@ -2199,22 +2516,60 @@ bond_rebalance_port(struct port *port) /* 'from' is carrying significantly more load than 'to', and that * load is split across at least two different hashes. Pick a hash * to migrate to 'to' (the least-loaded slave), given that doing so - * must not cause 'to''s load to exceed 'from''s load. + * must decrease the ratio of the load on the two slaves by at + * least 0.1. * * The sort order we use means that we prefer to shift away the * smallest hashes instead of the biggest ones. There is little * reason behind this decision; we could use the opposite sort * order to shift away big hashes ahead of small ones. */ size_t i; + bool order_swapped; for (i = 0; i < from->n_hashes; i++) { + double old_ratio, new_ratio; uint64_t delta = from->hashes[i]->tx_bytes; - if (to->tx_bytes + delta < from->tx_bytes - delta) { + + if (delta == 0 || from->tx_bytes - delta == 0) { + /* Pointless move. */ + continue; + } + + order_swapped = from->tx_bytes - delta < to->tx_bytes + delta; + + if (to->tx_bytes == 0) { + /* Nothing on the new slave, move it. */ + break; + } + + old_ratio = (double)from->tx_bytes / to->tx_bytes; + new_ratio = (double)(from->tx_bytes - delta) / + (to->tx_bytes + delta); + + if (new_ratio == 0) { + /* Should already be covered but check to prevent division + * by zero. */ + continue; + } + + if (new_ratio < 1) { + new_ratio = 1 / new_ratio; + } + + if (old_ratio - new_ratio > 0.1) { + /* Would decrease the ratio, move it. */ break; } } if (i < from->n_hashes) { - bond_shift_load(from, to, from->hashes[i]); + bond_shift_load(from, to, i); + port->bond_compat_is_stale = true; + + /* If the result of the migration changed the relative order of + * 'from' and 'to' swap them back to maintain invariants. */ + if (order_swapped) { + swap_bals(from, to); + } /* Re-sort 'bals'. Note that this may make 'from' and 'to' * point to different slave_balance structures. It is only @@ -2243,17 +2598,14 @@ bond_send_learning_packets(struct port *port) struct ofpbuf packet; int error, n_packets, n_errors; - if (!port->n_ifaces || port->active_iface < 0 || !br->ml) { + if (!port->n_ifaces || port->active_iface < 0) { return; } ofpbuf_init(&packet, 128); error = n_packets = n_errors = 0; LIST_FOR_EACH (e, struct mac_entry, lru_node, &br->ml->lrus) { - static const char s[] = "Open vSwitch Bond Failover"; union ofp_action actions[2], *a; - struct eth_header *eth; - struct llc_snap_header *llc_snap; uint16_t dp_ifidx; tag_type tags = 0; flow_t flow; @@ -2264,23 +2616,6 @@ bond_send_learning_packets(struct port *port) continue; } - /* Compose packet to send. */ - ofpbuf_clear(&packet); - eth = ofpbuf_put_zeros(&packet, ETH_HEADER_LEN); - llc_snap = ofpbuf_put_zeros(&packet, LLC_SNAP_HEADER_LEN); - ofpbuf_put(&packet, s, sizeof s); /* Includes null byte. */ - ofpbuf_put(&packet, e->mac, ETH_ADDR_LEN); - - memcpy(eth->eth_dst, eth_addr_broadcast, ETH_ADDR_LEN); - memcpy(eth->eth_src, e->mac, ETH_ADDR_LEN); - eth->eth_type = htons(packet.size - ETH_HEADER_LEN); - - llc_snap->llc.llc_dsap = LLC_DSAP_SNAP; - llc_snap->llc.llc_ssap = LLC_SSAP_SNAP; - llc_snap->llc.llc_cntl = LLC_CNTL_SNAP; - memcpy(llc_snap->snap.snap_org, "\x00\x23\x20", 3); - llc_snap->snap.snap_type = htons(0xf177); /* Random number. */ - /* Compose actions. */ memset(actions, 0, sizeof actions); a = actions; @@ -2297,6 +2632,8 @@ bond_send_learning_packets(struct port *port) /* Send packet. */ n_packets++; + compose_benign_packet(&packet, "Open vSwitch Bond Failover", 0xf177, + e->mac); flow_extract(&packet, ODPP_NONE, &flow); retval = ofproto_send_packet(br->ofproto, &flow, actions, a - actions, &packet); @@ -2412,14 +2749,10 @@ bond_unixctl_show(struct unixctl_conn *conn, const char *args) continue; } - ds_put_format(&ds, "\thash %d: %lld kB load\n", + ds_put_format(&ds, "\thash %d: %"PRIu64" kB load\n", hash, be->tx_bytes / 1024); /* MACs. */ - if (!port->bridge->ml) { - break; - } - LIST_FOR_EACH (me, struct mac_entry, lru_node, &port->bridge->ml->lrus) { uint16_t dp_ifidx; @@ -2491,6 +2824,7 @@ bond_unixctl_migrate(struct unixctl_conn *conn, const char *args_) ofproto_revalidate(port->bridge->ofproto, entry->iface_tag); entry->iface_idx = iface->port_ifidx; entry->iface_tag = tag_create_random(); + port->bond_compat_is_stale = true; unixctl_command_reply(conn, 200, "migrated"); } @@ -2586,6 +2920,25 @@ bond_unixctl_disable_slave(struct unixctl_conn *conn, const char *args) enable_slave(conn, args, false); } +static void +bond_unixctl_hash(struct unixctl_conn *conn, const char *args) +{ + uint8_t mac[ETH_ADDR_LEN]; + uint8_t hash; + char *hash_cstr; + + if (sscanf(args, ETH_ADDR_SCAN_FMT, ETH_ADDR_SCAN_ARGS(mac)) + == ETH_ADDR_SCAN_COUNT) { + hash = bond_hash(mac); + + hash_cstr = xasprintf("%u", hash); + unixctl_command_reply(conn, 200, hash_cstr); + free(hash_cstr); + } else { + unixctl_command_reply(conn, 501, "invalid mac"); + } +} + static void bond_init(void) { @@ -2596,6 +2949,7 @@ bond_init(void) bond_unixctl_set_active_slave); unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave); unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave); + unixctl_command_register("bond/hash", bond_unixctl_hash); } /* Port functions. */ @@ -2755,6 +3109,7 @@ port_destroy(struct port *port) size_t i; proc_net_compat_update_vlan(port->name, NULL, 0); + proc_net_compat_update_bond(port->name, NULL); for (i = 0; i < MAX_MIRRORS; i++) { struct mirror *m = br->mirrors[i]; @@ -2821,7 +3176,7 @@ port_update_bonding(struct port *port) if (port->bond_hash) { free(port->bond_hash); port->bond_hash = NULL; - proc_net_compat_update_bond(port->name, NULL); + port->bond_compat_is_stale = true; } } else { if (!port->bond_hash) { @@ -2836,36 +3191,79 @@ port_update_bonding(struct port *port) port->no_ifaces_tag = tag_create_random(); bond_choose_active_iface(port); } - port_update_bond_compat(port); + port->bond_compat_is_stale = true; } } static void port_update_bond_compat(struct port *port) { + struct compat_bond_hash compat_hashes[BOND_MASK + 1]; struct compat_bond bond; size_t i; if (port->n_ifaces < 2) { + proc_net_compat_update_bond(port->name, NULL); return; } bond.up = false; bond.updelay = port->updelay; bond.downdelay = port->downdelay; + + bond.n_hashes = 0; + bond.hashes = compat_hashes; + if (port->bond_hash) { + const struct bond_entry *e; + for (e = port->bond_hash; e <= &port->bond_hash[BOND_MASK]; e++) { + if (e->iface_idx >= 0 && e->iface_idx < port->n_ifaces) { + struct compat_bond_hash *cbh = &bond.hashes[bond.n_hashes++]; + cbh->hash = e - port->bond_hash; + cbh->netdev_name = port->ifaces[e->iface_idx]->name; + } + } + } + bond.n_slaves = port->n_ifaces; bond.slaves = xmalloc(port->n_ifaces * sizeof *bond.slaves); for (i = 0; i < port->n_ifaces; i++) { struct iface *iface = port->ifaces[i]; struct compat_bond_slave *slave = &bond.slaves[i]; slave->name = iface->name; - slave->up = ((iface->enabled && iface->delay_expires == LLONG_MAX) || - (!iface->enabled && iface->delay_expires != LLONG_MAX)); + + /* We need to make the same determination as the Linux bonding + * code to determine whether a slave should be consider "up". + * The Linux function bond_miimon_inspect() supports four + * BOND_LINK_* states: + * + * - BOND_LINK_UP: carrier detected, updelay has passed. + * - BOND_LINK_FAIL: carrier lost, downdelay in progress. + * - BOND_LINK_DOWN: carrier lost, downdelay has passed. + * - BOND_LINK_BACK: carrier detected, updelay in progress. + * + * The function bond_info_show_slave() only considers BOND_LINK_UP + * to be "up" and anything else to be "down". + */ + slave->up = iface->enabled && iface->delay_expires == LLONG_MAX; if (slave->up) { bond.up = true; } - memcpy(slave->mac, iface->mac, ETH_ADDR_LEN); + netdev_get_etheraddr(iface->netdev, slave->mac); } + + if (cfg_get_bool(0, "bonding.%s.fake-iface", port->name)) { + struct netdev *bond_netdev; + + if (!netdev_open(port->name, NETDEV_ETH_TYPE_NONE, &bond_netdev)) { + if (bond.up) { + netdev_turn_flags_on(bond_netdev, NETDEV_UP, true); + } else { + netdev_turn_flags_off(bond_netdev, NETDEV_UP, true); + } + netdev_close(bond_netdev); + } + } + proc_net_compat_update_bond(port->name, &bond); free(bond.slaves); } @@ -2894,7 +3292,8 @@ port_update_vlan_compat(struct port *port) && p->n_ifaces && (!vlandev_name || strcmp(p->name, vlandev_name) <= 0)) { - const uint8_t *ea = p->ifaces[0]->mac; + uint8_t ea[ETH_ADDR_LEN]; + netdev_get_etheraddr(p->ifaces[0]->netdev, ea); if (!eth_addr_is_multicast(ea) && !eth_addr_is_reserved(ea) && !eth_addr_is_zero(ea)) { @@ -2920,9 +3319,7 @@ iface_create(struct port *port, const char *name) iface->dp_ifidx = -1; iface->tag = tag_create_random(); iface->delay_expires = LLONG_MAX; - - netdev_nodev_get_etheraddr(name, iface->mac); - netdev_nodev_get_carrier(name, &iface->enabled); + iface->netdev = NULL; if (port->n_ifaces >= port->allocated_ifaces) { port->ifaces = x2nrealloc(port->ifaces, &port->allocated_ifaces, @@ -2935,7 +3332,6 @@ iface_create(struct port *port, const char *name) VLOG_DBG("attached network device %s to port %s", iface->name, port->name); - port_update_bonding(port); bridge_flush(port->bridge); } @@ -2955,6 +3351,7 @@ iface_destroy(struct iface *iface) del = port->ifaces[iface->port_ifidx] = port->ifaces[--port->n_ifaces]; del->port_ifidx = iface->port_ifidx; + netdev_close(iface->netdev); free(iface->name); free(iface); @@ -2964,7 +3361,6 @@ iface_destroy(struct iface *iface) bond_send_learning_packets(port); } - port_update_bonding(port); bridge_flush(port->bridge); } } @@ -2991,6 +3387,60 @@ iface_from_dp_ifidx(const struct bridge *br, uint16_t dp_ifidx) { return port_array_get(&br->ifaces, dp_ifidx); } + +/* Returns true if 'iface' is the name of an "internal" interface on bridge + * 'br', that is, an interface that is entirely simulated within the datapath. + * The local port (ODPP_LOCAL) is always an internal interface. Other local + * interfaces are created by setting "iface..internal = true". + * + * In addition, we have a kluge-y feature that creates an internal port with + * the name of a bonded port if "bonding..fake-iface = true" is set. + * This feature needs to go away in the long term. Until then, this is one + * reason why this function takes a name instead of a struct iface: the fake + * interfaces created this way do not have a struct iface. */ +static bool +iface_is_internal(const struct bridge *br, const char *iface) +{ + if (!strcmp(iface, br->name) + || cfg_get_bool(0, "iface.%s.internal", iface)) { + return true; + } + + if (cfg_get_bool(0, "bonding.%s.fake-iface", iface)) { + struct port *port = port_lookup(br, iface); + if (port && port->n_ifaces > 1) { + return true; + } + } + + return false; +} + +/* Set Ethernet address of 'iface', if one is specified in the configuration + * file. */ +static void +iface_set_mac(struct iface *iface) +{ + uint64_t mac = cfg_get_mac(0, "iface.%s.mac", iface->name); + if (mac) { + static uint8_t ea[ETH_ADDR_LEN]; + + eth_addr_from_uint64(mac, ea); + if (eth_addr_is_multicast(ea)) { + VLOG_ERR("interface %s: cannot set MAC to multicast address", + iface->name); + } else if (iface->dp_ifidx == ODPP_LOCAL) { + VLOG_ERR("ignoring iface.%s.mac; use bridge.%s.mac instead", + iface->name, iface->name); + } else { + int error = netdev_set_etheraddr(iface->netdev, ea); + if (error) { + VLOG_ERR("interface %s: setting MAC failed (%s)", + iface->name, strerror(error)); + } + } + } +} /* Port mirroring. */ @@ -2998,7 +3448,8 @@ static void mirror_reconfigure(struct bridge *br) { struct svec old_mirrors, new_mirrors; - size_t i; + size_t i, n_rspan_vlans; + unsigned long *rspan_vlans; /* Collect old and new mirrors. */ svec_init(&old_mirrors); @@ -3047,6 +3498,29 @@ mirror_reconfigure(struct bridge *br) m->out_port->is_mirror_output_port = true; } } + + /* Update learning disabled vlans (for RSPAN). */ + rspan_vlans = NULL; + n_rspan_vlans = cfg_count("vlan.%s.disable-learning", br->name); + if (n_rspan_vlans) { + rspan_vlans = bitmap_allocate(4096); + + for (i = 0; i < n_rspan_vlans; i++) { + int vlan = cfg_get_vlan(i, "vlan.%s.disable-learning", br->name); + if (vlan >= 0) { + bitmap_set1(rspan_vlans, vlan); + VLOG_INFO("bridge %s: disabling learning on vlan %d\n", + br->name, vlan); + } else { + VLOG_ERR("bridge %s: invalid value '%s' for learning disabled " + "VLAN", br->name, + cfg_get_string(i, "vlan.%s.disable-learning", br->name)); + } + } + } + if (mac_learning_set_disabled_vlans(br->ml, rspan_vlans)) { + bridge_flush(br); + } } static void @@ -3191,6 +3665,7 @@ mirror_reconfigure_one(struct mirror *m) int *vlans; size_t i; bool mirror_all_ports; + bool any_ports_specified; /* Get output port. */ out_port_name = cfg_get_key(0, "mirror.%s.%s.output.port", @@ -3229,11 +3704,18 @@ mirror_reconfigure_one(struct mirror *m) cfg_get_all_keys(&src_ports, "%s.select.src-port", pfx); cfg_get_all_keys(&dst_ports, "%s.select.dst-port", pfx); cfg_get_all_keys(&ports, "%s.select.port", pfx); + any_ports_specified = src_ports.n || dst_ports.n || ports.n; svec_append(&src_ports, &ports); svec_append(&dst_ports, &ports); svec_destroy(&ports); prune_ports(m, &src_ports); prune_ports(m, &dst_ports); + if (any_ports_specified && !src_ports.n && !dst_ports.n) { + VLOG_ERR("%s: none of the specified ports exist; " + "disabling port mirror %s", pfx, pfx); + mirror_destroy(m); + goto exit; + } /* Get all the vlans, and drop duplicate and invalid vlans. */ svec_init(&vlan_strings); @@ -3285,6 +3767,7 @@ mirror_reconfigure_one(struct mirror *m) } /* Clean up. */ +exit: svec_destroy(&src_ports); svec_destroy(&dst_ports); free(pfx); @@ -3303,23 +3786,25 @@ brstp_send_bpdu(struct ofpbuf *pkt, int port_no, void *br_) if (!iface) { VLOG_WARN_RL(&rl, "%s: cannot send BPDU on unknown port %d", br->name, port_no); - } else if (eth_addr_is_zero(iface->mac)) { - VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d with unknown MAC", - br->name, port_no); } else { - union ofp_action action; struct eth_header *eth = pkt->l2; - flow_t flow; - memcpy(eth->eth_src, iface->mac, ETH_ADDR_LEN); + netdev_get_etheraddr(iface->netdev, eth->eth_src); + if (eth_addr_is_zero(eth->eth_src)) { + VLOG_WARN_RL(&rl, "%s: cannot send BPDU on port %d " + "with unknown MAC", br->name, port_no); + } else { + union ofp_action action; + flow_t flow; - memset(&action, 0, sizeof action); - action.type = htons(OFPAT_OUTPUT); - action.output.len = htons(sizeof action); - action.output.port = htons(port_no); + memset(&action, 0, sizeof action); + action.type = htons(OFPAT_OUTPUT); + action.output.len = htons(sizeof action); + action.output.port = htons(port_no); - flow_extract(pkt, ODPP_NONE, &flow); - ofproto_send_packet(br->ofproto, &flow, &action, 1, pkt); + flow_extract(pkt, ODPP_NONE, &flow); + ofproto_send_packet(br->ofproto, &flow, &action, 1, pkt); + } } ofpbuf_delete(pkt); }