+/* LACP functions. */
+
+static void
+lacp_process_packet(const struct ofpbuf *packet, struct iface *iface)
+{
+ const struct lacp_pdu *pdu;
+
+ if (!iface->port->lacp) {
+ return;
+ }
+
+ pdu = parse_lacp_packet(packet);
+ if (!pdu) {
+ return;
+ }
+
+ iface->lacp_status |= LACP_CURRENT;
+ iface->lacp_status &= ~(LACP_EXPIRED | LACP_DEFAULTED);
+ iface->lacp_rx = time_msec() + LACP_SLOW_TIME_RX;
+
+ iface->lacp_actor.state = iface_get_lacp_state(iface);
+ if (memcmp(&iface->lacp_actor, &pdu->partner, sizeof pdu->partner)) {
+ iface->lacp_tx = 0;
+ }
+
+ if (memcmp(&iface->lacp_partner, &pdu->actor, sizeof pdu->actor)) {
+ iface->port->lacp_need_update = true;
+ iface->lacp_partner = pdu->actor;
+ }
+}
+
+static void
+lacp_update_ifaces(struct port *port)
+{
+ size_t i;
+ struct iface *lead;
+ struct lacp_info lead_pri;
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 10);
+
+ port->lacp_need_update = false;
+ COVERAGE_INC(bridge_lacp_update);
+
+ if (!port->lacp) {
+ return;
+ }
+
+ VLOG_DBG_RL(&rl, "port %s: re-evaluating LACP link status", port->name);
+
+ lead = NULL;
+ for (i = 0; i < port->n_ifaces; i++) {
+ struct iface *iface = port->ifaces[i];
+ struct lacp_info pri;
+
+ iface->lacp_status |= LACP_ATTACHED;
+ ofproto_revalidate(port->bridge->ofproto, iface->tag);
+
+ /* Don't allow loopback interfaces to send traffic or lead. */
+ if (eth_addr_equals(iface->lacp_partner.sysid,
+ iface->lacp_actor.sysid)) {
+ VLOG_WARN_RL(&rl, "iface %s: Loopback detected. Interface is "
+ "connected to its own bridge", iface->name);
+ iface->lacp_status &= ~LACP_ATTACHED;
+ continue;
+ }
+
+ if (iface->lacp_status & LACP_DEFAULTED) {
+ continue;
+ }
+
+ iface_get_lacp_priority(iface, &pri);
+
+ if (!lead || memcmp(&pri, &lead_pri, sizeof pri) < 0) {
+ lead = iface;
+ lead_pri = pri;
+ }
+ }
+
+ if (!lead) {
+ port->lacp &= ~LACP_NEGOTIATED;
+ return;
+ }
+
+ port->lacp |= LACP_NEGOTIATED;
+
+ for (i = 0; i < port->n_ifaces; i++) {
+ struct iface *iface = port->ifaces[i];
+
+ if (iface->lacp_status & LACP_DEFAULTED
+ || lead->lacp_partner.key != iface->lacp_partner.key
+ || !eth_addr_equals(lead->lacp_partner.sysid,
+ iface->lacp_partner.sysid)) {
+ iface->lacp_status &= ~LACP_ATTACHED;
+ }
+ }
+}
+
+static bool
+lacp_iface_may_tx(const struct iface *iface)
+{
+ return iface->port->lacp & LACP_ACTIVE
+ || iface->lacp_status & (LACP_CURRENT | LACP_EXPIRED);
+}
+
+static void
+lacp_run(struct port *port)
+{
+ size_t i;
+ struct ofpbuf packet;
+
+ if (!port->lacp) {
+ return;
+ }
+
+ ofpbuf_init(&packet, ETH_HEADER_LEN + LACP_PDU_LEN);
+
+ for (i = 0; i < port->n_ifaces; i++) {
+ struct iface *iface = port->ifaces[i];
+
+ if (time_msec() > iface->lacp_rx) {
+ if (iface->lacp_status & LACP_CURRENT) {
+ iface_set_lacp_expired(iface);
+ } else if (iface->lacp_status & LACP_EXPIRED) {
+ iface_set_lacp_defaulted(iface);
+ }
+ }
+ }
+
+ if (port->lacp_need_update) {
+ lacp_update_ifaces(port);
+ }
+
+ for (i = 0; i < port->n_ifaces; i++) {
+ struct iface *iface = port->ifaces[i];
+ uint8_t ea[ETH_ADDR_LEN];
+ int error;
+
+ if (time_msec() < iface->lacp_tx || !lacp_iface_may_tx(iface)) {
+ continue;
+ }
+
+ error = netdev_get_etheraddr(iface->netdev, ea);
+ if (!error) {
+ iface->lacp_actor.state = iface_get_lacp_state(iface);
+ compose_lacp_packet(&packet, &iface->lacp_actor,
+ &iface->lacp_partner, ea);
+ iface_send_packet(iface, &packet);
+ } else {
+ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 10);
+ VLOG_ERR_RL(&rl, "iface %s: failed to obtain Ethernet address "
+ "(%s)", iface->name, strerror(error));
+ }
+
+ iface->lacp_tx = time_msec() +
+ (iface->lacp_partner.state & LACP_STATE_TIME
+ ? LACP_FAST_TIME_TX
+ : LACP_SLOW_TIME_TX);
+ }
+ ofpbuf_uninit(&packet);
+}
+
+static void
+lacp_wait(struct port *port)
+{
+ size_t i;
+
+ if (!port->lacp) {
+ return;
+ }
+
+ for (i = 0; i < port->n_ifaces; i++) {
+ struct iface *iface = port->ifaces[i];
+
+ if (lacp_iface_may_tx(iface)) {
+ poll_timer_wait_until(iface->lacp_tx);
+ }
+
+ if (iface->lacp_status & (LACP_CURRENT | LACP_EXPIRED)) {
+ poll_timer_wait_until(iface->lacp_rx);
+ }
+ }
+}
+\f