| /* Copyright 2004 Linux Networx */ |
| #ifdef PROTO_LACP |
| #if 0 |
| #include "nic.h" |
| #include "timer.h" |
| #endif |
| |
| #define LACP_DEBUG 0 |
| |
| /* Structure definitions originally taken from the linux bond_3ad driver */ |
| |
| #define SLOW_DST_MAC "\x01\x80\xc2\x00\x00\x02" |
| static const char slow_dest[] = SLOW_DST_MAC; |
| |
| |
| #define SLOW_SUBTYPE_LACP 1 |
| #define SLOW_SUBTYPE_MARKER 2 |
| |
| struct slow_header { |
| uint8_t subtype; |
| }; |
| |
| struct lacp_info { |
| uint16_t system_priority; |
| uint8_t system[ETH_ALEN]; |
| uint16_t key; |
| uint16_t port_priority; |
| uint16_t port; |
| uint8_t state; |
| uint8_t reserved[3]; |
| } PACKED; |
| |
| #define LACP_CMP_LEN (2 + 6 + 2 + 2 + 2) |
| #define LACP_CP_LEN (2 + 6 + 2 + 2 + 2 + 1) |
| |
| /* Link Aggregation Control Protocol(LACP) data unit structure(43.4.2.2 in the 802.3ad standard) */ |
| struct slow_lacp { |
| uint8_t subtype; /* = LACP(= 0x01) */ |
| uint8_t version_number; |
| uint8_t tlv_type_actor_info; /* = actor information(type/length/value) */ |
| #define LACP_TLV_TERMINATOR 0 |
| #define LACP_TLV_ACTOR 1 |
| #define LACP_TLV_PARTNER 2 |
| #define LACP_TLV_COLLECTOR 3 |
| uint8_t actor_information_length; /* = 20 */ |
| struct lacp_info actor; |
| uint8_t tlv_type_partner_info; /* = partner information */ |
| uint8_t partner_information_length; /* = 20 */ |
| struct lacp_info partner; |
| uint8_t tlv_type_collector_info; /* = collector information */ |
| uint8_t collector_information_length; /* = 16 */ |
| uint16_t collector_max_delay; |
| uint8_t reserved_12[12]; |
| uint8_t tlv_type_terminator; /* = terminator */ |
| uint8_t terminator_length; /* = 0 */ |
| uint8_t reserved_50[50]; /* = 0 */ |
| } PACKED; |
| |
| /* Marker Protocol Data Unit(PDU) structure(43.5.3.2 in the 802.3ad standard) */ |
| struct slow_marker { |
| uint8_t subtype; /* = 0x02 (marker PDU) */ |
| uint8_t version_number; /* = 0x01 */ |
| uint8_t tlv_type; |
| #define MARKER_TLV_TERMINATOR 0 /* marker terminator */ |
| #define MARKER_TLV_INFO 1 /* marker information */ |
| #define MARKER_TLV_RESPONSE 2 /* marker response information */ |
| uint8_t marker_length; /* = 0x16 */ |
| uint16_t requester_port; /* The number assigned to the port by the requester */ |
| uint8_t requester_system[ETH_ALEN]; /* The requester's system id */ |
| uint32_t requester_transaction_id; /* The transaction id allocated by the requester, */ |
| uint16_t pad; /* = 0 */ |
| uint8_t tlv_type_terminator; /* = 0x00 */ |
| uint8_t terminator_length; /* = 0x00 */ |
| uint8_t reserved_90[90]; /* = 0 */ |
| } PACKED; |
| |
| union slow_union { |
| struct slow_header header; |
| struct slow_lacp lacp; |
| struct slow_marker marker; |
| }; |
| |
| #define FAST_PERIODIC_TIME (1*TICKS_PER_SEC) |
| #define SLOW_PERIODIC_TIME (30*TICKS_PER_SEC) |
| #define SHORT_TIMEOUT_TIME (3*FAST_PERIODIC_TIME) |
| #define LONG_TIMEOUT_TIME (3*SLOW_PERIODIC_TIME) |
| #define CHURN_DETECTION_TIME (60*TICKS_PER_SEC) |
| #define AGGREGATE_WAIT_TIME (2*TICKS_PER_SEC) |
| |
| #define LACP_ACTIVITY (1 << 0) |
| #define LACP_TIMEOUT (1 << 1) |
| #define LACP_AGGREGATION (1 << 2) |
| #define LACP_SYNCHRONIZATION (1 << 3) |
| #define LACP_COLLECTING (1 << 4) |
| #define LACP_DISTRIBUTING (1 << 5) |
| #define LACP_DEFAULTED (1 << 6) |
| #define LACP_EXPIRED (1 << 7) |
| |
| #define UNSELECTED 0 |
| #define STANDBY 1 |
| #define SELECTED 2 |
| |
| |
| struct lacp_state { |
| struct slow_lacp pkt; |
| unsigned long current_while_timer; /* Time when the LACP information expires */ |
| unsigned long periodic_timer; /* Time when I need to send my partner an update */ |
| }; |
| |
| static struct lacp_state lacp; |
| |
| |
| #if LACP_DEBUG > 0 |
| static void print_lacp_state(uint8_t state) |
| { |
| printf("%hhx", state); |
| if (state & LACP_ACTIVITY) { |
| printf(" Activity"); |
| } |
| if (state & LACP_TIMEOUT) { |
| printf(" Timeout"); |
| } |
| if (state & LACP_AGGREGATION) { |
| printf(" Aggregation"); |
| } |
| if (state & LACP_SYNCHRONIZATION) { |
| printf(" Syncronization"); |
| } |
| if (state & LACP_COLLECTING) { |
| printf(" Collecting"); |
| } |
| if (state & LACP_DISTRIBUTING) { |
| printf(" Distributing"); |
| } |
| if (state & LACP_DEFAULTED) { |
| printf(" Defaulted"); |
| } |
| if (state & LACP_EXPIRED) { |
| printf(" Expired"); |
| } |
| printf("\n"); |
| } |
| |
| static inline void print_lacpdu(struct slow_lacp *pkt) |
| { |
| printf("subtype version: %hhx %hhx\n", |
| pkt->subtype, pkt->version_number); |
| printf("actor_tlv %hhx", pkt->tlv_type_actor_info); |
| printf(" len: %hhx (\n", pkt->actor_information_length); |
| printf(" sys_pri: %hx", ntohs(pkt->actor.system_priority)); |
| printf(" mac: %!", pkt->actor.system); |
| printf(" key: %hx", ntohs(pkt->actor.key)); |
| printf(" port_pri: %hx", ntohs(pkt->actor.port_priority)); |
| printf(" port: %hx\n", ntohs(pkt->actor.port)); |
| printf(" state: "); |
| print_lacp_state(pkt->actor.state); |
| #if LACP_DEBUG > 1 |
| printf(" reserved: %hhx %hhx %hhx\n", |
| pkt->actor.reserved[0], pkt->actor.reserved[1], pkt->actor.reserved[2]); |
| #endif |
| printf(")\n"); |
| printf("partner_tlv: %hhx", pkt->tlv_type_partner_info); |
| printf(" len: %hhx (\n", pkt->partner_information_length); |
| printf(" sys_pri: %hx", ntohs(pkt->partner.system_priority)); |
| printf(" mac: %!", pkt->partner.system); |
| printf(" key: %hx", ntohs(pkt->partner.key)); |
| printf(" port_pri: %hx", ntohs(pkt->partner.port_priority)); |
| printf(" port: %hx\n", ntohs(pkt->partner.port)); |
| printf(" state: "); |
| print_lacp_state(pkt->partner.state); |
| #if LACP_DEBUG > 1 |
| printf(" reserved: %hhx %hhx %hhx\n", |
| pkt->partner.reserved[0], pkt->partner.reserved[1], pkt->partner.reserved[2]); |
| #endif |
| printf(")\n"); |
| printf("collector_tlv: %hhx ", pkt->tlv_type_collector_info); |
| printf(" len: %hhx (", pkt->collector_information_length); |
| printf(" max_delay: %hx", ntohs(pkt->collector_max_delay)); |
| #if LACP_DEBUG > 1 |
| printf("reserved_12: %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx %hhx\n", |
| pkt->reserved_12[0], pkt->reserved_12[1], pkt->reserved_12[2], |
| pkt->reserved_12[3], pkt->reserved_12[4], pkt->reserved_12[5], |
| pkt->reserved_12[6], pkt->reserved_12[7], pkt->reserved_12[8], |
| pkt->reserved_12[9], pkt->reserved_12[10], pkt->reserved_12[11]); |
| #endif |
| printf(" )\n"); |
| printf("terminator_tlv: %hhx", pkt->tlv_type_terminator); |
| printf(" len: %hhx ()\n", pkt->terminator_length); |
| } |
| |
| static inline unsigned long lacp_timer_val(unsigned long now, unsigned long when) |
| { |
| return when?(when - now)/TICKS_PER_SEC : 0; |
| } |
| static void print_lacp(const char *which, struct slow_lacp *pkt, unsigned long now) |
| { |
| printf("%s\n", which); |
| print_lacpdu(pkt); |
| printf("timers: c %ds p %ds\n", |
| lacp_timer_val(now, lacp.current_while_timer), |
| lacp_timer_val(now, lacp.periodic_timer) |
| ); |
| printf("\n"); |
| } |
| #else /* LACP_DEBUG */ |
| #define print_lacp(which, pkt, now) do {} while(0) |
| #endif /* LACP_DEBUG */ |
| |
| static void lacp_init_state(const uint8_t *mac) |
| { |
| memset(&lacp, 0, sizeof(lacp)); |
| |
| /* Initialize the packet constants */ |
| lacp.pkt.subtype = 1; |
| lacp.pkt.version_number = 1; |
| |
| |
| /* The default state of my interface */ |
| lacp.pkt.tlv_type_actor_info = LACP_TLV_ACTOR; |
| lacp.pkt.actor_information_length = 0x14; |
| lacp.pkt.actor.system_priority = htons(1); |
| memcpy(lacp.pkt.actor.system, mac, ETH_ALEN); |
| lacp.pkt.actor.key = htons(1); |
| lacp.pkt.actor.port = htons(1); |
| lacp.pkt.actor.port_priority = htons(1); |
| lacp.pkt.actor.state = |
| LACP_SYNCHRONIZATION | |
| LACP_COLLECTING | |
| LACP_DISTRIBUTING | |
| LACP_DEFAULTED; |
| |
| /* Set my partner defaults */ |
| lacp.pkt.tlv_type_partner_info = LACP_TLV_PARTNER; |
| lacp.pkt.partner_information_length = 0x14; |
| lacp.pkt.partner.system_priority = htons(1); |
| /* memset(lacp.pkt.parnter_system, 0, ETH_ALEN); */ |
| lacp.pkt.partner.key = htons(1); |
| lacp.pkt.partner.port = htons(1); |
| lacp.pkt.partner.port_priority = htons(1); |
| lacp.pkt.partner.state = |
| LACP_ACTIVITY | |
| LACP_SYNCHRONIZATION | |
| LACP_COLLECTING | |
| LACP_DISTRIBUTING | |
| LACP_DEFAULTED; |
| |
| lacp.pkt.tlv_type_collector_info = LACP_TLV_COLLECTOR; |
| lacp.pkt.collector_information_length = 0x10; |
| lacp.pkt.collector_max_delay = htons(0x8000); /* ???? */ |
| |
| lacp.pkt.tlv_type_terminator = LACP_TLV_TERMINATOR; |
| lacp.pkt.terminator_length = 0; |
| } |
| |
| #define LACP_NTT_MASK (LACP_ACTIVITY | LACP_TIMEOUT | \ |
| LACP_SYNCHRONIZATION | LACP_AGGREGATION) |
| |
| static inline int lacp_update_ntt(struct slow_lacp *pkt) |
| { |
| int ntt = 0; |
| if ((memcmp(&pkt->partner, &lacp.pkt.actor, LACP_CMP_LEN) != 0) || |
| ((pkt->partner.state & LACP_NTT_MASK) != |
| (lacp.pkt.actor.state & LACP_NTT_MASK))) |
| { |
| ntt = 1; |
| } |
| return ntt; |
| } |
| |
| static inline void lacp_record_pdu(struct slow_lacp *pkt) |
| { |
| memcpy(&lacp.pkt.partner, &pkt->actor, LACP_CP_LEN); |
| |
| lacp.pkt.actor.state &= ~LACP_DEFAULTED; |
| lacp.pkt.partner.state &= ~LACP_SYNCHRONIZATION; |
| if ((memcmp(&pkt->partner, &lacp.pkt.actor, LACP_CMP_LEN) == 0) && |
| ((pkt->partner.state & LACP_AGGREGATION) == |
| (lacp.pkt.actor.state & LACP_AGGREGATION))) |
| { |
| lacp.pkt.partner.state |= LACP_SYNCHRONIZATION; |
| } |
| if (!(pkt->actor.state & LACP_AGGREGATION)) { |
| lacp.pkt.partner.state |= LACP_SYNCHRONIZATION; |
| } |
| |
| /* ACTIVITY? */ |
| } |
| |
| static inline int lacp_timer_expired(unsigned long now, unsigned long when) |
| { |
| return when && (now > when); |
| } |
| |
| static inline void lacp_start_periodic_timer(unsigned long now) |
| { |
| if ((lacp.pkt.partner.state & LACP_ACTIVITY) || |
| (lacp.pkt.actor.state & LACP_ACTIVITY)) { |
| lacp.periodic_timer = now + |
| (((lacp.pkt.partner.state & LACP_TIMEOUT)? |
| FAST_PERIODIC_TIME : SLOW_PERIODIC_TIME)); |
| } |
| } |
| |
| static inline void lacp_start_current_while_timer(unsigned long now) |
| { |
| lacp.current_while_timer = now + |
| ((lacp.pkt.actor.state & LACP_TIMEOUT) ? |
| SHORT_TIMEOUT_TIME : LONG_TIMEOUT_TIME); |
| |
| lacp.pkt.actor.state &= ~LACP_EXPIRED; |
| } |
| |
| static void send_lacp_reports(unsigned long now, int ntt) |
| { |
| if (memcmp(nic.node_addr, lacp.pkt.actor.system, ETH_ALEN) != 0) { |
| lacp_init_state(nic.node_addr); |
| } |
| /* If the remote information has expired I need to take action */ |
| if (lacp_timer_expired(now, lacp.current_while_timer)) { |
| if (!(lacp.pkt.actor.state & LACP_EXPIRED)) { |
| lacp.pkt.partner.state &= ~LACP_SYNCHRONIZATION; |
| lacp.pkt.partner.state |= LACP_TIMEOUT; |
| lacp.pkt.actor.state |= LACP_EXPIRED; |
| lacp.current_while_timer = now + SHORT_TIMEOUT_TIME; |
| ntt = 1; |
| } |
| else { |
| lacp_init_state(nic.node_addr); |
| } |
| } |
| /* If the periodic timer has expired I need to transmit */ |
| if (lacp_timer_expired(now, lacp.periodic_timer)) { |
| ntt = 1; |
| /* Reset by lacp_start_periodic_timer */ |
| } |
| if (ntt) { |
| eth_transmit(slow_dest, ETH_P_SLOW, sizeof(lacp.pkt), &lacp.pkt); |
| |
| /* Restart the periodic timer */ |
| lacp_start_periodic_timer(now); |
| |
| print_lacp("Trasmitted", &lacp.pkt, now); |
| } |
| } |
| |
| static inline void send_eth_slow_reports(unsigned long now) |
| { |
| send_lacp_reports(now, 0); |
| } |
| |
| static inline void process_eth_slow(unsigned short ptype, unsigned long now) |
| { |
| union slow_union *pkt; |
| if ((ptype != ETH_P_SLOW) || |
| (nic.packetlen < (ETH_HLEN + sizeof(pkt->header)))) { |
| return; |
| } |
| pkt = (union slow_union *)&nic.packet[ETH_HLEN]; |
| if ((pkt->header.subtype == SLOW_SUBTYPE_LACP) && |
| (nic.packetlen >= ETH_HLEN + sizeof(pkt->lacp))) { |
| int ntt; |
| if (memcmp(nic.node_addr, lacp.pkt.actor.system, ETH_ALEN) != 0) { |
| lacp_init_state(nic.node_addr); |
| } |
| /* As long as nic.packet is 2 byte aligned all is good */ |
| print_lacp("Received", &pkt->lacp, now); |
| /* I don't actually implement the MUX or SELECT |
| * machines. |
| * |
| * What logically happens when the client and I |
| * disagree about an aggregator is the current |
| * aggregtator is unselected. The MUX machine places |
| * me in DETACHED. The SELECT machine runs and |
| * reslects the same aggregator. If I go through |
| * these steps fast enough an outside observer can not |
| * notice this. |
| * |
| * Since the process will not generate any noticeable |
| * effect it does not need an implmenetation. This |
| * keeps the code simple and the code and binary |
| * size down. |
| */ |
| /* lacp_update_selected(&pkt->lacp); */ |
| ntt = lacp_update_ntt(&pkt->lacp); |
| lacp_record_pdu(&pkt->lacp); |
| lacp_start_current_while_timer(now); |
| send_lacp_reports(now, ntt); |
| } |
| /* If we receive a marker information packet return it */ |
| else if ((pkt->header.subtype == SLOW_SUBTYPE_MARKER) && |
| (nic.packetlen >= ETH_HLEN + sizeof(pkt->marker)) && |
| (pkt->marker.tlv_type == MARKER_TLV_INFO) && |
| (pkt->marker.marker_length == 0x16)) |
| { |
| pkt->marker.tlv_type = MARKER_TLV_RESPONSE; |
| eth_transmit(slow_dest, ETH_P_SLOW, |
| sizeof(pkt->marker), &pkt->marker); |
| } |
| |
| } |
| #else |
| |
| #define send_eth_slow_reports(now) do {} while(0) |
| #define process_eth_slow(ptype, now) do {} while(0) |
| |
| #endif |