diff --git a/doc/iec61375-hello.bpf b/doc/iec61375-hello.bpf new file mode 100644 index 0000000..4bbacca --- /dev/null +++ b/doc/iec61375-hello.bpf @@ -0,0 +1,218 @@ + ld [2] ; check dest. MAC and other Ethernet stuff */ + jneq #0xc200000e, drop */ + ldh [0] + jneq #0x0180, drop + ld vlan_avail + jneq #1, drop + ld vlan_tci + and #0x0FFF + jneq #0x01EC, drop + ldh [12] + jneq #0x88CC, drop + ld poff ; have M[0] hold the last ok header start position + tax + ld len + sub x + sub #2 ; size of the first TLV header + st M[0] + ld #0x0e ; the first TLV header starts here + ldx #0x0 + st M[1] ; this snippet tests whether we'd + stx M[2] ; jump out of bounds, i.e. if + ld M[0] ; ((accumulated TLV sizes in x) - + ldx M[1] ; (packet payload size)) < 0 + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] ; test one TLV - load TLV header + and #0xFE00 ; extract type + jeq #0x0000, drop ; type 0 is EOF TLV + jneq #0xFE00, skip_tlv_1 ; skip if not HELLO + ldh [x + 0] ; reload TLV header + and #0x01FF ; extract length + jeq #0x56, okay ; must be 86 +skip_tlv_1: ldh [x + 0] ; reload TLV header + and #0x01FF ; extract length + add #2 ; header length not included + add x ; previous header offset + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] ; test next TLV... etc + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_2 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_2: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_3 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_3: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_4 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_4: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_5 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_5: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_6 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_6: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_7 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_7: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_8 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_8: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, skip_tlv_9 + ldh [x + 0] + and #0x01FF + jeq #0x56, okay +skip_tlv_9: ldh [x + 0] + and #0x01FF + add #2 + add x + st M[1] + stx M[2] + ld M[0] + ldx M[1] + sub x + jset #0x80000000, drop + ld M[1] + ldx M[2] + tax + ldh [x + 0] + and #0xFE00 + jeq #0x0000, drop + jneq #0xFE00, drop + ldh [x + 0] + and #0x01FF + jeq #0x56, okay + jmp drop + okay: ret #-1 ; accept entire packet + drop: ret #0 ; accept nothing diff --git a/include/team.h b/include/team.h index 2d5739e..15366ea 100644 --- a/include/team.h +++ b/include/team.h @@ -275,6 +275,9 @@ bool team_is_our_port(struct team_handle *th, uint32_t port_ifindex); int team_carrier_set(struct team_handle *th, bool carrier_up); int team_carrier_get(struct team_handle *th, bool *carrier_up); int team_link_set(struct team_handle *th, int ifindex, bool link_up); +/* +int team_link_state_set(struct team_handle *th, int ifindex, int new_state); +*/ int team_hwaddr_set(struct team_handle *th, uint32_t ifindex, const char *addr, unsigned int addr_len); int team_hwaddr_get(struct team_handle *th, uint32_t ifindex, diff --git a/libteam/ifinfo.c b/libteam/ifinfo.c index a15788b..f6c0530 100644 --- a/libteam/ifinfo.c +++ b/libteam/ifinfo.c @@ -327,11 +327,13 @@ int ifinfo_event_handler(struct nl_msg *msg, void *arg) switch (nlmsg_hdr(msg)->nlmsg_type) { case RTM_NEWLINK: if (nl_msg_parse(msg, &event_handler_obj_input_newlink, th) < 0) - err(th, "Unknown message type."); + // err(th, "Unknown message type."); + ; break; case RTM_DELLINK: if (nl_msg_parse(msg, &event_handler_obj_input_dellink, th) < 0) - err(th, "Unknown message type."); + // err(th, "Unknown message type."); + ; break; default: return NL_OK; @@ -358,7 +360,8 @@ static int valid_handler(struct nl_msg *msg, void *arg) return NL_OK; if (nl_msg_parse(msg, &valid_handler_obj_input_newlink, th) < 0) - err(th, "Unknown message type."); + // err(th, "Unknown message type."); + ; return NL_OK; } diff --git a/libteam/libteam.c b/libteam/libteam.c index 408b87d..a0c885f 100644 --- a/libteam/libteam.c +++ b/libteam/libteam.c @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ #include #include #include +#include #include #include #include @@ -1712,8 +1714,59 @@ int team_link_set(struct team_handle *th, int ifindex, bool link_up) return -EOPNOTSUPP; #endif } +/* +TEAM_EXPORT +int team_link_state_set(struct team_handle *th, int ifindex, int new_state) { +#ifdef HAVE_RTNL_LINK_SET_CARRIER + int err = 0; + char ifname[16]; + struct nl_sock *sk; + struct nl_cache *brcache; + struct rtnl_link *link; + + team_ifindex2ifname(th, ifindex, ifname, 16); + + sk = nl_socket_alloc(); + if (!sk) + return -ENOMEM; + + err = nl_connect(sk, NETLINK_ROUTE); + if (err) + goto free_sock; + + err = rtnl_link_alloc_cache(sk, AF_BRIDGE, &brcache); + if (err) + goto free_sock; + + link = rtnl_link_get_by_name(brcache, ifname); + if (!link) { + err = -NLE_NODEV; + goto free_cache; + } + rtnl_link_bridge_set_port_state(link, new_state); + err = rtnl_link_change(sk, link, link, NLM_F_REPLACE); + info(th, "...result %d", err); + err = -nl2syserr(err); + + rtnl_link_put(link); + + free_cache: + nl_cache_free(brcache); + free_sock: + nl_socket_free(sk); + + if (err) + warn(th, "NETLINK: could not set state %d on %s", new_state, ifname); + + return err; + +#else + return -EOPNOTSUPP; +#endif +} +*/ /** * @param th libteam library context * @param ifindex interface index diff --git a/man/teamd.conf.5 b/man/teamd.conf.5 index 350ffc9..a88cf9e 100644 --- a/man/teamd.conf.5 +++ b/man/teamd.conf.5 @@ -43,6 +43,8 @@ To do passive load balancing, runner only sets up BPF hash function which will d .BR "lacp "\(em Implements 802.3ad LACP protocol. Can use same Tx port selection possibilities as loadbalance runner. .PP +.BR "ttdp "\(em +Implements IEC61375-2-5 TTDP HELLO protocol. Default: .BR "roundrobin" .RE @@ -99,6 +101,9 @@ ARP requests are sent through a port. If an ARP reply is received, the link is c .PP .BR "nsna_ping "\(em Similar to the previous, except that it uses IPv6 Neighbor Solicitation / Neighbor Advertisement mechanism. This is an alternative to arp_ping and becomes handy in pure-IPv6 environments. +.PP +.BR "ttdp "\(em +Uses IEC61375 TTDP HELLO. .RE .TP .BR "ports " (object) @@ -298,6 +303,41 @@ Default: .BR "0" .RE .PP +.SH TTDP RUNNER SPECIFIC OPTIONS +.TP +.BR "runner.notify_peers.count " (int) +Inherited from the activebackup mode. In order to follow IEC61375-2-5, set to 0. +.TP +.BR "runner.mcast_rejoin.count " (int) +Inherited from the activebackup mode. In order to follow IEC61375-2-5, set to 0. +.TP +.BR "runner.hwaddr_policy " (string) +Inherited from the activebackup mode. The ttdp runner adds two new values for this option: +.RS 7 +.PP +.BR "first "\(em +In this mode, the team device will assume the MAC address of the first member port added to it (which should be the first port mentioned in the configuration file). After this, no MAC address changes are done. +.PP +.BR "fixed "\(em +In this mode, the team device will use the MAC address specified in the teamd configuration file with the "hwaddr" directive, while each member port will use its\' real address. No changes are made otherwise. Either this mode or "first" should be used for TTDP, as MAC address handling is left unspecified in the standard, and the other hwaddr policies are likely to cause confusion and breakage. +.RE +.TP +.BR "runner.chassis_hwaddr " (string) +Sets the MAC address used in the mandatory LLDP Chassis TLV sent in TTDP HELLO frames. IEC61375-2-5:2014 mandates that this TLV is included. Required. +.TP +.BR "runner.identity_hwaddr " (string) +The MAC address used to identify this ETBN. This is copied to the "srcId" field in transmitted HELLO packets. Optional. May be specified either in the link watcher scope or here, in which case the this takes precedence and all member port link watchers inherit the value. +.TP +.BR "runner.local_uuid " (string) +Local consist UUID of the consist that this ETBN is in. Sent in HELLO frames, determines consist identity. Must be in the regular UUID format like "99999999-8888-7777-aabb-ccccddddeeee"; this is encoded in big-endian format. Required, either here or in the link watcher scope. If specified only here, all member port link watchers inherit this value. If specified in both scopes, link watcher scope take precedence. +.TP +.BR "runner.silent " (int) +If set to 2, disables all IPC communication; if set to 1, disables outbound IPC communication (the runner does not send updates, but still listens for IPC communication). If set to 0, two-way IPC communication is enabled. Optional. +.RS 7 +.PP +Default: +.BR "0" +.RE .SH ETHTOOL LINK WATCH SPECIFIC OPTIONS .TP .BR "link_watch.delay_up "| " ports.PORTIFNAME.link_watch.delay_up " (int) @@ -407,6 +447,95 @@ Default: .TP .BR "link_watch.target_host "| " ports.PORTIFNAME.link_watch.target_host " (hostname) Hostname to be converted to IPv6 address which will be filled into NS packet as target address. +.PP +.SH TTDP LINK WATCH SPECIFIC OPTIONS +.TP +.BR "link_watch.slow_interval "| " ports.PORTIFNAME.link_watch.slow_interval " (int) +Time (in ms) between transmissions in SLOW mode. Optional. +.RS 7 +.PP +Default: +.BR 100 +.RE +.TP +.BR "link_watch.fast_interval "| " ports.PORTIFNAME.link_watch.fast_interval " (int) +Time (in ms) between transmissions in FAST mode. Optional. +.RS 7 +.PP +Default: +.BR 15 +.RE +.TP +.BR "link_watch.slow_timeout "| " ports.PORTIFNAME.link_watch.slow_timeout " (int) +Maximum time (in ms) to wait for a packet, before recovery mode is entered and transmission speed is changed to FAST. Optional. +.RS 7 +.PP +Default: +.BR 130 +.RE +.TP +.BR "link_watch.fast_timeout "| " ports.PORTIFNAME.link_watch.fast_timeout " (int) +Maximum time (in ms) to wait for a packet in recovery mode. After this runs out, we consider the link logically DOWN. Optional. +.RS 7 +.PP +Default: +.BR 45 +.RE +.TP +.BR "link_watch.link_state_delay_up "| " ports.PORTIFNAME.link_watch.link_state_delay_up " (int) +Minimum time (in ms) that actual physical link status ("ethtool status") must be UP before the link watcher considers the link physically UP and reports it as such. Used to counter links that keep toggling on and off. Optional. +.RS 7 +.PP +Default: +.BR 0 +.RE +.TP +.BR "link_watch.link_state_delay_down "| " ports.PORTIFNAME.link_watch.link_state_delay_down " (int) +Same as above, but for physical link DOWN status. For instance, if this is set to 100, a link going down physically and then coming back up within 100 ms is not reported as having come down at all (though logical link status, as controlled by the _timeout fields above, might change due to missed HELLO packets during this time). Optional. +.RS 7 +.PP +Default: +.BR 0 +.RE +.TP +.BR "link_watch.local_uuid "| " ports.PORTIFNAME.link_watch.local_uuid " (string) +Local consist UUID of the consist that this ETBN is in. Sent in HELLO frames, determines consist identity. Must be in the regular UUID format like "99999999-8888-7777-aabb-ccccddddeeee"; this is encoded in big-endian format. Required, either here or in the runner scope; if both, the value specified here takes precedence. +.TP +.BR "link_watch.identify_hwaddr "| " ports.PORTIFNAME.link_watch.identify_hwaddr " (string) +The MAC address to use to identify this ETBN. This is copied to the "srcId" field in transmitted HELLO packets. Optional, may be specified either here or in the runner scope, in which case the former takes precedence. If not specified at all, the address of the team device is used, which may cause strange behavior. +.TP +.BR "link_watch.direction "| " ports.PORTIFNAME.link_watch.direction " (int) +TTDP direction of this individual port. Transmitted on the wire and determines a lot of things in TTDP. Optional, but if not set here, must be set in the runner scope (and that value is then used for all member links). +.TP +.BR "link_watch.line "| " ports.PORTIFNAME.link_watch.line " (string) +TTDP link number of this individual port. Transmitted on the wire and determines a lot of things in TTDP. Required, must be "a", "b", "c" or "d" (not case sensitive). +.TP +.BR "link_watch.initial_mode "| " ports.PORTIFNAME.link_watch.initial_mode " (int) +Determines which transmission mode this port starts up in. Use 1 for regular SLOW startup, and 2 to start in FAST mode. This may change the behavior of any other ETBNs that are already up as we\'re starting up. Optional. +.RS 7 +.PP +Default: +.BR 1 +.RE +.TP +.BR "link_watch.fast_failed_recovery_mode "| " ports.PORTIFNAME.link_watch.fast_failed_recovery_mode " (bool) +Determines what to do when we leave (fail) recovery mode due to not hearing from any neighbor. The default is to return to SLOW mode, which may delay actual recovery. Setting this to true stays in FAST mode instead. Optional. +.RS 7 +.PP +Default: +.BR "false" +.RE +.TP +.BR "link_watch.immediate_timer_start_mode "| " ports.PORTIFNAME.link_watch.immediate_timer_start_mode " (bool) +Determines the startup behavior of the timers used for packet transmission (there is one SLOW timer and one FAST timer, only one of which runs at any given time). If this option is set to false, the initial timer interval is equal to the actual timer interval. In other words, if a change to the FAST transmission mode is decided upon at time t=0, the first FAST mode packet will be transmitted at t=fast_interval, the next at t=2*fast_interval, and so on. If this setting set to true, the initial interval is set to 0, so that in this example, the initial packet is transmitted immediately at t=0, the next one at t=fast_interval, and so on. Optional. +.RS 7 +.PP +Default: +.BR "false" +.RE +.TP +.BR "link_watch.strict_peer_recv_status "| " ports.PORTIFNAME.link_watch.strict_peer_recv_status " (bool) +Determines whether the link watcher requires its\' neighbor (peer) to acknowledge its existence by setting the corresponding bits in the peer\'s recvStatus field of HELLO frame to \'10\' before considering the link as logically up. If this option is set to false, these bits are ignored and only receiving HELLo frames from the neighbor suffices to consider the link logically up. .SH EXAMPLES .PP .nf diff --git a/teamd/Makefile.am b/teamd/Makefile.am index 904d76c..6234ba0 100644 --- a/teamd/Makefile.am +++ b/teamd/Makefile.am @@ -2,7 +2,7 @@ MAINTAINERCLEANFILES = Makefile.in ACLOCAL_AMFLAGS = -I m4 -AM_CFLAGS = -I${top_srcdir}/include +AM_CFLAGS = -I${top_srcdir}/include -Wstrict-aliasing AM_CPPFLAGS='-DLOCALSTATEDIR="$(localstatedir)"' @@ -19,7 +19,9 @@ teamd_SOURCES=teamd.c teamd_common.c teamd_json.c teamd_config.c teamd_state.c \ teamd_zmq.c teamd_usock.c teamd_phys_port_check.c \ teamd_bpf_chef.c teamd_hash_func.c teamd_balancer.c \ teamd_runner_basic_ones.c teamd_runner_activebackup.c \ - teamd_runner_loadbalance.c teamd_runner_lacp.c + teamd_runner_loadbalance.c teamd_runner_lacp.c \ + teamd_runner_ttdp.c teamd_lw_ttdp.c ttdp_checksum.c \ + teamd_lag_state_persistence.c EXTRA_DIST = example_configs dbus redhat teamd.conf.in @@ -27,4 +29,4 @@ noinst_HEADERS = teamd.h teamd_workq.h teamd_bpf_chef.h teamd_ctl.h \ teamd_json.h teamd_dbus.h teamd_zmq.h teamd_usock.h \ teamd_dbus_common.h teamd_usock_common.h teamd_config.h \ teamd_state.h teamd_phys_port_check.h teamd_link_watch.h \ - teamd_zmq_common.h + teamd_zmq_common.h teamd_lw_ttdp.h teamd_lag_state_persistence.h diff --git a/teamd/teamd.c b/teamd/teamd.c index 4a0aad7..27be988 100644 --- a/teamd/teamd.c +++ b/teamd/teamd.c @@ -65,6 +65,7 @@ static const struct teamd_runner *teamd_runner_list[] = { &teamd_runner_activebackup, &teamd_runner_loadbalance, &teamd_runner_lacp, + &teamd_runner_ttdp }; #define TEAMD_RUNNER_LIST_SIZE ARRAY_SIZE(teamd_runner_list) @@ -280,7 +281,7 @@ static int handle_period_fd(int fd) return -EINVAL; } if (exp > 1) - teamd_log_warn("some periodic function calls missed (%" PRIu64 ")", + teamd_log_warn("fd %d: some periodic function calls missed (%" PRIu64 ")", fd, exp - 1); return 0; } @@ -693,12 +694,23 @@ int teamd_loop_callback_disable(struct teamd_context *ctx, const char *cb_name, static int callback_daemon_signal(struct teamd_context *ctx, int events, void *priv) { + static int failed = 0; int sig; /* Get signal */ if ((sig = daemon_signal_next()) <= 0) { - teamd_log_err("daemon_signal_next() failed."); - return -EINVAL; + teamd_log_err("daemon_signal_next() failed = %d", sig); + daemon_signal_done(); + + if (daemon_signal_init(SIGINT, SIGTERM, SIGQUIT, SIGHUP, 0) != 0) { + sig = SIGINT; + } else if (++failed < 10) { + return 0; + } else { + sig = SIGINT; + } + } else { + failed = 0; } /* Dispatch signal */ @@ -1467,13 +1479,18 @@ static void teamd_fini(struct teamd_context *ctx) team_free(ctx->th); } +static void exiting(void) { + teamd_log_info("Exiting, clearing PID file\n"); + daemon_pid_file_remove(); +} + static int teamd_start(struct teamd_context *ctx, enum teamd_exit_code *p_ret) { pid_t pid; int err = 0; if (getuid() == 0) - teamd_log_warn("This program is not intended to be run as root."); + teamd_log_dbg(ctx, "This program is not intended to be run as root."); if (daemon_reset_sigs(-1) < 0) { teamd_log_err("Failed to reset all signal handlers."); @@ -1535,6 +1552,8 @@ static int teamd_start(struct teamd_context *ctx, enum teamd_exit_code *p_ret) return -errno; } + atexit(exiting); + if (daemon_signal_init(SIGINT, SIGTERM, SIGQUIT, SIGHUP, 0) < 0) { teamd_log_err("Could not register signal handlers."); daemon_retval_send(errno); diff --git a/teamd/teamd.h b/teamd/teamd.h index d2411fd..959460a 100644 --- a/teamd/teamd.h +++ b/teamd/teamd.h @@ -52,8 +52,11 @@ #define teamd_log_dbgx(ctx, val, args...) \ ({ if (val <= ctx->debug) daemon_log(LOG_DEBUG, ##args); }) - +#ifdef DEBUG #define teamd_log_dbg(ctx, args...) teamd_log_dbgx(ctx, 1, ##args) +#else +#define teamd_log_dbg(ctx, args...) do {} while (0) +#endif static inline void TEAMD_BUG(void) { @@ -275,6 +278,7 @@ extern const struct teamd_runner teamd_runner_random; extern const struct teamd_runner teamd_runner_activebackup; extern const struct teamd_runner teamd_runner_loadbalance; extern const struct teamd_runner teamd_runner_lacp; +extern const struct teamd_runner teamd_runner_ttdp; struct teamd_port_priv { int (*init)(struct teamd_context *ctx, struct teamd_port *tdport, @@ -366,6 +370,10 @@ int teamd_packet_sock_open_type(int type, int *sock_p, const uint32_t ifindex, const unsigned short family, const struct sock_fprog *fprog, const struct sock_fprog *alt_fprog); +int teamd_packet_sock_open_type_ext(int type, int *sock_p, const uint32_t ifindex, + const unsigned short family, + const struct sock_fprog *fprog, + const struct sock_fprog *alt_fprog); int teamd_packet_sock_open(int *sock_p, const uint32_t ifindex, const unsigned short family, const struct sock_fprog *fprog, diff --git a/teamd/teamd_common.c b/teamd/teamd_common.c index f32c2ef..3d56baf 100644 --- a/teamd/teamd_common.c +++ b/teamd/teamd_common.c @@ -81,6 +81,49 @@ static int attach_filter(int sock, const struct sock_fprog *pref_fprog, return 0; } +/* sockfds[i] = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_8021Q)); */ +int teamd_packet_sock_open_type_ext(int type, int *sock_p, const uint32_t ifindex, + const unsigned short family, + const struct sock_fprog *fprog, + const struct sock_fprog *alt_fprog) +{ + struct sockaddr_ll ll_my; + int sock; + int ret; + int err; + + sock = socket(AF_PACKET, type, family); + if (sock == -1) { + teamd_log_err("Failed to create packet socket."); + return -errno; + } + + if (fprog) { + err = attach_filter(sock, fprog, alt_fprog); + if (err) { + teamd_log_err("Failed to attach filter."); + goto close_sock; + } + } + + memset(&ll_my, 0, sizeof(ll_my)); + ll_my.sll_family = AF_PACKET; + ll_my.sll_ifindex = ifindex; + ll_my.sll_protocol = family; + ret = bind(sock, (struct sockaddr *) &ll_my, sizeof(ll_my)); + if (ret == -1) { + teamd_log_err("Failed to bind socket."); + err = -errno; + goto close_sock; + } + + *sock_p = sock; + return 0; +close_sock: + close(sock); + return err; +} + int teamd_packet_sock_open_type(int type, int *sock_p, const uint32_t ifindex, const unsigned short family, const struct sock_fprog *fprog, diff --git a/teamd/teamd_lag_state_persistence.c b/teamd/teamd_lag_state_persistence.c new file mode 100644 index 0000000..5216d65 --- /dev/null +++ b/teamd/teamd_lag_state_persistence.c @@ -0,0 +1,348 @@ +/* + * teamd_lag_state_persistence.c teamd TTDP runner state persistence + * Copyright (C) 2019 Westermo + * Author: Jacques de Laval + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include "teamd_lag_state_persistence.h" + +#define TEAMNAME_OR_EMPTY(P) ((P)\ + ? P : "ttdp-runner") + +#define teamd_ttdp_log_warnx(P, format, args...) daemon_log(LOG_WARNING, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) + +#define MAC_FMT_STR "%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 +#define UUID_FMT_STR "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 \ + "-" \ + "%.2" PRIx8 "%.2" PRIx8 \ + "-" \ + "%.2" PRIx8 "%.2" PRIx8 \ + "-" \ + "%.2" PRIx8 "%.2" PRIx8 \ + "-" \ + "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 "%.2" PRIx8 + +#define TEAMD_STATE_PATH "/run/train/lag" + +#define TTDP_MAX_LINES_PER_AGG 4 + +static int mkpathdir(char *file_path, mode_t mode) +{ + char *p; + + for (p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) { + *p = '\0'; + if (mkdir(file_path, mode) == -1) { + if (errno != EEXIST) { + *p = '/'; + return -1; + } + } + *p = '/'; + } + return 0; +} + +FILE *open_lag_state_file(const char *team_devname, const char *path, const char *mode) { + char pathname[PATH_MAX] = {'\0'}; + + int written = snprintf(pathname, sizeof(pathname), TEAMD_STATE_PATH "/%s/%s", team_devname, path); + if (written >= sizeof(pathname)) { + return NULL; + } + + int err = mkpathdir(pathname, S_IRWXU | (S_IRGRP | S_IXGRP) | (S_IROTH | S_IXOTH)); + if (err < 0) { + return NULL; + } + + return fopen(pathname, mode); +} + +/** + * lag_state_write_identity - Persist aggregate identity to state files + * + * State files: + * /run/train/lag//identity/mac - Hardware address (MAC) as string + * /run/train/lag//identity/uuid - UUID of local consist as string + */ +int lag_state_write_identity(struct teamd_context *ctx, void *priv) { + struct ab *ab = priv; + if (!(ab->identity_hwaddr_set) || !(ab->local_uuid_set)) { + teamd_ttdp_log_warnx(ctx->team_devname, "Could not set identity state" + " files: not all values configued: (identity hwaddr:%d" + " local uuid:%d)", + ab->identity_hwaddr_set, ab->local_uuid_set); + return 1; + } + + FILE *state_fp = NULL; + + state_fp = open_lag_state_file(ctx->team_devname, "identity/mac", "w"); + if (!state_fp) { + return -1; + } + fprintf(state_fp, MAC_FMT_STR "\n", + ab->identity_hwaddr[0], + ab->identity_hwaddr[1], + ab->identity_hwaddr[2], + ab->identity_hwaddr[3], + ab->identity_hwaddr[4], + ab->identity_hwaddr[5]); + fclose(state_fp); + + state_fp = open_lag_state_file(ctx->team_devname, "identity/uuid", "w"); + if (!state_fp) { + return -1; + } + fprintf(state_fp, UUID_FMT_STR "\n", + ab->local_uuid[0], ab->local_uuid[1], ab->local_uuid[2], ab->local_uuid[3], + ab->local_uuid[4], ab->local_uuid[5], + ab->local_uuid[6], ab->local_uuid[7], + ab->local_uuid[8], ab->local_uuid[9], + ab->local_uuid[10], ab->local_uuid[11], ab->local_uuid[12], ab->local_uuid[13], ab->local_uuid[14], ab->local_uuid[15]); + fclose(state_fp); + + return 0; +} + +/** + * lag_state_write_line_status - Persist aggregate line statuses to state files + * + * State files: + * /run/train/lag//line/direction - Direction of aggregated lines, possible values: "0" or "1" + * /run/train/lag//line//status - Status of line, possible values: "0" (Error), "1" (False), "2" (True) or "3" (Undefined) + * /run/train/lag//line//connected_neighbor - Connected neighbor line, possible values: "A", "B", "C", "D" or "-" (Undefined) + * /run/train/lag//line//port_state - Port state, possible values: "0" (Disabled), "2" (Forwarding) or "3" (Discarding) + * /run/train/lag//line/_commit - A dummy value is written to this file to indicate that aggregate line statuses have changed + */ +int lag_state_write_line_status(struct teamd_context *ctx, void *priv) { + struct ab *ab = priv; + FILE *state_fp = NULL; + + state_fp = open_lag_state_file(ctx->team_devname, "line/direction", "w"); + if (!state_fp) { + return -1; + } + fprintf(state_fp, "%"PRIu8"\n", ab->direction - 1); + fclose(state_fp); + + char path[PATH_MAX] = {'\0'}; + uint8_t port_status; + for (int i = 0; i < TTDP_MAX_LINES_PER_AGG; i++) { + memset(path, 0, sizeof(path)); + snprintf(path, sizeof(path), "line/%c/status", 'A' + i); + state_fp = open_lag_state_file(ctx->team_devname, path, "w"); + if (!state_fp) { + return -1; + } + port_status = ab->port_statuses[i]; + if (port_status == 0) { + /* To make sure we don't write uninitialized data, make sure we write + * "UNDEFINED" (3) instead of "ERROR" (0). */ + port_status = TTDP_LOGIC_UNDEFINED; + } + fprintf(state_fp, "%" PRIu8 "\n", port_status); + fclose(state_fp); + + memset(path, 0, sizeof(path)); + snprintf(path, sizeof(path), "line/%c/connected_neighbor", 'A' + i); + state_fp = open_lag_state_file(ctx->team_devname, path, "w"); + if (!state_fp) { + return -1; + } + fprintf(state_fp, "%c\n", i >= TTDP_MAX_PORTS_PER_TEAM ? '-' : ab->neighbor_lines[i]); + fclose(state_fp); + + memset(path, 0, sizeof(path)); + snprintf(path, sizeof(path), "line/%c/port_state", 'A' + i); + state_fp = open_lag_state_file(ctx->team_devname, path, "w"); + if (!state_fp) { + return -1; + } + if (i >= TTDP_MAX_PORTS_PER_TEAM) { + fprintf(state_fp, "%d\n", TTDP_PORT_STATE_DISABLED); + } else if (ab->is_discarding) { + fprintf(state_fp, "%d\n", TTDP_PORT_STATE_DISCARDING); + } else { + fprintf(state_fp, "%d\n", TTDP_PORT_STATE_FORWARDING); + } + fclose(state_fp); + } + + /* After all line status values have been written we write to a separate file + * so that any receiver knows that a full line status update is available */ + state_fp = open_lag_state_file(ctx->team_devname, "line/_commit", "w"); + if (!state_fp) { + return -1; + } + fprintf(state_fp, "1\n"); + fclose(state_fp); + + return 0; +} + +/** + * lag_state_write_aggregate_role - Persist aggregate role to state file + * + * State files: + * /run/train/lag//aggregate_role - Aggregate role, possible values: "0" (Floating end), "1" (Floating intermediate), "2" (Fixed end) or "3" (Fixed intermediate) + */ +int lag_state_write_aggregate_role(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "aggregate_role", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%"PRIu8"\n", ab->aggregate_status); + fclose(fp); + return 0; +} + +/** + * lag_state_write_diag_crossed_lines_detected - Persist aggregate crossed lines status to state file + * + * State files: + * /run/train/lag//diag/crossed_lines - Crossed lines status, possible values: "0" (False) or "1" (True) + */ +int lag_state_write_diag_crossed_lines_detected(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "diag/crossed_lines", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->crossed_lines_detected); + fclose(fp); + return 0; +} + +/** + * lag_state_write_diag_mixed_consist_orientation_detected - Persist aggregate mixed consist orientation status to state file + * + * State files: + * /run/train/lag//diag/mixed_consist_orientation - Mixed consist orientation status, possible values: "0" (False) or "1" (True) + */ +int lag_state_write_diag_mixed_consist_orientation_detected(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "diag/mixed_consist_orientation", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->mixed_consist_orientation_detected); + fclose(fp); + return 0; +} + +/** + * lag_state_write_remote_inhibition - Persist aggregate remote inhibition status to state file + * + * State files: + * /run/train/lag//remote_inhibition - Remove inhibition, possible values: "1" (False), "2" (True) or "3" (Undefined) + */ +int lag_state_write_remote_inhibition(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "remote_inhibition", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->remote_inhibition_actual); + fclose(fp); + return 0; +} + +/** + * lag_state_write_hello_timeouts - Persist aggregate hello timeout values to state files + * + * State files: + * /run/train/lag//ttdp_hello_timeout/slow - Slow TTDP HELLO timeout as integer + * /run/train/lag//ttdp_hello_timeout/fast - Fast TTDP HELLO timeout as integer + */ +int lag_state_write_hello_timeouts(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "ttdp_hello_timeout/slow", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->latest_line_slow_timeout_ms); + fclose(fp); + + fp = open_lag_state_file(ctx->team_devname, "ttdp_hello_timeout/fast", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->latest_line_fast_timeout_ms); + fclose(fp); + + return 0; +} + +/** + * lag_state_write_elected_neighbor - Persist aggregate elected neighbor to state file + * + * State files: + * /run/train/lag//elected_neighbor/mac - Elected neighbor hardware address (MAC) as string + */ +int lag_state_write_elected_neighbor(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "elected_neighbor/mac", "w"); + if (!fp) { + return -1; + } + fprintf(fp, MAC_FMT_STR "\n", + ab->elected_neighbor.neighbor_mac[0], + ab->elected_neighbor.neighbor_mac[1], + ab->elected_neighbor.neighbor_mac[2], + ab->elected_neighbor.neighbor_mac[3], + ab->elected_neighbor.neighbor_mac[4], + ab->elected_neighbor.neighbor_mac[5]); + fclose(fp); + return 0; +} + +/** + * lag_state_write_shortening_detected - Persist aggregate shortening status to state file + * + * State files: + * /run/train/lag//shortening_detected - Shortening detected, possible values "0" (False) or "1" (True) + */ +int lag_state_write_shortening_detected(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "shortening_detected", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->shortening_detected); + fclose(fp); + return 0; +} + +/** + * lag_state_write_lengthening_detected - Persist aggregate lengthening status to state file + * + * State files: + * /run/train/lag//lengthening_detected - Lengthening detected, possible values "0" (False) or "1" (True) + */ +int lag_state_write_lengthening_detected(struct teamd_context *ctx, struct ab *ab) { + FILE *fp = open_lag_state_file(ctx->team_devname, "lengthening_detected", "w"); + if (!fp) { + return -1; + } + fprintf(fp, "%d\n", ab->lengthening_detected); + fclose(fp); + return 0; +} \ No newline at end of file diff --git a/teamd/teamd_lag_state_persistence.h b/teamd/teamd_lag_state_persistence.h new file mode 100644 index 0000000..12bcdd2 --- /dev/null +++ b/teamd/teamd_lag_state_persistence.h @@ -0,0 +1,51 @@ +/* + * teamd_lag_state_persistence.h teamd TTDP runner state persistence + * Copyright (C) 2019 Westermo + * Author: Jacques de Laval + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _TEAMD_LAG_STATE_PERSISTENCE_H_ +#define _TEAMD_LAG_STATE_PERSISTENCE_H_ + +#include + +#include "teamd_lw_ttdp.h" + +FILE *open_lag_state_file(const char *team_devname, const char *path, const char *mode); + +int lag_state_write_line_status(struct teamd_context *ctx, void *priv); + +int lag_state_write_identity(struct teamd_context *ctx, void *priv); + +int lag_state_write_aggregate_role(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_diag_crossed_lines_detected(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_diag_mixed_consist_orientation_detected(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_remote_inhibition(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_hello_timeouts(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_elected_neighbor(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_shortening_detected(struct teamd_context *ctx, struct ab *ab); + +int lag_state_write_lengthening_detected(struct teamd_context *ctx, struct ab *ab); + +#endif // _TEAMD_LAG_STATE_PERSISTENCE_H_ \ No newline at end of file diff --git a/teamd/teamd_link_watch.c b/teamd/teamd_link_watch.c index cae6549..dc758a2 100644 --- a/teamd/teamd_link_watch.c +++ b/teamd/teamd_link_watch.c @@ -38,6 +38,7 @@ extern const struct teamd_link_watch teamd_link_watch_ethtool; extern const struct teamd_link_watch teamd_link_watch_arp_ping; extern const struct teamd_link_watch teamd_link_watch_nsnap; extern const struct teamd_link_watch teamd_link_watch_tipc; +extern const struct teamd_link_watch teamd_link_watch_ttdp; int __set_sockaddr(struct sockaddr *sa, socklen_t sa_len, sa_family_t family, const char *hostname) @@ -104,6 +105,7 @@ static const struct teamd_link_watch *teamd_link_watch_list[] = { &teamd_link_watch_arp_ping, &teamd_link_watch_nsnap, &teamd_link_watch_tipc, + &teamd_link_watch_ttdp }; #define TEAMD_LINK_WATCH_LIST_SIZE ARRAY_SIZE(teamd_link_watch_list) diff --git a/teamd/teamd_lw_ttdp.c b/teamd/teamd_lw_ttdp.c new file mode 100644 index 0000000..ae87cde --- /dev/null +++ b/teamd/teamd_lw_ttdp.c @@ -0,0 +1,2468 @@ +/* + * teamd_lw_ttdp.c teamd TTDP link watcher + * Copyright (C) 2017-2018 Westermo + * Author: Andrzej Koszela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Description: + * This file contains the TTDP linkwatcher. + * + * The ttdp linkwatcher implements parts of the TTDP protocol, specifically relating to neighbor + * discovery via TTDP HELLO packets. The linkwatcher maintains two link states for its link; + * a "logical" state, determined by whether the port is receiving valid TTDP HELLO frames, and a + * "physical" state, which is determined by the physical link status received from the NIC driver + * (aka the ethtool link status). If both these states are true, the link is considered up. The + * linkwatcher also includes some configurable state transition logic which attempts to reduce the + * number of spurious link state transitions. + * + * See the WeOS IEC61375 README for more information. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "teamd.h" +#include "teamd_link_watch.h" +#include "teamd_config.h" +#include "teamd_workq.h" +#include "teamd_lw_ttdp.h" + +uint16_t frame_checksum(const uint8_t *cp, int len); + +#define IFNAME_OR_EMPTY(P) ((P && P->start.common.tdport && P->start.common.tdport->ifname)\ + ? P->start.common.tdport->ifname : "ttdp-lw") +#ifdef DEBUG +#define teamd_ttdp_log_infox(P, format, args...) do {\ + struct timeval _debug_tv;\ + gettimeofday(&_debug_tv, NULL);\ + fprintf(stderr, "%s %ld.%ld :" format "\n", IFNAME_OR_EMPTY(P), _debug_tv.tv_sec, _debug_tv.tv_usec, ## args);\ + } while (0) +#define teamd_ttdp_log_dbgx(P, format, args...) daemon_log(LOG_DEBUG, "%s: " format, IFNAME_OR_EMPTY(P), ## args) +#else +#define teamd_ttdp_log_infox(P, format, args...) do {} while (0) +#define teamd_ttdp_log_dbgx(P, format, args...) do {} while (0) +#endif +#define teamd_ttdp_log_info(format, args...) daemon_log(LOG_INFO, format, ## args) +#define teamd_ttdp_log_dbg(format, args...) daemon_log(LOG_DEBUG, format, ## args) + + +/* BPF types */ + +/* Filters out only TTDP HELLO packets, based on the following: + * (see ttdp_hello.bpf for the raw source code, also copied below) + * destination MAC 0x0180C200000E + * EtherType 0x8100 + * VLAN ID 0x01EC + * Encapsulated proto 0x88CC + * If these match, then check the first 10 LLDP TLVs + * (code duplicated since BPF disallows loops): + * If TLV type is 0xFE00 and TLV length is 0x56, accept + * If TLV type is 0xFE00 but length differs, skip that TLV + * If TLV claims to exceed packet boundary, drop entire packet + * If TLV has type 0, drop the entire packet (EOL TLV) + * + * + * Drop packet if no TLV matches (to ignore regular LLDP) + */ + +static struct sock_filter ttdp_hello_filter[] = { + { 0x20, 0, 0, 0x00000002 }, /* ld [2] ; check dest. MAC and other Ethernet stuff */ + { 0x15, 0, 215, 0xc200000e }, /* jneq #0xc200000e, drop */ + { 0x28, 0, 0, 0000000000 }, /* ldh [0] */ + { 0x15, 0, 213, 0x00000180 }, /* jneq #0x0180, drop */ + { 0x20, 0, 0, 0xfffff030 }, /* ld vlan_avail */ + { 0x15, 0, 211, 0x00000001 }, /* jneq #1, drop */ + { 0x20, 0, 0, 0xfffff02c }, /* ld vlan_tci */ + { 0x54, 0, 0, 0x00000fff }, /* and #0x0FFF */ + { 0x15, 0, 208, 0x000001ec }, /* jneq #0x01EC, drop */ + { 0x28, 0, 0, 0x0000000c }, /* ldh [12] */ + { 0x15, 0, 206, 0x000088cc }, /* jneq #0x88CC, drop */ + { 0x20, 0, 0, 0xfffff034 }, /* ld poff ; have M[0] hold the last ok header start position */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x80, 0, 0, 0000000000 }, /* ld len */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x14, 0, 0, 0x00000002 }, /* sub #2 ; size of the first TLV header */ + { 0x02, 0, 0, 0000000000 }, /* st M[0] */ + { 0000, 0, 0, 0x0000000e }, /* ld #0x0e ; the first TLV header starts here */ + { 0x01, 0, 0, 0000000000 }, /* ldx #0x0 */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] ; this snippet tests whether we'd */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] ; jump out of bounds, i.e. if */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] ; ((accumulated TLV sizes in x) - */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] ; (packet payload size)) < 0 */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 192, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] ; test one TLV - load TLV header */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 ; extract type */ + { 0x15, 186, 0, 0000000000 }, /* jeq #0x0000, drop ; type 0 is EOF TLV */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_1 ; skip if not HELLO */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] ; reload TLV header */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF ; extract length */ + { 0x15, 181, 0, 0x00000056 }, /* jeq #0x56, okay ; must be 86 */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_1: ldh [x + 0] ; reload TLV header */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF ; extract length */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 ; header length not included */ + { 0x0c, 0, 0, 0000000000 }, /* add x ; previous header offset */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 172, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] ; test next TLV... etc */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 166, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_2 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 161, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_2: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 152, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 146, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_3 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 141, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_3: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 132, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 126, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_4 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 121, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_4: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 112, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 106, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_5 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 101, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_5: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 92, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 86, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_6 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 81, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_6: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 72, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 66, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_7 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 61, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_7: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 52, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 46, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_8 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 41, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_8: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 32, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 26, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 3, 0x0000fe00 }, /* jneq #0xFE00, skip_tlv_9 */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 21, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x48, 0, 0, 0000000000 }, /* skip_tlv_9: ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x04, 0, 0, 0x00000002 }, /* add #2 */ + { 0x0c, 0, 0, 0000000000 }, /* add x */ + { 0x02, 0, 0, 0x00000001 }, /* st M[1] */ + { 0x03, 0, 0, 0x00000002 }, /* stx M[2] */ + { 0x60, 0, 0, 0000000000 }, /* ld M[0] */ + { 0x61, 0, 0, 0x00000001 }, /* ldx M[1] */ + { 0x1c, 0, 0, 0000000000 }, /* sub x */ + { 0x45, 12, 0, 0x80000000 }, /* jset #0x80000000, drop */ + { 0x60, 0, 0, 0x00000001 }, /* ld M[1] */ + { 0x61, 0, 0, 0x00000002 }, /* ldx M[2] */ + { 0x07, 0, 0, 0000000000 }, /* tax */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x0000fe00 }, /* and #0xFE00 */ + { 0x15, 6, 0, 0000000000 }, /* jeq #0x0000, drop */ + { 0x15, 0, 5, 0x0000fe00 }, /* jneq #0xFE00, drop */ + { 0x48, 0, 0, 0000000000 }, /* ldh [x + 0] */ + { 0x54, 0, 0, 0x000001ff }, /* and #0x01FF */ + { 0x15, 1, 0, 0x00000056 }, /* jeq #0x56, okay */ + { 0x05, 0, 0, 0x00000001 }, /* jmp drop */ + { 0x06, 0, 0, 0xffffffff }, /* okay: ret #-1 ; accept entire packet */ + { 0x06, 0, 0, 0000000000 }, /* drop: ret #0 ; accept nothing */ +}; + +static struct sock_fprog ttdp_hello_fprog = { + .len = (sizeof(ttdp_hello_filter)/sizeof(ttdp_hello_filter[0])), + .filter = ttdp_hello_filter +}; + +/* Ethernet frame header including VLAN tag */ +struct ttdp_frame_header { + struct ether_header ether_header; + u_int16_t vlan_tags; + u_int16_t enc_ethertype; +} __attribute__((packed)); + +/* The following definitions of the TLVs (mandatory LLDP ones + * and the TTDP-specific HELLO TLV) and default values for their + * fields are taken from IEC 61375-2-5:2014 */ +struct ttdp_lldp_tlv_header { + u_int16_t header; +} __attribute__((packed)); +#define TTDP_MAKE_TLV_TYPE_LEN(type, len) (ntohs(((type & 0x7F) << 9) | (len & 0x1FF))) + +struct ttdp_default_lldp_chassis_tlv { + struct ttdp_lldp_tlv_header header; /* type 1, length 7 */ + u_int8_t chassisIdSubtype; /* default: 4 */ + struct ether_addr chassisId; /* default: MAC of sender */ +} __attribute__((packed)); + +/* NOTE: This TLV follows the example in IEC 61375-2-5:2014 while the other + * implementation instead uses subtype 6 with a sender MAC address payload + * (just like the chassis TLV) - see below */ +struct ttdp_default_lldp_port_tlv { + struct ttdp_lldp_tlv_header header; /* type 2, length 2 */ + u_int8_t portIdSubtype; /* default: 6 */ + u_int8_t portId; /* default: "ETB, ETBN egress physical port nb" */ +} __attribute__((packed)); + +/* This is how the implementation in WeOS 4.x does it */ +struct ttdp_legacy_lldp_port_tlv { + struct ttdp_lldp_tlv_header header; /* type 2, length 7 */ + u_int8_t portIdSubtype; /* default: 3 */ + struct ether_addr mac; +} __attribute__((packed)); + +struct ttdp_default_lldp_ttl_tlv { + struct ttdp_lldp_tlv_header header; /* type 3, length 2 */ + u_int16_t ttl; /* default: LLDP TTL (seconds) */ +} __attribute__((packed)); + +struct ttdp_default_lldp_eol_tlv { + struct ttdp_lldp_tlv_header header; /* type 0, length 0 */ +} __attribute__((packed)); + +struct ttdp_hello_tlv { + struct ttdp_lldp_tlv_header header; /* type 127, length 86 */ + u_int8_t oui[3]; /* IEC TC9 WG43 Organizationally Unique ID */ + /* default: 0x200E95 */ + u_int8_t ttdpSubtype; /* TTDP HELLO TLV subtype, default: 1 */ + u_int16_t tlvCS; /* TLV checksum */ + u_int32_t version; /* HELLO TLV version, default: 0x01000000 */ + u_int32_t lifeSign; /* sequence number (increases) */ + u_int32_t etbTopoCnt; /* topo counter; CRC32 of the TND */ + u_int8_t vendor[32]; /* Vendor specific info */ + u_int8_t recvStatuses; /* receive line A-D statuses (bitfield, 2 bits + * per line, A to the left, see IEC61375-2-5:2014 + * section 8.7.5) */ + u_int8_t timeoutSpeed; /* timeout speed - slow mode (1) or fast mode (2) */ + struct ether_addr srcId; /* sender MAC */ + u_int8_t srcPortId; /* ETB, ETBN egress physical port number - the meaning + * of this field is not clearly defined; used here as + * which physical port this frame was sent from. Same + * meaning as portId in the port TLV. */ + u_int8_t egressLine; /* Which ETB line we sent this on */ + u_int8_t egressDir; /* Which direction we sent this in */ + /* reserved1; */ /* Shared byte with the field below */ + u_int8_t inaugInhibition; /* Inauguration inhibition status */ + struct ether_addr remoteId; /* Last known MAC of the neighbor on this line + * in this direction */ + u_int16_t reserved2; /* padding */ + u_int8_t cstUUid[16]; /* UUID of the local consist */ +} __attribute__((packed)); + +/* Change the definition below to to use either the "example" port TLV, + * or the 4.x one */ +struct ttdp_default_hello_frame { + struct ttdp_frame_header frame_header; /* Ethernet & VLAN headers */ + struct ttdp_default_lldp_chassis_tlv chassis_tlv; /* mandatory LLDP chassis TLV */ + //struct ttdp_default_lldp_port_tlv port_tlv; /* mandatory LLDP port TLV */ + struct ttdp_legacy_lldp_port_tlv legacy_port_tlv; + struct ttdp_default_lldp_ttl_tlv ttl_tlv; /* mandatory TTL TLV */ + struct ttdp_hello_tlv hello_tlv; /* the good stuff */ + struct ttdp_default_lldp_eol_tlv eol_tlv; /* mandatory LLDP EOL TLV (must be last) */ + /* Implicit Ethernet FCS added by hardware here */ +} __attribute__((packed)); + +/* IEC TC9 WG43 Organizationally Unique ID */ +static const uint8_t ttdp_hello_tlv_oui[3] = { 0x20, 0x0E, 0x95 }; +/* All TTDP HELLO frames go here */ +static const uint8_t ttdp_hello_destination_mac[6] = + { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E }; + +/* Timer callbacks */ +/* Send in slow mode - 100 ms default */ +#define TTDP_PERIODIC_SLOW_SEND_CB_NAME "ttdp_periodic_send_slow" +#define TTDP_SLOW_INTERVAL_DEFAULT 100 + +/* Send in fast mode - 15 ms default */ +#define TTDP_PERIODIC_FAST_SEND_CB_NAME "ttdp_periodic_send_fast" +#define TTDP_FAST_INTERVAL_DEFAULT 15 + +/* Timeout in slow mode - 130 ms default */ +#define TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME "ttdp_periodic_timeout_slow" +#define TTDP_SLOW_TIMEOUT_DEFAULT 130 + +/* Timeout in fast mode - 45 ms default */ +#define TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME "ttdp_periodic_timeout_fast" +#define TTDP_FAST_TIMEOUT_DEFAULT 45 + +/* Once we hear from a neighbor, we set our recvstatus to OK on that line. + * This timeout determines how long we wait (without receiving any frames) + * before giving up on this neighbor, and setting recvstatus back to ERROR. + * Default is 1000 ms. Note that this is different than the timeouts above + * that control whether we consider links as up or down; this one merely + * controls when we reset a flag in outbound telegrams. */ +#define TTDP_PERIODIC_FORGET_PEER_CB_NAME "ttdp_periodic_forget_peer" +#define TTDP_FORGET_PEER_TIMEOUT_DEFAULT 1000 + +/* Delay before reporting Ethernet link coming up - 0 ms default */ +#define TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME "ttdp_periodic_link_state_change" +#define TTDP_LINK_STATE_DELAY_UP_DEFAULT 0 +/* Delay before reporting Ethernet link going down - 0 ms default */ +#define TTDP_LINK_STATE_DELAY_DOWN_DEFAULT 0 + +/* Callback for socket events */ +#define TTDP_SOCKET_CB_NAME "ttdp_socket" + + +/* 14 for Ethernet header + 4 for VLAN header + the good stuff + EOL TLV */ +#define TTDP_HELLO_FRAME_SIZE_MIN \ + (18 + sizeof(struct ttdp_hello_tlv) + sizeof(struct ttdp_default_lldp_eol_tlv)) +/* Arbitrary */ +#define TTDP_HELLO_FRAME_SIZE_MAX 1024 + +#define RINGBUF_SIZE 100 +static struct ttdp_hello_tlv hello_data_storage[RINGBUF_SIZE] = {}; +static int hello_data_storage_next_idx = 0; + +static void dump_hello_frame(int i) { + struct ttdp_hello_tlv* data = &(hello_data_storage[i]); + const char* antiv_str[] = {"ERR", "NO ", "YES", "UND"}; + teamd_ttdp_log_info("HELLO %.2X:%.2X:%.2X:%.2X:%.2X:%.2X @ %u %c%d %s p:%d inh:%s recv:%s,%s,%s,%s", + data->srcId.ether_addr_octet[0], + data->srcId.ether_addr_octet[1], + data->srcId.ether_addr_octet[2], + data->srcId.ether_addr_octet[3], + data->srcId.ether_addr_octet[4], + data->srcId.ether_addr_octet[5], + data->lifeSign, + data->egressLine, + data->egressDir, + ((data->timeoutSpeed == 1) ? ("SLOW") : ((data->timeoutSpeed == 2) ? ("FAST") : ("UNKN"))), + data->srcPortId, + antiv_str[data->inaugInhibition], + antiv_str[(data->recvStatuses & 0xc0) >> 6], + antiv_str[(data->recvStatuses & 0x30) >> 4], + antiv_str[(data->recvStatuses & 0x0c) >> 2], + antiv_str[(data->recvStatuses & 0x03) >> 0] + ); +} +static void dump_hello_frames() { + int i = hello_data_storage_next_idx; + int count = 0; + teamd_ttdp_log_info("--- BEGIN HELLO FRAME DUMP ---"); + for (; count < RINGBUF_SIZE; count++, i = (i + 1) % RINGBUF_SIZE) { + dump_hello_frame(i); + } + teamd_ttdp_log_info("---- END HELLO FRAME DUMP ----"); +} + +/* C99 vs POSIX */ +size_t strnlen(const char *s, size_t maxlen); +int parse_uuid(const char* src, uint8_t* dest) { + if (strnlen(src, 38) > 36) { /* must be 36 bytes plus \0 */ + return 1; + } + + int err = sscanf(src, + "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8 + "-" + "%02"SCNx8"%02"SCNx8 + "-" + "%02"SCNx8"%02"SCNx8 + "-" + "%02"SCNx8"%02"SCNx8 + "-" + "%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8"%02"SCNx8, + &(dest[0]), &(dest[1]), &(dest[2]), &(dest[3]), + &(dest[4]), &(dest[5]), + &(dest[6]), &(dest[7]), + &(dest[8]), &(dest[9]), + &(dest[10]), &(dest[11]), &(dest[12]), &(dest[13]), &(dest[14]), &(dest[15]) + ); + + if (err == 16) { + return 0; + } else { + return err; + } +} + +void stringify_uuid(uint8_t* src, char* dest) { + sprintf(dest, + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, + src[0], src[1], src[2], src[3], + src[4], src[5], + src[6], src[7], + src[8], src[9], + src[10], src[11], src[12], src[13], src[14], src[15] + ); +} + +/* stolen from teamd.c */ +int parse_hwaddr(const char *hwaddr_str, char **phwaddr, + unsigned int *plen) +{ + const char *pos = hwaddr_str; + unsigned int byte_count = 0; + unsigned int tmp; + int err; + char *hwaddr = NULL; + char *new_hwaddr; + char *endptr; + + while (true) { + errno = 0; + tmp = strtoul(pos, &endptr, 16); + if (errno != 0 || tmp > 0xFF) { + err = -EINVAL; + goto err_out; + } + byte_count++; + new_hwaddr = realloc(hwaddr, sizeof(char) * byte_count); + if (!new_hwaddr) { + err = -ENOMEM; + goto err_out; + } + hwaddr = new_hwaddr; + hwaddr[byte_count - 1] = (char) tmp; + while (isspace(endptr[0]) && endptr[0] != '\0') + endptr++; + if (endptr[0] == ':') { + pos = endptr + 1; + } else if (endptr[0] == '\0') { + break; + } else { + err = -EINVAL; + goto err_out; + } + } + *phwaddr = hwaddr; + *plen = byte_count; + return 0; +err_out: + free(hwaddr); + return err; +} + +static bool get_overall_state(struct lw_ttdp_port_priv *ttdp_ppriv) { + return (ttdp_ppriv->local_phy_link_up && ttdp_ppriv->local_ttdp_link_up); +} + +static void update_neighbor(struct lw_ttdp_port_priv *ttdp_ppriv, + uint8_t* new_mac, uint8_t* new_uuid, uint32_t new_topocnt) { + memcpy(ttdp_ppriv->prev_neighbor_uuid, ttdp_ppriv->neighbor_uuid, + sizeof(ttdp_ppriv->prev_neighbor_uuid)); + memcpy(ttdp_ppriv->prev_neighbor_mac, ttdp_ppriv->neighbor_mac, + sizeof(ttdp_ppriv->prev_neighbor_mac)); + ttdp_ppriv->prev_neighbor_topocnt = ttdp_ppriv->neighbor_topocnt; + memcpy(ttdp_ppriv->neighbor_uuid, new_uuid, + sizeof(ttdp_ppriv->neighbor_uuid)); + memcpy(ttdp_ppriv->neighbor_mac, new_mac, + sizeof(ttdp_ppriv->neighbor_mac)); + ttdp_ppriv->neighbor_topocnt = new_topocnt; +} + +/* also sets it in the parent! */ +static void update_neighbor_to_none(struct lw_ttdp_port_priv *ttdp_ppriv) { + memcpy(ttdp_ppriv->prev_neighbor_uuid, ttdp_ppriv->neighbor_uuid, + sizeof(ttdp_ppriv->prev_neighbor_uuid)); + memcpy(ttdp_ppriv->prev_neighbor_mac, ttdp_ppriv->neighbor_mac, + sizeof(ttdp_ppriv->prev_neighbor_mac)); + ttdp_ppriv->prev_neighbor_topocnt = ttdp_ppriv->neighbor_topocnt; + + memset(ttdp_ppriv->neighbor_uuid, 0, sizeof(ttdp_ppriv->neighbor_uuid)); + memset(ttdp_ppriv->neighbor_mac, 0, sizeof(ttdp_ppriv->neighbor_mac)); + ttdp_ppriv->neighbor_topocnt = 0; + ttdp_ppriv->neighbor_inhibit = TTDP_LOGIC_UNDEFINED; + + teamd_ttdp_log_infox(ttdp_ppriv, "cleared neighbor"); + struct ab* p = ttdp_ppriv->start.common.ctx->runner_priv; + + if (p && ttdp_ppriv->line < TTDP_MAX_PORTS_PER_TEAM) { + memcpy(p->neighbors[ttdp_ppriv->line].neighbor_uuid, ttdp_ppriv->neighbor_uuid, + sizeof(p->neighbors[ttdp_ppriv->line].neighbor_uuid)); + memcpy(p->neighbors[ttdp_ppriv->line].neighbor_mac, ttdp_ppriv->neighbor_mac, + sizeof(p->neighbors[ttdp_ppriv->line].neighbor_mac)); + p->ifindex_by_line[ttdp_ppriv->line] = ttdp_ppriv->start.common.tdport->ifindex; + p->neighbors[ttdp_ppriv->line].neighbor_topocount = ttdp_ppriv->neighbor_topocnt; + p->neighbors[ttdp_ppriv->line].neighbor_inhibition_state = TTDP_LOGIC_UNDEFINED; + } else { + /* Should never happen, leaving it here for future 4-line support */ + } +} + +static void update_parent_port_status(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv) { + bool state = get_overall_state(ttdp_ppriv); + struct ab* ab = ctx->runner_priv; + uint8_t new_state = (state ? 2 : 1); + bool heard_prev = ab->lines_heard[ttdp_ppriv->line]; + if ((heard_prev != ttdp_ppriv->heard) || (ab->port_statuses[ttdp_ppriv->line] != new_state)) { + ab->lines_heard[ttdp_ppriv->line] = ttdp_ppriv->heard; + ab->port_statuses[ttdp_ppriv->line] = new_state; + ab->port_statuses_b = + (ab->port_statuses[0] << 6) | + (ab->port_statuses[1] << 4) | + (ab->port_statuses[2] << 2) | + (ab->port_statuses[3] << 0); + teamd_ttdp_log_dbgx(ttdp_ppriv, "setting line status to %d", state); + if (ab->line_state_update_func) { + ab->line_state_update_func(ctx, ctx->runner_priv); + } + } +} + +static void force_parent_port_status(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv, int state) { + struct ab* ab = ctx->runner_priv; + if (ab->port_statuses[ttdp_ppriv->line] != state) { + ab->port_statuses[ttdp_ppriv->line] = state; + ab->port_statuses_b = + (ab->port_statuses[0] << 6) | + (ab->port_statuses[1] << 4) | + (ab->port_statuses[2] << 2) | + (ab->port_statuses[3] << 0); + teamd_ttdp_log_dbgx(ttdp_ppriv, "forcing line status to %d", state); + if (ab->line_state_update_func) { + ab->line_state_update_func(ctx, ctx->runner_priv); + } + } +} + + +static int lw_ttdp_load_options(struct teamd_context *ctx, + struct teamd_port *tdport, + struct lw_ttdp_port_priv *ttdp_ppriv) { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_load_options"); + struct teamd_config_path_cookie *cpcookie = ttdp_ppriv->start.common.cpcookie; + + int tmp; + bool tmpb; + const char* tmpstr; + int err; + if (ctx->runner && ctx->runner->name && (strncmp("ttdp", ctx->runner->name, 5) != 0)) { + teamd_log_err("This linkwatcher requires the \"ttdp\" runner. Aborting."); + return 1; + } + struct ab* ab = ctx->runner_priv; + + if (ab == NULL) { + teamd_log_err("Configuration error"); + return 1; + } + + err = teamd_config_int_get(ctx, &tmp, "@.initial_mode", cpcookie); + if (err) { + teamd_log_warn("Failed to get initial_mode, defaulting to 1 (slow)"); + tmp = 1; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp initial_mode %d", tmp); + } + ttdp_ppriv->initial_mode = tmp; + + err = teamd_config_bool_get(ctx, &tmpb, "@.fast_failed_recovery_mode", cpcookie); + if (err) { + teamd_log_warn("Failed to get fast_failed_recovery_mode, defaulting to off"); + tmpb = false; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp fast_failed_recovery_mode %d", tmpb); + } + ttdp_ppriv->fast_failed_recovery_mode = tmpb; + + err = teamd_config_int_get(ctx, &tmp, "@.slow_interval", cpcookie); + if (err) { + teamd_log_warn("Failed to get slow_interval, defaulting to %d ms", TTDP_SLOW_INTERVAL_DEFAULT); + tmp = TTDP_SLOW_INTERVAL_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp slow_interval %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->slow_interval), tmp); + + err = teamd_config_int_get(ctx, &tmp, "@.fast_interval", cpcookie); + if (err) { + teamd_log_warn("Failed to get fast_interval, defaulting to %d ms", TTDP_FAST_INTERVAL_DEFAULT); + tmp = TTDP_FAST_INTERVAL_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp fast_interval %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->fast_interval), tmp); + + err = teamd_config_int_get(ctx, &tmp, "@.slow_timeout", cpcookie); + if (err) { + teamd_log_warn("Failed to get slow_timeout, defaulting to %d ms", TTDP_SLOW_TIMEOUT_DEFAULT); + tmp = TTDP_SLOW_TIMEOUT_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp slow_timeout %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->slow_timeout), tmp); + + err = teamd_config_int_get(ctx, &tmp, "@.fast_timeout", cpcookie); + if (err) { + teamd_log_warn("Failed to get fast_timeout, defaulting to %d ms", TTDP_FAST_TIMEOUT_DEFAULT); + tmp = TTDP_FAST_TIMEOUT_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp fast_timeout %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->fast_timeout), tmp); + + err = teamd_config_int_get(ctx, &tmp, "@.forget_peer_timeout", cpcookie); + if (err) { + teamd_log_warn("Failed to get forget_peer_timeout, defaulting to %d ms", + TTDP_FORGET_PEER_TIMEOUT_DEFAULT); + tmp = TTDP_FORGET_PEER_TIMEOUT_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp forget_peer_timeout %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->forget_peer), tmp); + + err = teamd_config_bool_get(ctx, &tmpb, "@.immediate_timer_start_mode", cpcookie); + if (err) { + teamd_log_warn("Failed to get immediate_timer_start_mode, defaulting to off"); + tmpb = false; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp immediate_timer_start_mode %d", tmpb); + } + ttdp_ppriv->immediate_timer_start_mode = tmpb; + + + err = teamd_config_int_get(ctx, &tmp, "@.link_state_delay_up", cpcookie); + if (err) { + teamd_log_warn("Failed to get link_state_delay_up, defaulting to %d ms", TTDP_LINK_STATE_DELAY_UP_DEFAULT); + tmp = TTDP_LINK_STATE_DELAY_UP_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp link_state_delay_up %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->link_state_delay_up), tmp); + + err = teamd_config_int_get(ctx, &tmp, "@.link_state_delay_down", cpcookie); + if (err) { + teamd_log_warn("Failed to get link_state_delay_down, defaulting to %d ms", TTDP_LINK_STATE_DELAY_DOWN_DEFAULT); + tmp = TTDP_LINK_STATE_DELAY_DOWN_DEFAULT; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp link_state_delay_down %d", tmp); + } + ms_to_timespec(&(ttdp_ppriv->link_state_delay_down), tmp); + + err = teamd_config_string_get(ctx, &tmpstr, "@.local_uuid", cpcookie); + if (err) { + struct ab* ab = ctx->runner_priv; + if (ab && ab->local_uuid_set) { + memcpy(ttdp_ppriv->local_uuid, ab->local_uuid, sizeof(ttdp_ppriv->local_uuid)); + memcpy(ttdp_ppriv->local_uuid_str, ab->local_uuid_str, sizeof(ttdp_ppriv->local_uuid_str)); + } else { + teamd_log_err("TTDP: Error, failed to read UUID string in linkwatcher and runner"); + return 1; + } + } else { + err = parse_uuid(tmpstr, ttdp_ppriv->local_uuid); + if (err) { + teamd_log_err("TTDP: Error, failed to parse UUID string: %d", err); + return 1; + } + } + + err = teamd_config_int_get(ctx, &tmp, "@.direction", cpcookie); + if (err) { + struct ab* ab = ctx->runner_priv; + if (ab && (ab->direction == 1 || ab->direction == 2)) { + teamd_ttdp_log_infox(ttdp_ppriv, "Watcher direction not specified - using runner direction %d", ab->direction); + ttdp_ppriv->direction = ab->direction; + } else { + teamd_log_err("TTDP: Error, failed to read direction"); + return 1; + } + } else { + if (tmp != 1 && tmp != 2) { + teamd_log_err("TTDP: Error, invalid direction - use 1 or 2"); + return 1; + } + ttdp_ppriv->direction = tmp; + } + + err = teamd_config_string_get(ctx, &tmpstr, "@.line", cpcookie); + if (err) { + teamd_log_err("TTDP: Error, failed to read line"); + return 1; + } else { + if (tmpstr[1] != 0 || (((tmpstr[0] & 0x5f) != 0x41) && ((tmpstr[0] & 0x5f) != 0x42))) { + teamd_log_err("TTDP: Error, line must be 'A' or 'B', got %c %X", tmpstr[0], (tmpstr[0] & 0x5f)); + return 1; + } + ttdp_ppriv->line = (tmpstr[0] & 0x5f) - 0x41; + } + + err = teamd_config_string_get(ctx, &tmpstr, "@.identity_hwaddr", cpcookie); + if (err) { + struct ab* ab = ctx->runner_priv; + if (ab && ab->identity_hwaddr_set) { + teamd_ttdp_log_infox(ttdp_ppriv, "Identity hwaddr not given - using runner configuration instead"); + memcpy(ttdp_ppriv->identity_hwaddr, ab->identity_hwaddr, sizeof(ttdp_ppriv->identity_hwaddr)); + memcpy(ttdp_ppriv->identity_hwaddr_str, ab->identity_hwaddr_str, sizeof(ttdp_ppriv->identity_hwaddr_str)); + ttdp_ppriv->identity_hwaddr_ptr = ttdp_ppriv->identity_hwaddr; + } else { + teamd_ttdp_log_infox(ttdp_ppriv, "Identity hwaddr not given - using team device hwaddr instead"); + ttdp_ppriv->identity_hwaddr_ptr = (ttdp_ppriv->start.common.ctx->hwaddr); + } + } else { + char* tempmac; + unsigned int templen = 0; + if (parse_hwaddr(tmpstr, &tempmac, &templen) != 0) { + teamd_log_err("TTDP: Error, could not parse identity hwaddr %s, aborting", tmpstr); + return 1; + } else if (templen != ctx->hwaddr_len) { + teamd_log_err("TTDP: Error, identity hwaddr has incorrect length %d, team device has %d", + templen, ctx->hwaddr_len); + free(tempmac); + return 1; + } else { + teamd_ttdp_log_infox(ttdp_ppriv, "Identity hwaddr set."); + memcpy(ttdp_ppriv->identity_hwaddr, tempmac, templen); + memcpy(ttdp_ppriv->identity_hwaddr_str, tmpstr, sizeof(ttdp_ppriv->identity_hwaddr_str)); + ttdp_ppriv->identity_hwaddr_ptr = ttdp_ppriv->identity_hwaddr; + free(tempmac); + } + } + + err = teamd_config_bool_get(ctx, &tmpb, "@.strict_peer_recv_status", cpcookie); + if (err) { + teamd_log_warn("Failed to get strict_peer_recv_status, defaulting to on"); + tmpb = true; + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp strict_peer_recv_status %d", tmpb); + } + ttdp_ppriv->strict_peer_recv_status = tmpb; + + /* copy chassis address from runner configuration */ + memcpy(ttdp_ppriv->chassis_hwaddr, ab->chassis_hwaddr, sizeof (ttdp_ppriv->chassis_hwaddr)); + + /* copy vendor info from runner configuration and ensure it is 0-terminated */ + memcpy(ttdp_ppriv->vendor_info, ab->vendor_info, sizeof (ttdp_ppriv->vendor_info)); + ttdp_ppriv->vendor_info[sizeof(ttdp_ppriv->vendor_info)-1] = 0; + + /* check that timer intervals are sane */ + if (timespec_is_zero(&ttdp_ppriv->fast_interval) || timespec_is_zero(&ttdp_ppriv->slow_interval)) { + teamd_log_err("TTDP: Error, timer intervals not sane"); + return 1; + } + + /* check that timeouts are greater than timer intervals */ + if ((timespec_to_ms(&ttdp_ppriv->slow_timeout) <= timespec_to_ms(&ttdp_ppriv->slow_interval)) || + (timespec_to_ms(&ttdp_ppriv->fast_timeout) <= timespec_to_ms(&ttdp_ppriv->fast_interval))) { + teamd_log_err("TTDP: Error, timeouts must be greater than intervals"); + return 1; + } + + /* check that timer intervals are ordered correctly */ + if (timespec_to_ms(&ttdp_ppriv->slow_interval) <= timespec_to_ms(&ttdp_ppriv->fast_interval)) { + teamd_log_warn("TTDP: Warning, SLOW interval is not greater than FAST interval. Misconfiguration?"); + } + + /* check that timeouts are ordered correctly */ + if (timespec_to_ms(&ttdp_ppriv->slow_timeout) <= timespec_to_ms(&ttdp_ppriv->fast_timeout)) { + teamd_log_warn("TTDP: Warning, SLOW timeout is not greater than FAST timeout. Misconfiguration?"); + } + + return 0; +} + +static int update_overall_state(struct teamd_context *ctx, void* priv){ + struct lw_common_port_priv *common_ppriv = priv; + struct lw_ttdp_port_priv* ttdp_ppriv = (struct lw_ttdp_port_priv*)priv; + /*struct ab* ab = ctx->runner_priv;*/ + + bool overall_state = (ttdp_ppriv->local_ttdp_link_up + && ttdp_ppriv->local_phy_link_up && ttdp_ppriv->heard + /*&& (ab->aggregate_status != TTDP_AGG_STATE_FIXED_END)*/); + teamd_ttdp_log_infox(ttdp_ppriv, "state change to -> %s", (overall_state ? "UP" : "DOWN")); + return teamd_link_watch_check_link_up(ctx, common_ppriv->tdport, priv, overall_state); +} + +static int lw_ttdp_sock_open(struct lw_ttdp_port_priv* ttdp_ppriv) { + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: ttdp_sock_open"); + int err; + + /* This is the one used for SENDING. */ + err = teamd_packet_sock_open_type_ext(SOCK_RAW | SOCK_NONBLOCK, &ttdp_ppriv->out_sock, + ttdp_ppriv->start.common.tdport->ifindex, + htons(ETH_P_8021Q), NULL, NULL); + teamd_ttdp_log_dbgx(ttdp_ppriv, "Socket (write) open result %d", err); + + /* This is the socket used for READING. It has to be left as ETH_P_ALL. */ + err = teamd_packet_sock_open_type(SOCK_RAW, &ttdp_ppriv->start.psr.sock, + ttdp_ppriv->start.common.tdport->ifindex, + htons(ETH_P_ALL), &ttdp_hello_fprog, NULL); + teamd_ttdp_log_dbgx(ttdp_ppriv, "Socket (read) open result %d", err); + + /* Enter promiscuous mode */ + teamd_ttdp_log_infox(ttdp_ppriv, "Enabling promiscuous mode for interface #%d", + ttdp_ppriv->start.common.tdport->ifindex); + struct packet_mreq mreq = { + .mr_ifindex = ttdp_ppriv->start.common.tdport->ifindex, + .mr_type = PACKET_MR_PROMISC, + .mr_alen = 0, + .mr_address = {0} + }; + err = setsockopt(ttdp_ppriv->start.psr.sock, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + + int priority = 7; + if (setsockopt(ttdp_ppriv->out_sock, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) != 0) { + syslog(LOG_ERR, "Error setting priority 7 for socket %d. Continuing anyway.", + ttdp_ppriv->start.common.tdport->ifindex); + } + + teamd_ttdp_log_dbgx(ttdp_ppriv, "packet sock open: %d flen %d\n", err, ttdp_hello_fprog.len); + return err; +} + +static void lw_ttdp_sock_close(struct lw_ttdp_port_priv *ttdp_ppriv) +{ + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_sock_close"); + /* Leave promiscuous mode */ + teamd_ttdp_log_infox(ttdp_ppriv, "Disabling promiscuous mode for interface #%d", + ttdp_ppriv->start.common.tdport->ifindex); + + struct packet_mreq mreq = { + .mr_ifindex = ttdp_ppriv->start.common.tdport->ifindex, + .mr_type = PACKET_MR_PROMISC, + .mr_alen = 0, + .mr_address = {0} + }; + setsockopt(ttdp_ppriv->start.psr.sock, SOL_PACKET, PACKET_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + + close(ttdp_ppriv->start.psr.sock); + close(ttdp_ppriv->out_sock); +} + +static int __get_port_curr_hwaddr_out(struct lw_ttdp_port_priv *priv, + struct sockaddr_ll *addr, size_t expected_len) +{ + struct team_ifinfo *ifinfo = priv->start.common.tdport->team_ifinfo; + size_t port_hwaddr_len = team_get_ifinfo_hwaddr_len(ifinfo); + char *port_hwaddr = team_get_ifinfo_hwaddr(ifinfo); + int err; + + err = teamd_getsockname_hwaddr(priv->out_sock, addr, expected_len); + if (err) + return err; + if ((addr->sll_halen != port_hwaddr_len) || + (expected_len && expected_len != port_hwaddr_len)) { + teamd_log_err("Unexpected length of hw address."); + return -ENOTSUP; + } + memcpy(addr->sll_addr, port_hwaddr, addr->sll_halen); + return 0; +} + +static int __get_port_curr_hwaddr(struct lw_ttdp_port_priv *priv, + struct sockaddr_ll *addr, size_t expected_len) +{ + struct team_ifinfo *ifinfo = priv->start.common.tdport->team_ifinfo; + size_t port_hwaddr_len = team_get_ifinfo_hwaddr_len(ifinfo); + char *port_hwaddr = team_get_ifinfo_hwaddr(ifinfo); + int err; + + err = teamd_getsockname_hwaddr(priv->start.psr.sock, addr, expected_len); + if (err) + return err; + if ((addr->sll_halen != port_hwaddr_len) || + (expected_len && expected_len != port_hwaddr_len)) { + teamd_log_err("Unexpected length of hw address."); + return -ENOTSUP; + } + memcpy(addr->sll_addr, port_hwaddr, addr->sll_halen); + return 0; +} + +static void construct_default_frame(struct ab* parent, struct lw_ttdp_port_priv *ttdp_ppriv, + struct ttdp_default_hello_frame* out) { + + struct sockaddr_ll source_addr; + + if (__get_port_curr_hwaddr_out(ttdp_ppriv, &source_addr, 6) != 0) { + /* FIXME ERR */ + teamd_log_warn("construct_default_frame: could not get source MAC, aborting"); + return; + } + + + + /* Ethernet header & VLAN */ + memcpy(&(out->frame_header.ether_header.ether_dhost), ttdp_hello_destination_mac, 6); + memcpy(&(out->frame_header.ether_header.ether_shost), source_addr.sll_addr, 6); + out->frame_header.ether_header.ether_type = htons(0x8100); + out->frame_header.vlan_tags = htons(0xE1EC); + out->frame_header.enc_ethertype = htons(0x88CC); + + /* Chassis TLV */ + out->chassis_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(1, 7); + out->chassis_tlv.chassisIdSubtype = 0x04; + memcpy(&(out->chassis_tlv.chassisId), ttdp_ppriv->chassis_hwaddr, sizeof(out->chassis_tlv.chassisId)); + + /* Port TLV */ + //out->port_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(2, 2); + //out->port_tlv.portIdSubtype = 0x06; + ///* FIXME */out->port_tlv.portId = 1; + + /* Legacy port TLV */ + out->legacy_port_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(2, 7); + out->legacy_port_tlv.portIdSubtype = 0x03; + memcpy(&(out->legacy_port_tlv.mac), source_addr.sll_addr, 6); + + /* TTL TLV */ + out->ttl_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(3, 2); + out->ttl_tlv.ttl = htons(120); + /* HELLO TLV */ + out->hello_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(127, 86); + + memcpy(&(out->hello_tlv.oui), ttdp_hello_tlv_oui, 3); + out->hello_tlv.ttdpSubtype = 0x01; + out->hello_tlv.tlvCS = 0; /* Calculated in lw_ttdp_send() / lw_ttdp_send_fast() */ + out->hello_tlv.version = htonl(0x01000000); + out->hello_tlv.lifeSign = htonl((ttdp_ppriv->lifesign)++); + out->hello_tlv.etbTopoCnt = htonl(parent->etb_topo_counter); + + /* We must ensure that the vendor string is zero terminated and -padded */ + memset(out->hello_tlv.vendor, 0, sizeof(out->hello_tlv.vendor)); + memcpy(out->hello_tlv.vendor, ttdp_ppriv->vendor_info, strnlen(ttdp_ppriv->vendor_info, + sizeof(ttdp_ppriv->vendor_info))); + out->hello_tlv.vendor[sizeof(out->hello_tlv.vendor)-1] = 0; + + out->hello_tlv.recvStatuses = parent->port_statuses_b; + out->hello_tlv.timeoutSpeed = 0; /* Set in lw_ttdp_send() / lw_ttdp_send_fast() */ + //memcpy(&(out->hello_tlv.srcId), source_addr.sll_addr, 6); + /* FIXME check sizes of these */ + memcpy(&(out->hello_tlv.srcId), ttdp_ppriv->identity_hwaddr, 6); + + out->hello_tlv.srcPortId = (ttdp_ppriv->start.common.tdport->ifindex + 1), + out->hello_tlv.egressLine = 0x41 + ttdp_ppriv->line; + out->hello_tlv.egressDir = ttdp_ppriv->direction; + + uint8_t inhibit_any = (parent->inhibition_flag_local | parent->inhibition_flag_any); + out->hello_tlv.inaugInhibition = inhibit_any;// | parent->inhibition_flag_remote); + /* fix the "ANTIVALENT" insanity */ + if (out->hello_tlv.inaugInhibition == 0) + out->hello_tlv.inaugInhibition = TTDP_LOGIC_FALSE; + if (out->hello_tlv.inaugInhibition != TTDP_LOGIC_FALSE) + out->hello_tlv.inaugInhibition = TTDP_LOGIC_TRUE; + memcpy(&(out->hello_tlv.remoteId), ttdp_ppriv->neighbor_mac, 6); + out->hello_tlv.reserved2 = 0; + memcpy(&(out->hello_tlv.cstUUid), ttdp_ppriv->local_uuid, 16); + + /* EOL TLV */ + out->eol_tlv.header.header = TTDP_MAKE_TLV_TYPE_LEN(0, 0); + +} + +static void ttdp_insert_checksum(struct ttdp_hello_tlv* tlv) { +/* Calculate HELLO TLV checksum over TLV payload "from first TLV word + * after the checksum to the last TLV word, both included" */ + size_t checksummed_length = + ((uint8_t*)&(tlv->cstUUid)+sizeof(tlv->cstUUid)) - (uint8_t*)&(tlv->version); + tlv->tlvCS = htons(frame_checksum((uint8_t*)&(tlv->version), checksummed_length)); +} + +static int ttdp_verify_checksum(struct ttdp_hello_tlv* tlv, struct lw_ttdp_port_priv *ttdp_ppriv) { + size_t checksummed_length = + ((uint8_t*)&(tlv->cstUUid)+sizeof(tlv->cstUUid)) - (uint8_t*)&(tlv->version); + uint16_t calc = frame_checksum((uint8_t*)&(tlv->version), checksummed_length); + if (calc != ntohs(tlv->tlvCS)) { + teamd_ttdp_log_infox(ttdp_ppriv, "HELLO TLV checksum mismatch: got %04hX, expected %04hX", tlv->tlvCS, calc); + return 1; + } + + /* FIXME move this out? */ + if ((ntohl(tlv->version) & 0xFF000000) != 0x01000000) { + teamd_ttdp_log_infox(ttdp_ppriv, "HELLO TLV version mismatch: got %08" PRIX32 ", expected %08" + PRIX32 " (only first byte matters)", + (ntohl(tlv->version)), 0x01000000); + return 2; + } + + return 0; +} + +static int lw_ttdp_send_fast(struct teamd_context *ctx, int events, void *priv) { + //fprintf(stderr, "ttdp lw: lw_ttdp_send_fast\n"); + struct lw_ttdp_port_priv* ttdp_ppriv = (struct lw_ttdp_port_priv*)priv; + + struct ttdp_default_hello_frame frame; + construct_default_frame(ctx->runner_priv, ttdp_ppriv, &frame); + frame.hello_tlv.timeoutSpeed = 2; /* fast */ + + struct sockaddr_ll ll_dest; + memset(&ll_dest, 0, sizeof(ll_dest)); + /* Get the format right first */ + __get_port_curr_hwaddr_out(ttdp_ppriv, &ll_dest, 0); + memcpy(&(ll_dest.sll_addr), ttdp_hello_destination_mac, 6); + ll_dest.sll_family = AF_PACKET; + ll_dest.sll_protocol = ntohs(0x8100); + + ttdp_insert_checksum(&(frame.hello_tlv)); + + /* Send TTDP HELLO frame here */ + if (ttdp_ppriv->silent == true) + return 0; + + /* Send TTDP HELLO frame here (FAST speed) */ + int err = teamd_sendto(ttdp_ppriv->out_sock, &frame, sizeof(frame), + 0, (struct sockaddr *) &ll_dest, + sizeof(ll_dest)); + struct ab *ab = ctx->runner_priv; + ab->lines_hello_stats[ttdp_ppriv->line].sent_hello_frames++; + //fprintf(stderr, "ttdp lw: lw_ttdp_send result %d\n", err); + return err; +} + +static int lw_ttdp_send(struct teamd_context *ctx, int events, void *priv) { + //fprintf(stderr, "ttdp lw: lw_ttdp_send_fast\n"); + struct lw_ttdp_port_priv* ttdp_ppriv = (struct lw_ttdp_port_priv*)priv; + + /* Construct TTDP HELLO frame */ + struct ttdp_default_hello_frame frame; + construct_default_frame(ctx->runner_priv, ttdp_ppriv, &frame); + frame.hello_tlv.timeoutSpeed = 1; /* slow */ + + struct sockaddr_ll ll_dest; + memset(&ll_dest, 0, sizeof(ll_dest)); + /* Get the format right first */ + __get_port_curr_hwaddr_out(ttdp_ppriv, &ll_dest, 0); + memcpy(&(ll_dest.sll_addr), ttdp_hello_destination_mac, 6); + ll_dest.sll_family = AF_PACKET; + ll_dest.sll_protocol = ntohs(0x8100); + + ttdp_insert_checksum(&(frame.hello_tlv)); + + if (ttdp_ppriv->silent == true) + return 0; + + /* Send TTDP HELLO frame here (SLOW speed) */ + int err = teamd_sendto(ttdp_ppriv->out_sock, &frame, sizeof(frame), + 0, (struct sockaddr *) &ll_dest, + sizeof(ll_dest)); + struct ab *ab = ctx->runner_priv; + ab->lines_hello_stats[ttdp_ppriv->line].sent_hello_frames++; + //fprintf(stderr, "ttdp lw: lw_ttdp_send result %d\n", err); + return err; +} + +static void ttdp_start_fast_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv); +static void ttdp_stop_fast_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv); +static void ttdp_start_slow_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv); +static void ttdp_stop_slow_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv); + +static inline void ttdp_start_fast_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv) { + struct ab *ab = ctx->runner_priv; + if (ttdp_ppriv->local_slow_timer_started) { + ttdp_stop_slow_send_timer(ctx, ttdp_ppriv); + } + teamd_ttdp_log_infox(ttdp_ppriv, "switched to FAST sending mode"); + if (!ttdp_ppriv->local_fast_timer_started) { + teamd_loop_callback_timer_add_set( + ctx, + TTDP_PERIODIC_FAST_SEND_CB_NAME, + ttdp_ppriv, + lw_ttdp_send_fast, + &(ttdp_ppriv->fast_interval), + (ttdp_ppriv->initial_fast_interval)); + ab->lines_hello_stats[ttdp_ppriv->line].local_fast_activated++; + } + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FAST_SEND_CB_NAME, ttdp_ppriv); + ttdp_ppriv->local_fast_timer_started = true; +} + +static inline void ttdp_stop_fast_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv) { + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FAST_SEND_CB_NAME, ttdp_ppriv); + teamd_loop_callback_del(ctx, TTDP_PERIODIC_FAST_SEND_CB_NAME, ttdp_ppriv); + ttdp_ppriv->local_fast_timer_started = false; +} + +static inline void ttdp_start_slow_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv) { + if (ttdp_ppriv->local_fast_timer_started) { + ttdp_stop_fast_send_timer(ctx, ttdp_ppriv); + } + teamd_ttdp_log_infox(ttdp_ppriv, "switched to SLOW sending mode"); + if (!ttdp_ppriv->local_slow_timer_started) { + teamd_loop_callback_timer_add_set( + ctx, + TTDP_PERIODIC_SLOW_SEND_CB_NAME, + ttdp_ppriv, + lw_ttdp_send, + &(ttdp_ppriv->slow_interval), + (ttdp_ppriv->initial_slow_interval)); + } + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_SLOW_SEND_CB_NAME, ttdp_ppriv); + ttdp_ppriv->local_slow_timer_started = true; +} + +static inline void ttdp_stop_slow_send_timer(struct teamd_context *ctx, + struct lw_ttdp_port_priv *ttdp_ppriv) { + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_SLOW_SEND_CB_NAME, ttdp_ppriv); + teamd_loop_callback_del(ctx, TTDP_PERIODIC_SLOW_SEND_CB_NAME, ttdp_ppriv); + ttdp_ppriv->local_slow_timer_started = false; +} + + + +/* Called SLOW_TIMEOUT (130 ms) after last receipt of a HELLO packet */ +static int lw_ttdp_enter_recovery_mode(struct teamd_context *ctx, int events, void *priv) { + /* Start fast sending mode and start fast timeout timer */ + struct lw_ttdp_port_priv *ttdp_ppriv = (struct lw_ttdp_port_priv *)priv; + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: tw_ttdp_enter_recovery_mode"); + teamd_ttdp_log_infox(ttdp_ppriv, "Entered recovery mode - logical link state pending..."); + + //teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FAST_SEND_CB_NAME, priv); + ttdp_start_fast_send_timer(ctx, ttdp_ppriv); + + + ttdp_ppriv->local_recovery_mode = 1; + + /* Disable this callback so we don't call it while in recovery mode */ + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + + /* Reset & start the "fast timeout" timer */ + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, + priv, + &(ttdp_ppriv->fast_timeout), + &(ttdp_ppriv->fast_timeout)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, priv); + + return 0; +} +/* Called FAST_TIMEOUT (45 ms) after entering recovery mode */ +static int lw_ttdp_fail_recovery_mode(struct teamd_context *ctx, int events, void *priv) { + //struct lw_common_port_priv *common_ppriv = priv; + struct lw_ttdp_port_priv* ttdp_ppriv = (struct lw_ttdp_port_priv*)priv; + struct ab *ab = ctx->runner_priv; + + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_fail_recovery_mode in mode %d", ttdp_ppriv->local_recovery_mode); + teamd_ttdp_log_infox(ttdp_ppriv, "Failed recovery mode - logical link state is now DOWN"); + /* Set port status to "not good" and stop the timeout timers + * since we're now in recovery mode and will just keep sending + * frames until we get a reply */ + + /* set port status */ + ttdp_ppriv->local_ttdp_link_up = false; + + /* consider neighbor down */ + ab->neighbor_lines[ttdp_ppriv->line] = '-'; + update_parent_port_status(ctx, ttdp_ppriv); + update_neighbor_to_none(ttdp_ppriv); + update_overall_state(ctx, priv); + teamd_event_port_changed(ctx, ttdp_ppriv->start.common.tdport); + + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, priv); + + /* resume sending in SLOW mode by default; if configured, stay in + * FAST mode otherwise */ + if (ttdp_ppriv->fast_failed_recovery_mode) { + ttdp_start_fast_send_timer(ctx, ttdp_ppriv); + } else { + ttdp_start_slow_send_timer(ctx, ttdp_ppriv); + } + + return 0; +} + +static void fixup_parse_ttdp_frame(struct ttdp_hello_tlv* data) { + data->tlvCS = ntohs(data->tlvCS); + data->etbTopoCnt = ntohl(data->etbTopoCnt); + //data->etbTopoCnt = data->etbTopoCnt; + data->lifeSign = ntohl(data->lifeSign); + data->version = ntohl(data->version); +} + +static void detect_ttdp_line_mismatch(struct ttdp_hello_tlv* out, struct lw_ttdp_port_priv* ttdp_ppriv, + struct teamd_context *ctx) { + struct ab* p = (struct ab*)ctx->runner_priv; + /* egressLine is 'A', 'B', 'C', 'D' or '-'. Our local line is 0 for 'A', or 1 for 'B'. + * '-' is assumed to mean "not supported". */ + if (out->egressLine != '-') { + p->neighbor_lines[ttdp_ppriv->line] = out->egressLine; + if (out->egressLine - 'A' != ttdp_ppriv->line) { + p->crossed_lines_detected = true; + } + } +} + +static void detect_ttdp_mixed_consist_orientation(struct ttdp_hello_tlv* out, + struct lw_ttdp_port_priv* ttdp_ppriv, struct teamd_context *ctx) { + struct ab* p = (struct ab*)ctx->runner_priv; + /* Check if our neighbor is another node in our own consist, and is not connected + * 1-2 or 2-1. Nodes in the same consist should not have different orientations! */ + /* egressDir is 1 or 2. Our local direction uses the same format. */ + if (memcmp(out->cstUUid, ttdp_ppriv->local_uuid, sizeof(out->cstUUid)) == 0) { + if (out->egressDir == ttdp_ppriv->direction) { + p->mixed_consist_orientation_detected = true; + } + } +} + +static int parse_ttdp_frame(const uint8_t* frame, size_t frame_len, struct ttdp_hello_tlv* out, + struct lw_ttdp_port_priv* ttdp_ppriv, struct teamd_context *ctx) { + + if (frame_len < TTDP_HELLO_FRAME_SIZE_MIN) { + teamd_ttdp_log_infox(ttdp_ppriv, "TTDP HELLO frame is short - got %zd, wanted %zd", + frame_len, TTDP_HELLO_FRAME_SIZE_MIN); + return -1; + } + + /* VLAN tags removed here for some reason or other, so the first TLV starts at 14 */ + size_t offset = 14; /* start of first TVL header */ + int tlv_idx; + for (tlv_idx = 0; tlv_idx < 10; ++tlv_idx) { + if (offset >= frame_len) { + teamd_ttdp_log_dbgx(ttdp_ppriv, "Malformed packet, offset %zu would be outside length %zu", offset, frame_len); + return 1; + } + uint16_t tlv_header = (frame[offset] << 8) | frame[offset+1]; + uint8_t tlv_type = (tlv_header & 0xFE00) >> 9; + uint8_t tlv_len = tlv_header & 0x01FF; + //fprintf(stderr, "parsing TLV header %0.4X type %d len %d\n", tlv_header, tlv_type, tlv_len); + + switch (tlv_type) { + case 127: + //fprintf(stderr, "Specific TLV #%d len %d\n", tlv_idx, tlv_len); + /* Check that the OUI matches and do a final size check */ + /* we need at least 2+3 bytes (header+OUI) left in the frame to read the OUI. + * Test this first, then check the OUI, then the size of the TLV */ + if (((offset + 5) <= frame_len) && (memcmp(frame+offset+2, ttdp_hello_tlv_oui, 3) == 0) + && ((offset + sizeof(struct ttdp_hello_tlv)) < frame_len)) { + //fprintf(stderr, "HELLO!\n"); + memcpy(out, frame+offset, sizeof(struct ttdp_hello_tlv)); + /* verify frame CRC before byte order conversion */ + if (ttdp_verify_checksum(out, ttdp_ppriv) != 0) { + (ttdp_ppriv->checksum_fail_counter)++; + return 1; + } + fixup_parse_ttdp_frame(out); + detect_ttdp_line_mismatch(out, ttdp_ppriv, ctx); + detect_ttdp_mixed_consist_orientation(out, ttdp_ppriv, ctx); + return 0; + } + case 0: /* EOF */ + teamd_ttdp_log_dbgx(ttdp_ppriv, "Early EOF in HELLO frame, idx %d", tlv_idx); + return 1; + case 1: + case 2: + case 3: + default: + offset += (tlv_len + 2); + break; + } + } + teamd_ttdp_log_dbgx(ttdp_ppriv, "No relevant TLVs in HELLO frame, idx %d", tlv_idx); + return 1; +} + +static void store_hello_frame(struct ttdp_hello_tlv* data) { + memcpy(&(hello_data_storage[hello_data_storage_next_idx]), data, sizeof(struct ttdp_hello_tlv)); + hello_data_storage_next_idx = (hello_data_storage_next_idx + 1) % RINGBUF_SIZE; +} + +static struct ttdp_hello_tlv *get_latest_hello_frame(void) { + return &hello_data_storage[(hello_data_storage_next_idx - 1) % RINGBUF_SIZE]; +} + +static int ttdp_frame_is_peer_status_ok(struct ttdp_hello_tlv* hello_recv) { + /* return 0 if the peer claims he can hear us */ + + int idx = hello_recv->egressLine - 'A'; + /* 2 bits per line, with line A to the left: AABBCCDD */ + uint8_t peer_status = ((hello_recv->recvStatuses >> (6-(2*idx))) & 3); + return (peer_status == 2) ? 0 : 1; /* status 2 means OK */ +} + +static void set_neigh_hears_us(struct teamd_context *ctx, struct lw_ttdp_port_priv* ttdp_ppriv, bool state) { + struct ab *ab = (struct ab*)ctx->runner_priv; + if (!ab) + return; + if (ttdp_ppriv->line >= TTDP_MAX_PORTS_PER_TEAM) + return; + bool prev = ab->lines_heard[ttdp_ppriv->line]; + ttdp_ppriv->heard = state; + ab->lines_heard[ttdp_ppriv->line] = state; + if (prev != state) { + if (ab->line_state_update_func) { + ab->line_state_update_func(ctx, ctx->runner_priv); + update_parent_port_status(ctx, ttdp_ppriv); + } + } +} + +static int lw_ttdp_receive(struct teamd_context *ctx, int events, void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = (struct lw_ttdp_port_priv*)priv; + static struct ttdp_hello_tlv hello_recv; + //fprintf(stderr, "ttdp lw: lw_ttdp_receive\n"); + + /* Receive and parse TTDP HELLO message here */ + static u_int8_t buf[TTDP_HELLO_FRAME_SIZE_MAX]; + struct sockaddr_ll ll_from; + + memset(&hello_recv, 0, sizeof(hello_recv)); + + int err = teamd_recvfrom(ttdp_ppriv->start.psr.sock, &buf, TTDP_HELLO_FRAME_SIZE_MAX, 0, + (struct sockaddr *) &ll_from, sizeof(ll_from)); + if (err <= 0) { + teamd_ttdp_log_infox(ttdp_ppriv, "lw_ttdp_receive: Error %d from recvfrom", err); + return err; + } + + if (ttdp_ppriv->deaf == true) + return 0; + + //fprintf(stderr, "Parsing TTDP HELLO frame, length %d\n", err); + + /* FIXME this should maybe be decoupled from _recv and not done immediately */ + /* see ab_state_active_port_set() in teamd_runner_activebackup.c */ + if (parse_ttdp_frame(buf, err, &hello_recv, ttdp_ppriv, ctx) == 0) { + struct ab* ab = ctx->runner_priv; + ab->lines_hello_stats[ttdp_ppriv->line].recv_hello_frames++; + + /* Stop the line status timer until we know what to do */ + teamd_loop_callback_timer_set( + ctx, + TTDP_PERIODIC_FORGET_PEER_CB_NAME, + priv, + &(ttdp_ppriv->forget_peer), + &(ttdp_ppriv->forget_peer)); + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FORGET_PEER_CB_NAME, priv); + + if (ttdp_frame_is_peer_status_ok(&hello_recv) != 0) { + /* Received a frame from a neighbor that doesn't hear us */ + /* Set our peer status to OK, but don't set link logical state to up */ + + // teamd_ttdp_log_infox(ttdp_ppriv, "AGREE Got peer frame with bad RecvStatus " + // "%c %d %.4X %d %d %d %d", + // hello_recv.egressLine, + // hello_recv.egressLine, + // hello_recv.recvStatuses, + + // (hello_recv.egressLine - 'A'), + // (2*(hello_recv.egressLine - 'A')), + // (6-(2*(hello_recv.egressLine - 'A'))), + + // ((hello_recv.recvStatuses >> (6-(2*(hello_recv.egressLine - 'A')))) & 3) + // ); + + /* FIXME should have a separate timer here, to set recvstatus back + * down to ERROR in case we don't get frames for a while */ + + set_neigh_hears_us(ctx, ttdp_ppriv, false); + + force_parent_port_status(ctx, ttdp_ppriv, 2); + if (ttdp_ppriv->forget_peer_timer_running == false) + teamd_ttdp_log_infox(ttdp_ppriv, "Neighbor heard - starting line status timer..."); + + ttdp_ppriv->forget_peer_timer_running = true; + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FORGET_PEER_CB_NAME, priv); + + if (ttdp_ppriv->strict_peer_recv_status) { + /* If this option is set, we require the peer to also hear us + * before we let the line come up. Otherwise, continue as normal */ + return 0; + } + } + + /* valid frame received */ + +#ifdef TTDP_PHYSICAL_LINK_STATE_OVERRIDE + /* reset link down delay, if any */ + if (ttdp_ppriv->local_phy_link_event_delayed) { + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, priv); + teamd_ttdp_log_infox(ttdp_ppriv, "Resetting link state DOWN reporting delay due to HELLO frame"); + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, + priv, + NULL, + &(ttdp_ppriv->link_state_delay_down)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, priv); + } +#endif + + if (ttdp_ppriv->local_recovery_mode) { + ttdp_ppriv->local_recovery_mode = 0; + /* reset recovery mode timers */ + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, priv); + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, + priv, + &(ttdp_ppriv->slow_timeout), + &(ttdp_ppriv->slow_timeout)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + + /* Stop sending in fast mode && start sending in slow mode again */ + teamd_ttdp_log_infox(ttdp_ppriv, "Recovered - logical link state is now NO LONGER PENDING"); + ttdp_start_slow_send_timer(ctx, ttdp_ppriv); + + } else { + + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, priv); + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, + priv, + &(ttdp_ppriv->slow_timeout), + &(ttdp_ppriv->slow_timeout)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, priv); + } + struct ttdp_hello_tlv *prev_hello = get_latest_hello_frame(); + if (prev_hello->timeoutSpeed == 1 && hello_recv.timeoutSpeed == 2) + ab->lines_hello_stats[ttdp_ppriv->line].remote_fast_activated++; + + store_hello_frame(&hello_recv); + + /* do necessary processing; store prev_neighbor; notify the runner of changes */ + update_neighbor(ttdp_ppriv, hello_recv.srcId.ether_addr_octet, hello_recv.cstUUid, hello_recv.etbTopoCnt); + + int notify = 0; + if ((memcmp(ttdp_ppriv->neighbor_uuid, ttdp_ppriv->prev_neighbor_uuid, + sizeof(ttdp_ppriv->neighbor_uuid)) != 0) + || (memcmp(ttdp_ppriv->neighbor_mac, ttdp_ppriv->prev_neighbor_mac, + sizeof(ttdp_ppriv->neighbor_mac)) != 0)) { + + /* neighbor change detected, setup runner data & notify the runner */ + teamd_ttdp_log_infox(ttdp_ppriv, "New neighbor detected!"); + struct ab* p = (struct ab*)ctx->runner_priv; + + if (p && ttdp_ppriv->line < TTDP_MAX_PORTS_PER_TEAM) { + /* copy uuid */ + memcpy(p->neighbors[ttdp_ppriv->line].neighbor_uuid, ttdp_ppriv->neighbor_uuid, + sizeof(p->neighbors[ttdp_ppriv->line].neighbor_uuid)); + /* copy mac */ + memcpy(p->neighbors[ttdp_ppriv->line].neighbor_mac, ttdp_ppriv->neighbor_mac, + sizeof(p->neighbors[ttdp_ppriv->line].neighbor_mac)); + /* copy previous uuid */ + memcpy(p->prev_neighbors[ttdp_ppriv->line].neighbor_uuid, ttdp_ppriv->prev_neighbor_uuid, + sizeof(p->prev_neighbors[ttdp_ppriv->line].neighbor_uuid)); + /* copy previous mac */ + memcpy(p->prev_neighbors[ttdp_ppriv->line].neighbor_mac, ttdp_ppriv->prev_neighbor_mac, + sizeof(p->prev_neighbors[ttdp_ppriv->line].neighbor_mac)); + /* finally, update this... should not be needed every time */ + p->ifindex_by_line[ttdp_ppriv->line] = ttdp_ppriv->start.common.tdport->ifindex; + } else { + /* Should never happen, leaving it here for future 4-line support */ + } + notify = 1; + } + + if (ttdp_ppriv->neighbor_topocnt != ttdp_ppriv->prev_neighbor_topocnt) { + teamd_ttdp_log_infox(ttdp_ppriv, "Neighbor has new topocnt! %08X -> %08X", + ttdp_ppriv->prev_neighbor_topocnt, ttdp_ppriv->neighbor_topocnt); + ttdp_ppriv->prev_neighbor_topocnt = ttdp_ppriv->neighbor_topocnt; + struct ab *p = (struct ab*)ctx->runner_priv; + if (p && ttdp_ppriv->line < TTDP_MAX_PORTS_PER_TEAM) { + p->neighbors[ttdp_ppriv->line].neighbor_topocount = ttdp_ppriv->neighbor_topocnt; + } else { + /* Should never happen, leaving it here for future 4-line support */ + } + notify = 1; + } + + /* we don't force a new neighbor election on inhibition changes, just set it for + * the runner to consume */ + if (hello_recv.inaugInhibition != ttdp_ppriv->neighbor_inhibit) { + struct ab *p = (struct ab*)ctx->runner_priv; + ttdp_ppriv->neighbor_inhibit = hello_recv.inaugInhibition; + p->neighbors[ttdp_ppriv->line].neighbor_inhibition_state + = hello_recv.inaugInhibition; + if (p->remote_inhibit_update_func) { + p->remote_inhibit_update_func(ctx, p); + } + } + + if (notify) + teamd_event_port_changed(ctx, ttdp_ppriv->start.common.tdport); + + + if (ttdp_ppriv->local_ttdp_link_up != true || ttdp_ppriv->heard != true) { + ttdp_ppriv->local_ttdp_link_up = true; + ttdp_ppriv->forget_peer_timer_running = false; + set_neigh_hears_us(ctx, ttdp_ppriv, true); + update_parent_port_status(ctx, ttdp_ppriv); + update_overall_state(ctx, ttdp_ppriv); + teamd_ttdp_log_infox(ttdp_ppriv, "Logical link state is now UP"); + } + + if (hello_recv.timeoutSpeed == 2) { + teamd_ttdp_log_dbgx(ttdp_ppriv, "Recv HELLO in FAST MODE, replying"); + /* send a reply */ + lw_ttdp_send(ctx, events, priv); + } + + } else { + teamd_ttdp_log_dbgx(ttdp_ppriv, "Invalid frame, aborting"); + return 1; + } + + return 0; +} + +/* This is called if the logical link is down, but we've heard our neighbor some time + * ago. We have however not heard from them since, so it's time to lower the peer recv + * status back down to ERROR. */ +static int lw_ttdp_forget_peer(struct teamd_context* ctx, int events, + void* priv) { + struct lw_ttdp_port_priv *ttdp_ppriv = (struct lw_ttdp_port_priv *)priv; + struct ab *ab = ctx->runner_priv; + + if ((ttdp_ppriv->local_phy_link_up == true) + && (ttdp_ppriv->local_ttdp_link_up == false) + && (ttdp_ppriv->forget_peer_timer_running == true) + ) { + teamd_ttdp_log_infox(ttdp_ppriv, "Strict Peer timer expired - resetting line status"); + ab->neighbor_lines[ttdp_ppriv->line] = '-'; + force_parent_port_status(ctx, ttdp_ppriv, 1); + } + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_FORGET_PEER_CB_NAME, priv); + ttdp_ppriv->forget_peer_timer_running = false; + return 0; +} + +/* This callback is called after a delay if we delayed reporting of physical port status */ +static int lw_ttdp_link_status_delayed(struct teamd_context *ctx, int events, + void *priv) { + struct lw_common_port_priv *common_ppriv = priv; + struct lw_ttdp_port_priv *ttdp_ppriv = (struct lw_ttdp_port_priv *)priv; + + struct teamd_port *tdport; + bool link_up; + + tdport = common_ppriv->tdport; + link_up = team_is_port_link_up(tdport->team_port); + + ttdp_ppriv->local_phy_link_event_delayed = false; + ttdp_ppriv->local_phy_link_up = link_up; + + if (!link_up) { + update_neighbor_to_none(ttdp_ppriv); + } + teamd_ttdp_log_infox(ttdp_ppriv, "Reporting delayed link state %s", link_up ? "UP" : "DOWN"); + + //teamd_event_port_changed(ctx, tdport); + update_parent_port_status(ctx, ttdp_ppriv); + return update_overall_state(ctx, priv); +} + +/* FIXME check if the delayed thing is actually aborted properly */ +static int lw_ttdp_event_watch_port_changed(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv) { + struct lw_common_port_priv *common_ppriv = priv; + struct lw_ttdp_port_priv *ttdp_ppriv = (struct lw_ttdp_port_priv *)priv; + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_event_watch_port_changed"); + //struct timespec delay; + + /* Check if we got a spurious event #1 (wrong interface) - these are from teamlib + * directly, not sure if they actually ever happen */ + if (common_ppriv->tdport != tdport || + !team_is_port_changed(tdport->team_port)) { + //fprintf(stderr, "spurious event #1\n"); + return 0; + } + + bool link_up = team_is_port_link_up(tdport->team_port); + + /* Check if we got a spurious event #2 (no state change) - these are from teamlib + * directly, not sure if they actually ever happen */ + //if (!teamd_link_watch_link_up_differs(common_ppriv, link_up)) { + // fprintf(stderr, "spurious event #2\n"); + // return 0; + //} + + teamd_ttdp_log_infox(ttdp_ppriv, "Physical link state is now %s", link_up ? "UP" : "DOWN"); + + /* Disable any running delay callback, we'll restart later if needed */ + teamd_loop_callback_disable(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, priv); + + if (link_up) { + /* Link went down -> up */ + if (timespec_is_zero(&(ttdp_ppriv->link_state_delay_up))) { + /* No delay, report immediately */ + teamd_ttdp_log_infox(ttdp_ppriv, "Setting link state to UP immediately"); + ttdp_ppriv->local_phy_link_up = link_up; + //return teamd_link_watch_check_link_up(ctx, tdport, common_ppriv, link_up); + return lw_ttdp_link_status_delayed(ctx, 0, priv); + } else { + teamd_ttdp_log_infox(ttdp_ppriv, "Starting link state UP reporting delay"); + ttdp_ppriv->local_phy_link_event_delayed = true; + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, + priv, + NULL, + &(ttdp_ppriv->link_state_delay_up)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, priv); + return 0; + } + } else { + /* Link went up -> down */ + if (timespec_is_zero(&(ttdp_ppriv->link_state_delay_down))) { + /* No delay, report immediately */ + teamd_ttdp_log_infox(ttdp_ppriv, "Setting link state to DOWN immediately"); + ttdp_ppriv->local_phy_link_up = link_up; + //return teamd_link_watch_check_link_up(ctx, tdport, common_ppriv, link_up); + return lw_ttdp_link_status_delayed(ctx, 0, priv); + } else { + teamd_ttdp_log_infox(ttdp_ppriv, "Starting link state DOWN reporting delay"); + ttdp_ppriv->local_phy_link_event_delayed = true; + teamd_loop_callback_timer_set(ctx, + TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, + priv, + NULL, + &(ttdp_ppriv->link_state_delay_down)); + teamd_loop_callback_enable(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, priv); + return 0; + } + } + + return 0; +} + +static const struct teamd_event_watch_ops lw_ttdp_port_watch_ops = { + .port_changed = lw_ttdp_event_watch_port_changed, +}; + + +static int lw_ttdp_port_added(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv, void *creator_priv) +{ + //struct ab * runner = creator_priv; + struct lw_ttdp_port_priv *ttdp_ppriv = (struct lw_ttdp_port_priv *)priv; + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_port_added"); + + int err; + + /* load options & set timespec fields of ttdp_ppriv */ + err = lw_ttdp_load_options(ctx, tdport, priv); + if (err) { + teamd_log_err("Failed to parse options, aborting"); + return err; + } + + /* newly added ports should be disabled in the aggregate until known good */ + //team_set_port_enabled(ctx->th, tdport->ifindex, false); + + /* set up remaining local data */ + ttdp_ppriv->local_recovery_mode = 0; + ttdp_ppriv->silent = false; + ttdp_ppriv->lifesign = 0; + ttdp_ppriv->local_phy_link_event_delayed = false; + ttdp_ppriv->local_ttdp_link_up = false; + ttdp_ppriv->neighbor_inhibit = TTDP_LOGIC_UNDEFINED; + ms_to_timespec(&ttdp_ppriv->immediate, 1); + if (ttdp_ppriv->immediate_timer_start_mode) { + ttdp_ppriv->initial_fast_interval = &ttdp_ppriv->immediate; + ttdp_ppriv->initial_slow_interval = &ttdp_ppriv->immediate; + } else { + ttdp_ppriv->initial_fast_interval = &ttdp_ppriv->fast_interval; + ttdp_ppriv->initial_slow_interval = &ttdp_ppriv->slow_interval; + } + stringify_uuid(ttdp_ppriv->local_uuid, ttdp_ppriv->local_uuid_str); + + + err = lw_ttdp_sock_open(ttdp_ppriv); + if (err) { + teamd_log_err("Failed to create socket."); + return err; + } + err = teamd_loop_callback_fd_add(ctx, TTDP_SOCKET_CB_NAME, ttdp_ppriv, + lw_ttdp_receive, + ttdp_ppriv->start.psr.sock, + TEAMD_LOOP_FD_EVENT_READ); + if (err) { + teamd_log_err("Failed add socket callback."); + } + + + err = team_set_port_user_linkup_enabled(ctx->th, tdport->ifindex, false); + if (err) { + teamd_log_err("%s: Failed to enable user linkup.", + tdport->ifname); + } + + + err = teamd_event_watch_register(ctx, &lw_ttdp_port_watch_ops, priv); + if (err) { + teamd_log_err("Failed to register event watch."); + //goto delay_callback_del; + } + + /* FIXME add error handling from here on */ + + teamd_loop_callback_timer_add_set( + ctx, + TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, + ttdp_ppriv, + lw_ttdp_fail_recovery_mode, + &(ttdp_ppriv->fast_timeout), + &(ttdp_ppriv->fast_timeout)); + + teamd_loop_callback_timer_add_set( + ctx, + TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, + ttdp_ppriv, + lw_ttdp_enter_recovery_mode, + &(ttdp_ppriv->slow_timeout), + &(ttdp_ppriv->slow_timeout)); + + teamd_loop_callback_timer_add(ctx, + TTDP_PERIODIC_FORGET_PEER_CB_NAME, + ttdp_ppriv, + lw_ttdp_forget_peer); + + + //teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FAST_TIMEOUT_CB_NAME, ttdp_ppriv); + + //teamd_loop_callback_enable(ctx, TTDP_PERIODIC_SLOW_TIMEOUT_CB_NAME, ttdp_ppriv); + + + /* This one we only add - it gets set up when needed upon link status change */ + teamd_loop_callback_timer_add(ctx, TTDP_PERIODIC_LINK_STATE_DELAY_CB_NAME, + ttdp_ppriv, lw_ttdp_link_status_delayed); + + teamd_loop_callback_enable(ctx, TTDP_SOCKET_CB_NAME, ttdp_ppriv); + + if (ttdp_ppriv->initial_mode == TTDP_INITIAL_MODE_FAST) { + //teamd_ttdp_log_infox("Starting in fast mode"); + teamd_ttdp_log_infox(ttdp_ppriv, "Starting in FAST transmission mode"); + //teamd_loop_callback_enable(ctx, TTDP_PERIODIC_FAST_SEND_CB_NAME, ttdp_ppriv); + ttdp_start_fast_send_timer(ctx, ttdp_ppriv); + } else { + //teamd_ttdp_log_infox("Starting in slow mode"); + teamd_ttdp_log_infox(ttdp_ppriv, "Starting in SLOW transmission mode"); + //teamd_loop_callback_enable(ctx, TTDP_PERIODIC_SLOW_SEND_CB_NAME, ttdp_ppriv); + ttdp_start_slow_send_timer(ctx, ttdp_ppriv); + } + + /* For the -2-5 SNMP MIB, we have to transmit out fast/slow timeout values to the runner. + * However, the MIB only contains one of each of these values, while we could theoretically + * have two configured runners with two links each, and each of those could have a different + * configured value. We solve this dilemma in the simplest way possible, by having all links + * report up their value to the runners, at which point the runners send it to TCNd, and the + * latest report is shown via SNMP. */ + struct ab* ab = ctx->runner_priv; + ab->latest_line_fast_timeout_ms = timespec_to_ms(&(ttdp_ppriv->fast_timeout)); + ab->latest_line_slow_timeout_ms = timespec_to_ms(&(ttdp_ppriv->slow_timeout)); + if (ab->line_timeout_value_update_func) { + ab->line_timeout_value_update_func(ctx, ab); + } + + ttdp_ppriv->local_phy_link_up = team_is_port_link_up(tdport->team_port); + force_parent_port_status(ctx, ttdp_ppriv, 1); + return err; +} + +static void lw_ttdp_port_removed(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv, void *creator_priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + teamd_ttdp_log_dbgx(ttdp_ppriv, "ttdp lw: lw_ttdp_port_removed"); + /* In case teamd_ttdp_log_dbgx is not defined: */ + (void)ttdp_ppriv; + + teamd_event_watch_unregister(ctx, &lw_ttdp_port_watch_ops, priv); + lw_ttdp_sock_close(priv); +} + + +static int lw_ttdp_get_overall_state(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + + gsc->data.bool_val = get_overall_state(ttdp_ppriv); + + return 0; +} +static int lw_ttdp_get_physical_state(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + + static const char* state_names[3] = { "UP", "WAIT", "DOWN" }; + + int result = 0; + bool state = ttdp_ppriv->local_phy_link_up; + if (ttdp_ppriv->local_phy_link_event_delayed) + result = 1; + else result = (state ? 0 : 2); + + gsc->data.str_val.ptr = state_names[result]; + return 0; +} +static int lw_ttdp_get_logical_state(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + + gsc->data.bool_val = ttdp_ppriv->local_ttdp_link_up; + return 0; +} +static int lw_ttdp_get_recovery_mode_state(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + + gsc->data.bool_val = ttdp_ppriv->local_recovery_mode; + return 0; +} + +static int lw_ttdp_state_initial_mode_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.int_val = ttdp_ppriv->initial_mode; + return 0; +} + +static int lw_ttdp_get_fast_failed_recovery_mode(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.bool_val = ttdp_ppriv->fast_failed_recovery_mode; + return 0; +} + +static int lw_ttdp_set_fast_failed_recovery_mode(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + ttdp_ppriv->fast_failed_recovery_mode = gsc->data.bool_val; + return 0; +} + +static int lw_ttdp_state_delay_up_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + + ts = &(ttdp_ppriv->link_state_delay_up); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} +static int lw_ttdp_state_delay_down_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + + ts = &(ttdp_ppriv->link_state_delay_down); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} + +static int lw_ttdp_immediate_timer_start_mode_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.bool_val = ttdp_ppriv->immediate_timer_start_mode; + return 0; +} + +static int lw_ttdp_slow_interval_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + ts = &(ttdp_ppriv->slow_interval); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} +static int lw_ttdp_fast_interval_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + ts = &(ttdp_ppriv->fast_interval); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} +static int lw_ttdp_slow_timeout_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + ts = &(ttdp_ppriv->slow_timeout); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} +static int lw_ttdp_fast_timeout_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + ts = &(ttdp_ppriv->fast_timeout); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} + +static int lw_ttdp_forget_peer_timeout_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct timespec* ts; + ts = &(ttdp_ppriv->forget_peer); + gsc->data.int_val = timespec_to_ms(ts); + return 0; +} + +static int lw_ttdp_local_uuid_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.str_val.ptr = ttdp_ppriv->local_uuid_str; + return 0; +} + +static int lw_ttdp_local_topocnt_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + struct ab* ab = ctx->runner_priv; + int err = snprintf(ttdp_ppriv->local_topocnt_str, sizeof(ttdp_ppriv->local_topocnt_str), + "%.8X", ab->etb_topo_counter); + if (err > 0 && err < sizeof(ttdp_ppriv->local_topocnt_str)) { + gsc->data.str_val.ptr = ttdp_ppriv->local_topocnt_str; + return 0; + } else return err; +} + +static int lw_ttdp_identity_hwaddr_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.str_val.ptr = ttdp_ppriv->identity_hwaddr_str; + return 0; +} + +static int lw_ttdp_failed_crcs_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.int_val = ttdp_ppriv->checksum_fail_counter; + return 0; +} + +static int lw_ttdp_vendor_info_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.str_val.ptr = ttdp_ppriv->vendor_info; + return 0; +} + +static int lw_ttdp_dump_frames_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + //struct lw_ttdp_port_priv* ttdp_ppriv = priv; + if (gsc && gsc->data.int_val > 0) { + dump_hello_frames(); + } else { + hello_data_storage_next_idx++; + } + return 0; +} + +static int lw_ttdp_clear_neighbor_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + if (gsc && gsc->data.int_val > 0) { + update_neighbor_to_none(ttdp_ppriv); + } + return 0; +} + + +static int lw_ttdp_remote_uuid_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + stringify_uuid(ttdp_ppriv->neighbor_uuid, ttdp_ppriv->remote_uuid_str); + gsc->data.str_val.ptr = ttdp_ppriv->remote_uuid_str; + return 0; +} + +static int lw_ttdp_remote_mac_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + int err = snprintf(ttdp_ppriv->remote_mac_str, sizeof(ttdp_ppriv->remote_mac_str), + "%.2X:%.2X:%.2X:%.2X:%.2X:%.2X", + ttdp_ppriv->neighbor_mac[0], + ttdp_ppriv->neighbor_mac[1], + ttdp_ppriv->neighbor_mac[2], + ttdp_ppriv->neighbor_mac[3], + ttdp_ppriv->neighbor_mac[4], + ttdp_ppriv->neighbor_mac[5] + ); + if (err > 0 && err < sizeof(ttdp_ppriv->remote_mac_str)) { + gsc->data.str_val.ptr = ttdp_ppriv->remote_mac_str; + return 0; + } else return err; +} + +static int lw_ttdp_remote_topocnt_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + int err = snprintf(ttdp_ppriv->remote_topocnt_str, sizeof(ttdp_ppriv->remote_topocnt_str), + "%.8X", htonl(ttdp_ppriv->neighbor_topocnt)); + if (err > 0 && err < sizeof(ttdp_ppriv->remote_topocnt_str)) { + gsc->data.str_val.ptr = ttdp_ppriv->remote_topocnt_str; + return 0; + } else return err; +} + +static int lw_ttdp_remote_inhibit_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.int_val = ttdp_ppriv->neighbor_inhibit; + return 0; +} + +static int lw_ttdp_linedir_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + int err = snprintf(ttdp_ppriv->linedir_str, sizeof(ttdp_ppriv->linedir_str), + "%c%c", + ttdp_ppriv->line == 0 ? 'A' : 'B', + ttdp_ppriv->direction == 1 ? '1' : '2' + ); + if (err > 0 && err < sizeof(ttdp_ppriv->linedir_str)) { + gsc->data.str_val.ptr = ttdp_ppriv->linedir_str; + return 0; + } else return err; +} + +static int strict_peer_recv_status_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.bool_val = ttdp_ppriv->strict_peer_recv_status; + return 0; +} + +static int lw_ttdp_silent_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.int_val = (ttdp_ppriv->silent == true) ? 1 : 0; + return 0; +} + +static int lw_ttdp_silent_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + ttdp_ppriv->silent = (gsc->data.int_val == 1) ? true : false; + return 0; +} + +static int lw_ttdp_member_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + bool val = false; + team_get_port_user_linkup(ctx->th, ttdp_ppriv->start.common.tdport->ifindex, &val); + //teamd_port_enabled(ctx, ttdp_ppriv->start.common.tdport, &val); + gsc->data.bool_val = val; + return 0; +} + +static int lw_ttdp_force_disabled_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + gsc->data.bool_val = ttdp_ppriv->deaf; + return 0; +} + +static int lw_ttdp_force_disabled_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct lw_ttdp_port_priv* ttdp_ppriv = priv; + ttdp_ppriv->deaf = gsc->data.bool_val; + if (gsc->data.bool_val == true) { + //team_set_port_enabled(ctx->th, ttdp_ppriv->start.common.tdport->ifindex, false); + } else { + //team_set_port_enabled(ctx->th, ttdp_ppriv->start.common.tdport->ifindex, true); + update_neighbor_to_none(ttdp_ppriv); + } + return 0; +} + +static int lw_ttdp_noop(struct teamd_context __attribute__((unused)) *ctx, + struct team_state_gsc __attribute__((unused)) *gsc, + void __attribute__((unused)) *priv) { + return 0; +} + +static const struct teamd_state_val lw_ttdp_state_vals[] = { + /* Runtime state */ + { + .subpath = "overall_link_state", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_get_overall_state, + }, + + /* Return 0 for down, 1 for intermediate state (delayed transition), 2 for up */ + { + .subpath = "physical_link_state", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_get_physical_state, + }, + + { + .subpath = "logical_link_state", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_get_logical_state, + }, + + { + .subpath = "recovery_mode", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_get_recovery_mode_state, + }, + + /* Config variables */ + /* The sending mode that we should start in. 1 is slow, 2 is fast, anything else + * is slow */ + { + .subpath = "initial_mode", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_state_initial_mode_get, + }, + + /* If enabled, we stay in FAST mode after failing recovery; otherwise, return to SLOW mode */ + { + .subpath = "fast_failed_recovery_mode", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_get_fast_failed_recovery_mode, + .setter = lw_ttdp_set_fast_failed_recovery_mode, + }, + + /* Ethernet physical link status delays */ + { + .subpath = "link_state_delay_up", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_state_delay_up_get, + }, + { + .subpath = "link_state_delay_down", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_state_delay_down_get, + }, + + { + .subpath = "immediate_timer_start_mode", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_immediate_timer_start_mode_get, + }, + + /* Slow mode sending interval in ms, aka SlowPeriod (recommended 100 ms) */ + { + .subpath = "slow_interval", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_slow_interval_get, + }, + + /* Fast mode sending interval in ms, aka FastPeriod (rec. 15 ms) */ + { + .subpath = "fast_interval", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_fast_interval_get, + }, + + /* Recovery mode enter timeout in ms, aka SlowTimeout (rec. 130 ms) */ + { + .subpath = "slow_timeout", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_slow_timeout_get, + }, + + /* Recovery mode exit timeout in ms, aka FastTimeout (rec. 45 ms) */ + { + .subpath = "fast_timeout", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_fast_timeout_get, + }, + + { + .subpath = "forget_peer_timeout", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_forget_peer_timeout_get, + }, + + { + .subpath = "local_uuid", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_local_uuid_get, + }, + + { + .subpath = "local_topocnt", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_local_topocnt_get, + }, + + { + .subpath = "identity_hwaddr", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_identity_hwaddr_get, + }, + + { + .subpath = "strict_peer_recv_status", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = strict_peer_recv_status_get, + }, + + { + .subpath = "failed_crcs", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_failed_crcs_get, + }, + + { + .subpath = "current_neighbor_uuid", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_remote_uuid_get, + }, + + { + .subpath = "current_neighbor_mac", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_remote_mac_get, + }, + + { + .subpath = "current_neighbor_topocnt", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_remote_topocnt_get, + }, + + { + .subpath = "current_neighbor_inhibit_antivalent", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_remote_inhibit_get, + }, + + { + .subpath = "line_and_direction", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_linedir_get, + }, + + { + .subpath = "enabled_in_team", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_member_get, + }, + + /* triggers a dump of received HELLO frames */ + { + .subpath = "poke_dump_frames", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_noop, + .setter = lw_ttdp_dump_frames_set, + }, + /* triggers a reset of memorized neighbor mac and uuid */ + { + .subpath = "poke_clear_neighbor", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_noop, + .setter = lw_ttdp_clear_neighbor_set, + }, + /* if set to 1, operate as normal but never send any frames. + * if set to 0, or any other value, normal operation resumes */ + { + .subpath = "poke_silent", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = lw_ttdp_silent_get, + .setter = lw_ttdp_silent_set, + }, + /* if set to 1, operate as normal but never send any frames. + * also forcibly remove myself from the aggregate. + * if set to 0, or any other value, normal operation resumes */ + { + .subpath = "poke_deaf", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = lw_ttdp_force_disabled_get, + .setter = lw_ttdp_force_disabled_set, + }, + /* Value of the "vendor specific" field in transmitted HELLO frames. This + * can be set using the runner-scope configuration option + * "runner.vendor_info", but only takes effect on startup, and is then set + * in all of that runner's child linkwatchers. Up to 32 characters including + * a terminating zero byte are available. Read-only. */ + { + .subpath = "vendor_info", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = lw_ttdp_vendor_info_get, + } +}; + +const struct teamd_link_watch teamd_link_watch_ttdp = { + .name = "ttdp", + .state_vg = { + .vals = lw_ttdp_state_vals, + .vals_count = ARRAY_SIZE(lw_ttdp_state_vals), + }, + .port_priv = { + .init = lw_ttdp_port_added, + .fini = lw_ttdp_port_removed, + .priv_size = sizeof(struct lw_ttdp_port_priv), + }, +}; diff --git a/teamd/teamd_lw_ttdp.h b/teamd/teamd_lw_ttdp.h new file mode 100644 index 0000000..96be875 --- /dev/null +++ b/teamd/teamd_lw_ttdp.h @@ -0,0 +1,503 @@ +/* + * teamd_lw_ttdp.h teamd TTDP structures & definitions + * Copyright (C) 2017-2018 Westermo + * Author: Andrzej Koszela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef _TEAMD_LW_TTDP_H +#define _TEAMD_LW_TTDP_H + +#include +#include + +#include "teamd_workq.h" + +/* ipc setting */ +/* FIXME REMOVE */ +#define TTDP_SILENT_NO_OUTPUT 1 +#define TTDP_SILENT_NO_OUTPUT_INPUT 2 +#define TTDP_NOT_SILENT 0 + +/* how many times to try sending the inital IPC data */ +#define IPC_TRIES_MAX 5 + +#define TTDP_TOPOCNT_STR_BUF_SIZE 12 + +/* used for aggregate_status in struct ab */ +#define TTDP_AGG_STATE_FLOATING_END 0 +#define TTDP_AGG_STATE_FLOATING_MIDDLE 1 +#define TTDP_AGG_STATE_FIXED_END 2 +#define TTDP_AGG_STATE_FIXED_MIDDLE 3 +#define TTDP_AGG_STATE_DEFAULT TTDP_AGG_STATE_FLOATING_END +#define TTDP_AGG_STATE_MAX TTDP_AGG_STATE_FIXED_MIDDLE + +/* old behavior - don't disable disagreeing ports */ +#define TTDP_NEIGH_AGREE_MODE_MULTI 0 +/* new behavior - ensure that all ports that disagree with the currently elected + * neighbor are disabled */ +#define TTDP_NEIGH_AGREE_MODE_SINGLE 1 +#define TTDP_NEIGH_AGREE_MODE_DEFAULT TTDP_NEIGH_AGREE_MODE_SINGLE + +/* If this is defined, receipt of a HELLO frame will restart any active + * physical link state down delay timer. */ +#define TTDP_PHYSICAL_LINK_STATE_OVERRIDE + +/* this has not been tested completely and is likely not needed */ +//#define SET_USER_LINK +/* Define this to use port disabling/enabling in the aggregate. Without it, + * we will not touch underlying aggregate functions. */ +#define SET_PORT_ENABLED_DISABLED +/* Define this to force-enable all ports when going to FORWARDING mode. */ +//#define FORCE_PORT_ENABLED_IN_FORWARDING +/* Define this to force-disable all ports when going to DISCARDING mode. */ +#define FORCE_PORT_DISABLED_IN_DISCARDING + +#define TTDP_NEIGH_PORT_DISAGREES 1 +#define TTDP_NEIGH_PORT_AGREES 2 + +#define TTDP_INITIAL_MODE_SLOW 1 +#define TTDP_INITIAL_MODE_FAST 2 + +#define TTDP_VENDOR_INFO_DEFAULT "UNSPECIFIED" + +#include "teamd_link_watch.h" + +/* FIXME: if this is changed, teamd_lw_ttdp needs to read its config better. + * Look for teamd_config_int_get(ctx, &tmp, "@.direction" ...) */ +#define TTDP_MAX_PORTS_PER_TEAM 2 + +#define TTDP_MAX_LINES 4 + +#define TTDP_LOGIC_FALSE 1 +#define TTDP_LOGIC_TRUE 2 +#define TTDP_LOGIC_UNDEFINED 3 + +#define TTDP_PORT_STATE_DISABLED 0 +#define TTDP_PORT_STATE_ERROR 1 +#define TTDP_PORT_STATE_FORWARDING 2 +#define TTDP_PORT_STATE_DISCARDING 3 + +/* private data for a single ttdp port/linkwatcher */ +struct lw_ttdp_port_priv { + union { + struct lw_common_port_priv common; + struct lw_psr_port_priv psr; + } start; /* must be first */ + + /* USER OPTIONS */ + /* TTDP slow interval, default is TTDP_SLOW_INTERVAL_DEFAULT */ + struct timespec slow_interval; + /* TTDP fast interval, default is TTDP_FAST_INTERVAL_DEFAULT */ + struct timespec fast_interval; + + /* Timeout before we go from slow to fast (recovery) mode, default is + * TTDP_SLOW_TIMEOUT_DEFAULT */ + struct timespec slow_timeout; + /* Time after which, if in recovery mode, we consider logical link status as + * down. Default is TTDP_FAST_TIMEOUT_DEFAULT */ + struct timespec fast_timeout; + + /* set to 1 ms, and used as a value for "don't wait" below */ + struct timespec immediate; + /* depending on the value of immediate_timer_start_mode below, these variables + * are either set to the respective _interval values, or to 'immediate'. The + * values are then used as the length of the first interval of the respective + * send timer - thus, if immediate_timer_start_mode is true, the first time we + * try to send the corresponding emssage we will do so immediately and not + * wait for the whole interval. */ + struct timespec* initial_slow_interval; /* either immediate or slow_interval */ + struct timespec* initial_fast_interval; /* either immediate or fast_interval */ + + /* These delays control how long we wait after a change of the physical link + * state before reportng & accepting such a change. If the change is reversed + * withing the delay, we don't report it at all. */ + /* default is TTDP_LINK_STATE_DELAY_UP_DEFAULT */ + struct timespec link_state_delay_up; + /* default is TTDP_LINK_STATE_DELAY_DOWN_DEFAULT */ + struct timespec link_state_delay_down; + + /* Transmission mode to start in initially - 1 for SLOW and 2 for FAST. + * Default is 1 (slow). */ + int initial_mode; + /* Transmission mode to enter when we've given up recovery i.e. when no + * neighbor is talking to us. If set to TRUE, we remain in fast transmission + * mode; if set to FALSE, we jump back into slow mode. We consider logical + * link state as down in both cases regardless. Default is FALSE. */ + bool fast_failed_recovery_mode; + + /* This controls the start values of the initial interval timers above. Set + * in the configuration file and only used at startup. */ + bool immediate_timer_start_mode; + + /* If FALSE, we ignore the "peer_recv" flag in incoming telegrams. This lets + * us consider the port as up even though the neighbor says they cannot hear + * us. If TRUE, use the 61375 behavior, where we shall consider the logical + * port status as down, even if we can hear a neighbor, but that neighbor + * has these flags set to 0 - indicating that they cannot hear us. */ + bool strict_peer_recv_status; + + /* How long to wait for before permanently forgetting a neighbor. When this + * expires, we set the "neighbor lines" values in outbound telegrams to '-' + * for all of our lines. Default is TTDP_FORGET_PEER_TIMEOUT_DEFAULT */ + struct timespec forget_peer; + /* Set to TRUE whenever the forget_peer timer mentioned above is running. */ + bool forget_peer_timer_running; + + /* ttdp direction of our parent aggregate, 1 or 2 */ + uint8_t direction; + /* ttdp line of this link, 0 for 'A', or 1 for 'B' */ + uint8_t line; + /* String version of the above. Set on configuration load. */ + char linedir_str[3]; + + /* Local UUID of the consist that our parent is part of. */ + uint8_t local_uuid[16]; + /* String version of the above - set on configuration load. */ + char local_uuid_str[37]; + char vendor_info[32]; + + /* Buffer to hold the string value of the current topocount. This is set by + * the getter function and almost always outdated, except for immediately + * when the state var is being read. */ + char local_topocnt_str[12]; + + /* This points to either identity_hwaddr or the hwaddr of the team device. + * The latter case is useful mostly for testing as it will often result in + * broken topologies, since the two team devices on a node often have + * different MACS - and this in turn results in them being seen as two + * different nodes on the ETB. The value is set on configuration load. */ + char* identity_hwaddr_ptr; + /* Buffer to hold the identity MAC address set at configuration time. */ + char identity_hwaddr[ETH_ALEN]; + /* String version of the above, read at configuration time */ + char identity_hwaddr_str[18]; + + /* This holds the MAC address used for the mandatory LLDP TLV in the HELLO + * frame. Should not be used for anything of importance. */ + char chassis_hwaddr[ETH_ALEN]; + + /* CURRENT INTERNAL STATE */ + /* current physical status */ + bool local_phy_link_up; + /* are we delaying a physical transition? */ + bool local_phy_link_event_delayed; + /* current logical (TTDP HELLO) status */ + bool local_ttdp_link_up; + /* are we currently in recovery mode? */ + bool local_recovery_mode; + + /* increments every time we've read a frame with an incorrect checksum. Note + * that this is not the topocounter, but the frame checksum of the entire + * HELLO frame. */ + uint32_t checksum_fail_counter; + + /* Current neighbor node MAC... */ + uint8_t neighbor_mac[ETH_ALEN]; + /* ...UUID... */ + uint8_t neighbor_uuid[16]; + /* ...topocnt... */ + uint32_t neighbor_topocnt; + /* ...and inhibition flag. This one is stored as-is using TTDP logic + * (1 is FALSE, etc). */ + uint8_t neighbor_inhibit; + + /* As above, but these are saved from the previous neighbor node. */ + uint8_t prev_neighbor_mac[ETH_ALEN]; + uint8_t prev_neighbor_uuid[16]; + uint32_t prev_neighbor_topocnt; + + /* String buffers to hold the string versions of some neighbor values. They + * are only updated when the corresponding state var is read, and will be + * outdated/incorrect at other times. */ + char remote_uuid_str[37]; + char remote_mac_str[18]; + char remote_topocnt_str[12]; + + /* Internal timer bookkeeping, whether transmission and timeout timers are + * running. */ + bool local_fast_timer_started; + bool local_slow_timer_started; + bool local_fast_timeout_started; + bool local_slow_timeout_started; + + /* Lifesign value sent in HELLO frames and incrememnted every time. */ + uint16_t lifesign; + + /* This gets set to TRUE if the neighbor reports that he can hear our frames + * on this line. */ + bool heard; + + /* TEST MODES */ + /* if true, operate as normal but do not send any frames */ + bool silent; + /* if true, discard all incoming HELLO frames */ + bool deaf; + + /* This is the socket used for sending data. */ + int out_sock; +}; + +/* Structure that holds all data for a single neighbor. */ +struct ttdp_neighbor { + uint32_t neighbor_topocount; + uint8_t neighbor_mac[ETH_ALEN]; + uint8_t neighbor_uuid[16]; + uint8_t neighbor_inhibition_state; +}; + +/* Structure for holding statistics hello frames and fast mode activation */ +struct hello_stats { + uint32_t sent_hello_frames; + uint32_t recv_hello_frames; + uint32_t local_fast_activated; + uint32_t remote_fast_activated; +}; + +/* Private data for one runner/aggregate. Nevermind the name, this used to be + * the activebackup runner at one point. */ +struct ab { + uint32_t active_ifindex; + char active_orig_hwaddr[MAX_ADDR_LEN]; + const struct ab_hwaddr_policy *hwaddr_policy; + int hwaddr_policy_first_set; + struct teamd_workq link_watch_handler_workq; + struct teamd_workq tcnd_notify_tcnd_workq; + struct teamd_workq link_state_update_workq; + struct teamd_workq remote_inhibition_workq; + struct teamd_workq link_timeout_update_workq; + struct teamd_workq end_agg_detection_workq; + + int parasite_sockfd; + + /* Number of TOPOLOGY frames heard being sent through this aggregate */ + uint32_t topo_ctr_out; + /* Number of inbound TOPOLOGY frames heard during the last end agg detection interval. + This is then reset. */ + uint32_t topo_ctr_in; + /* As above, but never reset */ + uint32_t topo_ctr_in_total; + bool is_topo_ctr_in_increasing; + + /* maps an ifindex to a line number. Because reasons, this + * is updated by the lws every time and needs to be improved. */ + uint32_t ifindex_by_line[TTDP_MAX_PORTS_PER_TEAM]; + + /* direction of this aggregate - should be the same as direction of all + * members lest weirdness ensue */ + uint8_t direction; + + /* This is one of the TTDP_SILENT_ defines above, or TTDP_NOT_SILENT. This + * controls how much we send/receive via IPC. Deprecated and to be removed. + * */ + int silent; + + /* Whether we've set up the identity MAC (the address used to identify this + * node) */ + bool identity_hwaddr_set; + /* Identity MAC of this node. See the corresponding field in the port struct + * above. */ + char identity_hwaddr[ETH_ALEN]; + /* String version of the above, read at configuration time */ + char identity_hwaddr_str[18]; + + /* Used for LLDP TLVs for all children - see above */ + char chassis_hwaddr[ETH_ALEN]; + /* String version of the above, read at configuration time */ + char chassis_hwaddr_str[18]; + + /* Whether we've set the local UUID. If we haven't set this is either the + * runner or the individual LWs, refuse to start. */ + bool local_uuid_set; + /* Consist UUID, used for all children lws. See above. */ + uint8_t local_uuid[16]; + /* String version of the above, read at configuration time */ + char local_uuid_str[37]; + + /* Current neighbors, by line. These are to be set by ttdp lws on neighbor + * change. [0] is our neighbor on line A, [1] is line B. */ + struct ttdp_neighbor neighbors[TTDP_MAX_PORTS_PER_TEAM]; + /* keep track of which ports we need to forcibly exclude from the aggregate + * 0 = don't touch, 1 = exclude, 2 = include */ + uint8_t neighbor_agreement[TTDP_MAX_PORTS_PER_TEAM]; + + /* One of the TTDP_NEIGH_AGREE_MODE_ defines. Determines how we act with + * regards to member ports that disagree with a neighbor election. */ + uint8_t neighbor_agreement_mode; + + /* Each member sets whether its neighbor can hear it on that line. + * [0] for line A, and so on. */ + bool lines_heard[TTDP_MAX_PORTS_PER_TEAM]; + + /* Each member sets which neighbor line it's connected to. Contains the line + * name character 'A' etc. Same indexation as above. */ + uint8_t neighbor_lines[TTDP_MAX_PORTS_PER_TEAM]; + + /* 2 bits used per port, also set by the watcher directly. Up to 4 in the standard, + * but only TTDP_MAX_PORTS_PER_TEAM supported currently. + * The lw sets this in update_parent_port_status() before + * updating its overall state, which in turn calls one of the + * port event watchers in the runner */ + uint8_t port_statuses[4]; + /* same as above but packed into one byte - used in outbound telegrams */ + uint8_t port_statuses_b; + /* copy of the above, used for comparisions and IPC updates */ + uint8_t port_statuses_b_prev; + /* same as port_statuses, but whether we're distributing/blocking etc - NYI */ + // uint8_t port_states[4]; + + /* Fields sent to us by IPC, read by our linkwatchers and transmitted in HELLO frames */ + /* topo counter */ + uint32_t etb_topo_counter; + /* text buffer for the hex string representation of the above - only set when needed, + * so not always reliable */ + uint8_t etb_topo_counter_str[TTDP_TOPOCNT_STR_BUF_SIZE]; + + /* Inhibition flags */ + /* Inhibition set on our node,; we get this via IPC */ + uint8_t inhibition_flag_local; + /* Inhibition set on any node in our train; we get this via IPC (calculated + * higher up in the 61375 stack) */ + uint8_t inhibition_flag_any; + /* As heard from our neighbor in their HELLO frames */ + uint8_t inhibition_flag_neighbor; + /* In certain cases, when we are on a consist boundary, we set this and notify via IPC */ + uint8_t inhibition_flag_remote_consist; + + /* NYI */ + struct ttdp_neighbor prev_neighbors[TTDP_MAX_PORTS_PER_TEAM]; + + /* This is the neighbor we've decided upon and will report up the stack */ + struct ttdp_neighbor elected_neighbor; + /* This is elected_neighbor as of before the latest election */ + struct ttdp_neighbor prev_elected_neighbor; + /* Set to 1 if our elected_neighbor above is "all zeroes" */ + int neighbor_is_none; + /* List of topocount values. This list contains valid topocounts + * which will be used in recovery mode to let a ETBN join the + * backbone again if a ETBN previously was lost. + */ + uint32_t fixed_possible_topocnts[63]; + uint8_t num_fixed_possible_topocnts; + + /* Elected neighbor at the time of the latest positive edge of our parent + * node becoming inhibited (either by local request or due to train + * inhibition). This is used for end node management and recovery cases. */ + struct ttdp_neighbor fixed_elected_neighbor; + /* Our local topocount at the inhibition time as described above. */ + uint32_t fixed_etb_topo_counter; + + /* String buffers to hold elected_neighbor values. These are populated by + * the corresponding state val getter functions, and will be outdated or + * incorrect at other times. */ + char elected_neighbor_uuid_str[37]; + char elected_neighbor_mac_str[18]; + char elected_neighbor_topocnt_str[12]; + /* Same as above, but for fixed_elected_neighbor. */ + char fixed_elected_neighbor_uuid_str[37]; + char fixed_elected_neighbor_mac_str[18]; + char fixed_elected_neighbor_topocnt_str[12]; + char neighbor_agreement_str[4]; + + /* Vendor specific information string */ + char vendor_info[32]; + + /* buffer used for the port_statuses statevar, up to 9 chars per port. Also + * set by the statevar getter. */ + char port_statuses_str[(9*4)+1]; + + /* These values are used e.g. for SNMP. Since the MIB only wants one instance + * of each of these values, while they are individually configurable in each + * link watcher, we will just go with the latest. Each child linkwatcher sets + * these two variables to their configured values, and then calls + * line_timeout_value_update_func, at which point we send the IPC message. + * Higher up in the stack, the latest such message received is used to + * reply to SNMP queries for the values in question. */ + uint32_t latest_line_fast_timeout_ms; + uint32_t latest_line_slow_timeout_ms; + void*(*line_timeout_value_update_func)(void*,void*); + + /* The values in this struct are exposed as state variables, per line, and + * holds counters for how many HELLO frames have been sent/received and + * how many times fast mode has been activated. */ + struct hello_stats lines_hello_stats[TTDP_MAX_LINES]; + + /* Child lws can call this function to notify us that a line state has changed, + * in cases that it does not happen automatically due to a port changing + * up/down state. */ + void*(*line_state_update_func)(void*,void*); + /* Same as above, but called when the child has a neighbor inhibit update */ + void*(*remote_inhibit_update_func)(void*,void*); + + /* Current value of the remote-inhibition flag. This is sent via IPC + * and uses TTDP logic, UNDEFINED if n/a. */ + uint8_t remote_inhibition_actual; + + /* Current state of the aggregate state machine, one of the TTDP_AGG_STATE_* + * defines above */ + uint8_t aggregate_status; + + /* Current "aggregate port state" in the 61375 sense. All ports share this. + * Can be "DISCARDING" or "FORWARDING"; in the former case, ports will not be + * activated in the aggregate. Used by inhibited end nodes. */ + bool is_discarding; + + /* EXTERNALLY-SET VALUES */ + /* These values are set by other, external parts of the 61375 stack. This + * used to be done by our IPC, but is now done by the regular statevar + * functionality. */ + /* Set to TRUE whenever we are currently receiving valid TTDP TOPOLOGY frames + * from one or more nodes in this direction. These frames are received and + * parsed higher up in the stack, but we need to know whether we have + * neighbors in our direction. */ + bool receiving_topology_frames; + /* Set to TRUE when the higher parts of the 61375-2-5 stack consider us + * inaugurated. This only refers to -2-5 inauguration - we don't care about + * -2-3 or any of the more advanced funtions here. This is used for our + * state machine with regards to end node management etc. */ + bool inaugurated; + + /* SHORTENING/LENGTHENING DETECTION */ + /* We set this to TRUE if we detect train shortening on the aggregate level. + * This can only be detected on the (post-shortening) end nodes. + * Specifically, if we previously + * - had a neighbor _from a different consist_, + * - find ourselves inhibited, and + * - no longer detect a neighbor, then we set this to TRUE. */ + bool shortening_detected; + /* Conversely, we set this to TRUE when we detect lengthening. This can also + * only be detected on the end nodes while we're on the aggregate level + * (however, higher up in the 61375-2-5 stack we can detect this on every + * node). We detect a few different kinds of lengthening, and intend to be + * compatible with -2-5 in this regard. */ + bool lengthening_detected; + + /* DIAGNOSTICS */ + /* These are certain conditions that we can detect on the aggregate layer + * and warn about. Currently these warnings are done by writing to a + * statefile, one per diagnostic condition. */ + /* Our ports are not mapped to out neighbor in order, i.e. 'A' to 'A', 'B' + * to 'B', and so on. This is indicative of a mistake in cabling. */ + bool crossed_lines_detected; + /* Our in-consist neighbor in this direction has a different orientation + * than we do. In other words, our direction X is connected to the same + * direction X of our in-consist neighbor. This is indicative of a mistake + * in cabling, as the consist orientation becomes inconsistent. */ + bool mixed_consist_orientation_detected; +}; + +#endif diff --git a/teamd/teamd_runner_ttdp.c b/teamd/teamd_runner_ttdp.c new file mode 100644 index 0000000..05808b7 --- /dev/null +++ b/teamd/teamd_runner_ttdp.c @@ -0,0 +1,2782 @@ +/* + * teamd_lw_ttdp.c teamd TTDP runner + * Copyright (C) 2017-2018 Westermo + * Author: Andrzej Koszela + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "private/misc.h" +#include + +#include "teamd.h" +#include "teamd_config.h" +#include "teamd_state.h" +#include "teamd_workq.h" +#include "teamd_lw_ttdp.h" +#include "teamd_lag_state_persistence.h" + +/* This code is based on the the activebackup runner, with IEC61375-specific + additions and changes. The following major changes have been made: + - Sticky is now the default for all ports + - Includes basic peer detection logic using IEC61375-2-5 TTDP HELLO + - Does aggregate control, link supervision & end node management according to + the above + - Communicates neighbor detection & status to an IPC socket. +*/ + +/* Define to set user link up/down when ports don't agree with the elected neighbor */ +// #define SET_USER_LINK +/* Define to set user link up/down on all ports when in FORWARDING/DISCARDING */ +// #define SET_USER_LINK_TEAM_IFACE +/* Define to set link up/down on the agg iface when in FORWARDING/DISCARDING */ +#define SET_LINK_TEAM_IFACE + +#define TEAMNAME_OR_EMPTY(P) ((P)\ + ? P : "ttdp-runner") + +#ifdef DEBUG +#define teamd_ttdp_log_infox(P, format, args...) daemon_log(LOG_DEBUG, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) +#define teamd_ttdp_log_dbgx(P, format, args...) daemon_log(LOG_DEBUG, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) +#define teamd_ttdp_log_end_agg(P, format, args...) fprintf(stderr, "%s: " format "\n", TEAMNAME_OR_EMPTY(P), ## args) +#define teamd_ttdp_log_end_agg_dbg(P, format, args...) fprintf(stderr, "%s: " format "\n", TEAMNAME_OR_EMPTY(P), ## args) +#else +#define teamd_ttdp_log_infox(P, format, args...) do {} while (0) +#define teamd_ttdp_log_dbgx(P, format, args...) do {} while (0) +#define teamd_ttdp_log_end_agg(P, format, args...) daemon_log(LOG_INFO, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) +#define teamd_ttdp_log_end_agg_dbg(P, format, args...) daemon_log(LOG_DEBUG, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) +#endif +#define teamd_ttdp_log_info(format, args...) daemon_log(LOG_INFO, format, ## args) +#define teamd_ttdp_log_dbg(format, args...) daemon_log(LOG_DEBUG, format, ## args) +#define teamd_ttdp_log_warnx(P, format, args...) daemon_log(LOG_WARNING, "%s: " format, TEAMNAME_OR_EMPTY(P), ## args) +#define teamd_ttdp_log_always(P, format, args...) daemon_log(LOG_WARNING, "%s: " format TEAMNAME_OR_EMPTY(P), ## args) + +#define TTDP_PARASITE_SOCKET_CB_NAME "ttdp_parasite_sock" +#define MSEC_TO_NSEC(ms) (ms * 1000000) + +static struct sock_filter ttdp_topology_filter[] = { + { 0x20, 0, 0, 0x00000002 }, /* ld [2] ; check dest. MAC and other Ethernet stuff */ + { 0x15, 0, 7, 0xc2000010 }, /* jneq #0xc2000010, drop */ + { 0x28, 0, 0, 0000000000 }, /* ldh [0] */ + { 0x15, 0, 5, 0x00000180 }, /* jneq #0x0180, drop */ + { 0x20, 0, 0, 0xfffff030 }, /* ld vlan_avail */ + { 0x15, 0, 3, 0x00000001 }, /* jneq #1, drop */ + { 0x28, 0, 0, 0x0000000c }, /* ldh [12] */ + { 0x15, 0, 1, 0x0000894c }, /* jneq #0x894C, drop */ + { 0x06, 0, 0, 0xffffffff }, /* okay: ret #-1 ; accept entire packet */ + { 0x06, 0, 0, 0000000000 } /* drop: ret #0 ; accept nothing */ +}; + +static struct sock_fprog ttdp_topology_fprog = { + .len = (sizeof(ttdp_topology_filter)/sizeof(ttdp_topology_filter[0])), + .filter = ttdp_topology_filter +}; + +/* synchronize with teamd_lw_ttdp.h */ +static const char* agg_state_strings[TTDP_AGG_STATE_MAX+1] = { + "Floating end", + "Floating intermediate", + "Fixed end", + "Fixed intermediate" +}; +#define AGG_STATE_STRING(x) (((x >= 0) && (x <= TTDP_AGG_STATE_MAX)) ? agg_state_strings[x] : "NONE") + +static const char* ttdp_runner_oneshot_initial_agg_state_name = + "ttdp_runner_oneshot_initial_agg_state"; +static struct timespec ttdp_runner_oneshot_timer = { + .tv_sec = 5, + .tv_nsec = 0 +}; + +static const char* ttdp_runner_periodic_neighbor_macs_name = + "ttdp_runner_periodic_neighbor_macs"; +static struct timespec ttdp_runner_periodic_neighbor_macs_timer = { + .tv_sec = 1, + .tv_nsec = 0 /* 1 s */ +}; + +static const char *ttdp_runner_periodic_end_agg_detection_name = + "ttdp_runner_periodic_end_agg"; +static struct timespec ttdp_runner_periodic_end_agg_detection_timer = { + .tv_sec = 0, + .tv_nsec = MSEC_TO_NSEC(500) +}; + +static const char* port_state_strings[] = {"ERROR", "FALSE", " TRUE", "UNDEF"}; +static int ab_link_watch_handler(struct teamd_context *ctx, struct ab *ab); +static int ab_link_watch_handler_internal(struct teamd_context *ctx, struct ab *ab, + bool allow_update_aggregate_state); +void* remote_inhibition_update(void* c, void* a); + +struct ab; +/* policy that determines what to do with MAC addresses of team & ports */ +/* Uses the same policies as activebackup, with the addition of "first", + which sets the team MAC to the one of the first added port, and then does nothing, + and "none", which sets the team device to the "hwaddr" specified in the config, + and then does nothing. */ +struct ab_hwaddr_policy { + const char *name; + /* called when the hwaddr of the team device is changed */ + int (*hwaddr_changed)(struct teamd_context *ctx, + struct ab *data); + /* called when a new port is added */ + int (*port_added)(struct teamd_context *ctx, struct ab *data, + struct teamd_port *tdport); + /* called when a port becomes active */ + int (*active_set)(struct teamd_context *ctx, struct ab *data, + struct teamd_port *tdport); + /* called when a port is no longer active */ + int (*active_clear)(struct teamd_context *ctx, struct ab *data, + struct teamd_port *tdport); +}; + +struct ab_port { + struct teamd_port *tdport; + struct { + bool sticky; +#define AB_DFLT_PORT_STICKY true + } cfg; +}; + +static int is_uuid_none(const uint8_t* uuid) { + static const uint8_t zero[16] = {0}; + return (memcmp(zero, uuid, sizeof(zero)) == 0); +} + +static int is_mac_none(const uint8_t* mac) { + static const uint8_t zero[ETH_ALEN] = {0}; + return (memcmp(zero, mac, sizeof(zero)) == 0); +} + +static int is_neighbor_none(struct ttdp_neighbor* neigh) { + return (is_uuid_none(neigh->neighbor_uuid) && is_mac_none(neigh->neighbor_mac)); +} + +static void all_ports_forwarding(struct teamd_context *ctx, struct ab* ab) { +teamd_ttdp_log_infox(ctx->team_devname, "Move to FORWARDING"); + struct team_port* port; + ab->is_discarding = false; + team_for_each_port(port, ctx->th) { + uint32_t ifindex = team_get_port_ifindex(port); + if (ab->neighbor_agreement_mode == TTDP_NEIGH_AGREE_MODE_SINGLE) { + /* don't set ports with a known zero neighbor as enabled... */ + // for (int i = 0; i < TTDP_MAX_PORTS_PER_TEAM; ++i) { + // if ((ab->ifindex_by_line[i] == ifindex) && !is_neighbor_none(&ab->neighbors[i])) { + #if defined FORCE_PORT_ENABLED_IN_FORWARDING && defined SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, true); + #endif + // } + // } + } else if (ab->neighbor_agreement_mode == TTDP_NEIGH_AGREE_MODE_MULTI) { + #ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, true); + #endif + } + //team_set_port_user_linkup(ctx->th, ifindex, true); + //teamd_ttdp_log_dbgx(ctx->team_devname, "set %d FORWARDING: %d", ifindex, err); + } + #ifdef SET_USER_LINK_TEAM_IFACE + team_set_port_user_linkup(ctx->th, ctx->ifindex, true); + // fprintf(stderr, "FORWARDING set user_link %d\n", ctx->ifindex); + #endif + #ifdef SET_LINK_TEAM_IFACE + team_link_set(ctx->th, ctx->ifindex, true); + // fprintf(stderr, "FORWARDING set link %d\n", ctx->ifindex); + #endif + + ab_link_watch_handler_internal(ctx, ab, false); +} + +static void all_ports_discarding(struct teamd_context *ctx, struct ab* ab) { + teamd_ttdp_log_infox(ctx->team_devname, "Move to DISCARDING"); + struct team_port* port; + ab->is_discarding = true; + team_for_each_port(port, ctx->th) { + uint32_t ifindex = team_get_port_ifindex(port); + #if defined FORCE_PORT_DISABLED_IN_DISCARDING && defined SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, false); + #endif + //team_set_port_user_linkup(ctx->th, ifindex, false); + //teamd_ttdp_log_dbgx(ctx->team_devname, "set %d BLOCKING: %d", ifindex, err); + } + #ifdef SET_USER_LINK_TEAM_IFACE + team_set_port_user_linkup(ctx->th, ctx->ifindex, false); + // fprintf(stderr, "DISCARDING set %d\n", ctx->ifindex); + #endif + #ifdef SET_LINK_TEAM_IFACE + team_link_set(ctx->th, ctx->ifindex, false); + // fprintf(stderr, "DISCARDING set link %d\n", ctx->ifindex); + #endif + //ab_link_watch_handler(ctx, ab); +} + +static void set_shortening(struct teamd_context *ctx, + struct ab* ab, bool shortening) { + ab->shortening_detected = shortening; + lag_state_write_shortening_detected(ctx, ab); +} + +static void set_lengthening(struct teamd_context *ctx, + struct ab* ab, bool lengthening) { + ab->lengthening_detected = lengthening; + lag_state_write_lengthening_detected(ctx, ab); +} + +static void clear_shorten_lengthen(struct teamd_context *ctx, + struct ab* ab) { + ab->shortening_detected = false; + ab->lengthening_detected = false; + lag_state_write_shortening_detected(ctx, ab); + lag_state_write_lengthening_detected(ctx, ab); +} + +static int detect_trivial_shortening(struct teamd_context *ctx, + struct ab* ab) { + /* trivial shortening case - used to have a neighbor from a different consist. */ + if (!is_neighbor_none(&ab->fixed_elected_neighbor)) { + if ((memcmp(&ab->local_uuid, &ab->fixed_elected_neighbor.neighbor_uuid, + sizeof(ab->local_uuid)) != 0)) { + teamd_ttdp_log_infox(ctx->team_devname, "Trivial shortening detected."); + return 1; + } + } + return 0; +} + +static int detect_trivial_lengthening(struct teamd_context *ctx, + struct ab* ab) { + /* trivial lengthening case - we were the last node of the fixed topology, and + * a node has come up that is across the consist boundary. */ + if (is_neighbor_none(&ab->fixed_elected_neighbor)) { + if (!is_uuid_none(ab->elected_neighbor.neighbor_uuid)) { + teamd_ttdp_log_infox(ctx->team_devname, "Trivial lengthening detected."); + return 1; + } + } + return 0; +} + +static int detect_reappearing_lengthening(struct teamd_context *ctx, + struct ab* ab) { + /* reappearing neighbor consist case - same foreign consist is our neighbor */ + if (!is_uuid_none(ab->fixed_elected_neighbor.neighbor_uuid)) { + if (memcmp(&ab->fixed_elected_neighbor.neighbor_uuid, &ab->local_uuid, 16) != 0) { + if (memcmp(&ab->fixed_elected_neighbor.neighbor_uuid, + &ab->elected_neighbor.neighbor_uuid, 16) == 0) { + teamd_ttdp_log_infox(ctx->team_devname, "\'Reappearing consist\'' lengthening detected."); + return 1; + } + } + } + return 0; +} + +static int is_topocount_valid(struct ab* ab) +{ + int i; + + if (!ab->fixed_etb_topo_counter) + return 0; + + if (ab->elected_neighbor.neighbor_topocount == ab->fixed_etb_topo_counter) + return 1; + + for (i = 0; i < ab->num_fixed_possible_topocnts; i++) { + if (ab->elected_neighbor.neighbor_topocount == ab->fixed_possible_topocnts[i]) + return 1; + } + + return 0; +} + +/* FIXME is this sane? */ +static int detect_neigh_node_recovery(struct teamd_context *ctx, + struct ab* ab) { + /* If a neighbor node with the same topocount as we have has come up + * next to us, we can move to FIXED MIDDLE. Do this only if the neighbor + * is not inhibited itself. + * + * Optionally, we could also do this regardless of anything if the node is + * the same one that we had previously, but for now we don't do this since + * there is no good way to guarantee that we only get that node, and not any + * nodes that might have appeared behind it. */ + + if (!is_neighbor_none(&ab->elected_neighbor) && is_topocount_valid(ab)) { + if (memcmp(&ab->local_uuid, &ab->elected_neighbor.neighbor_uuid, + sizeof(ab->local_uuid)) == 0) { + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor node recovery detected - same consist " + "with agreeing topocount."); + } else { + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor node recovery detected - remote consist " + "with agreeing topocount."); + } + return 1; + } + // /* otherwise, if we lost and then recovered the same node, we may also + // * accept it. */ + // if (memcmp(&ab->fixed_elected_neighbor, &ab->elected_neighbor, + // sizeof(ab->fixed_elected_neighbor)) == 0) + // return 1; + + return 0; +} + +static void memorize_neighbor(struct teamd_context *ctx, struct ab* ab) { + memcpy(&(ab->fixed_elected_neighbor), &(ab->elected_neighbor), + sizeof(ab->fixed_elected_neighbor)); + ab->fixed_etb_topo_counter = (ab->etb_topo_counter); + teamd_ttdp_log_infox(ctx->team_devname, "Memorized neighbor with topocount %08X, own %08X", + ab->fixed_etb_topo_counter, ab->etb_topo_counter); +} + +static void forget_neighbor(struct teamd_context *ctx, struct ab* ab) { + memset(&ab->fixed_elected_neighbor, 0, sizeof(ab->fixed_elected_neighbor)); + clear_shorten_lengthen(ctx, ab); +} +static int ab_clear_active_port(struct teamd_context *ctx, struct ab *ab, + struct teamd_port *tdport); +static void move_to_aggregate_state(struct teamd_context *ctx, + struct ab* ab, uint8_t next, bool change) { + uint8_t prev = ab->aggregate_status; + /* when going from floating to fixed, do not touch the links, to avoid weird behavior */ + switch (next) { + case TTDP_AGG_STATE_FLOATING_END: + all_ports_forwarding(ctx, ab); + break; + case TTDP_AGG_STATE_FLOATING_MIDDLE: + all_ports_forwarding(ctx, ab); + break; + case TTDP_AGG_STATE_FIXED_MIDDLE: + // if (prev == TTDP_AGG_STATE_FLOATING_END + // || prev == TTDP_AGG_STATE_FLOATING_MIDDLE) { + // do_refresh_links = 1; + // } + //ab_clear_active_port(ctx, ab, NULL); + all_ports_forwarding(ctx, ab); + break; + case TTDP_AGG_STATE_FIXED_END: + // if (prev == TTDP_AGG_STATE_FLOATING_END + // || prev == TTDP_AGG_STATE_FLOATING_MIDDLE) { + // do_refresh_links = 1; + // } + all_ports_discarding(ctx, ab); + break; + default: + break; + } + if (change) { + ab->aggregate_status = next; + ab_link_watch_handler(ctx, ab); + lag_state_write_aggregate_role(ctx, ab); + } else if (prev != next) { + lag_state_write_aggregate_role(ctx, ab); + prev = next; + } +} + +static uint8_t update_aggregate_state(struct teamd_context *ctx, + struct ab* ab) { + uint8_t current = ab->aggregate_status; + uint8_t next = current; + + uint8_t inaugurated = ab->inaugurated; + uint8_t inhibit_any = (ab->inhibition_flag_local | ab->inhibition_flag_any); + uint8_t inhibit = ((inhibit_any & TTDP_LOGIC_TRUE) != 0); + uint8_t mid = (ab->receiving_topology_frames) && !(is_neighbor_none(&ab->elected_neighbor)); + uint8_t end = !(mid); + + teamd_ttdp_log_infox(ctx->team_devname, "update state curr %d inaug %d inhibit %d " + "end %d neighbor_zero(fixed) %d neighbor_zero(elect) %d", + current, inaugurated, inhibit, end, is_neighbor_none(&ab->fixed_elected_neighbor), + is_neighbor_none(&ab->elected_neighbor)); + + if (inhibit && !inaugurated) { + teamd_ttdp_log_warnx(ctx->team_devname, "state change while inhibited but not inaugurated; ignoring"); + return current; + } + + switch (current) { + case TTDP_AGG_STATE_FLOATING_END: + if (inhibit && inaugurated) { + memorize_neighbor(ctx, ab); + next = TTDP_AGG_STATE_FIXED_END; + break; + } else if (!end) { + next = TTDP_AGG_STATE_FLOATING_MIDDLE; + forget_neighbor(ctx, ab); + break; + } + break; + case TTDP_AGG_STATE_FLOATING_MIDDLE: + if (inhibit && inaugurated) { + memorize_neighbor(ctx, ab); + next = TTDP_AGG_STATE_FIXED_MIDDLE; + break; + } else if (end) { + next = TTDP_AGG_STATE_FLOATING_END; + forget_neighbor(ctx, ab); + } + break; + case TTDP_AGG_STATE_FIXED_END: + if (!inhibit) { + /* if a node was discovered while we were gone, we need to become a + * middle node */ + if (!is_neighbor_none(&ab->elected_neighbor)) { + teamd_ttdp_log_infox(ctx->team_devname, + "Inhibit lifted, not end node anymore"); + next = TTDP_AGG_STATE_FLOATING_MIDDLE; + } else { + teamd_ttdp_log_infox(ctx->team_devname, + "Inhibit lifted, still end node"); + next = TTDP_AGG_STATE_FLOATING_END; + } + forget_neighbor(ctx, ab); + clear_shorten_lengthen(ctx, ab); + } else if (detect_neigh_node_recovery(ctx, ab)) { + /* node recovery - if another node in our own consist has come up, + * we are happy to move to FIXED MIDDLE */ + next = TTDP_AGG_STATE_FIXED_MIDDLE; + clear_shorten_lengthen(ctx, ab); + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor node late/recovery detected!"); + } else if (detect_trivial_lengthening(ctx, ab)) { + teamd_ttdp_log_infox(ctx->team_devname, "Lengthening detected due to new foreign node"); + set_lengthening(ctx, ab, true); + next = TTDP_AGG_STATE_FIXED_END; + } else if (!end && detect_reappearing_lengthening(ctx, ab)) { + /* this is an odd one - we're an end aggregate but have detected a consist + * which does not lead to trivial lengthening. This is likely the same + * consist whose disappearance led us to becoming the end node. In this case + * we're expected to set shorten & lengthen, so set lengthen here. */ + set_lengthening(ctx, ab, true); + } else if ((ab->lengthening_detected == true) + && is_neighbor_none(&ab->fixed_elected_neighbor)) { + /* We had set lengthening before, but now have no neighbor. The consist that caused + * us to set lengthening in the first place must have disappeared again, so clear it. */ + set_lengthening(ctx, ab, false); + } else if ((ab->lengthening_detected == true) + && is_neighbor_none(&ab->elected_neighbor)) { + /* This is similar to the case above, but we went from having a neighbor, to having + * a different one (setting shortening and lengthening), to not having one again - + * in which case we should reset lengthening. */ + set_lengthening(ctx, ab, false); + } + break; + case TTDP_AGG_STATE_FIXED_MIDDLE: + if (!inhibit) { + next = TTDP_AGG_STATE_FLOATING_MIDDLE; + forget_neighbor(ctx, ab); + break; + } else if (end) { + next = TTDP_AGG_STATE_FIXED_END; + /* We used to be a middle node, but are now at the end of the fixed topology. + * set shortening only if a complete consist has vanished, i.e. if we were + * at the edge of our consist previously. More complex cases are handled higher + * in the stack. */ + if ((ab->shortening_detected == false) && detect_trivial_shortening(ctx, ab)) { + set_shortening(ctx, ab, true); + } else if ((ab->lengthening_detected == true) + && is_neighbor_none(&ab->fixed_elected_neighbor) + && is_neighbor_none(&ab->elected_neighbor)) { + /* special case of the above - if lengthening was set, but the new node + * has again disappeared, then reset lengthening */ + clear_shorten_lengthen(ctx, ab); + } + break; + } + break; + default: + next = current; + break; + } + if (current != next) { + teamd_ttdp_log_infox(ctx->team_devname, "Aggregate state transition %s -> %s", + AGG_STATE_STRING(current), AGG_STATE_STRING(next)); + move_to_aggregate_state(ctx, ab, next, true); + } else { + /* do this to avoid ports sometimes being stuck in DISCARDING on reboot */ + move_to_aggregate_state(ctx, ab, next, false); + } + return next; +} + +static struct ab_port *ab_port_get(struct ab *ab, struct teamd_port *tdport) +{ + /* + * When calling this after teamd_event_watch_register() which is in + * ab_init() it is ensured that this will always return valid priv + * pointer for an existing port. + */ + return teamd_get_first_port_priv_by_creator(tdport, ab); +} + +static inline bool ab_is_port_sticky(struct ab *ab, struct teamd_port *tdport) +{ + return ab_port_get(ab, tdport)->cfg.sticky; +} + +static int ab_hwaddr_policy_first_port_added(struct teamd_context *ctx, + struct ab *ab, + struct teamd_port *tdport) { + int err; + teamd_ttdp_log_dbgx(ctx->team_devname, "ab_hwaddr_policy_first_port_added: %d ports, run: %d", + ctx->port_obj_list_count, ab->hwaddr_policy_first_set); + // if (ctx->port_obj_list_count == 1) { + if (ab->hwaddr_policy_first_set++ == 0) { + /* first! */ + err = team_hwaddr_set(ctx->th, ctx->ifindex, team_get_ifinfo_hwaddr(tdport->team_ifinfo), + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + teamd_ttdp_log_infox(ctx->team_devname, "set team address to that of first member port"); + } +#ifdef SET_PORT_ENABLED_DISABLED + //team_set_port_enabled(ctx->th, tdport->ifindex, false); +#endif + return 0; +} + +static const struct ab_hwaddr_policy ab_hwaddr_policy_first = { + .name = "first", + .port_added = ab_hwaddr_policy_first_port_added, +}; + +static const struct ab_hwaddr_policy ab_hwaddr_policy_fixed = { + .name = "fixed", +}; + + +static int ttdp_hwaddr_policy_first_set_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab* ab = priv; + gsc->data.int_val = ab->hwaddr_policy_first_set; + return 0; +} + +static int ab_hwaddr_policy_same_all_hwaddr_changed(struct teamd_context *ctx, + struct ab *ab) +{ + struct teamd_port *tdport; + int err; + + teamd_for_each_tdport(tdport, ctx) { + err = team_hwaddr_set(ctx->th, tdport->ifindex, ctx->hwaddr, + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + } + return 0; +} + +static int ab_hwaddr_policy_same_all_port_added(struct teamd_context *ctx, + struct ab *ab, + struct teamd_port *tdport) +{ + int err; + + err = team_hwaddr_set(ctx->th, tdport->ifindex, ctx->hwaddr, + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + return 0; +} + +static const struct ab_hwaddr_policy ab_hwaddr_policy_same_all = { + .name = "same_all", + .hwaddr_changed = ab_hwaddr_policy_same_all_hwaddr_changed, + .port_added = ab_hwaddr_policy_same_all_port_added, +}; + +static int ab_hwaddr_policy_by_active_active_set(struct teamd_context *ctx, + struct ab *ab, + struct teamd_port *tdport) +{ + int err; + + err = team_hwaddr_set(ctx->th, ctx->ifindex, + team_get_ifinfo_hwaddr(tdport->team_ifinfo), + ctx->hwaddr_len); + if (err) { + teamd_log_err("Failed to set team hardware address."); + return err; + } + return 0; +} + +static const struct ab_hwaddr_policy ab_hwaddr_policy_by_active = { + .name = "by_active", + .active_set = ab_hwaddr_policy_by_active_active_set, +}; + +static int ab_hwaddr_policy_only_active_hwaddr_changed(struct teamd_context *ctx, + struct ab *ab) +{ + struct teamd_port *tdport; + int err; + + tdport = teamd_get_port(ctx, ab->active_ifindex); + if (!tdport || !teamd_port_present(ctx, tdport)) + return 0; + err = team_hwaddr_set(ctx->th, tdport->ifindex, ctx->hwaddr, + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + return 0; +} + +static int ab_hwaddr_policy_only_active_active_set(struct teamd_context *ctx, + struct ab *ab, + struct teamd_port *tdport) +{ + int err; + + memcpy(ab->active_orig_hwaddr, + team_get_ifinfo_hwaddr(tdport->team_ifinfo), + ctx->hwaddr_len); + err = team_hwaddr_set(ctx->th, tdport->ifindex, ctx->hwaddr, + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + return 0; +} + +static int ab_hwaddr_policy_only_active_active_clear(struct teamd_context *ctx, + struct ab *ab, + struct teamd_port *tdport) +{ + int err; + + err = team_hwaddr_set(ctx->th, tdport->ifindex, + ab->active_orig_hwaddr, + ctx->hwaddr_len); + if (err) { + teamd_log_err("%s: Failed to set port hardware address.", + tdport->ifname); + return err; + } + return 0; +} + +static const struct ab_hwaddr_policy ab_hwaddr_policy_only_active = { + .name = "only_active", + .hwaddr_changed = ab_hwaddr_policy_only_active_hwaddr_changed, + .active_set = ab_hwaddr_policy_only_active_active_set, + .active_clear = ab_hwaddr_policy_only_active_active_clear, +}; + +static const struct ab_hwaddr_policy *ab_hwaddr_policy_list[] = { + &ab_hwaddr_policy_same_all, + &ab_hwaddr_policy_by_active, + &ab_hwaddr_policy_only_active, + &ab_hwaddr_policy_first, + &ab_hwaddr_policy_fixed, +}; + +#define AB_HWADDR_POLICY_LIST_SIZE ARRAY_SIZE(ab_hwaddr_policy_list) + +static int ab_assign_hwaddr_policy(struct ab *ab, + const char *hwaddr_policy_name) +{ + int i = 0; + + if (!hwaddr_policy_name) + goto found; + for (i = 0; i < AB_HWADDR_POLICY_LIST_SIZE; i++) + if (!strcmp(ab_hwaddr_policy_list[i]->name, hwaddr_policy_name)) + goto found; + return -ENOENT; +found: + ab->hwaddr_policy = ab_hwaddr_policy_list[i]; + return 0; +} + +static int ab_clear_active_port(struct teamd_context *ctx, struct ab *ab, + struct teamd_port *tdport) +{ + + int err; + + ab->active_ifindex = 0; + if (!tdport || !teamd_port_present(ctx, tdport)) + return 0; + teamd_ttdp_log_dbgx(ctx->team_devname, "Clearing active port \"%s\".", tdport->ifname); +#ifdef SET_PORT_ENABLED_DISABLED + err = team_set_port_enabled(ctx->th, tdport->ifindex, false); + if (err) { + teamd_log_err("%s: Failed to disable active port.", + tdport->ifname); + return err; + } +#endif + if (ab->hwaddr_policy->active_clear) { + err = ab->hwaddr_policy->active_clear(ctx, ab, tdport); + if (err) + return err; + } + + return 0; +} + +static int ab_set_active_port(struct teamd_context *ctx, struct ab *ab, + struct teamd_port *tdport) +{ + int err; +#ifdef SET_PORT_ENABLED_DISABLED + err = team_set_port_enabled(ctx->th, tdport->ifindex, true); + if (err) { + teamd_log_err("%s: Failed to enable active port.", + tdport->ifname); + //return err; + } +#endif +/* Define MODE_ACTIVEBACKUP to have the behavior below. If not defined, we only + * change active ports following elections and/or port state changes */ +#ifdef MODE_ACTIVEBACKUP + err = team_set_active_port(ctx->th, tdport->ifindex); + if (err) { + teamd_log_err("%s: Failed to set as active port.", + tdport->ifname); + //goto err_set_active_port; + } + if (ab->hwaddr_policy->active_set) { + err = ab->hwaddr_policy->active_set(ctx, ab, tdport); + //if (err) + //goto err_hwaddr_policy_active_set; + } + ab->active_ifindex = tdport->ifindex; + teamd_ttdp_log_infox(ctx->team_devname, "Changed active port to \"%s\".", tdport->ifname); + return 0; + +err_set_active_port: +err_hwaddr_policy_active_set: +#ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, tdport->ifindex, false); +#endif + return err; +#else + return 0; +#endif +} + +struct ab_port_state_info { + struct teamd_port *tdport; + uint32_t speed; + uint8_t duplex; + int prio; +}; + +static void ab_best_port_check_set(struct teamd_context *ctx, + struct ab_port_state_info *best, + struct teamd_port *tdport) +{ + struct team_port *port = tdport->team_port; + uint32_t speed; + uint8_t duplex; + int prio; + + if (!teamd_link_watch_port_up(ctx, tdport) || best->tdport == tdport) + return; + + speed = team_get_port_speed(port); + duplex = team_get_port_duplex(port); + prio = teamd_port_prio(ctx, tdport); + + if (!best->tdport || (prio > best->prio) || (speed > best->speed) || + (speed == best->speed && duplex > best->duplex)) { + best->tdport = tdport; + best->prio = prio; + best->speed = speed; + best->duplex = duplex; + } +} + +static int ab_change_active_port(struct teamd_context *ctx, struct ab *ab, + struct teamd_port *active_tdport, + struct teamd_port *new_active_tdport) +{ + int err; + + err = ab_clear_active_port(ctx, ab, active_tdport); + if (err && !TEAMD_ENOENT(err)) + return err; + err = ab_set_active_port(ctx, ab, new_active_tdport); + if (err) { + if (TEAMD_ENOENT(err)) + /* Queue another best port selection */ + teamd_workq_schedule_work(ctx, &ab->link_watch_handler_workq); + else + return err; + } + return 0; +} + +/* Neighbor election. Returns 0 if nothing has changed. + * One of the cases that we want to support is "staggered bypass relay activation", where the bypass relays responsible + * for one line are toggled together, and the other line being toggled only after some time. This should, in theory, + * help with certain issues that occur if all relays are toggled together - for instance if a late node comes up in the + * middle of a previously-inaugurated train. If not using staggered activation, a total loss of connectivity across the + * node is likely to happen, depending on how long it takes for the new node to get layer 1 up and running (think + * Gigabit Ethernet...). + * + * Instead, the following should happen: + * 1. Nodes X and Z are connected using two lines, A and B. Assume that line A is the currently active line. + * 2. Node Y powers up, in between X and Z. Y's bypass relays are all in the bypass position. + * 3. When Y is done starting up and ready to participate in TTDP, the relays on one line will be toggled to the + * "connect" position (the opposite of "bypass"). Assume for now that this is line A. + * 4. This creates a short loss of physical and/or logical link between X and Z on line A. X and Z switch to using + * line B as the active line. + * 5. Once the connection comes up on line A, node Y is ready to communicate with X and Z. However, these are still + * considered as each others' neighbors, and communicate on line B only. + * 6. At some point after 3., node Y will toggle the relays on line B. This also causes a short link break, causing + * X and Z to switch back to line A. + * 7. At this point, X and Z start considering Y as their neighbor, and will exchange topology frames and so on. +*/ + +/* FIXME must ensure that we always change active port, if we get a neighbor change on the currently active port. */ +static int elect_neighbor(struct teamd_context *ctx, struct ab *ab, uint8_t *next_port_status) { + teamd_ttdp_log_infox(ctx->team_devname, "Starting neighbor election... (%d ports)", ctx->port_obj_list_count); + if (ctx->port_obj_list_count > 0) { + if ((ctx->port_obj_list_count <= TTDP_MAX_PORTS_PER_TEAM) && (ctx->port_obj_list_count <= 2)) { + uint8_t* src; + /* Special case, don't need to do an actual election between 2 ports. Do nothing unless + * everyone alive agrees. */ + uint8_t candidate_mac[ETH_ALEN] = {0}; + uint32_t candidate_topocnt = 0; + uint8_t zero[ETH_ALEN] = {0}; + uint8_t internal_next_port_state[TTDP_MAX_PORTS_PER_TEAM]; + int ignore_candidate = -1; + int candidate_suggestion; + for (candidate_suggestion = 0; candidate_suggestion < ctx->port_obj_list_count; + ++candidate_suggestion) { + bool currently_active = (ab->active_ifindex == candidate_suggestion); + bool has_changed = + (memcmp(&(ab->neighbors[candidate_suggestion]), + &(ab->prev_neighbors[candidate_suggestion]), + sizeof(ab->neighbors[candidate_suggestion])) == 0) + && !is_neighbor_none(&(ab->neighbors[candidate_suggestion])); + if ((ab->neighbor_agreement_mode == TTDP_NEIGH_AGREE_MODE_SINGLE) && currently_active && has_changed) { + /* The currently active port has changed its neighbor. This handles cases + * where we do not get a link-down event in between neighbors; we need to handle + * this by ensuring that we switch to the other neighbor, if available, and take + * down the currently active port. */ + ignore_candidate = candidate_suggestion; + internal_next_port_state[candidate_suggestion] = 1; + continue; + } + int agreement = 1; + memcpy(candidate_mac, ab->neighbors[candidate_suggestion].neighbor_mac, sizeof(candidate_mac)); + candidate_topocnt = ab->neighbors[candidate_suggestion].neighbor_topocount; + teamd_ttdp_log_infox(ctx->team_devname, "A new challenger %d, %.2X:%.2X:%.2X:%.2X:%.2X:%.2X, %.8X" + " appears!", + candidate_suggestion, + candidate_mac[0], + candidate_mac[1], + candidate_mac[2], + candidate_mac[3], + candidate_mac[4], + candidate_mac[5], + candidate_topocnt + ); + int i; + /* one (itself) will always agree, but this makes for simpler code... */ + for (i = 0; i < ctx->port_obj_list_count; ++i) { + if (i == ignore_candidate) + continue; + if ((memcmp(candidate_mac, ab->neighbors[i].neighbor_mac, sizeof(candidate_mac)) != 0) + || (candidate_topocnt != ab->neighbors[i].neighbor_topocount)) { + /* disagreement - check if the offending MAC is zero, then we can ignore it */ + if (memcmp(ab->neighbors[i].neighbor_mac, zero, + sizeof(ab->neighbors[i].neighbor_mac)) == 0) { + /* this port says it doesn't have a neighbor, so we can skip it */ + internal_next_port_state[i] = 1; + continue; + } else { + /* in the special case we're considering here, one non-zero disagreeing + * port means the election has failed and we do nothing */ + teamd_ttdp_log_infox(ctx->team_devname, "Election failed! Port %d differs." + " Not changing elected neighbor.", i); + agreement = 0; + internal_next_port_state[i] = 1; + } + } else { + /* this one agrees. */ + teamd_ttdp_log_infox(ctx->team_devname, "Port %d agrees with this candidate.", i); + internal_next_port_state[i] = 2; + } + if (agreement == 0) { + break; + } + } + if (agreement) { + goto winner_winner; + } + } + + return 0; + + winner_winner: + memcpy(next_port_status, internal_next_port_state, sizeof(internal_next_port_state)); + + src = ab->neighbors[candidate_suggestion].neighbor_uuid; + teamd_ttdp_log_infox(ctx->team_devname, "Candidate %d has won the election." + " New neighbor is %.2X:%.2X:%.2X:%.2X:%.2X:%.2X" + ", uuid " + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8 + ", topocnt %.8X agreement [%d %d]", + candidate_suggestion, + candidate_mac[0], + candidate_mac[1], + candidate_mac[2], + candidate_mac[3], + candidate_mac[4], + candidate_mac[5], + src[0], src[1], src[2], src[3], + src[4], src[5], + src[6], src[7], + src[8], src[9], + src[10], src[11], src[12], src[13], src[14], src[15], + candidate_topocnt, + next_port_status[0], next_port_status[1] + ); + /* This is here in case teamd_ttdp_log_infox is not defined to anything */ + (void)src; + + /* curse you, aliasing rules */ + uint32_t candidate_first; + uint16_t candidate_last; + memcpy(&candidate_first, candidate_mac, 4); + memcpy(&candidate_last, candidate_mac + 4, 2); + if (candidate_first == 0 && candidate_last == 0) { + ab->neighbor_is_none = 1; + teamd_ttdp_log_infox(ctx->team_devname, "Null-MAC set as neighbor."); + } else { + ab->neighbor_is_none = 0; + } + + lag_state_write_diag_crossed_lines_detected(ctx, ab); + lag_state_write_diag_mixed_consist_orientation_detected(ctx, ab); + + /* store winner mac */ + memcpy(ab->elected_neighbor.neighbor_mac, ab->neighbors[candidate_suggestion].neighbor_mac, + sizeof(ab->elected_neighbor.neighbor_mac)); + /* store winner uuid */ + memcpy(ab->elected_neighbor.neighbor_uuid, ab->neighbors[candidate_suggestion].neighbor_uuid, + sizeof(ab->elected_neighbor.neighbor_uuid)); + /* store winner topocnt */ + ab->elected_neighbor.neighbor_topocount = ab->neighbors[candidate_suggestion].neighbor_topocount; + + /* check if the new neighbor has a different inhibition flag */ + remote_inhibition_update(ctx, ab); + + /* update previous MAC and/or uuid */ + if ( + (memcmp(ab->elected_neighbor.neighbor_mac, ab->prev_elected_neighbor.neighbor_mac, + sizeof(ab->elected_neighbor.neighbor_mac)) != 0) + || (memcmp(ab->elected_neighbor.neighbor_uuid, ab->prev_elected_neighbor.neighbor_uuid, + sizeof(ab->elected_neighbor.neighbor_uuid)) != 0) + ) + { + memcpy(ab->prev_elected_neighbor.neighbor_mac, ab->elected_neighbor.neighbor_mac, + sizeof(ab->elected_neighbor.neighbor_mac)); + memcpy(ab->prev_elected_neighbor.neighbor_uuid, ab->elected_neighbor.neighbor_uuid, + sizeof(ab->elected_neighbor.neighbor_uuid)); + ab->prev_elected_neighbor.neighbor_topocount = ab->elected_neighbor.neighbor_topocount; + return 1; + } else { + return 0; + } + + + } else { + /* FIXME not yet implemented - implement elections between 3 or 4 ports here */ + return 0; + } + } else { + return 0; + } +} + +static int ab_link_watch_handler_internal(struct teamd_context *ctx, struct ab *ab, + bool allow_update_aggregate_state) { + struct teamd_port *tdport; + struct teamd_port *active_tdport; + struct ab_port_state_info best; + int err; + uint32_t active_ifindex; + + /* this is to allow for recursive calls in some cases. */ + if (allow_update_aggregate_state) { + /* if we are in FIXED END state, we need some special handling. We don't + * add links that have come up to the aggregate, but we do elect neighbors. */ + if (ab->aggregate_status == TTDP_AGG_STATE_FIXED_END) { + teamd_ttdp_log_infox(ctx->team_devname, "Linkwatch handler update in FIXED END mode..."); + /* Neighbor data is automatically set by my ports. Perform election + * and notify tcnd if it's changed. */ + if (elect_neighbor(ctx, ab, ab->neighbor_agreement) != 0) { + /* Notify tcnd that something has changed */ + + //if (ab->silent == TTDP_NOT_SILENT) { + lag_state_write_elected_neighbor(ctx, ab); + //} + /* FIXME */ + teamd_ttdp_log_infox(ctx->team_devname, "Wrote elected neighbor state file."); + } + + update_aggregate_state(ctx, ab); + return 0; + } + } + + memset(&best, 0, sizeof(best)); + best.prio = INT_MIN; + + active_tdport = teamd_get_port(ctx, ab->active_ifindex); + if (active_tdport) { + teamd_ttdp_log_dbgx(ctx->team_devname, "Current active port: \"%s\" (ifindex \"%d\"," + " prio \"%d\").", + active_tdport->ifname, active_tdport->ifindex, + teamd_port_prio(ctx, active_tdport)); + + err = team_get_active_port(ctx->th, &active_ifindex); + if (err) { + teamd_log_err("Failed to get active port."); + return err; + } + + /* + * When active port went down or it is other than currently set, + * clear it and proceed as if none was set in the first place. + */ + if (!teamd_link_watch_port_up(ctx, active_tdport) || + active_ifindex != active_tdport->ifindex) { + err = ab_clear_active_port(ctx, ab, active_tdport); + if (err) + return err; + active_tdport = NULL; + } + } + + /* Neighbor data is automatically set by my ports. Perform election + * and notify tcnd if it's changed. */ + if (elect_neighbor(ctx, ab, ab->neighbor_agreement) != 0) { + if (allow_update_aggregate_state) + update_aggregate_state(ctx, ab); + + /* Notify tcnd that something has changed */ + teamd_ttdp_log_infox(ctx->team_devname, "Writing elected neighbor to state file."); + lag_state_write_elected_neighbor(ctx, ab); + } + + teamd_ttdp_log_dbgx(ctx->team_devname, "AGREE mode %d count %d %d", ab->neighbor_agreement_mode, + ctx->port_obj_list_count, TTDP_MAX_PORTS_PER_TEAM); + + if (ab->neighbor_agreement_mode == TTDP_NEIGH_AGREE_MODE_SINGLE + && ctx->port_obj_list_count == TTDP_MAX_PORTS_PER_TEAM) { + /* Make sure we don't shoot ourselves in the foot here. Don't disable any ports unless + * we know that we will still have a neighbor on the other side. */ + int avail = 0; + for (int i = 0; i < ctx->port_obj_list_count; ++i) { + if (!is_neighbor_none(&ab->neighbors[i]) && (ab->neighbor_agreement[i] == TTDP_NEIGH_PORT_AGREES)) + avail++; + } + teamd_ttdp_log_dbgx(ctx->team_devname, "AGREE avail %d", avail); + if (avail) { + for (int i = 0; i < ctx->port_obj_list_count; ++i) { + uint32_t ifindex = ab->ifindex_by_line[i]; + teamd_ttdp_log_dbgx(ctx->team_devname, "AGREE i %d ifindex %d agree %d heard %d", i, ifindex, + ab->neighbor_agreement[i], ab->lines_heard[i]); + /* We currently don't actively set user linkup/down, since this + * may conflict with other features. FIXME. */ + switch (ab->neighbor_agreement[i]) { + case TTDP_NEIGH_PORT_DISAGREES: + /* this port disagrees - take it down */ + #ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, false); + #endif + #ifdef SET_USER_LINK + team_set_port_user_linkup(ctx->th, ifindex, false); + #endif + break; + case TTDP_NEIGH_PORT_AGREES: + /* this port agrees - ensure it is up */ + if (ab->lines_heard[i] == true) { + #ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, true); + #endif + #ifdef SET_USER_LINK + team_set_port_user_linkup(ctx->th, ifindex, true); + #endif + } else { + #ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, false); + #endif + #ifdef SET_USER_LINK + team_set_port_user_linkup(ctx->th, ifindex, false); + #endif + } + break; + case 0: + default: + break; + } + } + } else { + /* no ports can be used - disable everything */ + for (int i = 0; i < ctx->port_obj_list_count; ++i) { + uint32_t ifindex = ab->ifindex_by_line[i]; + #ifdef SET_PORT_ENABLED_DISABLED + team_set_port_enabled(ctx->th, ifindex, false); + #endif + } + } + } + + /* + * Find the best port among all ports. Prefer the currently active + * port, if there's any. This is because other port might have the + * same prio, speed and duplex. We do not want to change in that case + */ + if (active_tdport && teamd_port_present(ctx, active_tdport)) + ab_best_port_check_set(ctx, &best, active_tdport); + teamd_for_each_tdport(tdport, ctx) + ab_best_port_check_set(ctx, &best, tdport); + + /* Link status data is automatically set by the lws before this function + * is called */ + if (ab->port_statuses_b != ab->port_statuses_b_prev) { + ab->port_statuses_b_prev = ab->port_statuses_b; + teamd_ttdp_log_infox(ctx->team_devname, "Writing line status to state files."); + lag_state_write_line_status(ctx, ab); + } + + if (!best.tdport || best.tdport == active_tdport) + return 0; + + teamd_ttdp_log_dbgx(ctx->team_devname, "Found best port: \"%s\" (ifindex \"%d\", prio \"%d\").", + best.tdport->ifname, best.tdport->ifindex, best.prio); + if (!active_tdport || !ab_is_port_sticky(ab, active_tdport)) { + err = ab_change_active_port(ctx, ab, active_tdport, + best.tdport); + if (err) + return err; + } + return 0; +} + +static int ab_link_watch_handler(struct teamd_context *ctx, struct ab *ab) { + return ab_link_watch_handler_internal(ctx, ab, true); +} + +static int ab_link_watch_handler_work(struct teamd_context *ctx, + struct teamd_workq *workq) +{ + struct ab *ab; + + ab = get_container(workq, struct ab, link_watch_handler_workq); + return ab_link_watch_handler(ctx, ab); +} + +static int ab_event_watch_hwaddr_changed(struct teamd_context *ctx, void *priv) +{ + struct ab *ab = priv; + + if (ab->hwaddr_policy->hwaddr_changed) + return ab->hwaddr_policy->hwaddr_changed(ctx, ab); + return 0; +} + +static int ab_port_load_config(struct teamd_context *ctx, + struct ab_port *ab_port) +{ + const char *port_name = ab_port->tdport->ifname; + int err; + + err = teamd_config_bool_get(ctx, &ab_port->cfg.sticky, + "$.ports.%s.sticky", port_name); + if (err) + ab_port->cfg.sticky = AB_DFLT_PORT_STICKY; + teamd_ttdp_log_dbgx(ctx->team_devname, "%s: Using sticky \"%d\".", port_name, + ab_port->cfg.sticky); + return 0; +} + +static int ab_port_added(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv, void *creator_priv) +{ + struct ab_port *ab_port = priv; + struct ab *ab = creator_priv; + int err; + + ab_port->tdport = tdport; + err = ab_port_load_config(ctx, ab_port); + if (err) { + teamd_log_err("Failed to load port config."); + return err; + } + /* Newly added ports are disabled */ +#ifdef SET_PORT_ENABLED_DISABLED + err = team_set_port_enabled(ctx->th, tdport->ifindex, false); + if (err) { + teamd_log_err("%s: Failed to disable port.", tdport->ifname); + return TEAMD_ENOENT(err) ? 0 : err; + } +#endif + +#ifdef SET_USER_LINK_TEAM_IFACE + team_set_port_user_linkup_enabled(ctx->th, tdport->ifindex, true); + team_set_port_user_linkup(ctx->th, tdport->ifindex, false); +#endif + + if (ab->hwaddr_policy->port_added) + return ab->hwaddr_policy->port_added(ctx, ab, tdport); + return 0; +} + +static void ab_port_removed(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv, void *creator_priv) +{ + struct ab *ab = creator_priv; + + ab_link_watch_handler(ctx, ab); +} + +static const struct teamd_port_priv ab_port_priv = { + .init = ab_port_added, + .fini = ab_port_removed, + .priv_size = sizeof(struct ab_port), +}; + +static int ab_event_watch_port_added(struct teamd_context *ctx, + struct teamd_port *tdport, void *priv) +{ + struct ab *ab = priv; + +#ifdef SET_USER_LINK_TEAM_IFACE + team_set_port_user_linkup_enabled(ctx->th, tdport->ifindex, true); + team_set_port_user_linkup(ctx->th, tdport->ifindex, true); +#endif + + return teamd_port_priv_create(tdport, &ab_port_priv, ab); +} + +static int ab_event_watch_port_link_changed(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv) +{ + struct ab *ab = priv; + teamd_ttdp_log_dbg(ctx->team_devname, "/// /// PORT LINK CHANGED \\\\\\ \\\\\\"); + /* at this point, the lws should have populated our privdata with relevant info */ + int i; + for (i = 0; i < TTDP_MAX_PORTS_PER_TEAM; ++i) { + teamd_ttdp_log_dbg(ctx->team_devname, "member %d ifindex %d neighbor %.2X:%.2X:%.2X:%.2X:%.2X:%.2X%s", + i, ab->ifindex_by_line[i], + ab->neighbors[i].neighbor_mac[0], + ab->neighbors[i].neighbor_mac[1], + ab->neighbors[i].neighbor_mac[2], + ab->neighbors[i].neighbor_mac[3], + ab->neighbors[i].neighbor_mac[4], + ab->neighbors[i].neighbor_mac[5], + (ab->ifindex_by_line[i] == tdport->ifindex) ? " ***" : "" + ); + } + return ab_link_watch_handler(ctx, priv); +} + +static int ab_event_watch_port_changed(struct teamd_context *ctx, + struct teamd_port *tdport, + void *priv) +{ + struct ab *ab = priv; + teamd_ttdp_log_dbg(ctx->team_devname, "/// /// PORT CHANGED \\\\\\ \\\\\\"); + /* at this point, the lws should have populated our privdata with relevant info */ + int i; + for (i = 0; i < TTDP_MAX_PORTS_PER_TEAM; ++i) { + teamd_ttdp_log_dbg(ctx->team_devname, "member %d ifindex %d neighbor %.2X:%.2X:%.2X:%.2X:%.2X:%.2X%s", + i, ab->ifindex_by_line[i], + ab->neighbors[i].neighbor_mac[0], + ab->neighbors[i].neighbor_mac[1], + ab->neighbors[i].neighbor_mac[2], + ab->neighbors[i].neighbor_mac[3], + ab->neighbors[i].neighbor_mac[4], + ab->neighbors[i].neighbor_mac[5], + (ab->ifindex_by_line[i] == tdport->ifindex) ? " ***" : "" + ); + } + + return ab_link_watch_handler(ctx, priv); +} + +static int ab_event_watch_prio_option_changed(struct teamd_context *ctx, + struct team_option *option, + void *priv) +{ + return ab_link_watch_handler(ctx, priv); +} + +static const struct teamd_event_watch_ops ab_event_watch_ops = { + .hwaddr_changed = ab_event_watch_hwaddr_changed, + .port_added = ab_event_watch_port_added, + .port_link_changed = ab_event_watch_port_link_changed, + .port_changed = ab_event_watch_port_changed, + .option_changed = ab_event_watch_prio_option_changed, + .option_changed_match_name = "priority", +}; + +extern int parse_hwaddr(const char *hwaddr_str, char **phwaddr, + unsigned int *plen); +extern int parse_uuid(const char* src, uint8_t* dest); +extern void stringify_uuid(uint8_t* src, char* dest); + +static int ab_load_config(struct teamd_context *ctx, struct ab *ab) +{ + int err, tmp; + const char* hwaddr_policy_name; + const char* tmpstr; + + err = teamd_config_string_get(ctx, &hwaddr_policy_name, "$.runner.hwaddr_policy"); + if (err) + hwaddr_policy_name = NULL; + err = ab_assign_hwaddr_policy(ab, hwaddr_policy_name); + if (err) { + teamd_log_err("Unknown \"hwaddr_policy\" named \"%s\" passed.", + hwaddr_policy_name); + return err; + } + teamd_ttdp_log_dbgx(ctx->team_devname, "Using hwaddr_policy \"%s\".", ab->hwaddr_policy->name); + + err = teamd_config_int_get(ctx, &tmp, "$.runner.direction"); + if (err) { + teamd_log_err("Error: Failed to get runner direction, aborting"); + return 1; + } + ab->direction = tmp; + + err = teamd_config_string_get(ctx, &tmpstr, "$.runner.identity_hwaddr"); + if (err) { + ab->identity_hwaddr_set = false; + } else { + char* tempmac; + unsigned int templen = 0; + if (parse_hwaddr(tmpstr, &tempmac, &templen) != 0) { + teamd_log_warn("Could not parse runner scope identity hwaddr, ignoring"); + } else if (templen != ctx->hwaddr_len) { + teamd_log_warn("Runner scope identity hwaddr has incorrect length %d, team device has %d, ignoring", + templen, ctx->hwaddr_len); + free(tempmac); + } else { + teamd_ttdp_log_infox(ctx->team_devname, "Identity hwaddr set (runner)."); + ab->identity_hwaddr_set = true; + memcpy(ab->identity_hwaddr, tempmac, templen); + memcpy(ab->identity_hwaddr_str, tmpstr, sizeof(ab->identity_hwaddr_str)); + free(tempmac); + } + } + + err = teamd_config_string_get(ctx, &tmpstr, "$.runner.chassis_hwaddr"); + if (err) { + if (ab->identity_hwaddr_set) { + teamd_ttdp_log_infox(ctx->team_devname, "Chassis address not set, using identity instead (runner)."); + memcpy(ab->chassis_hwaddr, ab->identity_hwaddr, ctx->hwaddr_len); + memcpy(ab->chassis_hwaddr_str, ab->identity_hwaddr_str, sizeof(ab->chassis_hwaddr_str)); + } else { + teamd_log_err("TTDP: Error, could not read chassis_hwaddr, aborting"); + return 1; + } + } else { + char* tempmac; + unsigned int templen = 0; + if (parse_hwaddr(tmpstr, &tempmac, &templen) != 0) { + teamd_log_err("TTDP: Error, could not read chassis_hwaddr, aborting"); + return 1; + } else if (templen != ctx->hwaddr_len) { + teamd_log_warn("Chassis hwaddr has incorrect length %d, team device has %d, aborting", + templen, ctx->hwaddr_len); + free(tempmac); + return 1; + } else { + teamd_ttdp_log_infox(ctx->team_devname, "Chassis hwaddr set (runner)."); + memcpy(ab->chassis_hwaddr, tempmac, templen); + memcpy(ab->chassis_hwaddr_str, tmpstr, sizeof(ab->chassis_hwaddr_str)); + free(tempmac); + } + } + + err = teamd_config_string_get(ctx, &tmpstr, "$.runner.local_uuid"); + if (err) { + ab->local_uuid_set = false; + } else { + err = parse_uuid(tmpstr, ab->local_uuid); + if (err) { + teamd_log_warn("Incorrect UUID string in runner scope, ignoring: %d", err); + } else { + teamd_ttdp_log_infox(ctx->team_devname, "Local UUID set (runner)."); + ab->local_uuid_set = true; + stringify_uuid(ab->local_uuid, ab->local_uuid_str); + } + } + + err = teamd_config_int_get(ctx, &tmp, "$.runner.silent"); + if (err) { + teamd_ttdp_log_infox(ctx->team_devname, "TTDP: Silent setting not given - defaulting to %d", TTDP_NOT_SILENT); + ab->silent = TTDP_NOT_SILENT; + } else if (tmp != TTDP_SILENT_NO_OUTPUT && tmp != TTDP_SILENT_NO_OUTPUT_INPUT) { + teamd_log_warn("TTDP: Incorrect silent setting - use %d or %d; defaulting to %d", + TTDP_SILENT_NO_OUTPUT, TTDP_SILENT_NO_OUTPUT_INPUT, TTDP_NOT_SILENT); + ab->silent = TTDP_NOT_SILENT; + } else { + teamd_ttdp_log_infox(ctx->team_devname, "TTDP: Setting silent mode to %d", tmp); + ab->silent = tmp; + } + + err = teamd_config_int_get(ctx, &tmp, "$.neigh_agree_mode"); + if (err) { + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor agreement mode not set - defaulting to \"%d\"", + TTDP_NEIGH_AGREE_MODE_DEFAULT); + ab->neighbor_agreement_mode = TTDP_NEIGH_AGREE_MODE_DEFAULT; + } else if (tmp == 0 || tmp == 1) { + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor agreement mode set to %d", tmp); + ab->neighbor_agreement_mode = tmp; + } else { + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor agreement mode %d not known - defaulting to \"%d\"", tmp, + TTDP_NEIGH_AGREE_MODE_DEFAULT); + ab->neighbor_agreement_mode = TTDP_NEIGH_AGREE_MODE_DEFAULT; + } + + err = teamd_config_string_get(ctx, &tmpstr, "$.runner.vendor_info"); + memset(ab->vendor_info, 0, sizeof(ab->vendor_info)); + if (err) { + teamd_ttdp_log_infox(ctx->team_devname, "TTDP: Vendor info not set, defaulting to \"%s\".", TTDP_VENDOR_INFO_DEFAULT); + memcpy(ab->vendor_info, TTDP_VENDOR_INFO_DEFAULT, sizeof(ab->vendor_info)); + } + else { + teamd_ttdp_log_infox(ctx->team_devname, "Vendor info set to \"%s\".", tmpstr); + memcpy(ab->vendor_info, tmpstr, sizeof(ab->vendor_info)); + } + + return 0; +} + +static int ab_state_active_port_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + struct ab *ab = priv; + struct teamd_port *active_tdport; + + active_tdport = teamd_get_port(ctx, ab->active_ifindex); + gsc->data.str_val.ptr = active_tdport ? active_tdport->ifname : ""; + return 0; +} + +struct ab_active_port_set_info { + struct teamd_workq workq; + struct ab *ab; + uint32_t ifindex; +}; + +static int ab_active_port_set_work(struct teamd_context *ctx, + struct teamd_workq *workq) +{ + struct ab_active_port_set_info *info; + struct ab *ab; + uint32_t ifindex; + struct teamd_port *tdport; + struct teamd_port *active_tdport; + + info = get_container(workq, struct ab_active_port_set_info, workq); + ab = info->ab; + ifindex = info->ifindex; + free(info); + tdport = teamd_get_port(ctx, ifindex); + if (!tdport) + /* Port disappeared in between, ignore */ + return 0; + active_tdport = teamd_get_port(ctx, ab->active_ifindex); + return ab_change_active_port(ctx, ab, active_tdport, tdport); +} + +static int ab_state_active_port_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + struct ab_active_port_set_info *info; + struct ab *ab = priv; + struct teamd_port *tdport; + + tdport = teamd_get_port_by_ifname(ctx, (const char *) gsc->data.str_val.ptr); + if (!tdport) + return -ENODEV; + info = malloc(sizeof(*info)); + if (!info) + return -ENOMEM; + teamd_workq_init_work(&info->workq, ab_active_port_set_work); + info->ab = ab; + info->ifindex = tdport->ifindex; + teamd_workq_schedule_work(ctx, &info->workq); + return 0; +} + +static int send_tcnd_update_message_work(struct teamd_context *ctx, + struct teamd_workq *workq) { + struct ab *ab; + ab = get_container(workq, struct ab, link_watch_handler_workq); + return lag_state_write_elected_neighbor(ctx, ab); +} + +static int link_state_update_work(struct teamd_context *ctx, + struct teamd_workq *workq) { + struct ab *ab; + + ab = get_container(workq, struct ab, link_state_update_workq); + + teamd_ttdp_log_infox(ctx->team_devname, "Updated port statuses: 1:%s; 2:%s; 3:%s; 4:%s", + port_state_strings[ab->port_statuses[0]], + port_state_strings[ab->port_statuses[1]], + port_state_strings[ab->port_statuses[2]], + port_state_strings[ab->port_statuses[3]] + ); + + lag_state_write_line_status(ctx, ab); + + return 0; +} + +static int teamd_end_agg_detection_work(struct teamd_context *ctx, + struct teamd_workq *workq) { + struct ab *ab; + ab = get_container(workq, struct ab, end_agg_detection_workq); + if (!ab) + return -1; + + if (ab->topo_ctr_in) { + ab->topo_ctr_in = 0; + if (!ab->is_topo_ctr_in_increasing) { + ab->is_topo_ctr_in_increasing = 1; + ab->receiving_topology_frames = true; + uint8_t prev = ab->aggregate_status; + uint8_t next = update_aggregate_state(ctx, ab); + + teamd_ttdp_log_end_agg(ctx->team_devname, "%s: topo_ctr_in now increasing, state change %d -> %d", + __FUNCTION__, prev, next); + + lag_state_write_aggregate_role(ctx, ab); + } + } else { + if (ab->is_topo_ctr_in_increasing) { + ab->is_topo_ctr_in_increasing = 0; + ab->receiving_topology_frames = false; + uint8_t prev = ab->aggregate_status; + uint8_t next = update_aggregate_state(ctx, ab); + + teamd_ttdp_log_end_agg(ctx->team_devname, "%s: topo_ctr_in no longer increasing! state change %d -> %d", + __FUNCTION__, prev, next); + + lag_state_write_aggregate_role(ctx, ab); + } + } + return 0; +} + +static int send_link_timeout_update_work(struct teamd_context *ctx, + struct teamd_workq *workq) { + struct ab *ab; + + ab = get_container(workq, struct ab, link_timeout_update_workq); + + teamd_ttdp_log_infox(ctx->team_devname, "Updated link timeouts: fast %" PRIu32 " slow %" PRIu32 "", + ab->latest_line_fast_timeout_ms, ab->latest_line_slow_timeout_ms); + + return lag_state_write_hello_timeouts(ctx, ab); +} + +static int remote_inhibition_update_work(struct teamd_context *ctx, + struct teamd_workq *workq) { + struct ab *ab; +/* FIXME elected port does not work here... check individual ports instead and see if they agree */ + ab = get_container(workq, struct ab, remote_inhibition_workq); + uint8_t remote_inhibition_prev = ab->remote_inhibition_actual; +#ifdef WEOS_UNINHIBITED_LENGTHENING + /* For uninhibited lengthening to work, we need to detect remote inhibition even if we ourselves + * are not inhibited. This will not be used for inauguration inhibition on our end, but will be + * used to detect lengthening. In other words, we do not care what state we are in here any longer. + * The only excpetion is in the FIXED-MIDDLE state, when we'd always hear remote inhibition from + * our neighbors in our own train composition. */ + if (ab->aggregate_status != TTDP_AGG_STATE_FIXED_MIDDLE) { +#else + + if (ab->aggregate_status == TTDP_AGG_STATE_FIXED_END + || ab->aggregate_status == TTDP_AGG_STATE_FLOATING_END) { +#endif + if (is_neighbor_none(&(ab->elected_neighbor))) { + /* zero neighbor has inhibition UNDEFINED per definition */ + ab->remote_inhibition_actual = TTDP_LOGIC_UNDEFINED; + } else { + ab->remote_inhibition_actual = TTDP_LOGIC_FALSE; + for (int i = 0; i < TTDP_MAX_PORTS_PER_TEAM; ++i) { + if (ab->neighbor_agreement[i] == TTDP_NEIGH_PORT_AGREES) { + if (ab->neighbors[i].neighbor_inhibition_state == TTDP_LOGIC_TRUE) { + ab->remote_inhibition_actual = TTDP_LOGIC_TRUE; + break; + } + } + } + } + } else { + ab->remote_inhibition_actual = TTDP_LOGIC_UNDEFINED; + } + + teamd_ttdp_log_infox(ctx->team_devname, "Calculated remote inhibition: e:%d state:%d %d->%d", + ab->elected_neighbor.neighbor_inhibition_state, + ab->aggregate_status, + remote_inhibition_prev, + ab->remote_inhibition_actual); + if (remote_inhibition_prev != ab->remote_inhibition_actual) + lag_state_write_remote_inhibition(ctx, ab); + return 0; +} + +static int ab_state_port_statuses_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + snprintf(ab->port_statuses_str, sizeof(ab->port_statuses_str), + "1:%s; 2:%s; 3:%s; 4:%s", + port_state_strings[ab->port_statuses[0]], + port_state_strings[ab->port_statuses[1]], + port_state_strings[ab->port_statuses[2]], + port_state_strings[ab->port_statuses[3]] + ); + gsc->data.str_val.ptr = ab->port_statuses_str; + return 0; +} + +static int ab_neighbor_etbn_mac_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + snprintf(ab->elected_neighbor_mac_str, sizeof(ab->elected_neighbor_mac_str), + "%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 "", + ab->elected_neighbor.neighbor_mac[0], + ab->elected_neighbor.neighbor_mac[1], + ab->elected_neighbor.neighbor_mac[2], + ab->elected_neighbor.neighbor_mac[3], + ab->elected_neighbor.neighbor_mac[4], + ab->elected_neighbor.neighbor_mac[5] + ); + gsc->data.str_val.ptr = ab->elected_neighbor_mac_str; + return 0; +} + +static int ab_neighbor_etbn_uuid_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + uint8_t* src = ab->elected_neighbor.neighbor_uuid; + snprintf(ab->elected_neighbor_uuid_str, sizeof(ab->elected_neighbor_uuid_str), + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, + src[0], src[1], src[2], src[3], + src[4], src[5], + src[6], src[7], + src[8], src[9], + src[10], src[11], src[12], src[13], src[14], src[15] + ); + gsc->data.str_val.ptr = ab->elected_neighbor_uuid_str; + return 0; +} + +static int ab_neighbor_etbn_topocnt_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + snprintf(ab->elected_neighbor_topocnt_str, sizeof(ab->elected_neighbor_topocnt_str), + "%.8X", ab->elected_neighbor.neighbor_topocount); + gsc->data.str_val.ptr = ab->elected_neighbor_topocnt_str; + return 0; +} + +static int ab_fixed_neighbor_etbn_mac_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + snprintf(ab->fixed_elected_neighbor_mac_str, sizeof(ab->fixed_elected_neighbor_mac_str), + "%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 ":%.2" PRIX8 "", + ab->fixed_elected_neighbor.neighbor_mac[0], + ab->fixed_elected_neighbor.neighbor_mac[1], + ab->fixed_elected_neighbor.neighbor_mac[2], + ab->fixed_elected_neighbor.neighbor_mac[3], + ab->fixed_elected_neighbor.neighbor_mac[4], + ab->fixed_elected_neighbor.neighbor_mac[5] + ); + gsc->data.str_val.ptr = ab->fixed_elected_neighbor_mac_str; + return 0; +} + +static int ab_fixed_neighbor_etbn_uuid_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + uint8_t* src = ab->fixed_elected_neighbor.neighbor_uuid; + snprintf(ab->fixed_elected_neighbor_uuid_str, sizeof(ab->fixed_elected_neighbor_uuid_str), + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8 + "-" + "%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8"%02"PRIx8, + src[0], src[1], src[2], src[3], + src[4], src[5], + src[6], src[7], + src[8], src[9], + src[10], src[11], src[12], src[13], src[14], src[15] + ); + gsc->data.str_val.ptr = ab->fixed_elected_neighbor_uuid_str; + return 0; +} + +static int ab_fixed_neighbor_etbn_topocnt_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + snprintf(ab->fixed_elected_neighbor_topocnt_str, sizeof(ab->fixed_elected_neighbor_topocnt_str), + "%.8X", ab->fixed_elected_neighbor.neighbor_topocount); + gsc->data.str_val.ptr = ab->fixed_elected_neighbor_topocnt_str; + return 0; +} + +static int ttdp_shorten_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab* ab = priv; + gsc->data.bool_val = ab->shortening_detected; + return 0; +} + +static int ttdp_lengthen_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab* ab = priv; + gsc->data.bool_val = ab->lengthening_detected; + return 0; +} + +static int ttdp_aggregate_state_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab* ab = priv; + if ((ab->aggregate_status < 0) || (ab->aggregate_status > TTDP_AGG_STATE_MAX)) { + /* strange value, reset */ + ab->aggregate_status = TTDP_AGG_STATE_DEFAULT; + } + + gsc->data.str_val.ptr = agg_state_strings[ab->aggregate_status]; + return 0; +} + +static int ab_chassis_hwaddr_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab* ab = priv; + gsc->data.str_val.ptr = ab->chassis_hwaddr_str; + return 0; +} + +static int ab_state_port_statuses_b_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->port_statuses_b; + return 0; +} + +static int ttdp_runner_noop_setter(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + return 0; +} + +/* functions affecting the aggregate state machine */ +static int ttdp_topology_stop_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + (void)ctx; + (void)gsc; + struct ab *ab = priv; + (void)ab; + /* ab->receiving_topology_frames = false; */ + /* uint8_t prev = ab->aggregate_status; */ + /* uint8_t next = update_aggregate_state(ctx, ab); */ + /* teamd_ttdp_log_always(ctx->team_devname, "Not getting topologies; state change %d->%d", */ + /* prev, next); */ + return 0; +} +static int ttdp_topology_stop_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + gsc->data.int_val = (ab->receiving_topology_frames) ? 0 : 1; + return 0; +} +static int ttdp_topology_start_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + (void)ctx; + (void)gsc; + struct ab *ab = priv; + (void)ab; + /* ab->receiving_topology_frames = true; */ + /* uint8_t prev = ab->aggregate_status; */ + /* uint8_t next = update_aggregate_state(ctx, ab); */ + /* teamd_ttdp_log_always(ctx->team_devname, "Getting topologies again! state change %d->%d", */ + /* prev, next); */ + return 0; +} +static int ttdp_topology_start_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + gsc->data.int_val = (ab->receiving_topology_frames) ? 1 : 0; + return 0; +} +static int ttdp_neighbor_data_req_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + teamd_ttdp_log_infox(ctx->team_devname, "Neighbor data update requested by statevar"); + lag_state_write_elected_neighbor(ctx, ab); + return 0; +} +static int ttdp_etb_topocount_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->etb_topo_counter; + return 0; +} +static int ttdp_etb_topocount_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + ab->etb_topo_counter = (uint32_t)gsc->data.int_val; + teamd_ttdp_log_dbgx(ctx->team_devname, + "Set ETB topo count to %#.8x from statevar", ab->etb_topo_counter); + return 0; +} + +static int ttdp_etb_topocount_list_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + gsc->data.str_val.ptr = "N/A"; + + return 0; +} + +static int ttdp_etb_topocount_list_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) +{ + struct ab *ab = priv; + FILE *fp; + int ret; + + if (!gsc->data.str_val.ptr) { + ab->num_fixed_possible_topocnts = 0; + return 0; + } + + fp = fopen(gsc->data.str_val.ptr, "r"); + if (!fp) + return 0; + + ab->num_fixed_possible_topocnts = 0; + + if (fp) { + uint32_t value; + + while((ret = fscanf(fp, "%x", &value)) != EOF) { + if (ret != 1) + continue; + + ab->fixed_possible_topocnts[ab->num_fixed_possible_topocnts++] = value; + } + fclose(fp); + } + + return 0; +} + +static int ttdp_etb_topocount_str_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + memset(ab->etb_topo_counter_str, 0, sizeof(ab->etb_topo_counter_str)); + snprintf((char*)(ab->etb_topo_counter_str), sizeof(ab->etb_topo_counter_str)-1, "%#8X", ab->etb_topo_counter); + gsc->data.str_val.ptr = (char*)(ab->etb_topo_counter_str); + return 0; +} +static int ttdp_inaugurated_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + ab->inaugurated = gsc->data.bool_val; + uint8_t prev = ab->aggregate_status; + uint8_t next = update_aggregate_state(ctx, ab); + teamd_ttdp_log_infox(ctx->team_devname, "Now considering myself %sinaugurated, state change" + " %d->%d", (gsc->data.bool_val ? "" : "not "), prev, next); + /* In case teamd_ttdp_log_infox is not defined: */ + (void)prev; + (void)next; + return 0; +} +static int ttdp_inaugurated_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct ab *ab = priv; + gsc->data.bool_val = ab->inaugurated; + return 0; +} +static int ttdp_agg_state_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + if (ab->is_discarding) { + gsc->data.str_val.ptr = "DISCARDING"; + } else { + gsc->data.str_val.ptr = "FORWARDING"; + } + return 0; +} +static int ttdp_neighbor_agreement_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + snprintf(ab->neighbor_agreement_str, sizeof(ab->neighbor_agreement_str), + "%1d %1d", ab->neighbor_agreement[0], ab->neighbor_agreement[1]); + ab->neighbor_agreement_str[sizeof(ab->neighbor_agreement_str)-1] = 0; + gsc->data.str_val.ptr = ab->neighbor_agreement_str; + return 0; +} +static int ttdp_neighbor_agreement_mode_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->neighbor_agreement_mode; + return 0; +} +static int ttdp_neighbor_agreement_mode_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + if (gsc->data.int_val == 0 || gsc->data.int_val == 1) { + ab->neighbor_agreement_mode = gsc->data.int_val; + return 0; + } else { + return 1; + } +} +static int ttdp_neighbor_inhibition_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->remote_inhibition_actual; + return 0; +} +static int ttdp_inhibition_local_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->inhibition_flag_local; + return 0; +} +static int ttdp_inhibition_local_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + ab->inhibition_flag_local = gsc->data.int_val; + teamd_ttdp_log_infox(ctx->team_devname, "Set local inhibition flag to %d from statevar", + ab->inhibition_flag_local); + update_aggregate_state(ctx, ab); + return 0; +} +static int ttdp_inhibition_nonlocal_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + gsc->data.int_val = ab->inhibition_flag_any; + return 0; +} +static int ttdp_inhibition_nonlocal_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void* priv) { + struct ab *ab = priv; + ab->inhibition_flag_any = gsc->data.int_val; + teamd_ttdp_log_infox(ctx->team_devname, "Set non-local (\"any\") inhibition flag to %d from statevar", + ab->inhibition_flag_any); + update_aggregate_state(ctx, ab); + return 0; +} + +static int ttdp_stats_topo_frames_in_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + if (!priv) { + return -1; + } + struct ab *ab = priv; + ab->topo_ctr_in = gsc->data.uint32_val; + return 0; +} + +static int ttdp_stats_topo_frames_in_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + if (!priv) { + return -1; + } + struct ab *ab = priv; + gsc->data.uint32_val = ab->topo_ctr_in_total; + return 0; +} + +static int ttdp_stats_topo_frames_out_set(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + if (!priv) { + return -1; + } + struct ab *ab = priv; + ab->topo_ctr_out = gsc->data.uint32_val; + return 0; +} + +static int ttdp_stats_topo_frames_out_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + if (!priv) { + return -1; + } + struct ab *ab = priv; + gsc->data.uint32_val = ab->topo_ctr_out; + return 0; +} + +static const struct teamd_state_val ab_state_vals[] = { + /* Currently active port. Only meaningful in activebackup mode. Read-write. + */ + { + .subpath = "active_port", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_state_active_port_get, + .setter = ab_state_active_port_set, + }, + /* String explaining the current statuses of all (up to) 4 member ports. + * For each port, one of "ERROR", "FALSE", " TRUE" or "UNDEF". Read-only. */ + { + .subpath = "port_statuses", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_state_port_statuses_get, + .setter = ttdp_runner_noop_setter, + }, + /* Combined binary status, in decimal format, of all member ports + * as sent on the wire. Read-only. */ + { + .subpath = "port_statuses_b", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ab_state_port_statuses_b_get, + .setter = ttdp_runner_noop_setter, + }, + /* MAC of the currently elected neighbor, in string format. If no neighbor, + * returns "00:00:00:00:00:00". Read-only. */ + { + .subpath = "neighbor_etbn_mac", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_neighbor_etbn_mac_get, + .setter = ttdp_runner_noop_setter, + }, + /* UUID of the current neighbor, in string format using the common UUID + * syntax. All zero if no neighbor. Read-only. */ + { + .subpath = "neighbor_etbn_uuid", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_neighbor_etbn_uuid_get, + .setter = ttdp_runner_noop_setter, + }, + /* Elected neighbor's ETB topocount - string representation of hex value. + * Read-only. */ + { + .subpath = "neighbor_etbn_topocnt", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_neighbor_etbn_topocnt_get, + .setter = ttdp_runner_noop_setter, + }, + /* MAC of the previously elected and saved neighbor, in string format. + * Only meaningful when inhibited; neighbor data is saved on the positive + * edge of the inhibition value. If no saved neighbor, returns + * "00:00:00:00:00:00". Read-only. */ + { + .subpath = "old_inaug_neigh_etbn_mac", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_fixed_neighbor_etbn_mac_get, + .setter = ttdp_runner_noop_setter, + }, + /* UUID of the previously elected and saved neighbor, in string format, + * common UUID syntax. + * Only meaningful when inhibited; neighbor data is saved on the positive + * edge of the inhibition value. If no saved neighbor, returns all zero. + * Read-only. */ + { + .subpath = "old_inaug_neigh_etbn_uuid", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_fixed_neighbor_etbn_uuid_get, + .setter = ttdp_runner_noop_setter, + }, + /* ETB topocount of the previously elected and saved neighbor - see above. + * Read-only. */ + { + .subpath = "old_neighbor_etbn_topocnt", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_fixed_neighbor_etbn_topocnt_get, + .setter = ttdp_runner_noop_setter, + }, + /* Set to true whenever we've detected train shortening on the aggregate + * level. Only meaningful when inhibited. Read-only. */ + { + .subpath = "shortening_detected", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = ttdp_shorten_get, + .setter = ttdp_runner_noop_setter, + }, + /* Returns true whenever we've detected train lengthening on the aggregate + * level. Only meaningful when inhibited. Read-only. */ + { + .subpath = "lengthening_detected", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = ttdp_lengthen_get, + .setter = ttdp_runner_noop_setter, + }, + /* Gets the MAC address used in the mandatory LLDP part of the HELLO frame. + * Set in the configuration file and read-only here. */ + { + .subpath = "chassis_hwaddr", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ab_chassis_hwaddr_get, + .setter = ttdp_runner_noop_setter, + }, + /* Current state of the aggregate, in "topology terms", meaning one of the + * following: + * - "Floating end", + * - "Floating intermediate", + * - "Fixed end", or + * - "Fixed intermediate". + * The "Fixed" states are only applicable when inhibited, while the + * "intermediate" or "end" states determine whether we're at the end of the + * current train topology. Read-only. */ + { + .subpath = "ttdp_state", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ttdp_aggregate_state_get, + }, + /* Writing anything here lets the runner know that we're not receiving + * topology frames anymore, from the direction of this aggregate. This is + * useful to determine if we're part of the end node in certain scenarios. + * Write-only. */ + { + .subpath = "poke_topology_stop", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_topology_stop_get, + .setter = ttdp_topology_stop_set, + }, + /* Writing anything here lets the runner know that we're again receiving + * topology frames anymore, from the direction of this aggregate. + * Write-only. */ + { + .subpath = "poke_topology_start", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_topology_start_get, + .setter = ttdp_topology_start_set, + }, + /* Writing anything here results in an IPC message being sent that contains + * the current neighbor MAC (IPC message 0x01). Write-only. */ + { + .subpath = "poke_neighbor_data_req", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_runner_noop_setter, + .setter = ttdp_neighbor_data_req_set + }, + /* Read-write, contains the current ETB topocount as calculated higher + * up in the stack. This has to be actively set from the outside for + * things to work properly. The format is INT, so it will unfortunately + * be converted to decimal (but see below). If a value is written here, it + * will be used as the current topo counter, but will very likely be + * overwritten by the rest of the IEC61375 stack quite soon following the + * next re-inauguration. + * + * This is how the IEC61375 stack ensures that a correct topocounter is sent + * by this code in TTDP HELLO frames. Since the state variable uses the + * teamd type TEAMD_STATE_ITEM_TYPE_INT, which is handled as a "long" + * internally, special care needs to be taken on architectures where "long" + * is 32 bits; in these cases, if the topocount has the highest bit set, + * this value is to be set as a signed negative number in decimal form. + * Basically, whatever this is set to will be read as a signed long in + * decmal format. */ + { + .subpath = "etb_topocount", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_etb_topocount_get, + .setter = ttdp_etb_topocount_set, + }, + /* The ETB topocount above, but converted to the common hexadecimal string + * format. Read-only. */ + { + .subpath = "etb_topocount_str", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ttdp_etb_topocount_str_get, + }, + /* Filename (with path) which holds the list of possible topocounts. + * Writing a filename will update this list with the topocounts in the + * file. Writing NULL will delete the stored list. Writing an invalid + * filename will keep the stored list. The file shall contain one + * topocount value on each row and stored as hexadecimal, i.e.: + * 11223344 + * aabbccdd + * The topocount values in this list contains valid topocounts which + * will be used in recovery mode to let a ETBN join the backbone + * again if the ETBN previously was lost. + */ + { + .subpath = "etb_topocount_list", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ttdp_etb_topocount_list_get, + .setter = ttdp_etb_topocount_list_set, + }, + + /* Writing 'true' or 'false' here results in the runner considering itself + * as inaugurated or not inaugurated, respectively. This value comes from + * higher up in the IEC61375 stack (it's set once a logical topology has + * been agreed and a TND calculated). The value is used to control the + * aggregate state machine here, in the runner. Read-write. */ + { + .subpath = "inaugurated", + .type = TEAMD_STATE_ITEM_TYPE_BOOL, + .getter = ttdp_inaugurated_get, + .setter = ttdp_inaugurated_set, + }, + /* String, says if the aggregate is in "DISCARDING" or "FORWARDING" mode. + * This is related to end node management. Read-only. */ + { + .subpath = "ttdp_agg_state", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ttdp_agg_state_get, + }, + /* Returns a string containing integers, one per aggregate member in order, + * with the value '1' if that member port agrees with the currently elected + * neighbor, and '0' if that member port is connected to either no neighbor, + * or to a neighbor other than the one that won the election. Read-only. */ + { + .subpath = "neighbor_agreement", + .type = TEAMD_STATE_ITEM_TYPE_STRING, + .getter = ttdp_neighbor_agreement_get, + }, + /* Gets or sets the current neighbor agreement mode value as an integer. + * This is currently either 0 or 1, where + * 0: "multi", actively take down member ports that disagree with the + * currently elected neighbor. + * 1: "single", don't touch non-agreeing ports + * + * For "multi" to work correctly, SET_USER_LINK should be defined. + * Read-write. */ + { + .subpath = "neighbor_agreement_mode", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_neighbor_agreement_mode_get, + .setter = ttdp_neighbor_agreement_mode_set + }, + /* Returns the value of the elected neighbor's inhibition flag, as read in + * their HELLO frames. Uses TTDP logic, 1 is FALSE, 2 is TRUE and 3 is + * undefined. Read-only. */ + { + .subpath = "neighbor_inhibition_antivalent", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_neighbor_inhibition_get + }, + /* Read-write, whether inhibition is requested by the local node. Uses TTDP + * logic, 1 is FALSE and 2 is TRUE. Other values invalid. */ + { + .subpath = "inhibition_local", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_inhibition_local_get, + .setter = ttdp_inhibition_local_set + }, + /* read-write, whether inhibition is requested by a non-local node. Uses TTDP + * logic, 1 is FALSE and 2 is TRUE. Other values invalid. */ + { + .subpath = "inhibition_nonlocal", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_inhibition_nonlocal_get, + .setter = ttdp_inhibition_nonlocal_set + }, + { + .subpath = "first_hwaddr_set", + .type = TEAMD_STATE_ITEM_TYPE_INT, + .getter = ttdp_hwaddr_policy_first_set_get + }, + /* read-only - gets the number of topology frames heard through this aggregate */ + { + .subpath = "topo_frames_out", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_stats_topo_frames_out_get, + .setter = ttdp_stats_topo_frames_out_set + }, + { + .subpath = "topo_frames_in", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_stats_topo_frames_in_get, + .setter = ttdp_stats_topo_frames_in_set + } +}; + +static const struct teamd_state_val ab_state_vg = { + .subpath = "runner", + .vals = ab_state_vals, + .vals_count = ARRAY_SIZE(ab_state_vals), +}; + +void* line_status_update(void* c, void* a) { + struct teamd_context* ctx = c; + struct ab* ab = a; + teamd_ttdp_log_infox(ctx->team_devname, "Extra line status update!"); + if (c && ab) + teamd_workq_schedule_work(ctx, &ab->link_state_update_workq); + return NULL; +} + +static int ttdp_lines_stats_sent_hello_frames_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct hello_stats *stats = priv; + gsc->data.uint32_val = stats->sent_hello_frames; + return 0; +} + +static int ttdp_lines_stats_recv_hello_frames_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct hello_stats *stats = priv; + gsc->data.uint32_val = stats->recv_hello_frames; + return 0; +} + +static int ttdp_lines_stats_local_fast_mode_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct hello_stats *stats = priv; + gsc->data.uint32_val = stats->local_fast_activated; + return 0; +} + +static int ttdp_lines_stats_remote_fast_mode_get(struct teamd_context *ctx, + struct team_state_gsc *gsc, + void *priv) { + struct hello_stats *stats = priv; + gsc->data.uint32_val = stats->remote_fast_activated; + return 0; +} + +static const struct teamd_state_val lines_stats_state_vals[] = { + /* Number of HELLO frames sent on a line. Read-only. */ + { + .subpath = "hello_frames_sent", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_lines_stats_sent_hello_frames_get, + .setter = ttdp_runner_noop_setter + }, + /* Number of HELLO frames received on a line. Read-only. */ + { + .subpath = "hello_frames_recv", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_lines_stats_recv_hello_frames_get, + .setter = ttdp_runner_noop_setter + }, + /* Number of times fast mode has been activated by this instance on a line. + * Read-only. */ + { + .subpath = "fast_mode_local_activated", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_lines_stats_local_fast_mode_get, + .setter = ttdp_runner_noop_setter + }, + /* Number of times fast mode has been activated by a peer on a line. + * Read-only. */ + { + .subpath = "fast_mode_remote_activated", + .type = TEAMD_STATE_ITEM_TYPE_UINT32, + .getter = ttdp_lines_stats_remote_fast_mode_get, + .setter = ttdp_runner_noop_setter + } +}; + +static const struct teamd_state_val lines_stats_vg = { + .vals = lines_stats_state_vals, + .vals_count = ARRAY_SIZE(lines_stats_state_vals), +}; + + +void* line_timeout_update(void* c, void* a) { + struct teamd_context* ctx = c; + struct ab* ab = a; + teamd_ttdp_log_infox(ctx->team_devname, "Line timeout update"); + if (c && a) + teamd_workq_schedule_work(ctx, &ab->link_timeout_update_workq); + return NULL; +} + +void* remote_inhibition_update(void* c, void* a) { + struct teamd_context* ctx = c; + struct ab* ab = a; + teamd_ttdp_log_infox(ctx->team_devname, "Extra neighbor inhibition update!"); + if (c && ab) + teamd_workq_schedule_work(ctx, &ab->remote_inhibition_workq); + return NULL; +} + +static int on_initial_timer(struct teamd_context *ctx, int events, void *priv) { + /* run until success, max. this many times */ + static int tries = IPC_TRIES_MAX; + teamd_loop_callback_disable(ctx, ttdp_runner_oneshot_initial_agg_state_name, priv); + struct ab* ab = (struct ab*)priv; + line_status_update(ctx, ab); + int err = 0, err2 = 0; + if (ab->silent != TTDP_SILENT_NO_OUTPUT_INPUT) { + err = lag_state_write_identity(ctx, ab); + if (err < 0) + err2 += err; + teamd_ttdp_log_infox(ctx->team_devname, "Wrote identity state file: %d", err); + + err = lag_state_write_hello_timeouts(ctx, ab); + teamd_ttdp_log_infox(ctx->team_devname, "Wrote TTDP HELLO timeouts to state file: %d", err); + if (err < 0) + err2 += err; + + err = lag_state_write_line_status(ctx, priv); + teamd_ttdp_log_infox(ctx->team_devname, "Wrote line status to state file: %d", err); + if (err < 0) + err2 += err; + + err = lag_state_write_aggregate_role(ctx, ab); + teamd_ttdp_log_infox(ctx->team_devname, "Wrote initial aggregate status to state file: %d, err"); + if (err < 0) + err2 += err; + + if (err2 < 0) { + if (tries-- <= 0) { + teamd_log_err("Could not write to state files after %d attempts - giving up.", IPC_TRIES_MAX); + } else { + teamd_loop_callback_enable(ctx, ttdp_runner_oneshot_initial_agg_state_name, priv); + } + } + } + return 0; +} + +static int on_periodic_neighbor_macs_timer(struct teamd_context *ctx, int events, void *priv) { + /* Periodically send current neighbor status to the rest of the stack */ + struct ab* ab = priv; + if (!ab) + return 1; + + teamd_loop_callback_disable(ctx, ttdp_runner_periodic_neighbor_macs_name, ab); + teamd_workq_schedule_work(ctx, &ab->tcnd_notify_tcnd_workq); + teamd_loop_callback_enable(ctx, ttdp_runner_periodic_neighbor_macs_name, ab); + return 0; +} + +static int on_periodic_end_agg_detection_timer(struct teamd_context *ctx, int events, void *priv) { + /* Periodically check if we're still receiving TOPOLOGY from this side */ + struct ab* ab = priv; + if (!ab) + return 1; + + teamd_loop_callback_disable(ctx, ttdp_runner_periodic_end_agg_detection_name, ab); + teamd_workq_schedule_work(ctx, &ab->end_agg_detection_workq); + teamd_loop_callback_enable(ctx, ttdp_runner_periodic_end_agg_detection_name, ab); + + return 0; +} + +static int on_parasite_socket_recv(struct teamd_context *ctx, int events, void *priv) { + //fprintf(stderr, "%s: %s ctx %p events %d priv %p\n", __FUNCTION__, ctx->team_devname, ctx, events, priv); + struct ab *ab = priv; + if (!ab) { + return -1; + } + + static uint8_t buf[1500]; + size_t len = 1500; + struct sockaddr_ll ll_from; + + errno = 0; + int err = teamd_recvfrom(ab->parasite_sockfd, &buf, len, 0, (struct sockaddr*)&ll_from, sizeof(ll_from)); + if (err < 0) { + teamd_ttdp_log_warnx(ctx->team_devname, "%s: Error %d %d in parasite recvfrom", __FUNCTION__, err, errno); + } + + if (ll_from.sll_protocol != htons(0x894C)) { + /* Not a TOPOLOGY frame, ignore. Note that we don't check the VLAN tagging at all. */ + return 0; + } + + if (ll_from.sll_pkttype == PACKET_OUTGOING) { + ab->topo_ctr_out++; + } else { + /* It's okay if both of these overflow. */ + ab->topo_ctr_in++; + ab->topo_ctr_in_total++; + } + + return 0; +} + +static int open_parasite_socket(struct teamd_context *ctx, struct ab *ab) { + teamd_ttdp_log_end_agg(ctx->team_devname, "%s: opening parasite socket %d for ctx->ifindex %d", + __FUNCTION__, ab->parasite_sockfd, ctx->ifindex); + + int err = teamd_packet_sock_open_type(SOCK_RAW | SOCK_NONBLOCK, &ab->parasite_sockfd, + ctx->ifindex, htons(ETH_P_ALL), &ttdp_topology_fprog, NULL); + + teamd_ttdp_log_end_agg_dbg(ctx->team_devname, "%s: teamd_packet_sock_open_type = %d", __FUNCTION__, err); + + struct packet_mreq mreq = { + .mr_ifindex = ctx->ifindex, + .mr_type = PACKET_MR_MULTICAST, /* PACKET_MR_ALLMULTI */ /* PACKET_MR_PROMISC */ + .mr_alen = ETH_ALEN, + .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x10 } + }; + + err = setsockopt(ab->parasite_sockfd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + teamd_ttdp_log_end_agg_dbg(ctx->team_devname, "%s: setsockopt PACKET_ADD_MEMBERSHIP = %d", __FUNCTION__, err); + + /* no need to set priority as we only use this for reading */ + + err = teamd_loop_callback_fd_add(ctx, TTDP_PARASITE_SOCKET_CB_NAME, ab, + on_parasite_socket_recv, + ab->parasite_sockfd, + TEAMD_LOOP_FD_EVENT_READ); + teamd_ttdp_log_end_agg_dbg(ctx->team_devname, "%s: teamd_loop_callback_fd_add = %d", __FUNCTION__, err); + + err = teamd_loop_callback_enable(ctx, TTDP_PARASITE_SOCKET_CB_NAME, ab); + + return 0; +} + +static int ab_init(struct teamd_context *ctx, void *priv) +{ + struct ab *ab = priv; + int err; + + /* log to syslog */ + daemon_log_use = DAEMON_LOG_SYSLOG; + + if (!teamd_config_path_exists(ctx, "$.notify_peers.count")) { + err = teamd_config_int_set(ctx, 1, "$.notify_peers.count"); + if (err) { + teamd_log_err("Failed to set notify_peers count config value."); + return err; + } + } + if (!teamd_config_path_exists(ctx, "$.mcast_rejoin.count")) { + err = teamd_config_int_set(ctx, 1, "$.mcast_rejoin.count"); + if (err) { + teamd_log_err("Failed to set mcast_rejoin count config value."); + return err; + } + } + err = ab_load_config(ctx, ab); + if (err) { + teamd_log_err("Failed to load config values."); + return err; + } + err = teamd_event_watch_register(ctx, &ab_event_watch_ops, ab); + if (err) { + teamd_log_err("Failed to register event watch."); + return err; + } + err = teamd_state_val_register(ctx, &ab_state_vg, ab); + if (err) { + teamd_log_err("Failed to register state value group."); + goto event_watch_unregister; + } + for (int i = 0; i < TTDP_MAX_LINES; i++) { + err = teamd_state_val_register_ex(ctx, &lines_stats_vg, &ab->lines_hello_stats[i], NULL, "runner.lines_stats.%c", 'a' + i); + if (err) { + teamd_log_err("Failed to register state value group (line_stats line %c).", 'A' + i); + goto event_watch_unregister; + } + } + teamd_workq_init_work(&ab->link_watch_handler_workq, + ab_link_watch_handler_work); + teamd_workq_init_work(&ab->tcnd_notify_tcnd_workq, + send_tcnd_update_message_work); + teamd_workq_init_work(&ab->link_state_update_workq, + link_state_update_work); + teamd_workq_init_work(&ab->remote_inhibition_workq, + remote_inhibition_update_work); + teamd_workq_init_work(&ab->link_timeout_update_workq, + send_link_timeout_update_work); + teamd_workq_init_work(&ab->end_agg_detection_workq, + teamd_end_agg_detection_work); + + memset(ab->port_statuses, 3, 4); + ab->etb_topo_counter = 0xFFFFFFFF; + ab->port_statuses_b = 0xFF; + ab->inhibition_flag_local = 0; + ab->inhibition_flag_any = 0; + ab->inhibition_flag_neighbor = 0; + ab->inhibition_flag_remote_consist = 0; + ab->aggregate_status = TTDP_AGG_STATE_DEFAULT; + ab->remote_inhibition_actual = 3; + memset(&ab->lines_hello_stats[0], 0, sizeof(ab->lines_hello_stats)); + + memset(ab->neighbor_lines, '-', sizeof(ab->neighbor_lines)); + + ab->line_state_update_func = line_status_update; + ab->line_timeout_value_update_func = line_timeout_update; + ab->remote_inhibit_update_func = remote_inhibition_update; + + teamd_loop_callback_timer_add_set(ctx, + ttdp_runner_oneshot_initial_agg_state_name, + ab, + on_initial_timer, + &ttdp_runner_oneshot_timer, + &ttdp_runner_oneshot_timer + ); + teamd_loop_callback_enable(ctx, ttdp_runner_oneshot_initial_agg_state_name, ab); + + teamd_loop_callback_timer_add_set(ctx, + ttdp_runner_periodic_neighbor_macs_name, + ab, + on_periodic_neighbor_macs_timer, + &ttdp_runner_periodic_neighbor_macs_timer, + &ttdp_runner_periodic_neighbor_macs_timer + ); + // teamd_loop_callback_enable(ctx, ttdp_runner_periodic_neighbor_macs_name, ab); + + teamd_loop_callback_timer_add_set(ctx, + ttdp_runner_periodic_end_agg_detection_name, + ab, + on_periodic_end_agg_detection_timer, + &ttdp_runner_periodic_end_agg_detection_timer, + &ttdp_runner_periodic_end_agg_detection_timer); + teamd_loop_callback_enable(ctx, ttdp_runner_periodic_end_agg_detection_name, ab); +#ifdef SET_USER_LINK_TEAM_IFACE + // team_set_port_user_linkup_enabled(ctx->th, ctx->ifindex, true); + // team_set_port_user_linkup(ctx->th, ctx->ifindex, true); +#endif + + /* Write initial aggregate state */ + lag_state_write_aggregate_role(ctx, ab); + + open_parasite_socket(ctx, ab); + + teamd_ttdp_log_infox(ctx->team_devname, "Started."); + + return 0; + +event_watch_unregister: + teamd_event_watch_unregister(ctx, &ab_event_watch_ops, ab); + return err; +} + +static void ab_fini(struct teamd_context *ctx, void *priv) +{ + struct ab *ab = priv; + + teamd_state_val_unregister(ctx, &ab_state_vg, ab); + teamd_event_watch_unregister(ctx, &ab_event_watch_ops, ab); + + struct packet_mreq mreq = { + .mr_ifindex = ctx->ifindex, + .mr_type = PACKET_MR_MULTICAST, /* PACKET_MR_ALLMULTI */ /* PACKET_MR_PROMISC */ + .mr_alen = ETH_ALEN, + .mr_address = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x0E } + }; + + int err = setsockopt(ab->parasite_sockfd, SOL_PACKET, PACKET_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + teamd_ttdp_log_infox(ctx->team_devname, "%s: setsockopt PACKET_DROP_MEMBERSHIP = %d\n", __FUNCTION__, err); + + err = close(ab->parasite_sockfd); + teamd_ttdp_log_infox(ctx->team_devnamem, "%s: close = %d\n", __FUNCTION__, err); + + /* In case teamd_ttdp_log_infox is not defined: */ + (void)err; +} + +const struct teamd_runner teamd_runner_ttdp = { + .name = "ttdp", +#ifdef MODE_ACTIVEBACKUP + .team_mode_name = "activebackup", +#else + .team_mode_name = "random", +#endif + .priv_size = sizeof(struct ab), + .init = ab_init, + .fini = ab_fini, +}; diff --git a/teamd/teamd_state.c b/teamd/teamd_state.c index 0714880..594b96e 100644 --- a/teamd/teamd_state.c +++ b/teamd/teamd_state.c @@ -241,6 +241,9 @@ static int teamd_state_val_dump(struct teamd_context *ctx, case TEAMD_STATE_ITEM_TYPE_INT: val_json_obj = json_integer(gsc.data.int_val); break; + case TEAMD_STATE_ITEM_TYPE_UINT32: + val_json_obj = json_integer(gsc.data.uint32_val); + break; case TEAMD_STATE_ITEM_TYPE_STRING: val_json_obj = json_string(gsc.data.str_val.ptr); if (gsc.data.str_val.free) @@ -367,6 +370,9 @@ int teamd_state_item_value_get(struct teamd_context *ctx, const char *item_path, case TEAMD_STATE_ITEM_TYPE_INT: ret = asprintf(p_value, "%d", gsc.data.int_val); break; + case TEAMD_STATE_ITEM_TYPE_UINT32: + ret = asprintf(p_value, "%"PRIu32, gsc.data.uint32_val); + break; case TEAMD_STATE_ITEM_TYPE_STRING: ret = asprintf(p_value, "%s", gsc.data.str_val.ptr); if (gsc.data.str_val.free) @@ -401,6 +407,14 @@ int __set_int_val(struct team_state_gsc *gsc, const char *value) return 0; } +int __set_uint32_val(struct team_state_gsc *gsc, const char *value) +{ + int num_parsed = sscanf(value, "%"SCNu32, &gsc->data.uint32_val); + if (num_parsed != 1) + return -ERANGE; + return 0; +} + int __set_bool_val(struct team_state_gsc *gsc, const char *value) { if (!strcasecmp("true", value)) @@ -435,6 +449,11 @@ int teamd_state_item_value_set(struct teamd_context *ctx, const char *item_path, if (err) return err; break; + case TEAMD_STATE_ITEM_TYPE_UINT32: + err = __set_uint32_val(&gsc, value); + if (err) + return err; + break; case TEAMD_STATE_ITEM_TYPE_STRING: gsc.data.str_val.ptr = value; break; diff --git a/teamd/teamd_state.h b/teamd/teamd_state.h index d1877d0..b7b848c 100644 --- a/teamd/teamd_state.h +++ b/teamd/teamd_state.h @@ -25,6 +25,7 @@ enum teamd_state_val_type { TEAMD_STATE_ITEM_TYPE_NODE = 0, TEAMD_STATE_ITEM_TYPE_INT, + TEAMD_STATE_ITEM_TYPE_UINT32, TEAMD_STATE_ITEM_TYPE_STRING, TEAMD_STATE_ITEM_TYPE_BOOL, }; @@ -32,6 +33,7 @@ enum teamd_state_val_type { struct team_state_gsc { union { int int_val; + uint32_t uint32_val; struct { const char *ptr; bool free; diff --git a/teamd/ttdp_checksum.c b/teamd/ttdp_checksum.c new file mode 100644 index 0000000..e05270d --- /dev/null +++ b/teamd/ttdp_checksum.c @@ -0,0 +1,43 @@ +/* -*- mode: c; c-file-style: "openbsd" -*- */ +/* + * Copyright (c) 2009 Vincent Bernat + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* From LLDPD; modified to remove LLDP dependency */ +#include + +uint16_t +frame_checksum(const uint8_t *cp, int len) +{ + unsigned int sum = 0, v = 0; + int oddbyte = 0; + + /* We compute in network byte order */ + while ((len -= 2) >= 0) { + sum += *cp++ << 8; + sum += *cp++; + } + if ((oddbyte = len & 1) != 0) + v = *cp; + + if (oddbyte) { + sum += v << 8; + } + + sum = (sum >> 16) + (sum & 0xffff); + sum += sum >> 16; + + return (0xffff & ~sum); +} diff --git a/utils/snmp/dot3_ad_agg_port_list_table.c b/utils/snmp/dot3_ad_agg_port_list_table.c new file mode 100644 index 0000000..d50139c --- /dev/null +++ b/utils/snmp/dot3_ad_agg_port_list_table.c @@ -0,0 +1,148 @@ +/* \\/ Westermo - Teamd snmpsubagent - dot3adAggPortListTable. + * + * Copyright (C) 2019 Westermo Network Technologies AB + * + * Author(s): Johan Askerin + * + * Description: + */ + +#include +#include "teamdagentd.h" + +#define MIN_COLUMN 1 +#define MAX_COLUMN 1 + +#define ELEMENT_SIZE(s,e) sizeof(((s*)0)->e) +#define ARRAY_ELEMENTS(arr) ((sizeof(arr)/sizeof(0[arr])) / ((size_t)(!(sizeof(arr) % sizeof(0[arr]))))) + +typedef struct table_data_t table_data_t; + +struct table_data_t +{ + uint32_t dot3ad_agg_port_list_index; + u_char dot3ad_agg_port_list_ports[4]; + table_data_t *next; +}; + +static struct table_data_t *table_head = NULL; + +static NetsnmpCacheLoad table_load; +static NetsnmpCacheFree table_free; +static Netsnmp_First_Data_Point table_get_first; +static Netsnmp_Next_Data_Point table_get_next; +static Netsnmp_Node_Handler table_handler; + +static nsh_table_index_t idx[] = { + NSH_TABLE_INDEX (ASN_INTEGER, table_data_t, dot3ad_agg_port_list_index, 0), +}; + +nsh_table_free(table_free, table_data_t, table_head) +nsh_table_get_first(table_get_first, table_get_next, table_head) +nsh_table_get_next(table_get_next, table_data_t, idx, 1) + +static int table_handler (netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + nsh_table_entry_t table[] = { + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_port_list_ports, 0), + }; + + return nsh_handle_table (reqinfo, requests, table, ARRAY_ELEMENTS(table)); +} + + +static void table_create_entry (long agg_index, + u_char *ports) +{ + table_data_t *entry; + entry = SNMP_MALLOC_TYPEDEF(table_data_t); + if (!entry) + return; + + entry->dot3ad_agg_port_list_index = agg_index; + memcpy (entry->dot3ad_agg_port_list_ports, ports, ELEMENT_SIZE(table_data_t, dot3ad_agg_port_list_ports)); + entry->next = table_head; + + table_head = entry; +} + + +static int portname_to_num (const char *port_name) +{ + FILE *fp; + char buf[64]; + snprintf (buf, sizeof(buf), "/var/run/swport/%s/index", port_name); + int swindex = 0; + + fp = fopen (buf, "r"); + if (fp) { + fgets (buf, sizeof(buf), fp); + swindex = strtoul (buf, NULL, 0); + fclose (fp); + } + return swindex; +} + +static u_char *set_bit (u_char *ports, const char *port_name) +{ + uint32_t bits = 0; + uint8_t num_bit = portname_to_num (port_name); + bits = bits | 1 << num_bit; + ports[0] |= (bits >> 24) & 0xFF; + ports[1] |= (bits >> 16) & 0xFF; + ports[2] |= (bits >> 8) & 0xFF; + ports[3] |= bits & 0xFF; + return ports; +} + + +static int parse_jason (json_t *dump_json) +{ + json_t *ports_json; + json_t *iter; + u_char ports[4] = { 0 }; + + uint32_t ifindex; + + if (json_unpack (dump_json, "{s:{s:{s:i}}}", "team_device", "ifinfo", "ifindex", &ifindex)) + return -1; + + if (json_unpack (dump_json, "{s:o}", "ports", &ports_json)) + return -1; + + for (iter = json_object_iter (ports_json); iter; iter = json_object_iter_next (ports_json, iter)) { + const char *port_name = json_object_iter_key (iter); + set_bit (ports, port_name); + + } + table_create_entry (ifindex, ports); + return 0; +} + +static int table_load (netsnmp_cache *cache, void* vmagic) +{ + return parse_status_dir (parse_jason); +} + +void snmp_init_mib_dot3adAggListPortTable_table (void) +{ + oid table_oid[] = { oid_dot3adAggPortListTable }; + int index[] = { ASN_INTEGER }; + + nsh_register_table ("dot3adAggListPortTable", + table_oid, + OID_LENGTH(table_oid), + MIN_COLUMN, + MAX_COLUMN, + index, + ARRAY_ELEMENTS(index), + table_handler, + table_get_first, + table_get_next, + table_load, + table_free, + HANDLER_CAN_RWRITE); +} \ No newline at end of file diff --git a/utils/snmp/dot3_ad_agg_port_table.c b/utils/snmp/dot3_ad_agg_port_table.c new file mode 100644 index 0000000..1c29105 --- /dev/null +++ b/utils/snmp/dot3_ad_agg_port_table.c @@ -0,0 +1,267 @@ +/* \\/ Westermo - Teamd snmpsubagent - dot3adAggPortTable. + * + * Copyright (C) 2019 Westermo Network Technologies AB + * + * Author(s): Johan Askerin + * + * Description: + */ + +#include +#include +#include "teamdagentd.h" + +#define MIN_COLUMN 1 +#define MAX_COLUMN 25 + +#define ELEMENT_SIZE(s,e) sizeof(((s*)0)->e) +#define ARRAY_ELEMENTS(arr) ((sizeof(arr)/sizeof(0[arr])) / ((size_t)(!(sizeof(arr) % sizeof(0[arr]))))) + +typedef struct table_data_t table_data_t; + +struct table_data_t +{ + uint32_t dot3ad_agg_port_index; + uint32_t dot3ad_agg_port_actor_system_priority; + uint8_t dot3ad_agg_port_actor_system_id[6]; + uint32_t dot3ad_agg_port_actor_admin_key; + uint32_t dot3ad_agg_port_actor_oper_key; + uint32_t dot3ad_agg_port_partner_admin_system_priority; + uint32_t dot3ad_agg_port_partner_oper_system_priority; + uint8_t dot3ad_agg_port_partner_admin_system_id[6]; + uint8_t dot3ad_agg_port_partner_oper_system_id[6]; + uint32_t dot3ad_agg_port_partner_admin_key; + uint32_t dot3ad_agg_port_partner_oper_key; + uint32_t dot3ad_agg_port_selected_agg_id; + uint32_t dot3ad_agg_port_attached_agg_id; + uint32_t dot3ad_agg_port_actor_port; + uint32_t dot3ad_agg_port_actor_port_priority; + uint32_t dot3ad_agg_port_partner_admin_port; + uint32_t dot3ad_agg_port_partner_oper_port; + uint32_t dot3ad_agg_port_partner_admin_port_priority; + uint32_t dot3ad_agg_port_partner_oper_port_priority; + uint8_t dot3ad_agg_port_actor_admin_state[1]; + uint8_t dot3ad_agg_port_actor_oper_state[1]; + uint8_t dot3ad_agg_port_partner_admin_state[1]; + uint8_t dot3ad_agg_port_partner_oper_state[1]; + uint32_t dot3ad_agg_port_aggregate_or_individual; + table_data_t *next; +}; + +static struct table_data_t *table_head = NULL; + +static NetsnmpCacheLoad table_load; +static NetsnmpCacheFree table_free; +static Netsnmp_First_Data_Point table_get_first; +static Netsnmp_Next_Data_Point table_get_next; +static Netsnmp_Node_Handler table_handler; + +static nsh_table_index_t idx[] = { + NSH_TABLE_INDEX (ASN_INTEGER, table_data_t, dot3ad_agg_port_index, 0), +}; + +nsh_table_free(table_free, table_data_t, table_head) +nsh_table_get_first(table_get_first, table_get_next, table_head) +nsh_table_get_next(table_get_next, table_data_t, idx, 1) + + +static int table_handler (netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + nsh_table_entry_t table[] = { + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_index, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_actor_system_priority, 0), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_port_actor_system_id, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_actor_admin_key ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_actor_oper_key, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_system_priority ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_partner_oper_system_priority, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_system_id ), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_port_partner_oper_system_id, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_key ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_partner_oper_key, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_selected_agg_id, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_attached_agg_id, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_actor_port, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_actor_port_priority, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_port ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_partner_oper_port, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_port_priority ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_partner_oper_port_priority, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_actor_admin_state ), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_port_actor_oper_state, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_port_partner_admin_state ), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_port_partner_oper_state, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_port_aggregate_or_individual, 0), + }; + + return nsh_handle_table (reqinfo, requests, table, ARRAY_ELEMENTS(table)); +} + +static void table_create_entry (uint32_t index, + uint32_t actor_system_priority, + uint8_t *actor_system_id, + uint32_t actor_oper_key, + uint32_t partner_oper_system_priority, + u_char *partner_oper_system_id, + uint32_t partner_oper_key, + uint32_t selected_agg_id, + uint32_t attached_agg_id, + uint32_t actor_port, + uint32_t actor_port_priority, + uint32_t partner_oper_port, + uint32_t partner_oper_port_priority, + uint8_t actor_oper_state, + uint8_t partner_oper_state, + uint32_t aggregate_or_individual) +{ + table_data_t *entry; + entry = SNMP_MALLOC_TYPEDEF(table_data_t); + if (!entry) + + return; + entry->dot3ad_agg_port_index = index; + entry->dot3ad_agg_port_actor_system_priority = actor_system_priority; + memcpy (entry->dot3ad_agg_port_actor_system_id, actor_system_id, + ELEMENT_SIZE(table_data_t, dot3ad_agg_port_actor_system_id)); + entry->dot3ad_agg_port_actor_oper_key = actor_oper_key; + entry->dot3ad_agg_port_partner_oper_system_priority = partner_oper_system_priority; + memcpy (entry->dot3ad_agg_port_partner_oper_system_id, partner_oper_system_id, + ELEMENT_SIZE(table_data_t, dot3ad_agg_port_partner_oper_system_id)); + entry->dot3ad_agg_port_partner_oper_key = partner_oper_key; + entry->dot3ad_agg_port_selected_agg_id = selected_agg_id; + entry->dot3ad_agg_port_attached_agg_id = attached_agg_id; + entry->dot3ad_agg_port_actor_port = actor_port; + entry->dot3ad_agg_port_actor_port_priority = actor_port_priority; + entry->dot3ad_agg_port_partner_oper_port = partner_oper_port; + entry->dot3ad_agg_port_partner_oper_port_priority = partner_oper_port_priority; + entry->dot3ad_agg_port_actor_oper_state[0] = actor_oper_state; + entry->dot3ad_agg_port_partner_oper_state[0] = partner_oper_state; + entry->dot3ad_agg_port_aggregate_or_individual = aggregate_or_individual; + entry->next = table_head; + + table_head = entry; +} + +static int parse_jason (json_t *dump_json) +{ + json_t *ports_json; + json_t *iter; + json_t *actor_json; + json_t *aggregator_json; + json_t *partner_json; + char *runner_name; + char *ifname = "0"; + + uint32_t ifindex; + + char *actor_system, *partner_system; + uint32_t actor_key = 0, actor_port = 0, actor_port_prio = 0, actor_state = 0, actor_prio = 0; + uint32_t partner_key = 0, partner_port = 0, partner_port_prio = 0, partner_state = 0, partner_prio = 0; + uint32_t selected, id = 0; + + unsigned char actor_mac[6] = { 0 }, partner_mac[6] = { 0 }; + + if (json_unpack (dump_json, "{s:{s:s}}", "setup", "runner_name", &runner_name)) + return -1; + + if (json_unpack (dump_json, "{s:o}", "ports", &ports_json)) + return -1; + + for (iter = json_object_iter (ports_json); iter; iter = json_object_iter_next (ports_json, iter)) { + + json_t *port_json = json_object_iter_value (iter); + + if (json_unpack (port_json, "{s:{s:i, s:s}}", "ifinfo", + "ifindex", &ifindex, + "ifname", &ifname + )) + return -1; + + if (strncmp (runner_name, "lacp", sizeof("lacp")) == 0) { + + if (json_unpack (port_json, + "{s:{s:o, s:o, s:o}}", + "runner", + "actor_lacpdu_info", &actor_json, + "aggregator", &aggregator_json, + "partner_lacpdu_info", &partner_json)) + return -1; + + if (json_unpack (actor_json, "{s:i, s:i, s:i, s:i, s:s, s:i}", + "key", &actor_key, + "port", &actor_port, + "port_priority", &actor_port_prio, + "state", &actor_state, + "system", &actor_system, + "system_priority", &actor_prio)) + return -1; + + ether_aton_r (actor_system, (struct ether_addr *) actor_mac); + + if (json_unpack (aggregator_json, "{s:i, s:b}", + "id", &id, + "selected", &selected)) + return -1; + + + if (json_unpack (partner_json, "{s:i, s:i, s:i, s:i, s:s, s:i}", + "key", &partner_key, + "port", &partner_port, + "port_priority", &partner_port_prio, + "state", &partner_state, + "system", &partner_system, + "system_priority", &partner_prio)) + return -1; + + ether_aton_r (partner_system, (struct ether_addr *) partner_mac); + + + } + + table_create_entry (ifindex, + actor_prio, + actor_mac, + actor_key, + partner_prio, + partner_mac, + partner_key, + id, + id, + actor_port, + actor_port_prio, + partner_port, + partner_port_prio, + actor_state, + partner_state, + 1); + } + return 0; +} + +static int table_load (netsnmp_cache *cache, void* vmagic) +{ + return parse_status_dir (parse_jason); +} + +void snmp_init_mib_dot3adAggtPortTable_table (void) +{ + oid table_oid[] = { oid_dot3adAggPortTable }; + int index[] = { ASN_INTEGER }; + + nsh_register_table ("dot3adAggPortTable", + table_oid, + OID_LENGTH(table_oid), + MIN_COLUMN, + MAX_COLUMN, + index, + ARRAY_ELEMENTS(index), + table_handler, + table_get_first, + table_get_next, + table_load, + table_free, + HANDLER_CAN_RWRITE); +} \ No newline at end of file diff --git a/utils/snmp/dot3_ad_agg_table.c b/utils/snmp/dot3_ad_agg_table.c new file mode 100644 index 0000000..8c75dc2 --- /dev/null +++ b/utils/snmp/dot3_ad_agg_table.c @@ -0,0 +1,203 @@ +/* \\/ Westermo - Teamd snmpsubagent - dot3adAggTable. + * + * Copyright (C) 2019 Westermo Network Technologies AB + * + * Author(s): Johan Askerin + * + * Description: + */ + +#include +#include +#include "teamdagentd.h" + + +#define MIN_COLUMN 1 +#define MAX_COLUMN 11 + +#define ELEMENT_SIZE(s,e) sizeof(((s*)0)->e) +#define ARRAY_ELEMENTS(arr) ((sizeof(arr)/sizeof(0[arr])) / ((size_t)(!(sizeof(arr) % sizeof(0[arr]))))) + +typedef struct table_data_t table_data_t; + +struct table_data_t +{ + uint32_t dot3ad_agg_index; + u_char dot3ad_agg_mac_address[ETHER_ADDR_LEN]; + uint32_t dot3ad_agg_actor_system_priority; + u_char dot3ad_agg_actor_systemID[ETHER_ADDR_LEN]; + u_char dot3ad_agg_aggregate_or_individual; + uint32_t dot3ad_agg_actor_admin_key; + uint32_t dot3ad_agg_actor_oper_key; + u_char dot3ad_agg_partner_systemID[ETHER_ADDR_LEN]; + uint32_t dot3ad_agg_partner_system_priority; + uint32_t dot3ad_agg_partner_oper_key; + uint32_t dot3ad_agg_collector_max_delay; + table_data_t *next; +}; + +static struct table_data_t *table_head = NULL; + +static NetsnmpCacheLoad table_load; +static NetsnmpCacheFree table_free; +static Netsnmp_First_Data_Point table_get_first; +static Netsnmp_Next_Data_Point table_get_next; +static Netsnmp_Node_Handler table_handler; + +static nsh_table_index_t idx[] = { + NSH_TABLE_INDEX (ASN_INTEGER, table_data_t, dot3ad_agg_index, 0), +}; + +nsh_table_free(table_free, table_data_t, table_head) +nsh_table_get_first(table_get_first, table_get_next, table_head) +nsh_table_get_next(table_get_next, table_data_t, idx, 1) + +static int table_handler (netsnmp_mib_handler *handler, + netsnmp_handler_registration *reginfo, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + nsh_table_entry_t table[] = { + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_index, 0), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_mac_address, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_actor_system_priority, 0), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_actor_systemID, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_aggregate_or_individual, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED ( table_data_t, dot3ad_agg_actor_admin_key ), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_actor_oper_key, 0), + NSH_TABLE_ENTRY_RO (ASN_OCTET_STR, table_data_t, dot3ad_agg_partner_systemID, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_partner_system_priority, 0), + NSH_TABLE_ENTRY_RO (ASN_INTEGER, table_data_t, dot3ad_agg_partner_oper_key, 0), + NSH_TABLE_ENTRY_NOTSUPPORTED (table_data_t, dot3ad_agg_collector_max_delay), + }; + + return nsh_handle_table (reqinfo, requests, table, ARRAY_ELEMENTS(table)); +} + + +static void table_create_entry (long agg_index, + u_char *mac_address, + long actor_system_priority, + u_char *actor_systemID, + u_char aggregate_or_individual, + long actor_admin_key, + long actor_oper_key, + u_char *partner_systemID, + long partner_system_priority, + long partner_oper_key, + long collector_max_delay) +{ + table_data_t *entry; + + entry = SNMP_MALLOC_TYPEDEF(table_data_t); + if (!entry) + return; + + entry->dot3ad_agg_index = agg_index; + memcpy (entry->dot3ad_agg_mac_address, mac_address, ELEMENT_SIZE(table_data_t, dot3ad_agg_mac_address)); + entry->dot3ad_agg_actor_system_priority = actor_system_priority; + memcpy (entry->dot3ad_agg_actor_systemID, actor_systemID, ELEMENT_SIZE(table_data_t, dot3ad_agg_actor_systemID)); + entry->dot3ad_agg_aggregate_or_individual = aggregate_or_individual; + entry->dot3ad_agg_actor_admin_key = actor_admin_key; + entry->dot3ad_agg_actor_oper_key = actor_oper_key; + memcpy (entry->dot3ad_agg_partner_systemID, partner_systemID, + ELEMENT_SIZE(table_data_t, dot3ad_agg_partner_systemID)); + entry->dot3ad_agg_partner_system_priority = partner_system_priority; + entry->dot3ad_agg_partner_oper_key = partner_oper_key; + entry->dot3ad_agg_collector_max_delay = collector_max_delay; + entry->next = table_head; + table_head = entry; +} + +static int parse_jason (json_t *dump_json) +{ + json_t *ports_json; + json_t *iter; + json_t *actor_json; + json_t *partner_json; + char *runner_name; + char *dev_addr; + + uint32_t ifindex; + + char *actor_system, *partner_system; + uint32_t actor_key = 0, actor_port = 0, actor_state = 0, actor_prio = 0; + uint32_t partner_key = 0, partner_port = 0, partner_state = 0, partner_prio = 0; + + unsigned char mac[6] = { 0 }, actor_mac[6] = { 0 }, partner_mac[6] = { 0 }; + + if (json_unpack (dump_json, "{s:{s:{s:s, s:i}}}", "team_device", "ifinfo", + "dev_addr", &dev_addr, + "ifindex", &ifindex + )) + return -1; + + ether_aton_r (dev_addr, (struct ether_addr *) mac); + + if (json_unpack (dump_json, "{s:{s:s}}", "setup", "runner_name", &runner_name)) + return -1; + + if (strncmp (runner_name, "lacp", sizeof("lacp")) == 0) { + if (json_unpack (dump_json, "{s:o}", "ports", &ports_json)) + return -1; + + iter = json_object_iter (ports_json); /* Extract actor info from first port */ + + json_t *port_json = json_object_iter_value (iter); + + if (json_unpack (port_json, + "{s:{s:o, s:o}}", + "runner", + "actor_lacpdu_info", &actor_json, + "partner_lacpdu_info", &partner_json)) + return -1; + + if (json_unpack (actor_json, "{s:i, s:i, s:i, s:s, s:i}", + "key", &actor_key, + "port", &actor_port, + "state", &actor_state, + "system", &actor_system, + "system_priority", &actor_prio)) + return -1; + + ether_aton_r (actor_system, (struct ether_addr *) actor_mac); + + if (json_unpack (partner_json, "{s:i, s:i, s:i, s:s, s:i}", + "key", &partner_key, + "port", &partner_port, + "state", &partner_state, + "system", &partner_system, + "system_priority", &partner_prio)) + return -1; + + ether_aton_r (partner_system, (struct ether_addr *) partner_mac); + } + table_create_entry (ifindex, mac, actor_prio, actor_mac, 1, actor_key, actor_key, partner_mac, partner_prio, + partner_key, 0); + return 0; +} + +static int table_load (netsnmp_cache *cache, void* vmagic) +{ + return parse_status_dir (parse_jason); +} + +void snmp_init_mib_dot3adAggTable_table (void) +{ + oid table_oid[] = { oid_dot3adAggTable }; + int index[] = { ASN_INTEGER }; + + nsh_register_table ("dot3adAggTable", + table_oid, + OID_LENGTH(table_oid), + MIN_COLUMN, + MAX_COLUMN, + index, + ARRAY_ELEMENTS(index), + table_handler, + table_get_first, + table_get_next, + table_load, + table_free, + HANDLER_CAN_RWRITE); +} \ No newline at end of file diff --git a/utils/snmp/teamdagentd.c b/utils/snmp/teamdagentd.c new file mode 100644 index 0000000..d7a9d9a --- /dev/null +++ b/utils/snmp/teamdagentd.c @@ -0,0 +1,143 @@ +/* \\/ Westermo - Teamd snmpsubagent. + * + * Copyright (C) 2019 Westermo Network Technologies AB + * + * Author(s): Johan Askerin + * + * Description: + */ + +#include +#include +#include +#include +#include +#include +#include "../../teamd/teamd.h" +#include "teamdctl.h" +#include "teamdagentd.h" +#include +#include + +static int run = 1; + +#ifndef FS_SW_VER +#define FS_SW_VER "0.0" +#endif + +static const char *program_version = "teamdagentd v" FS_SW_VER; + +/**************************************************************************/ +static int usage (char *progname) +{ + fprintf (stderr, "\n" + "------------------------------------------------------------------------------\n" + "Usage: %s [OPTIONS]\n" + " -v, --version Display version\n" + " -?, --help This help text\n" + "------------------------------------------------------------------------------\n" + "\n", progname); + + return 1; +} + +int main (int argc, char **argv) +{ + int c; + struct option long_options[] = { + { "version", 0, NULL, 'v' }, + { "help", 0, NULL, '?' }, + { NULL, 0, NULL, 0 } + }; + + while ((c = getopt_long (argc, argv, "h?vf:", long_options, NULL)) != EOF) + { + switch (c) + { + case 'v': + printf ("%s\n", program_version); + return 0; + case ':': /* Missing parameter for option. */ + case '?': /* Unknown option. */ + case 'h': + default: + return usage (argv[0]); + } + } + + netsnmp_enable_subagent (); + snmp_disable_log (); + netsnmp_ds_set_boolean (NETSNMP_DS_LIBRARY_ID, NETSNMP_DS_LIB_DONT_PERSIST_STATE, TRUE); + netsnmp_ds_set_boolean (NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_NO_CONNECTION_WARNINGS, TRUE); + + snmp_enable_stderrlog (); + init_agent ("teamdagentd"); + init_snmp ("teamdagentd"); + + snmp_init_mib_dot3adAggTable_table (); + snmp_init_mib_dot3adAggListPortTable_table (); + snmp_init_mib_dot3adAggtPortTable_table (); + + while (run) + { + agent_check_and_process (1); + } + return 0; +} + +static int __jsonload (json_t **pjson, char *inputstrjson) +{ + json_t *json; + json_error_t jerror; + + json = json_loads (inputstrjson, JSON_REJECT_DUPLICATES, &jerror); + if (!json) { + return -EINVAL; + } + *pjson = json; + return 0; +} + +int parse_status_dir (int (*parse_json) (json_t *json_obj)) +{ + int err = 0; + uint32_t ifindex; + char *ifname; + DIR * dir; + struct dirent *dir_ent; + json_t *dump_json; + + struct teamdctl *tdc; + tdc = teamdctl_alloc (); + if (!tdc) + return -1; + + dir = opendir ("/var/run/"); + if (dir) { + while ((dir_ent = readdir (dir)) != NULL) { + if (dir_ent->d_type == DT_REG) { + if (strstr (dir_ent->d_name, ".pid")) { + ifname = strtok (dir_ent->d_name, "."); + if (strstr(ifname, "lag") != NULL) { + err = ifname2ifindex (&ifindex, ifname); + if (!err) { + err = teamdctl_connect (tdc, ifname, NULL, NULL); + if (!err) { + char *dump = teamdctl_state_get_raw (tdc); + err = __jsonload (&dump_json, dump); + if (!err) { + err = parse_json (dump_json); + json_decref (dump_json); + } + teamdctl_disconnect (tdc); + } + } + } + } + } + } + closedir (dir); + } + teamdctl_free (tdc); + return err; +} \ No newline at end of file diff --git a/utils/snmp/teamdagentd.h b/utils/snmp/teamdagentd.h new file mode 100644 index 0000000..d723555 --- /dev/null +++ b/utils/snmp/teamdagentd.h @@ -0,0 +1,56 @@ +/* \\/ Westermo - Teamd snmpsubagent - dot3adAggPortListTable. + * + * Copyright (C) 2019 Westermo Network Technologies AB + * + * Author(s): Johan Askerin + * + * Description: + */ + +#ifndef TEAMD_SNMP_AGENT_H +#define TEAMD_SNMP_AGENT_H + +#include /* Needed by libnsh*/ +#include + +#include +#include + +#define oid_iso 1 /* 1 */ +#define oid_member_body oid_iso, 2 /* 1.2 */ +#define oid_us oid_member_body, 840 /* 1.2.840 */ +#define oid_ieee802dot3 oid_us, 10006 /* 1.2.840.10006 */ +#define oid_snmpmibs oid_ieee802dot3, 300 /* 1.2.840.10006.300 */ +#define oid_lagMIB oid_snmpmibs, 43 /* 1.2.840.10006.300.43 */ +#define oid_lagMIBObjects oid_lagMIB, 1 /* 1.2.840.10006.300.43.1 */ +#define oid_dot3adAgg oid_lagMIBObjects, 1 /* 1.2.840.10006.300.43.1.1 */ +#define oid_dot3adAggPort oid_lagMIBObjects, 2 /* 1.2.840.10006.300.43.1.2 */ +#define oid_dot3adAggTable oid_dot3adAgg, 1 /* 1.2.840.10006.300.43.1.1.1 */ +#define oid_dot3adAggPortTable oid_dot3adAggPort, 1 /* 1.2.840.10006.300.43.1.2.1 */ + +#define oid_dot3adAggEntry oid_dot3adAggTable, 1 /* 1.2.840.10006.300.43.1.1.1.1 */ +#define oid_dot3adAggIndex oid_dot3adAggEntry, 1 /* 1.2.840.10006.300.43.1.1.1.1.1 */ +#define oid_dot3adAggMACAddress oid_dot3adAggEntry, 2 /* 1.2.840.10006.300.43.1.1.1.1.2 */ +#define oid_dot3adAggActorSystemPriority oid_dot3adAggEntry, 3 /* 1.2.840.10006.300.43.1.1.1.1.3 */ +#define oid_dot3adAggActorSystemID oid_dot3adAggEntry, 4 /* 1.2.840.10006.300.43.1.1.1.1.4 */ +#define oid_dot3adAggAggregateOrIndividual oid_dot3adAggEntry, 5 /* 1.2.840.10006.300.43.1.1.1.1.5 */ +#define oid_dot3adAggActorAdminKey oid_dot3adAggEntry, 6 /* 1.2.840.10006.300.43.1.1.1.1.6 */ +#define oid_dot3adAggActorOperKey oid_dot3adAggEntry, 7 /* 1.2.840.10006.300.43.1.1.1.1.7 */ +#define oid_dot3adAggPartnerSystemID oid_dot3adAggEntry, 8 /* 1.2.840.10006.300.43.1.1.1.1.8 */ +#define oid_dot3adAggPartnerSystemPriority oid_dot3adAggEntry, 9 /* 1.2.840.10006.300.43.1.1.1.1.9 */ +#define oid_dot3adAggPartnerOperKey oid_dot3adAggEntry, 10 /* 1.2.840.10006.300.43.1.1.1.1.10 */ +#define oid_dot3adAggCollectorMaxDelay oid_dot3adAggEntry, 11 /* 1.2.840.10006.300.43.1.1.1.1.11 */ + +#define oid_dot3adAggPortListTable oid_dot3adAgg, 2 /* 1.2.840.10006.300.43.1.1.2 */ +#define oid_dot3adAggPortListEntry oid_dot3adAggPortListTable, 1 /* 1.2.840.10006.300.43.1.1.2.1 */ + +#define oid_dot3adAggPortEntry oid_dot3adAggPort, 1 /* 1.2.840.10006.300.43.1.2.1.1 */ + + +void snmp_init_mib_dot3adAggTable_table (void); +void snmp_init_mib_dot3adAggListPortTable_table(void); +void snmp_init_mib_dot3adAggtPortTable_table (void); + +int parse_status_dir (int (*parse_json) (json_t *json_obj)); + +#endif /* TEAMD_SNMP_AGENT_H */