void *aux; /* Client-provided handle for this slave. */
struct netdev *netdev; /* Network device, owned by the client. */
+ unsigned int change_seq; /* Tracks changes in 'netdev'. */
char *name; /* Name (a copy of netdev_get_name(netdev)). */
/* Link status. */
long long delay_expires; /* Time after which 'enabled' may change. */
bool enabled; /* May be chosen for flows? */
- bool lacp_may_enable; /* LACP considers this interface bondable. */
+ bool may_enable; /* Client considers this slave bondable. */
tag_type tag; /* Tag associated with this slave. */
/* Rebalancing info. Used only by bond_rebalance(). */
/* BM_STABLE specific bonding info. */
tag_type stb_tag; /* Tag associated with this bond. */
- /* Monitoring. */
- struct netdev_monitor *monitor; /* detect == BLSM_CARRIER only. */
-
/* Legacy compatibility. */
long long int next_fake_iface_update; /* LLONG_MAX if disabled. */
bond->no_slaves_tag = tag_create_random();
bond->stb_tag = tag_create_random();
bond->next_fake_iface_update = LLONG_MAX;
- bond->monitor = netdev_monitor_create();
bond_reconfigure(bond, s);
hmap_destroy(&bond->slaves);
free(bond->hash);
-
- netdev_monitor_destroy(bond->monitor);
-
free(bond->name);
free(bond);
}
}
static void
-bond_slave_set_netdev__(struct bond *bond, struct bond_slave *slave,
- struct netdev *netdev)
+bond_slave_set_netdev__(struct bond_slave *slave, struct netdev *netdev)
{
if (slave->netdev != netdev) {
- if (slave->netdev) {
- netdev_monitor_remove(bond->monitor, slave->netdev);
- }
- netdev_monitor_add(bond->monitor, netdev);
slave->netdev = netdev;
+ slave->change_seq = 0;
}
}
bond->bond_revalidate = true;
}
- bond_slave_set_netdev__(bond, slave, netdev);
+ bond_slave_set_netdev__(slave, netdev);
free(slave->name);
slave->name = xstrdup(netdev_get_name(netdev));
{
struct bond_slave *slave = bond_slave_lookup(bond, slave_);
if (slave) {
- bond_slave_set_netdev__(bond, slave, netdev);
+ bond_slave_set_netdev__(slave, netdev);
}
}
return;
}
- netdev_monitor_remove(bond->monitor, slave->netdev);
bond_enable_slave(slave, false, NULL);
del_active = bond->active_slave == slave;
}
}
-/* Should be called on each slave in 'bond' before bond_run() to indicate the
- * results of lacp_slave_may_enable() on 'slave_'. */
+/* Should be called on each slave in 'bond' before bond_run() to indicate
+ * whether or not 'slave_' may be enabled. This function is intended to allow
+ * other protocols to have some impact on bonding decisions. For example LACP
+ * or high level link monitoring protocols may decide that a given slave should
+ * not be able to send traffic. */
void
-bond_slave_set_lacp_may_enable(struct bond *bond, void *slave_,
- bool may_enable)
+bond_slave_set_may_enable(struct bond *bond, void *slave_, bool may_enable)
{
- bond_slave_lookup(bond, slave_)->lacp_may_enable = may_enable;
+ bond_slave_lookup(bond, slave_)->may_enable = may_enable;
}
/* Performs periodic maintenance on 'bond'. The caller must provide 'tags' to
bond->lacp_negotiated = lacp_negotiated;
- if (bond->monitor) {
- netdev_monitor_flush(bond->monitor);
- }
-
/* Enable slaves based on link status and LACP feedback. */
HMAP_FOR_EACH (slave, hmap_node, &bond->slaves) {
bond_link_status_update(slave, tags);
+ slave->change_seq = netdev_change_seq(slave->netdev);
}
if (!bond->active_slave || !bond->active_slave->enabled) {
bond_choose_active_slave(bond, tags);
{
struct bond_slave *slave;
- netdev_monitor_poll_wait(bond->monitor);
-
HMAP_FOR_EACH (slave, hmap_node, &bond->slaves) {
if (slave->delay_expires != LLONG_MAX) {
poll_timer_wait_until(slave->delay_expires);
}
+
+ if (slave->change_seq != netdev_change_seq(slave->netdev)) {
+ poll_immediate_wake();
+ }
}
if (bond->next_fake_iface_update != LLONG_MAX) {
static bool
may_send_learning_packets(const struct bond *bond)
{
- return !bond->lacp_negotiated && bond->balance != BM_AB;
+ return !bond->lacp_negotiated && bond->balance != BM_AB &&
+ bond->active_slave;
}
/* Returns true if 'bond' needs the client to send out packets to assist with
* MAC learning on 'bond'. If this function returns true, then the client
* should iterate through its MAC learning table for the bridge on which 'bond'
* is located. For each MAC that has been learned on a port other than 'bond',
- * it should call bond_send_learning_packet().
+ * it should call bond_compose_learning_packet().
*
* This function will only return true if 'bond' is in SLB mode and LACP is not
* negotiated. Otherwise sending learning packets isn't necessary.
/* Sends a gratuitous learning packet on 'bond' from 'eth_src' on 'vlan'.
*
- * See bond_should_send_learning_packets() for description of usage. */
-int
-bond_send_learning_packet(struct bond *bond,
- const uint8_t eth_src[ETH_ADDR_LEN],
- uint16_t vlan)
+ * See bond_should_send_learning_packets() for description of usage. The
+ * caller should send the composed packet on the port associated with
+ * port_aux and takes ownership of the returned ofpbuf. */
+struct ofpbuf *
+bond_compose_learning_packet(struct bond *bond,
+ const uint8_t eth_src[ETH_ADDR_LEN],
+ uint16_t vlan, void **port_aux)
{
struct bond_slave *slave;
- struct ofpbuf packet;
+ struct ofpbuf *packet;
struct flow flow;
- int error;
assert(may_send_learning_packets(bond));
- if (!bond->active_slave) {
- /* Nowhere to send the learning packet. */
- return 0;
- }
memset(&flow, 0, sizeof flow);
memcpy(flow.dl_src, eth_src, ETH_ADDR_LEN);
slave = choose_output_slave(bond, &flow, vlan);
- ofpbuf_init(&packet, 0);
- compose_benign_packet(&packet, "Open vSwitch Bond Failover", 0xf177,
+ packet = ofpbuf_new(0);
+ compose_benign_packet(packet, "Open vSwitch Bond Failover", 0xf177,
eth_src);
if (vlan) {
- eth_set_vlan_tci(&packet, htons(vlan));
+ eth_push_vlan(packet, htons(vlan));
}
- error = netdev_send(slave->netdev, &packet);
- ofpbuf_uninit(&packet);
- return error;
+ *port_aux = slave->aux;
+ return packet;
}
\f
/* Checks whether a packet that arrived on 'slave_' within 'bond', with an
bond_check_admissibility(struct bond *bond, const void *slave_,
const uint8_t eth_dst[ETH_ADDR_LEN], tag_type *tags)
{
- /* Admit all packets if LACP has been negotiated, because that means that
- * the remote switch is aware of the bond and will "do the right thing". */
+ struct bond_slave *slave = bond_slave_lookup(bond, slave_);
+
+ /* LACP bonds have very loose admissibility restrictions because we can
+ * assume the remote switch is aware of the bond and will "do the right
+ * thing". However, as a precaution we drop packets on disabled slaves
+ * because no correctly implemented partner switch should be sending
+ * packets to them. */
if (bond->lacp_negotiated) {
- return BV_ACCEPT;
+ return slave->enabled ? BV_ACCEPT : BV_DROP;
}
/* Drop all multicast packets on inactive slaves. */
}
}
- /* 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. Gratuitous ARP packets are an exception to this rule: the host
- * has moved to another switch. The exception to the exception is if we
- * locked the learning table to avoid reflections on bond slaves. */
- return BV_DROP_IF_MOVED;
+ switch (bond->balance) {
+ case BM_AB:
+ /* Drop all packets which arrive on backup slaves. This is similar to
+ * how Linux bonding handles active-backup bonds. */
+ *tags |= bond_get_active_slave_tag(bond);
+ if (bond->active_slave != slave) {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+
+ VLOG_DBG_RL(&rl, "active-backup bond received packet on backup"
+ " slave (%s) destined for " ETH_ADDR_FMT,
+ slave->name, ETH_ADDR_ARGS(eth_dst));
+ return BV_DROP;
+ }
+ return BV_ACCEPT;
+
+ case BM_TCP:
+ /* TCP balancing has degraded to SLB (otherwise the
+ * bond->lacp_negotiated check above would have processed this).
+ *
+ * Fall through. */
+ case BM_SLB:
+ /* 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. Gratuitous ARP packets are an exception to this rule:
+ * the host has moved to another switch. The exception to the
+ * exception is if we locked the learning table to avoid reflections on
+ * bond slaves. */
+ return BV_DROP_IF_MOVED;
+
+ case BM_STABLE:
+ return BV_ACCEPT;
+ }
+
+ NOT_REACHED();
}
/* Returns the slave (registered on 'bond' by bond_slave_register()) to which
bond_account(struct bond *bond, const struct flow *flow, uint16_t vlan,
uint64_t n_bytes)
{
-
if (bond_is_balanced(bond)) {
lookup_bond_entry(bond, flow, vlan)->tx_bytes += n_bytes;
}
slave->delay_expires - time_msec());
}
- ds_put_format(&ds, "\tlacp_may_enable: %s\n",
- slave->lacp_may_enable ? "true" : "false");
+ ds_put_format(&ds, "\tmay_enable: %s\n",
+ slave->may_enable ? "true" : "false");
if (!bond_is_balanced(bond)) {
continue;
ds_put_format(&ds, "\thash %d: %"PRIu64" kB load\n",
hash, be->tx_bytes / 1024);
- if (bond->balance != BM_SLB) {
- continue;
- }
-
- /* XXX How can we list the MACs assigned to hashes? */
+ /* XXX How can we list the MACs assigned to hashes of SLB bonds? */
}
}
unixctl_command_reply(conn, 200, ds_cstr(&ds));
return;
}
} else {
- vlan = OFP_VLAN_NONE;
+ vlan = 0;
}
if (basis_s) {
void
bond_init(void)
{
- unixctl_command_register("bond/list", bond_unixctl_list, NULL);
- unixctl_command_register("bond/show", bond_unixctl_show, NULL);
- unixctl_command_register("bond/migrate", bond_unixctl_migrate, NULL);
- unixctl_command_register("bond/set-active-slave",
+ unixctl_command_register("bond/list", "", bond_unixctl_list, NULL);
+ unixctl_command_register("bond/show", "port", bond_unixctl_show, NULL);
+ unixctl_command_register("bond/migrate", "port hash slave",
+ bond_unixctl_migrate, NULL);
+ unixctl_command_register("bond/set-active-slave", "port slave",
bond_unixctl_set_active_slave, NULL);
- unixctl_command_register("bond/enable-slave", bond_unixctl_enable_slave,
- NULL);
- unixctl_command_register("bond/disable-slave", bond_unixctl_disable_slave,
- NULL);
- unixctl_command_register("bond/hash", bond_unixctl_hash, NULL);
+ unixctl_command_register("bond/enable-slave", "port slave",
+ bond_unixctl_enable_slave, NULL);
+ unixctl_command_register("bond/disable-slave", "port slave",
+ bond_unixctl_disable_slave, NULL);
+ unixctl_command_register("bond/hash", "mac [vlan] [basis]",
+ bond_unixctl_hash, NULL);
}
\f
static void
struct bond *bond = slave->bond;
bool up;
- up = netdev_get_carrier(slave->netdev) && slave->lacp_may_enable;
+ up = netdev_get_carrier(slave->netdev) && slave->may_enable;
if ((up == slave->enabled) != (slave->delay_expires == LLONG_MAX)) {
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 20);
VLOG_INFO_RL(&rl, "interface %s: link state %s",
static bool
bond_is_tcp_hash(const struct bond *bond)
{
- return (bond->balance == BM_TCP || bond->balance == BM_STABLE)
- && bond->lacp_negotiated;
+ return (bond->balance == BM_TCP && bond->lacp_negotiated)
+ || bond->balance == BM_STABLE;
}
static unsigned int
best = NULL;
HMAP_FOR_EACH (slave, hmap_node, &bond->slaves) {
if (slave->delay_expires != LLONG_MAX
- && slave->lacp_may_enable
+ && slave->may_enable
&& (!best || slave->delay_expires < best->delay_expires)) {
best = slave;
}
}
}
- if (!netdev_open_default(bond->name, &bond_dev)) {
+ if (!netdev_open(bond->name, "system", &bond_dev)) {
netdev_set_stats(bond_dev, &bond_stats);
netdev_close(bond_dev);
}