5012 lines
126 KiB
C
5012 lines
126 KiB
C
/*
|
|
* Copyright (c) 2004-2021 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
|
|
/*
|
|
* if_bond.c
|
|
* - bond/failover interface
|
|
* - implements IEEE 802.3ad Link Aggregation
|
|
*/
|
|
|
|
/*
|
|
* Modification History:
|
|
*
|
|
* April 29, 2004 Dieter Siegmund (dieter@apple.com)
|
|
* - created
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/kern_event.h>
|
|
#include <net/bpf.h>
|
|
#include <net/ethernet.h>
|
|
#include <net/if.h>
|
|
#include <net/kpi_interface.h>
|
|
#include <net/kpi_interfacefilter.h>
|
|
#include <net/if_arp.h>
|
|
#include <net/if_dl.h>
|
|
#include <net/if_ether.h>
|
|
#include <net/if_types.h>
|
|
#include <net/if_bond_var.h>
|
|
#include <net/ieee8023ad.h>
|
|
#include <net/lacp.h>
|
|
#include <net/dlil.h>
|
|
#include <sys/time.h>
|
|
#include <net/devtimer.h>
|
|
#include <net/if_vlan_var.h>
|
|
#include <net/kpi_protocol.h>
|
|
#include <sys/protosw.h>
|
|
#include <kern/locks.h>
|
|
#include <kern/zalloc.h>
|
|
#include <os/refcnt.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/ip6.h>
|
|
|
|
#include <net/if_media.h>
|
|
#include <net/multicast_list.h>
|
|
|
|
#include <net/sockaddr_utils.h>
|
|
|
|
SYSCTL_DECL(_net_link);
|
|
SYSCTL_NODE(_net_link, OID_AUTO, bond, CTLFLAG_RW | CTLFLAG_LOCKED, 0,
|
|
"Bond interface");
|
|
|
|
static int if_bond_debug = 0;
|
|
SYSCTL_INT(_net_link_bond, OID_AUTO, debug, CTLFLAG_RW | CTLFLAG_LOCKED,
|
|
&if_bond_debug, 0, "Bond interface debug logs");
|
|
|
|
static struct ether_addr slow_proto_multicast = {
|
|
.octet = IEEE8023AD_SLOW_PROTO_MULTICAST
|
|
};
|
|
|
|
typedef struct ifbond_s ifbond, * ifbond_ref;
|
|
typedef struct bondport_s bondport, * bondport_ref;
|
|
|
|
#define BOND_MAXUNIT 128
|
|
#define BOND_ZONE_MAX_ELEM MIN(IFNETS_MAX, BOND_MAXUNIT)
|
|
#define BONDNAME "bond"
|
|
|
|
#define EA_FORMAT "%x:%x:%x:%x:%x:%x"
|
|
#define EA_CH(e, i) ((u_char)((u_char *)(e))[(i)])
|
|
#define EA_LIST(ea) EA_CH(ea,0),EA_CH(ea,1),EA_CH(ea,2),EA_CH(ea,3),EA_CH(ea,4),EA_CH(ea,5)
|
|
|
|
#define timestamp_printf printf
|
|
|
|
/**
|
|
** bond locks
|
|
**/
|
|
|
|
static LCK_GRP_DECLARE(bond_lck_grp, "if_bond");
|
|
static LCK_MTX_DECLARE(bond_lck_mtx, &bond_lck_grp);
|
|
|
|
static __inline__ void
|
|
bond_assert_lock_held(void)
|
|
{
|
|
LCK_MTX_ASSERT(&bond_lck_mtx, LCK_MTX_ASSERT_OWNED);
|
|
}
|
|
|
|
static __inline__ void
|
|
bond_assert_lock_not_held(void)
|
|
{
|
|
LCK_MTX_ASSERT(&bond_lck_mtx, LCK_MTX_ASSERT_NOTOWNED);
|
|
}
|
|
|
|
static __inline__ void
|
|
bond_lock(void)
|
|
{
|
|
lck_mtx_lock(&bond_lck_mtx);
|
|
}
|
|
|
|
static __inline__ void
|
|
bond_unlock(void)
|
|
{
|
|
lck_mtx_unlock(&bond_lck_mtx);
|
|
}
|
|
|
|
/**
|
|
** bond structures, types
|
|
**/
|
|
|
|
struct LAG_info_s {
|
|
lacp_system li_system;
|
|
lacp_system_priority li_system_priority;
|
|
lacp_key li_key;
|
|
};
|
|
typedef struct LAG_info_s LAG_info, * LAG_info_ref;
|
|
|
|
struct bondport_s;
|
|
TAILQ_HEAD(port_list, bondport_s);
|
|
struct ifbond_s;
|
|
TAILQ_HEAD(ifbond_list, ifbond_s);
|
|
struct LAG_s;
|
|
TAILQ_HEAD(lag_list, LAG_s);
|
|
|
|
typedef struct ifbond_s ifbond, * ifbond_ref;
|
|
typedef struct bondport_s bondport, * bondport_ref;
|
|
|
|
struct LAG_s {
|
|
TAILQ_ENTRY(LAG_s) lag_list;
|
|
struct port_list lag_port_list;
|
|
short lag_port_count;
|
|
short lag_selected_port_count;
|
|
int lag_active_media;
|
|
LAG_info lag_info;
|
|
};
|
|
typedef struct LAG_s LAG, * LAG_ref;
|
|
|
|
typedef struct partner_state_s {
|
|
LAG_info ps_lag_info;
|
|
lacp_port ps_port;
|
|
lacp_port_priority ps_port_priority;
|
|
lacp_actor_partner_state ps_state;
|
|
} partner_state, * partner_state_ref;
|
|
|
|
struct ifbond_s {
|
|
TAILQ_ENTRY(ifbond_s) ifb_bond_list;
|
|
int ifb_flags;
|
|
struct os_refcnt ifb_retain_count;
|
|
char ifb_name[IFNAMSIZ];
|
|
struct ifnet * ifb_ifp;
|
|
bpf_packet_func ifb_bpf_input;
|
|
bpf_packet_func ifb_bpf_output;
|
|
int ifb_altmtu;
|
|
struct port_list ifb_port_list;
|
|
short ifb_port_count;
|
|
struct lag_list ifb_lag_list;
|
|
lacp_key ifb_key;
|
|
short ifb_max_active;/* 0 == unlimited */
|
|
LAG_ref ifb_active_lag;
|
|
struct ifmultiaddr * ifb_ifma_slow_proto;
|
|
bondport_ref * ifb_distributing_array;
|
|
int ifb_distributing_count;
|
|
int ifb_distributing_max;
|
|
int ifb_last_link_event;
|
|
int ifb_mode;/* LACP, STATIC */
|
|
};
|
|
|
|
struct media_info {
|
|
int mi_active;
|
|
int mi_status;
|
|
};
|
|
|
|
enum {
|
|
ReceiveState_none = 0,
|
|
ReceiveState_INITIALIZE = 1,
|
|
ReceiveState_PORT_DISABLED = 2,
|
|
ReceiveState_EXPIRED = 3,
|
|
ReceiveState_LACP_DISABLED = 4,
|
|
ReceiveState_DEFAULTED = 5,
|
|
ReceiveState_CURRENT = 6,
|
|
};
|
|
|
|
typedef u_char ReceiveState;
|
|
|
|
enum {
|
|
SelectedState_UNSELECTED = IF_BOND_STATUS_SELECTED_STATE_UNSELECTED,
|
|
SelectedState_SELECTED = IF_BOND_STATUS_SELECTED_STATE_SELECTED,
|
|
SelectedState_STANDBY = IF_BOND_STATUS_SELECTED_STATE_STANDBY
|
|
};
|
|
typedef u_char SelectedState;
|
|
|
|
static __inline__ const char *
|
|
SelectedStateString(SelectedState s)
|
|
{
|
|
static const char * names[] = { "UNSELECTED", "SELECTED", "STANDBY" };
|
|
|
|
if (s <= SelectedState_STANDBY) {
|
|
return names[s];
|
|
}
|
|
return "<unknown>";
|
|
}
|
|
|
|
enum {
|
|
MuxState_none = 0,
|
|
MuxState_DETACHED = 1,
|
|
MuxState_WAITING = 2,
|
|
MuxState_ATTACHED = 3,
|
|
MuxState_COLLECTING_DISTRIBUTING = 4,
|
|
};
|
|
|
|
typedef u_char MuxState;
|
|
|
|
#define PORT_CONTROL_FLAGS_IN_LIST 0x01
|
|
#define PORT_CONTROL_FLAGS_PROTO_ATTACHED 0x02
|
|
#define PORT_CONTROL_FLAGS_FILTER_ATTACHED 0x04
|
|
#define PORT_CONTROL_FLAGS_LLADDR_SET 0x08
|
|
#define PORT_CONTROL_FLAGS_MTU_SET 0x10
|
|
#define PORT_CONTROL_FLAGS_PROMISCUOUS_SET 0x20
|
|
#define PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET 0x40
|
|
|
|
|
|
static inline bool
|
|
uint32_bit_is_set(uint32_t flags, uint32_t flags_to_test)
|
|
{
|
|
return (flags & flags_to_test) != 0;
|
|
}
|
|
|
|
static inline void
|
|
uint32_bit_set(uint32_t * flags_p, uint32_t flags_to_set)
|
|
{
|
|
*flags_p |= flags_to_set;
|
|
}
|
|
|
|
static inline void
|
|
uint32_bit_clear(uint32_t * flags_p, uint32_t flags_to_clear)
|
|
{
|
|
*flags_p &= ~flags_to_clear;
|
|
}
|
|
|
|
struct bondport_s {
|
|
TAILQ_ENTRY(bondport_s) po_port_list;
|
|
ifbond_ref po_bond;
|
|
struct multicast_list po_multicast;
|
|
struct ifnet * po_ifp;
|
|
struct ether_addr po_saved_addr;
|
|
int po_enabled;
|
|
char po_name[IFNAMSIZ];
|
|
struct ifdevmtu po_devmtu;
|
|
uint32_t po_control_flags;
|
|
interface_filter_t po_filter;
|
|
|
|
/* LACP */
|
|
TAILQ_ENTRY(bondport_s) po_lag_port_list;
|
|
devtimer_ref po_current_while_timer;
|
|
devtimer_ref po_periodic_timer;
|
|
devtimer_ref po_wait_while_timer;
|
|
devtimer_ref po_transmit_timer;
|
|
partner_state po_partner_state;
|
|
lacp_port_priority po_priority;
|
|
lacp_actor_partner_state po_actor_state;
|
|
u_char po_flags;
|
|
u_char po_periodic_interval;
|
|
u_char po_n_transmit;
|
|
ReceiveState po_receive_state;
|
|
MuxState po_mux_state;
|
|
SelectedState po_selected;
|
|
int32_t po_last_transmit_secs;
|
|
struct media_info po_media_info;
|
|
uint64_t po_force_link_event_time;
|
|
LAG_ref po_lag;
|
|
};
|
|
|
|
#define IFBF_PROMISC 0x1 /* promiscuous mode */
|
|
#define IFBF_IF_DETACHING 0x2 /* interface is detaching */
|
|
#define IFBF_LLADDR 0x4 /* specific link address requested */
|
|
#define IFBF_CHANGE_IN_PROGRESS 0x8 /* interface add/remove in progress */
|
|
|
|
static int bond_get_status(ifbond_ref ifb, struct if_bond_req * ibr_p,
|
|
user_addr_t datap);
|
|
|
|
static __inline__ bool
|
|
ifbond_flags_if_detaching(ifbond_ref ifb)
|
|
{
|
|
return (ifb->ifb_flags & IFBF_IF_DETACHING) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_flags_set_if_detaching(ifbond_ref ifb)
|
|
{
|
|
ifb->ifb_flags |= IFBF_IF_DETACHING;
|
|
return;
|
|
}
|
|
|
|
static __inline__ bool
|
|
ifbond_flags_lladdr(ifbond_ref ifb)
|
|
{
|
|
return (ifb->ifb_flags & IFBF_LLADDR) != 0;
|
|
}
|
|
|
|
static __inline__ bool
|
|
ifbond_flags_change_in_progress(ifbond_ref ifb)
|
|
{
|
|
return (ifb->ifb_flags & IFBF_CHANGE_IN_PROGRESS) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_flags_set_change_in_progress(ifbond_ref ifb)
|
|
{
|
|
ifb->ifb_flags |= IFBF_CHANGE_IN_PROGRESS;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_flags_clear_change_in_progress(ifbond_ref ifb)
|
|
{
|
|
ifb->ifb_flags &= ~IFBF_CHANGE_IN_PROGRESS;
|
|
return;
|
|
}
|
|
|
|
static __inline__ bool
|
|
ifbond_flags_promisc(ifbond_ref ifb)
|
|
{
|
|
return (ifb->ifb_flags & IFBF_PROMISC) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_flags_set_promisc(ifbond_ref ifb)
|
|
{
|
|
ifb->ifb_flags |= IFBF_PROMISC;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_flags_clear_promisc(ifbond_ref ifb)
|
|
{
|
|
ifb->ifb_flags &= ~IFBF_PROMISC;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* bondport_ref->po_flags bits
|
|
*/
|
|
#define BONDPORT_FLAGS_NTT 0x01
|
|
#define BONDPORT_FLAGS_READY 0x02
|
|
#define BONDPORT_FLAGS_SELECTED_CHANGED 0x04
|
|
#define BONDPORT_FLAGS_MUX_ATTACHED 0x08
|
|
#define BONDPORT_FLAGS_DISTRIBUTING 0x10
|
|
#define BONDPORT_FLAGS_UNUSED2 0x20
|
|
#define BONDPORT_FLAGS_UNUSED3 0x40
|
|
#define BONDPORT_FLAGS_UNUSED4 0x80
|
|
|
|
static __inline__ void
|
|
bondport_flags_set_ntt(bondport_ref p)
|
|
{
|
|
p->po_flags |= BONDPORT_FLAGS_NTT;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_clear_ntt(bondport_ref p)
|
|
{
|
|
p->po_flags &= ~BONDPORT_FLAGS_NTT;
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_flags_ntt(bondport_ref p)
|
|
{
|
|
return (p->po_flags & BONDPORT_FLAGS_NTT) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_set_ready(bondport_ref p)
|
|
{
|
|
p->po_flags |= BONDPORT_FLAGS_READY;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_clear_ready(bondport_ref p)
|
|
{
|
|
p->po_flags &= ~BONDPORT_FLAGS_READY;
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_flags_ready(bondport_ref p)
|
|
{
|
|
return (p->po_flags & BONDPORT_FLAGS_READY) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_set_selected_changed(bondport_ref p)
|
|
{
|
|
p->po_flags |= BONDPORT_FLAGS_SELECTED_CHANGED;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_clear_selected_changed(bondport_ref p)
|
|
{
|
|
p->po_flags &= ~BONDPORT_FLAGS_SELECTED_CHANGED;
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_flags_selected_changed(bondport_ref p)
|
|
{
|
|
return (p->po_flags & BONDPORT_FLAGS_SELECTED_CHANGED) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_set_mux_attached(bondport_ref p)
|
|
{
|
|
p->po_flags |= BONDPORT_FLAGS_MUX_ATTACHED;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_clear_mux_attached(bondport_ref p)
|
|
{
|
|
p->po_flags &= ~BONDPORT_FLAGS_MUX_ATTACHED;
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_flags_mux_attached(bondport_ref p)
|
|
{
|
|
return (p->po_flags & BONDPORT_FLAGS_MUX_ATTACHED) != 0;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_set_distributing(bondport_ref p)
|
|
{
|
|
p->po_flags |= BONDPORT_FLAGS_DISTRIBUTING;
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bondport_flags_clear_distributing(bondport_ref p)
|
|
{
|
|
p->po_flags &= ~BONDPORT_FLAGS_DISTRIBUTING;
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_flags_distributing(bondport_ref p)
|
|
{
|
|
return (p->po_flags & BONDPORT_FLAGS_DISTRIBUTING) != 0;
|
|
}
|
|
|
|
typedef struct bond_globals_s {
|
|
struct ifbond_list ifbond_list;
|
|
lacp_system system;
|
|
lacp_system_priority system_priority;
|
|
} * bond_globals_ref;
|
|
|
|
static bond_globals_ref g_bond;
|
|
|
|
/**
|
|
** packet_buffer routines
|
|
** - thin wrapper for mbuf
|
|
**/
|
|
|
|
typedef struct mbuf * packet_buffer_ref;
|
|
|
|
static packet_buffer_ref
|
|
packet_buffer_allocate(int length)
|
|
{
|
|
packet_buffer_ref m;
|
|
int size;
|
|
|
|
/* leave room for ethernet header */
|
|
size = length + sizeof(struct ether_header);
|
|
if (size > (int)MHLEN) {
|
|
if (size > (int)MCLBYTES) {
|
|
printf("bond: packet_buffer_allocate size %d > max %u\n",
|
|
size, MCLBYTES);
|
|
return NULL;
|
|
}
|
|
m = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR);
|
|
} else {
|
|
m = m_gethdr(M_WAITOK, MT_DATA);
|
|
}
|
|
if (m == NULL) {
|
|
return NULL;
|
|
}
|
|
m->m_len = size;
|
|
m->m_pkthdr.len = size;
|
|
return m;
|
|
}
|
|
|
|
static void *
|
|
packet_buffer_byteptr(packet_buffer_ref buf)
|
|
{
|
|
return m_mtod_current(buf) + sizeof(struct ether_header);
|
|
}
|
|
|
|
typedef enum {
|
|
LAEventStart,
|
|
LAEventTimeout,
|
|
LAEventPacket,
|
|
LAEventMediaChange,
|
|
LAEventSelectedChange,
|
|
LAEventPortMoved,
|
|
LAEventReady
|
|
} LAEvent;
|
|
|
|
/**
|
|
** Receive machine
|
|
**/
|
|
static void
|
|
bondport_receive_machine(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
/**
|
|
** Periodic Transmission machine
|
|
**/
|
|
static void
|
|
bondport_periodic_transmit_machine(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
/**
|
|
** Transmit machine
|
|
**/
|
|
#define TRANSMIT_MACHINE_TX_IMMEDIATE ((void *)1)
|
|
|
|
static void
|
|
bondport_transmit_machine(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
/**
|
|
** Mux machine
|
|
**/
|
|
static void
|
|
bondport_mux_machine(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
/**
|
|
** bond, LAG
|
|
**/
|
|
static void
|
|
ifbond_activate_LAG(ifbond_ref bond, LAG_ref lag, int active_media);
|
|
|
|
static void
|
|
ifbond_deactivate_LAG(ifbond_ref bond, LAG_ref lag);
|
|
|
|
static int
|
|
ifbond_all_ports_ready(ifbond_ref bond);
|
|
|
|
static LAG_ref
|
|
ifbond_find_best_LAG(ifbond_ref bond, int * active_media);
|
|
|
|
static int
|
|
LAG_get_aggregatable_port_count(LAG_ref lag, int * active_media);
|
|
|
|
static int
|
|
ifbond_selection(ifbond_ref bond);
|
|
|
|
static void
|
|
bond_handle_event(struct ifnet * port_ifp, int event_code);
|
|
|
|
/**
|
|
** bondport
|
|
**/
|
|
|
|
static void
|
|
bondport_receive_lacpdu(bondport_ref p, lacpdu_ref in_lacpdu_p);
|
|
|
|
static void
|
|
bondport_slow_proto_transmit(bondport_ref p, packet_buffer_ref buf);
|
|
|
|
static bondport_ref
|
|
bondport_create(struct ifnet * port_ifp, lacp_port_priority priority,
|
|
int active, int short_timeout, int * error);
|
|
static void
|
|
bondport_start(bondport_ref p);
|
|
|
|
static void
|
|
bondport_free(bondport_ref p);
|
|
|
|
static int
|
|
bondport_aggregatable(bondport_ref p);
|
|
|
|
static int
|
|
bondport_remove_from_LAG(bondport_ref p);
|
|
|
|
static void
|
|
bondport_set_selected(bondport_ref p, SelectedState s);
|
|
|
|
static int
|
|
bondport_matches_LAG(bondport_ref p, LAG_ref lag);
|
|
|
|
static void
|
|
bondport_link_status_changed(bondport_ref p);
|
|
|
|
static void
|
|
bondport_enable_distributing(bondport_ref p);
|
|
|
|
static void
|
|
bondport_disable_distributing(bondport_ref p);
|
|
|
|
static __inline__ int
|
|
bondport_collecting(bondport_ref p)
|
|
{
|
|
if (p->po_bond->ifb_mode == IF_BOND_MODE_LACP) {
|
|
return lacp_actor_partner_state_collecting(p->po_actor_state);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
** bond interface/dlil specific routines
|
|
**/
|
|
static int bond_clone_create(struct if_clone *, u_int32_t, void *);
|
|
static int bond_clone_destroy(struct ifnet *);
|
|
static int bond_output(struct ifnet *ifp, struct mbuf *m);
|
|
static int bond_ioctl(struct ifnet *ifp, u_long cmd, void * addr);
|
|
static int bond_set_bpf_tap(struct ifnet * ifp, bpf_tap_mode mode,
|
|
bpf_packet_func func);
|
|
static int bond_attach_protocol(struct ifnet *ifp);
|
|
static int bond_detach_protocol(struct ifnet *ifp);
|
|
static errno_t bond_iff_input(void *cookie, ifnet_t ifp,
|
|
protocol_family_t protocol, mbuf_t *data, char **frame_ptr);
|
|
static int bond_attach_filter(struct ifnet *ifp, interface_filter_t * filter_p);
|
|
static int bond_setmulti(struct ifnet *ifp);
|
|
static int bond_add_interface(struct ifnet * ifp, struct ifnet * port_ifp);
|
|
static int bond_remove_interface(ifbond_ref ifb, struct ifnet * port_ifp);
|
|
static void bond_if_free(struct ifnet * ifp);
|
|
static void interface_link_event(struct ifnet * ifp, u_int32_t event_code);
|
|
|
|
static struct if_clone bond_cloner = IF_CLONE_INITIALIZER(BONDNAME,
|
|
bond_clone_create,
|
|
bond_clone_destroy,
|
|
0,
|
|
BOND_MAXUNIT);
|
|
|
|
static int
|
|
siocsifmtu(struct ifnet * ifp, int mtu)
|
|
{
|
|
struct ifreq ifr;
|
|
|
|
bzero(&ifr, sizeof(ifr));
|
|
ifr.ifr_mtu = mtu;
|
|
return ifnet_ioctl(ifp, 0, SIOCSIFMTU, &ifr);
|
|
}
|
|
|
|
static int
|
|
siocgifdevmtu(struct ifnet * ifp, struct ifdevmtu * ifdm_p)
|
|
{
|
|
struct ifreq ifr;
|
|
int error;
|
|
|
|
bzero(&ifr, sizeof(ifr));
|
|
error = ifnet_ioctl(ifp, 0, SIOCGIFDEVMTU, &ifr);
|
|
if (error == 0) {
|
|
*ifdm_p = ifr.ifr_devmtu;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static __inline__ void
|
|
ether_addr_copy(void * dest, const void * source)
|
|
{
|
|
bcopy(source, dest, ETHER_ADDR_LEN);
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_retain(ifbond_ref ifb)
|
|
{
|
|
os_ref_retain(&ifb->ifb_retain_count);
|
|
}
|
|
|
|
static __inline__ void
|
|
ifbond_release(ifbond_ref ifb)
|
|
{
|
|
if (os_ref_release(&ifb->ifb_retain_count) != 0) {
|
|
return;
|
|
}
|
|
|
|
if (if_bond_debug) {
|
|
printf("ifbond_release(%s)\n", ifb->ifb_name);
|
|
}
|
|
if (ifb->ifb_ifma_slow_proto != NULL) {
|
|
if (if_bond_debug) {
|
|
printf("ifbond_release(%s) removing multicast\n",
|
|
ifb->ifb_name);
|
|
}
|
|
(void) if_delmulti_anon(ifb->ifb_ifma_slow_proto->ifma_ifp,
|
|
ifb->ifb_ifma_slow_proto->ifma_addr);
|
|
IFMA_REMREF(ifb->ifb_ifma_slow_proto);
|
|
}
|
|
kfree_type(bondport_ref, ifb->ifb_distributing_max,
|
|
ifb->ifb_distributing_array);
|
|
kfree_type(struct ifbond_s, ifb);
|
|
}
|
|
|
|
/*
|
|
* Function: ifbond_wait
|
|
* Purpose:
|
|
* Allows a single thread to gain exclusive access to the ifbond
|
|
* data structure. Some operations take a long time to complete,
|
|
* and some have side-effects that we can't predict. Holding the
|
|
* bond_lock() across such operations is not possible.
|
|
*
|
|
* For example:
|
|
* 1) The SIOCSIFLLADDR ioctl takes a long time (several seconds) to
|
|
* complete. Simply holding the bond_lock() would freeze all other
|
|
* data structure accesses during that time.
|
|
* 2) When we attach our protocol to the interface, a dlil event is
|
|
* generated and invokes our bond_event() function. bond_event()
|
|
* needs to take the bond_lock(), but we're already holding it, so
|
|
* we're deadlocked against ourselves.
|
|
* Notes:
|
|
* Before calling, you must be holding the bond_lock and have taken
|
|
* a reference on the ifbond_ref.
|
|
*/
|
|
static void
|
|
ifbond_wait(ifbond_ref ifb, const char * msg)
|
|
{
|
|
int waited = 0;
|
|
|
|
/* other add/remove in progress */
|
|
while (ifbond_flags_change_in_progress(ifb)) {
|
|
if (if_bond_debug) {
|
|
printf("%s: %s msleep\n", ifb->ifb_name, msg);
|
|
}
|
|
waited = 1;
|
|
(void)msleep(ifb, &bond_lck_mtx, PZERO, msg, 0);
|
|
}
|
|
/* prevent other bond list remove/add from taking place */
|
|
ifbond_flags_set_change_in_progress(ifb);
|
|
if (if_bond_debug && waited) {
|
|
printf("%s: %s woke up\n", ifb->ifb_name, msg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Function: ifbond_signal
|
|
* Purpose:
|
|
* Allows the thread that previously invoked ifbond_wait() to
|
|
* give up exclusive access to the ifbond data structure, and wake up
|
|
* any other threads waiting to access
|
|
* Notes:
|
|
* Before calling, you must be holding the bond_lock and have taken
|
|
* a reference on the ifbond_ref.
|
|
*/
|
|
static void
|
|
ifbond_signal(ifbond_ref ifb, const char * msg)
|
|
{
|
|
ifbond_flags_clear_change_in_progress(ifb);
|
|
wakeup((caddr_t)ifb);
|
|
if (if_bond_debug) {
|
|
printf("%s: %s wakeup\n", ifb->ifb_name, msg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Media information
|
|
**/
|
|
|
|
static int
|
|
link_speed(int active)
|
|
{
|
|
switch (IFM_SUBTYPE(active)) {
|
|
case IFM_AUTO:
|
|
case IFM_MANUAL:
|
|
case IFM_NONE:
|
|
return 0;
|
|
case IFM_10_T:
|
|
case IFM_10_2:
|
|
case IFM_10_5:
|
|
case IFM_10_STP:
|
|
case IFM_10_FL:
|
|
return 10;
|
|
case IFM_100_TX:
|
|
case IFM_100_FX:
|
|
case IFM_100_T4:
|
|
case IFM_100_VG:
|
|
case IFM_100_T2:
|
|
return 100;
|
|
case IFM_1000_SX:
|
|
case IFM_1000_LX:
|
|
case IFM_1000_CX:
|
|
case IFM_1000_TX:
|
|
case IFM_1000_CX_SGMII:
|
|
case IFM_1000_KX:
|
|
return 1000;
|
|
case IFM_HPNA_1:
|
|
return 1;
|
|
default:
|
|
/* assume that new defined types are going to be at least 10GigE */
|
|
case IFM_10G_SR:
|
|
case IFM_10G_LR:
|
|
case IFM_10G_KX4:
|
|
case IFM_10G_KR:
|
|
case IFM_10G_CR1:
|
|
case IFM_10G_ER:
|
|
return 10000;
|
|
case IFM_2500_T:
|
|
return 2500;
|
|
case IFM_5000_T:
|
|
return 5000;
|
|
case IFM_20G_KR2:
|
|
return 20000;
|
|
case IFM_25G_CR:
|
|
case IFM_25G_KR:
|
|
case IFM_25G_SR:
|
|
case IFM_25G_LR:
|
|
return 25000;
|
|
case IFM_40G_CR4:
|
|
case IFM_40G_SR4:
|
|
case IFM_40G_LR4:
|
|
case IFM_40G_KR4:
|
|
return 40000;
|
|
case IFM_50G_CR2:
|
|
case IFM_50G_KR2:
|
|
case IFM_50G_SR2:
|
|
case IFM_50G_LR2:
|
|
return 50000;
|
|
case IFM_56G_R4:
|
|
return 56000;
|
|
case IFM_100G_CR4:
|
|
case IFM_100G_SR4:
|
|
case IFM_100G_KR4:
|
|
case IFM_100G_LR4:
|
|
return 100000;
|
|
}
|
|
}
|
|
|
|
static __inline__ int
|
|
media_active(const struct media_info * mi)
|
|
{
|
|
if ((mi->mi_status & IFM_AVALID) == 0) {
|
|
return 1;
|
|
}
|
|
return (mi->mi_status & IFM_ACTIVE) != 0;
|
|
}
|
|
|
|
static __inline__ int
|
|
media_full_duplex(const struct media_info * mi)
|
|
{
|
|
return (mi->mi_active & IFM_FDX) != 0;
|
|
}
|
|
|
|
static __inline__ int
|
|
media_type_unknown(const struct media_info * mi)
|
|
{
|
|
int unknown;
|
|
|
|
switch (IFM_SUBTYPE(mi->mi_active)) {
|
|
case IFM_AUTO:
|
|
case IFM_MANUAL:
|
|
case IFM_NONE:
|
|
unknown = 1;
|
|
break;
|
|
default:
|
|
unknown = 0;
|
|
break;
|
|
}
|
|
return unknown;
|
|
}
|
|
|
|
static __inline__ int
|
|
media_ok(const struct media_info * mi)
|
|
{
|
|
return media_full_duplex(mi) || media_type_unknown(mi);
|
|
}
|
|
|
|
static __inline__ int
|
|
media_speed(const struct media_info * mi)
|
|
{
|
|
return link_speed(mi->mi_active);
|
|
}
|
|
|
|
static struct media_info
|
|
interface_media_info(struct ifnet * ifp)
|
|
{
|
|
struct ifmediareq ifmr;
|
|
struct media_info mi;
|
|
|
|
bzero(&mi, sizeof(mi));
|
|
bzero(&ifmr, sizeof(ifmr));
|
|
if (ifnet_ioctl(ifp, 0, SIOCGIFMEDIA, &ifmr) == 0) {
|
|
if (ifmr.ifm_count != 0) {
|
|
mi.mi_status = ifmr.ifm_status;
|
|
mi.mi_active = ifmr.ifm_active;
|
|
}
|
|
}
|
|
return mi;
|
|
}
|
|
|
|
static int
|
|
if_siflladdr(struct ifnet * ifp, const struct ether_addr * ea_p)
|
|
{
|
|
struct ifreq ifr;
|
|
|
|
/*
|
|
* XXX setting the sa_len to ETHER_ADDR_LEN is wrong, but the driver
|
|
* currently expects it that way
|
|
*/
|
|
ifr.ifr_addr.sa_family = AF_UNSPEC;
|
|
ifr.ifr_addr.sa_len = ETHER_ADDR_LEN;
|
|
ether_addr_copy(ifr.ifr_addr.sa_data, ea_p);
|
|
return ifnet_ioctl(ifp, 0, SIOCSIFLLADDR, &ifr);
|
|
}
|
|
|
|
/**
|
|
** bond_globals
|
|
**/
|
|
static bond_globals_ref
|
|
bond_globals_create(lacp_system_priority sys_pri,
|
|
lacp_system_ref sys)
|
|
{
|
|
bond_globals_ref b;
|
|
|
|
b = kalloc_type(struct bond_globals_s, Z_WAITOK | Z_ZERO | Z_NOFAIL);
|
|
TAILQ_INIT(&b->ifbond_list);
|
|
b->system = *sys;
|
|
b->system_priority = sys_pri;
|
|
return b;
|
|
}
|
|
|
|
static int
|
|
bond_globals_init(void)
|
|
{
|
|
bond_globals_ref b;
|
|
int i;
|
|
struct ifnet * ifp;
|
|
|
|
bond_assert_lock_not_held();
|
|
|
|
if (g_bond != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* use en0's ethernet address as the system identifier, and if it's not
|
|
* there, use en1 .. en3
|
|
*/
|
|
ifp = NULL;
|
|
for (i = 0; i < 4; i++) {
|
|
char ifname[IFNAMSIZ + 1];
|
|
snprintf(ifname, sizeof(ifname), "en%d", i);
|
|
ifp = ifunit(ifname);
|
|
if (ifp != NULL) {
|
|
break;
|
|
}
|
|
}
|
|
b = NULL;
|
|
if (ifp != NULL) {
|
|
b = bond_globals_create(0x8000, (lacp_system_ref)IF_LLADDR(ifp));
|
|
}
|
|
bond_lock();
|
|
if (g_bond != NULL) {
|
|
bond_unlock();
|
|
kfree_type(struct bond_globals_s, b);
|
|
return 0;
|
|
}
|
|
g_bond = b;
|
|
bond_unlock();
|
|
if (ifp == NULL) {
|
|
return ENXIO;
|
|
}
|
|
if (b == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
bond_bpf_vlan(struct ifnet * ifp, struct mbuf * m,
|
|
const struct ether_header * eh_p,
|
|
u_int16_t vlan_tag, bpf_packet_func func)
|
|
{
|
|
struct ether_vlan_header * vlh_p;
|
|
struct mbuf * vl_m;
|
|
|
|
vl_m = m_get(M_DONTWAIT, MT_DATA);
|
|
if (vl_m == NULL) {
|
|
return;
|
|
}
|
|
/* populate a new mbuf containing the vlan ethernet header */
|
|
vl_m->m_len = ETHER_HDR_LEN + ETHER_VLAN_ENCAP_LEN;
|
|
vlh_p = mtod(vl_m, struct ether_vlan_header *);
|
|
bcopy(eh_p, vlh_p, offsetof(struct ether_header, ether_type));
|
|
vlh_p->evl_encap_proto = htons(ETHERTYPE_VLAN);
|
|
vlh_p->evl_tag = htons(vlan_tag);
|
|
vlh_p->evl_proto = eh_p->ether_type;
|
|
vl_m->m_next = m;
|
|
(*func)(ifp, vl_m);
|
|
vl_m->m_next = NULL;
|
|
m_free(vl_m);
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bond_bpf_output(struct ifnet * ifp, struct mbuf * m,
|
|
bpf_packet_func func)
|
|
{
|
|
if (func != NULL) {
|
|
if (m->m_pkthdr.csum_flags & CSUM_VLAN_TAG_VALID) {
|
|
const struct ether_header * eh_p;
|
|
eh_p = mtod(m, const struct ether_header *);
|
|
m->m_data += ETHER_HDR_LEN;
|
|
m->m_len -= ETHER_HDR_LEN;
|
|
bond_bpf_vlan(ifp, m, eh_p, m->m_pkthdr.vlan_tag, func);
|
|
m->m_data -= ETHER_HDR_LEN;
|
|
m->m_len += ETHER_HDR_LEN;
|
|
} else {
|
|
(*func)(ifp, m);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static __inline__ void
|
|
bond_bpf_input(ifnet_t ifp, mbuf_t m, const struct ether_header * eh_p,
|
|
bpf_packet_func func)
|
|
{
|
|
if (func != NULL) {
|
|
if (m->m_pkthdr.csum_flags & CSUM_VLAN_TAG_VALID) {
|
|
bond_bpf_vlan(ifp, m, eh_p, m->m_pkthdr.vlan_tag, func);
|
|
} else {
|
|
/* restore the header */
|
|
m->m_data -= ETHER_HDR_LEN;
|
|
m->m_len += ETHER_HDR_LEN;
|
|
(*func)(ifp, m);
|
|
m->m_data += ETHER_HDR_LEN;
|
|
m->m_len -= ETHER_HDR_LEN;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Function: bond_setmulti
|
|
* Purpose:
|
|
* Enable multicast reception on "our" interface by enabling multicasts on
|
|
* each of the member ports.
|
|
*/
|
|
static int
|
|
bond_setmulti(struct ifnet * ifp)
|
|
{
|
|
ifbond_ref ifb;
|
|
int error;
|
|
int result = 0;
|
|
bondport_ref p;
|
|
|
|
bond_lock();
|
|
ifb = ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)
|
|
|| TAILQ_EMPTY(&ifb->ifb_port_list)) {
|
|
bond_unlock();
|
|
return 0;
|
|
}
|
|
ifbond_retain(ifb);
|
|
ifbond_wait(ifb, "bond_setmulti");
|
|
|
|
if (ifbond_flags_if_detaching(ifb)) {
|
|
/* someone destroyed the bond while we were waiting */
|
|
result = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
bond_unlock();
|
|
|
|
/* ifbond_wait() let's us safely walk the list without holding the lock */
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
struct ifnet * port_ifp = p->po_ifp;
|
|
|
|
error = multicast_list_program(&p->po_multicast,
|
|
ifp, port_ifp);
|
|
if (error != 0) {
|
|
printf("bond_setmulti(%s): "
|
|
"multicast_list_program(%s%d) failed, %d\n",
|
|
ifb->ifb_name, ifnet_name(port_ifp),
|
|
ifnet_unit(port_ifp), error);
|
|
result = error;
|
|
}
|
|
}
|
|
bond_lock();
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
bond_unlock();
|
|
ifbond_release(ifb);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
bond_clone_attach(void)
|
|
{
|
|
int error;
|
|
|
|
if ((error = if_clone_attach(&bond_cloner)) != 0) {
|
|
return error;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
ifbond_add_slow_proto_multicast(ifbond_ref ifb)
|
|
{
|
|
int error;
|
|
struct ifmultiaddr * ifma = NULL;
|
|
struct sockaddr_dl sdl;
|
|
|
|
bond_assert_lock_not_held();
|
|
|
|
SOCKADDR_ZERO(&sdl, sizeof(sdl));
|
|
sdl.sdl_len = sizeof(sdl);
|
|
sdl.sdl_family = AF_LINK;
|
|
sdl.sdl_type = IFT_ETHER;
|
|
sdl.sdl_nlen = 0;
|
|
sdl.sdl_alen = sizeof(slow_proto_multicast);
|
|
bcopy(&slow_proto_multicast, sdl.sdl_data, sizeof(slow_proto_multicast));
|
|
error = if_addmulti_anon(ifb->ifb_ifp, SA(&sdl), &ifma);
|
|
if (error == 0) {
|
|
ifb->ifb_ifma_slow_proto = ifma;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_clone_create(struct if_clone * ifc, u_int32_t unit, __unused void *params)
|
|
{
|
|
int error;
|
|
ifbond_ref ifb;
|
|
ifnet_t ifp;
|
|
struct ifnet_init_eparams bond_init;
|
|
|
|
error = bond_globals_init();
|
|
if (error != 0) {
|
|
return error;
|
|
}
|
|
|
|
ifb = kalloc_type(struct ifbond_s, Z_WAITOK_ZERO_NOFAIL);
|
|
os_ref_init(&ifb->ifb_retain_count, NULL);
|
|
TAILQ_INIT(&ifb->ifb_port_list);
|
|
TAILQ_INIT(&ifb->ifb_lag_list);
|
|
ifb->ifb_key = unit + 1;
|
|
|
|
/* use the interface name as the unique id for ifp recycle */
|
|
if ((u_int32_t)snprintf(ifb->ifb_name, sizeof(ifb->ifb_name), "%s%d",
|
|
ifc->ifc_name, unit) >= sizeof(ifb->ifb_name)) {
|
|
ifbond_release(ifb);
|
|
return EINVAL;
|
|
}
|
|
|
|
bzero(&bond_init, sizeof(bond_init));
|
|
bond_init.ver = IFNET_INIT_CURRENT_VERSION;
|
|
bond_init.len = sizeof(bond_init);
|
|
bond_init.flags = IFNET_INIT_LEGACY;
|
|
bond_init.uniqueid = ifb->ifb_name;
|
|
bond_init.uniqueid_len = strlen(ifb->ifb_name);
|
|
bond_init.name = ifc->ifc_name;
|
|
bond_init.unit = unit;
|
|
bond_init.family = IFNET_FAMILY_BOND;
|
|
bond_init.type = IFT_IEEE8023ADLAG;
|
|
bond_init.output = bond_output;
|
|
bond_init.demux = ether_demux;
|
|
bond_init.add_proto = ether_add_proto;
|
|
bond_init.del_proto = ether_del_proto;
|
|
bond_init.check_multi = ether_check_multi;
|
|
bond_init.framer_extended = ether_frameout_extended;
|
|
bond_init.ioctl = bond_ioctl;
|
|
bond_init.set_bpf_tap = bond_set_bpf_tap;
|
|
bond_init.detach = bond_if_free;
|
|
bond_init.broadcast_addr = etherbroadcastaddr;
|
|
bond_init.broadcast_len = ETHER_ADDR_LEN;
|
|
bond_init.softc = ifb;
|
|
error = ifnet_allocate_extended(&bond_init, &ifp);
|
|
|
|
if (error) {
|
|
ifbond_release(ifb);
|
|
return error;
|
|
}
|
|
|
|
ifb->ifb_ifp = ifp;
|
|
ifnet_set_offload(ifp, 0);
|
|
ifnet_set_addrlen(ifp, ETHER_ADDR_LEN); /* XXX ethernet specific */
|
|
ifnet_set_flags(ifp, IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX, 0xffff);
|
|
ifnet_set_mtu(ifp, ETHERMTU);
|
|
|
|
error = ifnet_attach(ifp, NULL);
|
|
if (error != 0) {
|
|
ifnet_release(ifp);
|
|
ifbond_release(ifb);
|
|
return error;
|
|
}
|
|
error = ifbond_add_slow_proto_multicast(ifb);
|
|
if (error != 0) {
|
|
printf("bond_clone_create(%s): "
|
|
"failed to add slow_proto multicast, %d\n",
|
|
ifb->ifb_name, error);
|
|
}
|
|
|
|
/* attach as ethernet */
|
|
bpfattach(ifp, DLT_EN10MB, sizeof(struct ether_header));
|
|
|
|
bond_lock();
|
|
TAILQ_INSERT_HEAD(&g_bond->ifbond_list, ifb, ifb_bond_list);
|
|
bond_unlock();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
bond_remove_all_interfaces(ifbond_ref ifb)
|
|
{
|
|
bondport_ref p;
|
|
|
|
bond_assert_lock_held();
|
|
|
|
/*
|
|
* do this in reverse order to avoid re-programming the mac address
|
|
* as each head interface is removed
|
|
*/
|
|
while ((p = TAILQ_LAST(&ifb->ifb_port_list, port_list)) != NULL) {
|
|
bond_remove_interface(ifb, p->po_ifp);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_remove(ifbond_ref ifb)
|
|
{
|
|
bond_assert_lock_held();
|
|
ifbond_flags_set_if_detaching(ifb);
|
|
TAILQ_REMOVE(&g_bond->ifbond_list, ifb, ifb_bond_list);
|
|
bond_remove_all_interfaces(ifb);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_if_detach(struct ifnet * ifp)
|
|
{
|
|
int error;
|
|
|
|
error = ifnet_detach(ifp);
|
|
if (error) {
|
|
printf("bond_if_detach %s%d: ifnet_detach failed, %d\n",
|
|
ifnet_name(ifp), ifnet_unit(ifp), error);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static int
|
|
bond_clone_destroy(struct ifnet * ifp)
|
|
{
|
|
ifbond_ref ifb;
|
|
|
|
bond_lock();
|
|
ifb = ifnet_softc(ifp);
|
|
if (ifb == NULL || ifnet_type(ifp) != IFT_IEEE8023ADLAG) {
|
|
bond_unlock();
|
|
return 0;
|
|
}
|
|
if (ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return 0;
|
|
}
|
|
bond_remove(ifb);
|
|
bond_unlock();
|
|
bond_if_detach(ifp);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bond_set_bpf_tap(struct ifnet * ifp, bpf_tap_mode mode, bpf_packet_func func)
|
|
{
|
|
ifbond_ref ifb;
|
|
|
|
bond_lock();
|
|
ifb = ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return ENODEV;
|
|
}
|
|
switch (mode) {
|
|
case BPF_TAP_DISABLE:
|
|
ifb->ifb_bpf_input = ifb->ifb_bpf_output = NULL;
|
|
break;
|
|
|
|
case BPF_TAP_INPUT:
|
|
ifb->ifb_bpf_input = func;
|
|
break;
|
|
|
|
case BPF_TAP_OUTPUT:
|
|
ifb->ifb_bpf_output = func;
|
|
break;
|
|
|
|
case BPF_TAP_INPUT_OUTPUT:
|
|
ifb->ifb_bpf_input = ifb->ifb_bpf_output = func;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
bond_unlock();
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t
|
|
ether_header_hash(struct ether_header * eh_p)
|
|
{
|
|
uint32_t h;
|
|
|
|
/* get 32-bits from destination ether and ether type */
|
|
h = (*((uint16_t *)&eh_p->ether_dhost[4]) << 16)
|
|
| eh_p->ether_type;
|
|
h ^= *((uint32_t *)&eh_p->ether_dhost[0]);
|
|
return h;
|
|
}
|
|
|
|
static struct mbuf *
|
|
S_mbuf_skip_to_offset(struct mbuf * m, int32_t * offset)
|
|
{
|
|
int len;
|
|
|
|
len = m->m_len;
|
|
while (*offset >= len) {
|
|
*offset -= len;
|
|
m = m->m_next;
|
|
if (m == NULL) {
|
|
break;
|
|
}
|
|
len = m->m_len;
|
|
}
|
|
return m;
|
|
}
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
static __inline__ uint32_t
|
|
make_uint32(u_char c0, u_char c1, u_char c2, u_char c3)
|
|
{
|
|
return ((uint32_t)c0 << 24) | ((uint32_t)c1 << 16)
|
|
| ((uint32_t)c2 << 8) | (uint32_t)c3;
|
|
}
|
|
#else /* BYTE_ORDER == LITTLE_ENDIAN */
|
|
static __inline__ uint32_t
|
|
make_uint32(u_char c0, u_char c1, u_char c2, u_char c3)
|
|
{
|
|
return ((uint32_t)c3 << 24) | ((uint32_t)c2 << 16)
|
|
| ((uint32_t)c1 << 8) | (uint32_t)c0;
|
|
}
|
|
#endif /* BYTE_ORDER == LITTLE_ENDIAN */
|
|
|
|
static int
|
|
S_mbuf_copy_uint32(struct mbuf * m, int32_t offset, uint32_t * val)
|
|
{
|
|
struct mbuf * current;
|
|
u_char * current_data;
|
|
struct mbuf * next;
|
|
u_char * next_data;
|
|
int space_current;
|
|
|
|
current = S_mbuf_skip_to_offset(m, &offset);
|
|
if (current == NULL) {
|
|
return 1;
|
|
}
|
|
current_data = mtod(current, u_char *) + offset;
|
|
space_current = current->m_len - offset;
|
|
if (space_current >= (int)sizeof(uint32_t)) {
|
|
*val = *((uint32_t *)current_data);
|
|
return 0;
|
|
}
|
|
next = current->m_next;
|
|
if (next == NULL || (next->m_len + space_current) < (int)sizeof(uint32_t)) {
|
|
return 1;
|
|
}
|
|
next_data = mtod(next, u_char *);
|
|
switch (space_current) {
|
|
case 1:
|
|
*val = make_uint32(current_data[0], next_data[0],
|
|
next_data[1], next_data[2]);
|
|
break;
|
|
case 2:
|
|
*val = make_uint32(current_data[0], current_data[1],
|
|
next_data[0], next_data[1]);
|
|
break;
|
|
default:
|
|
*val = make_uint32(current_data[0], current_data[1],
|
|
current_data[2], next_data[0]);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define IP_SRC_OFFSET (offsetof(struct ip, ip_src) - offsetof(struct ip, ip_p))
|
|
#define IP_DST_OFFSET (offsetof(struct ip, ip_dst) - offsetof(struct ip, ip_p))
|
|
|
|
static uint32_t
|
|
ip_header_hash(struct mbuf * m)
|
|
{
|
|
u_char * data;
|
|
struct in_addr ip_dst;
|
|
struct in_addr ip_src;
|
|
u_char ip_p;
|
|
int32_t offset;
|
|
struct mbuf * orig_m = m;
|
|
|
|
/* find the IP protocol field relative to the start of the packet */
|
|
offset = offsetof(struct ip, ip_p) + sizeof(struct ether_header);
|
|
m = S_mbuf_skip_to_offset(m, &offset);
|
|
if (m == NULL || m->m_len < 1) {
|
|
goto bad_ip_packet;
|
|
}
|
|
data = mtod(m, u_char *) + offset;
|
|
ip_p = *data;
|
|
|
|
/* find the IP src relative to the IP protocol */
|
|
if ((m->m_len - offset)
|
|
>= (int)(IP_SRC_OFFSET + sizeof(struct in_addr) * 2)) {
|
|
/* this should be the normal case */
|
|
ip_src = *(struct in_addr *)(data + IP_SRC_OFFSET);
|
|
ip_dst = *(struct in_addr *)(data + IP_DST_OFFSET);
|
|
} else {
|
|
if (S_mbuf_copy_uint32(m, offset + IP_SRC_OFFSET,
|
|
(uint32_t *)&ip_src.s_addr)) {
|
|
goto bad_ip_packet;
|
|
}
|
|
if (S_mbuf_copy_uint32(m, offset + IP_DST_OFFSET,
|
|
(uint32_t *)&ip_dst.s_addr)) {
|
|
goto bad_ip_packet;
|
|
}
|
|
}
|
|
return ntohl(ip_dst.s_addr) ^ ntohl(ip_src.s_addr) ^ ((uint32_t)ip_p);
|
|
|
|
bad_ip_packet:
|
|
return ether_header_hash(mtod(orig_m, struct ether_header *));
|
|
}
|
|
|
|
#define IP6_ADDRS_LEN (sizeof(struct in6_addr) * 2)
|
|
static uint32_t
|
|
ipv6_header_hash(struct mbuf * m)
|
|
{
|
|
u_char * data;
|
|
int i;
|
|
int32_t offset;
|
|
struct mbuf * orig_m = m;
|
|
uint32_t * scan;
|
|
uint32_t val;
|
|
|
|
/* find the IP protocol field relative to the start of the packet */
|
|
offset = offsetof(struct ip6_hdr, ip6_src) + sizeof(struct ether_header);
|
|
m = S_mbuf_skip_to_offset(m, &offset);
|
|
if (m == NULL) {
|
|
goto bad_ipv6_packet;
|
|
}
|
|
data = mtod(m, u_char *) + offset;
|
|
val = 0;
|
|
if ((m->m_len - offset) >= (int)IP6_ADDRS_LEN) {
|
|
/* this should be the normal case */
|
|
for (i = 0, scan = (uint32_t *)data;
|
|
i < (int)(IP6_ADDRS_LEN / sizeof(uint32_t));
|
|
i++, scan++) {
|
|
val ^= *scan;
|
|
}
|
|
} else {
|
|
for (i = 0; i < (int)(IP6_ADDRS_LEN / sizeof(uint32_t)); i++) {
|
|
uint32_t tmp;
|
|
if (S_mbuf_copy_uint32(m, offset + i * sizeof(uint32_t),
|
|
(uint32_t *)&tmp)) {
|
|
goto bad_ipv6_packet;
|
|
}
|
|
val ^= tmp;
|
|
}
|
|
}
|
|
return ntohl(val);
|
|
|
|
bad_ipv6_packet:
|
|
return ether_header_hash(mtod(orig_m, struct ether_header *));
|
|
}
|
|
|
|
static int
|
|
bond_output(struct ifnet * ifp, struct mbuf * m)
|
|
{
|
|
bpf_packet_func bpf_func;
|
|
uint32_t h;
|
|
ifbond_ref ifb;
|
|
struct ifnet * port_ifp = NULL;
|
|
int err;
|
|
struct flowadv adv = { .code = FADV_SUCCESS };
|
|
|
|
if (m == 0) {
|
|
return 0;
|
|
}
|
|
if ((m->m_flags & M_PKTHDR) == 0) {
|
|
m_freem(m);
|
|
return 0;
|
|
}
|
|
if (m->m_pkthdr.pkt_flowid != 0) {
|
|
h = m->m_pkthdr.pkt_flowid;
|
|
} else {
|
|
struct ether_header * eh_p;
|
|
|
|
eh_p = mtod(m, struct ether_header *);
|
|
switch (ntohs(eh_p->ether_type)) {
|
|
case ETHERTYPE_IP:
|
|
h = ip_header_hash(m);
|
|
break;
|
|
case ETHERTYPE_IPV6:
|
|
h = ipv6_header_hash(m);
|
|
break;
|
|
default:
|
|
h = ether_header_hash(eh_p);
|
|
break;
|
|
}
|
|
}
|
|
bond_lock();
|
|
ifb = ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)
|
|
|| ifb->ifb_distributing_count == 0) {
|
|
goto done;
|
|
}
|
|
h %= ifb->ifb_distributing_count;
|
|
port_ifp = ifb->ifb_distributing_array[h]->po_ifp;
|
|
bpf_func = ifb->ifb_bpf_output;
|
|
bond_unlock();
|
|
|
|
if (m->m_pkthdr.csum_flags & CSUM_VLAN_TAG_VALID) {
|
|
(void)ifnet_stat_increment_out(ifp, 1,
|
|
m->m_pkthdr.len + ETHER_VLAN_ENCAP_LEN,
|
|
0);
|
|
} else {
|
|
(void)ifnet_stat_increment_out(ifp, 1, m->m_pkthdr.len, 0);
|
|
}
|
|
bond_bpf_output(ifp, m, bpf_func);
|
|
|
|
err = dlil_output(port_ifp, PF_BOND, m, NULL, NULL, 1, &adv);
|
|
|
|
if (err == 0) {
|
|
if (adv.code == FADV_FLOW_CONTROLLED) {
|
|
err = EQFULL;
|
|
} else if (adv.code == FADV_SUSPENDED) {
|
|
err = EQSUSPENDED;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
|
|
done:
|
|
bond_unlock();
|
|
m_freem(m);
|
|
return 0;
|
|
}
|
|
|
|
static bondport_ref
|
|
ifbond_lookup_port(ifbond_ref ifb, struct ifnet * port_ifp)
|
|
{
|
|
bondport_ref p;
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
if (p->po_ifp == port_ifp) {
|
|
return p;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static bondport_ref
|
|
bond_lookup_port(struct ifnet * port_ifp)
|
|
{
|
|
ifbond_ref ifb;
|
|
bondport_ref port;
|
|
|
|
TAILQ_FOREACH(ifb, &g_bond->ifbond_list, ifb_bond_list) {
|
|
port = ifbond_lookup_port(ifb, port_ifp);
|
|
if (port != NULL) {
|
|
return port;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
bond_receive_lacpdu(struct mbuf * m, struct ifnet * port_ifp)
|
|
{
|
|
struct ifnet * bond_ifp = NULL;
|
|
ifbond_ref ifb;
|
|
int event_code = 0;
|
|
bool need_link_update = false;
|
|
bondport_ref p;
|
|
|
|
bond_lock();
|
|
if ((ifnet_eflags(port_ifp) & IFEF_BOND) == 0) {
|
|
goto done;
|
|
}
|
|
p = bond_lookup_port(port_ifp);
|
|
if (p == NULL) {
|
|
goto done;
|
|
}
|
|
if (p->po_enabled == 0) {
|
|
goto done;
|
|
}
|
|
ifb = p->po_bond;
|
|
if (ifb->ifb_mode != IF_BOND_MODE_LACP) {
|
|
goto done;
|
|
}
|
|
/*
|
|
* Work-around for rdar://problem/51372042
|
|
* Sometimes, the link comes up but the driver doesn't report the
|
|
* negotiated medium at that time. When we receive an LACPDU packet,
|
|
* and the medium is unknown, force a link status check. Don't force
|
|
* the link status check more often than _FORCE_LINK_EVENT_INTERVAL
|
|
* seconds.
|
|
*/
|
|
#define _FORCE_LINK_EVENT_INTERVAL 1
|
|
if (media_type_unknown(&p->po_media_info)) {
|
|
uint64_t now = net_uptime();
|
|
|
|
if ((now - p->po_force_link_event_time) >=
|
|
_FORCE_LINK_EVENT_INTERVAL) {
|
|
need_link_update = true;
|
|
p->po_force_link_event_time = now;
|
|
}
|
|
}
|
|
bondport_receive_lacpdu(p, (lacpdu_ref)m_mtod_current(m));
|
|
if (ifbond_selection(ifb)) {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
/* XXX need to take a reference on bond_ifp */
|
|
bond_ifp = ifb->ifb_ifp;
|
|
ifb->ifb_last_link_event = event_code;
|
|
} else {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
if (event_code != ifb->ifb_last_link_event) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("%s: (receive) generating LINK event\n",
|
|
ifb->ifb_name);
|
|
}
|
|
bond_ifp = ifb->ifb_ifp;
|
|
ifb->ifb_last_link_event = event_code;
|
|
}
|
|
}
|
|
|
|
done:
|
|
bond_unlock();
|
|
if (bond_ifp != NULL) {
|
|
interface_link_event(bond_ifp, event_code);
|
|
}
|
|
m_freem(m);
|
|
if (need_link_update) {
|
|
if (if_bond_debug != 0) {
|
|
printf("bond: simulating link status changed event");
|
|
}
|
|
bond_handle_event(port_ifp, KEV_DL_LINK_ON);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_receive_la_marker_pdu(struct mbuf * m, struct ifnet * port_ifp)
|
|
{
|
|
la_marker_pdu_ref marker_p;
|
|
bondport_ref p;
|
|
|
|
marker_p = (la_marker_pdu_ref)(m_mtod_current(m) + ETHER_HDR_LEN);
|
|
if (marker_p->lm_marker_tlv_type != LA_MARKER_TLV_TYPE_MARKER) {
|
|
goto failed;
|
|
}
|
|
bond_lock();
|
|
if ((ifnet_eflags(port_ifp) & IFEF_BOND) == 0) {
|
|
bond_unlock();
|
|
goto failed;
|
|
}
|
|
p = bond_lookup_port(port_ifp);
|
|
if (p == NULL || p->po_enabled == 0
|
|
|| p->po_bond->ifb_mode != IF_BOND_MODE_LACP) {
|
|
bond_unlock();
|
|
goto failed;
|
|
}
|
|
/* echo back the same packet as a marker response */
|
|
marker_p->lm_marker_tlv_type = LA_MARKER_TLV_TYPE_MARKER_RESPONSE;
|
|
bondport_slow_proto_transmit(p, (packet_buffer_ref)m);
|
|
bond_unlock();
|
|
return;
|
|
|
|
failed:
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_input(ifnet_t port_ifp, mbuf_t m, char *frame_header)
|
|
{
|
|
bpf_packet_func bpf_func;
|
|
const struct ether_header * eh_p;
|
|
ifbond_ref ifb;
|
|
struct ifnet * ifp;
|
|
bondport_ref p;
|
|
|
|
eh_p = (const struct ether_header *)frame_header;
|
|
if ((m->m_flags & M_MCAST) != 0
|
|
&& bcmp(eh_p->ether_dhost, &slow_proto_multicast,
|
|
sizeof(eh_p->ether_dhost)) == 0
|
|
&& ntohs(eh_p->ether_type) == IEEE8023AD_SLOW_PROTO_ETHERTYPE) {
|
|
u_char subtype = *mtod(m, u_char *);
|
|
|
|
if (subtype == IEEE8023AD_SLOW_PROTO_SUBTYPE_LACP) {
|
|
if (m->m_pkthdr.len < (int)offsetof(lacpdu, la_reserved)) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
/* send to lacp */
|
|
if (m->m_len < (int)offsetof(lacpdu, la_reserved)) {
|
|
m = m_pullup(m, offsetof(lacpdu, la_reserved));
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
bond_receive_lacpdu(m, port_ifp);
|
|
return;
|
|
} else if (subtype == IEEE8023AD_SLOW_PROTO_SUBTYPE_LA_MARKER_PROTOCOL) {
|
|
int min_size;
|
|
|
|
/* restore the ethernet header pointer in the mbuf */
|
|
m->m_pkthdr.len += ETHER_HDR_LEN;
|
|
m->m_data -= ETHER_HDR_LEN;
|
|
m->m_len += ETHER_HDR_LEN;
|
|
min_size = ETHER_HDR_LEN + offsetof(la_marker_pdu, lm_reserved);
|
|
if (m->m_pkthdr.len < min_size) {
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
/* send to lacp */
|
|
if (m->m_len < min_size) {
|
|
m = m_pullup(m, min_size);
|
|
if (m == NULL) {
|
|
return;
|
|
}
|
|
}
|
|
/* send to marker responder */
|
|
bond_receive_la_marker_pdu(m, port_ifp);
|
|
return;
|
|
} else if (subtype == 0
|
|
|| subtype > IEEE8023AD_SLOW_PROTO_SUBTYPE_RESERVED_END) {
|
|
/* invalid subtype, discard the frame */
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
}
|
|
bond_lock();
|
|
if ((ifnet_eflags(port_ifp) & IFEF_BOND) == 0) {
|
|
goto done;
|
|
}
|
|
p = bond_lookup_port(port_ifp);
|
|
if (p == NULL || bondport_collecting(p) == 0) {
|
|
goto done;
|
|
}
|
|
|
|
ifb = p->po_bond;
|
|
ifp = ifb->ifb_ifp;
|
|
bpf_func = ifb->ifb_bpf_input;
|
|
bond_unlock();
|
|
|
|
/*
|
|
* Need to clear the promiscous flags otherwise it will be
|
|
* dropped by DLIL after processing filters
|
|
*/
|
|
if ((mbuf_flags(m) & MBUF_PROMISC)) {
|
|
mbuf_setflags_mask(m, 0, MBUF_PROMISC);
|
|
}
|
|
|
|
if (m->m_pkthdr.csum_flags & CSUM_VLAN_TAG_VALID) {
|
|
(void)ifnet_stat_increment_in(ifp, 1,
|
|
(m->m_pkthdr.len + ETHER_HDR_LEN
|
|
+ ETHER_VLAN_ENCAP_LEN), 0);
|
|
} else {
|
|
(void)ifnet_stat_increment_in(ifp, 1,
|
|
(m->m_pkthdr.len + ETHER_HDR_LEN), 0);
|
|
}
|
|
|
|
/* make the packet appear as if it arrived on the bonded interface */
|
|
m->m_pkthdr.rcvif = ifp;
|
|
bond_bpf_input(ifp, m, eh_p, bpf_func);
|
|
m->m_pkthdr.pkt_hdr = frame_header;
|
|
dlil_input_packet_list(ifp, m);
|
|
return;
|
|
|
|
done:
|
|
bond_unlock();
|
|
m_freem(m);
|
|
return;
|
|
}
|
|
|
|
static errno_t
|
|
bond_iff_input(void *cookie, ifnet_t port_ifp, protocol_family_t protocol,
|
|
mbuf_t *data, char **frame_header_ptr)
|
|
{
|
|
#pragma unused(cookie)
|
|
#pragma unused(protocol)
|
|
mbuf_t m = *data;
|
|
char * frame_header = *frame_header_ptr;
|
|
|
|
bond_input(port_ifp, m, frame_header);
|
|
return EJUSTRETURN;
|
|
}
|
|
|
|
static __inline__ const char *
|
|
bondport_get_name(bondport_ref p)
|
|
{
|
|
return p->po_name;
|
|
}
|
|
|
|
static __inline__ int
|
|
bondport_get_index(bondport_ref p)
|
|
{
|
|
return ifnet_index(p->po_ifp);
|
|
}
|
|
|
|
static void
|
|
bondport_slow_proto_transmit(bondport_ref p, packet_buffer_ref buf)
|
|
{
|
|
struct ether_header * eh_p;
|
|
int error;
|
|
|
|
/* packet_buffer_allocate leaves room for ethernet header */
|
|
eh_p = mtod(buf, struct ether_header *);
|
|
bcopy(&slow_proto_multicast, &eh_p->ether_dhost, sizeof(eh_p->ether_dhost));
|
|
bcopy(&p->po_saved_addr, eh_p->ether_shost, sizeof(eh_p->ether_shost));
|
|
eh_p->ether_type = htons(IEEE8023AD_SLOW_PROTO_ETHERTYPE);
|
|
error = ifnet_output_raw(p->po_ifp, PF_BOND, buf);
|
|
if (error != 0) {
|
|
printf("bondport_slow_proto_transmit(%s) failed %d\n",
|
|
bondport_get_name(p), error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_timer_process_func(devtimer_ref timer,
|
|
devtimer_process_func_event event)
|
|
{
|
|
bondport_ref p;
|
|
|
|
switch (event) {
|
|
case devtimer_process_func_event_lock:
|
|
bond_lock();
|
|
devtimer_retain(timer);
|
|
break;
|
|
case devtimer_process_func_event_unlock:
|
|
if (devtimer_valid(timer)) {
|
|
/* as long as the devtimer is valid, we can look at arg0 */
|
|
int event_code = 0;
|
|
struct ifnet * bond_ifp = NULL;
|
|
|
|
p = (bondport_ref)devtimer_arg0(timer);
|
|
if (ifbond_selection(p->po_bond)) {
|
|
event_code = (p->po_bond->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
/* XXX need to take a reference on bond_ifp */
|
|
bond_ifp = p->po_bond->ifb_ifp;
|
|
p->po_bond->ifb_last_link_event = event_code;
|
|
} else {
|
|
event_code = (p->po_bond->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
if (event_code != p->po_bond->ifb_last_link_event) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("%s: (timer) generating LINK event\n",
|
|
p->po_bond->ifb_name);
|
|
}
|
|
bond_ifp = p->po_bond->ifb_ifp;
|
|
p->po_bond->ifb_last_link_event = event_code;
|
|
}
|
|
}
|
|
devtimer_release(timer);
|
|
bond_unlock();
|
|
if (bond_ifp != NULL) {
|
|
interface_link_event(bond_ifp, event_code);
|
|
}
|
|
} else {
|
|
/* timer is going away */
|
|
devtimer_release(timer);
|
|
bond_unlock();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static bondport_ref
|
|
bondport_create(struct ifnet * port_ifp, lacp_port_priority priority,
|
|
int active, int short_timeout, int * ret_error)
|
|
{
|
|
int error = 0;
|
|
bondport_ref p = NULL;
|
|
lacp_actor_partner_state s;
|
|
|
|
*ret_error = 0;
|
|
p = kalloc_type(struct bondport_s, Z_WAITOK | Z_ZERO | Z_NOFAIL);
|
|
multicast_list_init(&p->po_multicast);
|
|
if ((u_int32_t)snprintf(p->po_name, sizeof(p->po_name), "%s%d",
|
|
ifnet_name(port_ifp), ifnet_unit(port_ifp))
|
|
>= sizeof(p->po_name)) {
|
|
printf("if_bond: name too large\n");
|
|
*ret_error = EINVAL;
|
|
goto failed;
|
|
}
|
|
error = siocgifdevmtu(port_ifp, &p->po_devmtu);
|
|
if (error != 0) {
|
|
printf("if_bond: SIOCGIFDEVMTU %s failed, %d\n",
|
|
bondport_get_name(p), error);
|
|
goto failed;
|
|
}
|
|
/* remember the current interface MTU so it can be restored */
|
|
p->po_devmtu.ifdm_current = ifnet_mtu(port_ifp);
|
|
p->po_ifp = port_ifp;
|
|
p->po_media_info = interface_media_info(port_ifp);
|
|
p->po_current_while_timer = devtimer_create(bondport_timer_process_func, p);
|
|
if (p->po_current_while_timer == NULL) {
|
|
*ret_error = ENOMEM;
|
|
goto failed;
|
|
}
|
|
p->po_periodic_timer = devtimer_create(bondport_timer_process_func, p);
|
|
if (p->po_periodic_timer == NULL) {
|
|
*ret_error = ENOMEM;
|
|
goto failed;
|
|
}
|
|
p->po_wait_while_timer = devtimer_create(bondport_timer_process_func, p);
|
|
if (p->po_wait_while_timer == NULL) {
|
|
*ret_error = ENOMEM;
|
|
goto failed;
|
|
}
|
|
p->po_transmit_timer = devtimer_create(bondport_timer_process_func, p);
|
|
if (p->po_transmit_timer == NULL) {
|
|
*ret_error = ENOMEM;
|
|
goto failed;
|
|
}
|
|
p->po_receive_state = ReceiveState_none;
|
|
p->po_mux_state = MuxState_none;
|
|
p->po_priority = priority;
|
|
s = 0;
|
|
s = lacp_actor_partner_state_set_aggregatable(s);
|
|
if (short_timeout) {
|
|
s = lacp_actor_partner_state_set_short_timeout(s);
|
|
}
|
|
if (active) {
|
|
s = lacp_actor_partner_state_set_active_lacp(s);
|
|
}
|
|
p->po_actor_state = s;
|
|
return p;
|
|
|
|
failed:
|
|
bondport_free(p);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
bondport_start(bondport_ref p)
|
|
{
|
|
bondport_receive_machine(p, LAEventStart, NULL);
|
|
bondport_mux_machine(p, LAEventStart, NULL);
|
|
bondport_periodic_transmit_machine(p, LAEventStart, NULL);
|
|
bondport_transmit_machine(p, LAEventStart, NULL);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Function: bondport_invalidate_timers
|
|
* Purpose:
|
|
* Invalidate all of the timers for the bondport.
|
|
*/
|
|
static void
|
|
bondport_invalidate_timers(bondport_ref p)
|
|
{
|
|
devtimer_invalidate(p->po_current_while_timer);
|
|
devtimer_invalidate(p->po_periodic_timer);
|
|
devtimer_invalidate(p->po_wait_while_timer);
|
|
devtimer_invalidate(p->po_transmit_timer);
|
|
}
|
|
|
|
/*
|
|
* Function: bondport_cancel_timers
|
|
* Purpose:
|
|
* Cancel all of the timers for the bondport.
|
|
*/
|
|
static void
|
|
bondport_cancel_timers(bondport_ref p)
|
|
{
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
devtimer_cancel(p->po_periodic_timer);
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
devtimer_cancel(p->po_transmit_timer);
|
|
}
|
|
|
|
static void
|
|
bondport_free(bondport_ref p)
|
|
{
|
|
multicast_list_remove(&p->po_multicast);
|
|
devtimer_release(p->po_current_while_timer);
|
|
devtimer_release(p->po_periodic_timer);
|
|
devtimer_release(p->po_wait_while_timer);
|
|
devtimer_release(p->po_transmit_timer);
|
|
kfree_type(struct bondport_s, p);
|
|
return;
|
|
}
|
|
|
|
static __inline__ int
|
|
bond_device_mtu(struct ifnet * ifp, ifbond_ref ifb)
|
|
{
|
|
return ((int)ifnet_mtu(ifp) > ifb->ifb_altmtu)
|
|
? (int)ifnet_mtu(ifp) : ifb->ifb_altmtu;
|
|
}
|
|
|
|
static int
|
|
bond_add_interface(struct ifnet * ifp, struct ifnet * port_ifp)
|
|
{
|
|
u_int32_t eflags;
|
|
uint32_t control_flags = 0;
|
|
int devmtu;
|
|
int error = 0;
|
|
int event_code = 0;
|
|
interface_filter_t filter = NULL;
|
|
int first = FALSE;
|
|
ifbond_ref ifb;
|
|
bondport_ref * new_array = NULL;
|
|
bondport_ref * old_array = NULL;
|
|
bondport_ref p;
|
|
int old_max = 0;
|
|
int new_max = 0;
|
|
|
|
if (IFNET_IS_INTCOPROC(port_ifp) || IFNET_IS_MANAGEMENT(port_ifp)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
/* pre-allocate space for new port */
|
|
p = bondport_create(port_ifp, 0x8000, 1, 0, &error);
|
|
if (p == NULL) {
|
|
return error;
|
|
}
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
bondport_free(p);
|
|
return ifb == NULL ? EOPNOTSUPP : EBUSY;
|
|
}
|
|
|
|
/* make sure this interface can handle our current MTU */
|
|
devmtu = bond_device_mtu(ifp, ifb);
|
|
if (devmtu != 0
|
|
&& (devmtu > p->po_devmtu.ifdm_max || devmtu < p->po_devmtu.ifdm_min)) {
|
|
bond_unlock();
|
|
printf("if_bond: interface %s doesn't support mtu %d",
|
|
bondport_get_name(p), devmtu);
|
|
bondport_free(p);
|
|
return EINVAL;
|
|
}
|
|
|
|
/* make sure ifb doesn't get de-allocated while we wait */
|
|
ifbond_retain(ifb);
|
|
|
|
/* wait for other add or remove to complete */
|
|
ifbond_wait(ifb, __func__);
|
|
|
|
if (ifbond_flags_if_detaching(ifb)) {
|
|
/* someone destroyed the bond while we were waiting */
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
if (bond_lookup_port(port_ifp) != NULL) {
|
|
/* port is already part of a bond */
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
if ((ifnet_eflags(port_ifp) & (IFEF_VLAN | IFEF_BOND)) != 0) {
|
|
/* interface already has VLAN's, or is part of bond */
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
|
|
/* mark the interface busy */
|
|
eflags = if_set_eflags(port_ifp, IFEF_BOND);
|
|
if ((eflags & IFEF_VLAN) != 0) {
|
|
/* vlan got in ahead of us */
|
|
if_clear_eflags(port_ifp, IFEF_BOND);
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
|
|
if (TAILQ_EMPTY(&ifb->ifb_port_list)) {
|
|
ifnet_set_offload(ifp, ifnet_offload(port_ifp));
|
|
ifnet_set_flags(ifp, IFF_RUNNING, IFF_RUNNING);
|
|
if (ifbond_flags_lladdr(ifb) == FALSE) {
|
|
first = TRUE;
|
|
}
|
|
} else {
|
|
ifnet_offload_t ifp_offload;
|
|
ifnet_offload_t port_ifp_offload;
|
|
|
|
ifp_offload = ifnet_offload(ifp);
|
|
port_ifp_offload = ifnet_offload(port_ifp);
|
|
if (ifp_offload != port_ifp_offload) {
|
|
ifnet_offload_t offload;
|
|
|
|
offload = ifp_offload & port_ifp_offload;
|
|
printf("%s(%s, %s) "
|
|
"hwassist values don't match 0x%x != 0x%x, using 0x%x instead\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p),
|
|
ifp_offload, port_ifp_offload, offload);
|
|
/*
|
|
* XXX
|
|
* if the bond has VLAN's, we can't simply change the hwassist
|
|
* field behind its back: this needs work
|
|
*/
|
|
ifnet_set_offload(ifp, offload);
|
|
}
|
|
}
|
|
p->po_bond = ifb;
|
|
|
|
/* remember the port's ethernet address so it can be restored */
|
|
ether_addr_copy(&p->po_saved_addr, IF_LLADDR(port_ifp));
|
|
|
|
/* add it to the list of ports */
|
|
TAILQ_INSERT_TAIL(&ifb->ifb_port_list, p, po_port_list);
|
|
ifb->ifb_port_count++;
|
|
|
|
bond_unlock();
|
|
|
|
|
|
/* first port added to bond determines bond's ethernet address */
|
|
if (first) {
|
|
ifnet_set_lladdr_and_type(ifp, IF_LLADDR(port_ifp), ETHER_ADDR_LEN,
|
|
IFT_ETHER);
|
|
}
|
|
uint32_bit_set(&control_flags, PORT_CONTROL_FLAGS_IN_LIST);
|
|
|
|
/* allocate a larger distributing array */
|
|
new_max = ifb->ifb_port_count;
|
|
new_array = kalloc_type(bondport_ref, new_max, Z_WAITOK);
|
|
if (new_array == NULL) {
|
|
error = ENOMEM;
|
|
goto failed;
|
|
}
|
|
|
|
/* attach our BOND "protocol" to the interface */
|
|
error = bond_attach_protocol(port_ifp);
|
|
if (error) {
|
|
goto failed;
|
|
}
|
|
uint32_bit_set(&control_flags, PORT_CONTROL_FLAGS_PROTO_ATTACHED);
|
|
|
|
/* attach our BOND interface filter */
|
|
error = bond_attach_filter(port_ifp, &filter);
|
|
if (error != 0) {
|
|
goto failed;
|
|
}
|
|
uint32_bit_set(&control_flags, PORT_CONTROL_FLAGS_FILTER_ATTACHED);
|
|
|
|
/* set the interface MTU */
|
|
devmtu = bond_device_mtu(ifp, ifb);
|
|
error = siocsifmtu(port_ifp, devmtu);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s):"
|
|
" SIOCSIFMTU %d failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), devmtu, error);
|
|
goto failed;
|
|
}
|
|
uint32_bit_set(&control_flags, PORT_CONTROL_FLAGS_MTU_SET);
|
|
|
|
/* program the port with our multicast addresses */
|
|
error = multicast_list_program(&p->po_multicast, ifp, port_ifp);
|
|
if (error) {
|
|
printf("%s(%s, %s): multicast_list_program failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
goto failed;
|
|
}
|
|
|
|
/* mark the interface up */
|
|
ifnet_set_flags(port_ifp, IFF_UP, IFF_UP);
|
|
|
|
error = ifnet_ioctl(port_ifp, 0, SIOCSIFFLAGS, NULL);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): SIOCSIFFLAGS failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
goto failed;
|
|
}
|
|
|
|
/* re-program the port's ethernet address */
|
|
error = if_siflladdr(port_ifp,
|
|
(const struct ether_addr *)IF_LLADDR(ifp));
|
|
if (error == 0) {
|
|
if (memcmp(IF_LLADDR(ifp), IF_LLADDR(port_ifp), ETHER_ADDR_LEN)
|
|
!= 0) {
|
|
/* it lied, it really doesn't support setting lladdr */
|
|
error = EOPNOTSUPP;
|
|
}
|
|
}
|
|
if (error != 0) {
|
|
/* port doesn't support setting the link address */
|
|
printf("%s(%s, %s): if_siflladdr failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
error = ifnet_set_promiscuous(port_ifp, 1);
|
|
if (error != 0) {
|
|
/* port doesn't support setting promiscuous mode */
|
|
printf("%s(%s, %s): set promiscuous failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
goto failed;
|
|
}
|
|
uint32_bit_set(&control_flags,
|
|
PORT_CONTROL_FLAGS_PROMISCUOUS_SET);
|
|
} else {
|
|
uint32_bit_set(&control_flags,
|
|
PORT_CONTROL_FLAGS_LLADDR_SET);
|
|
}
|
|
|
|
/* if we're in promiscuous mode, enable that as well */
|
|
if (ifbond_flags_promisc(ifb)) {
|
|
error = ifnet_set_promiscuous(port_ifp, 1);
|
|
if (error != 0) {
|
|
/* port doesn't support setting promiscuous mode */
|
|
printf("%s(%s, %s): set promiscuous failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
goto failed;
|
|
}
|
|
uint32_bit_set(&control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET);
|
|
}
|
|
|
|
bond_lock();
|
|
|
|
/* no failures past this point */
|
|
p->po_enabled = 1;
|
|
p->po_control_flags = control_flags;
|
|
|
|
/* copy the contents of the existing distributing array */
|
|
if (ifb->ifb_distributing_count) {
|
|
bcopy(ifb->ifb_distributing_array, new_array,
|
|
sizeof(*new_array) * ifb->ifb_distributing_count);
|
|
}
|
|
old_array = ifb->ifb_distributing_array;
|
|
old_max = ifb->ifb_distributing_max;
|
|
ifb->ifb_distributing_array = new_array;
|
|
ifb->ifb_distributing_max = new_max;
|
|
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
bondport_start(p);
|
|
|
|
/* check if we need to generate a link status event */
|
|
if (ifbond_selection(ifb)) {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
ifb->ifb_last_link_event = event_code;
|
|
}
|
|
} else {
|
|
/* are we adding the first distributing interface? */
|
|
if (media_active(&p->po_media_info)) {
|
|
if (ifb->ifb_distributing_count == 0) {
|
|
ifb->ifb_last_link_event = event_code = KEV_DL_LINK_ON;
|
|
}
|
|
bondport_enable_distributing(p);
|
|
} else {
|
|
bondport_disable_distributing(p);
|
|
}
|
|
}
|
|
p->po_filter = filter;
|
|
|
|
/* clear the busy state, and wakeup anyone waiting */
|
|
ifbond_signal(ifb, __func__);
|
|
bond_unlock();
|
|
if (event_code != 0) {
|
|
interface_link_event(ifp, event_code);
|
|
}
|
|
kfree_type(bondport_ref, old_max, old_array);
|
|
return 0;
|
|
|
|
failed:
|
|
bond_assert_lock_not_held();
|
|
|
|
/* if this was the first port to be added, clear our address */
|
|
if (first) {
|
|
ifnet_set_lladdr_and_type(ifp, NULL, 0, IFT_IEEE8023ADLAG);
|
|
}
|
|
|
|
kfree_type(bondport_ref, new_max, new_array);
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_LLADDR_SET)) {
|
|
int error1;
|
|
|
|
error1 = if_siflladdr(port_ifp, &p->po_saved_addr);
|
|
if (error1 != 0) {
|
|
printf("%s(%s, %s): if_siflladdr restore failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error1);
|
|
}
|
|
}
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_PROMISCUOUS_SET)) {
|
|
int error1;
|
|
|
|
error1 = ifnet_set_promiscuous(port_ifp, 0);
|
|
if (error1 != 0) {
|
|
printf("%s(%s, %s): promiscous mode disable failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error1);
|
|
}
|
|
}
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET)) {
|
|
int error1;
|
|
|
|
error1 = ifnet_set_promiscuous(port_ifp, 0);
|
|
if (error1 != 0) {
|
|
printf("%s(%s, %s): promiscous mode disable failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error1);
|
|
}
|
|
}
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_PROTO_ATTACHED)) {
|
|
(void)bond_detach_protocol(port_ifp);
|
|
}
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_FILTER_ATTACHED)) {
|
|
iflt_detach(filter);
|
|
}
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_MTU_SET)) {
|
|
int error1;
|
|
|
|
error1 = siocsifmtu(port_ifp, p->po_devmtu.ifdm_current);
|
|
if (error1 != 0) {
|
|
printf("%s(%s, %s): SIOCSIFMTU %d failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p),
|
|
p->po_devmtu.ifdm_current, error1);
|
|
}
|
|
}
|
|
bond_lock();
|
|
if (uint32_bit_is_set(control_flags,
|
|
PORT_CONTROL_FLAGS_IN_LIST)) {
|
|
TAILQ_REMOVE(&ifb->ifb_port_list, p, po_port_list);
|
|
ifb->ifb_port_count--;
|
|
}
|
|
if_clear_eflags(ifp, IFEF_BOND);
|
|
if (TAILQ_EMPTY(&ifb->ifb_port_list)) {
|
|
ifb->ifb_altmtu = 0;
|
|
ifnet_set_mtu(ifp, ETHERMTU);
|
|
ifnet_set_offload(ifp, 0);
|
|
}
|
|
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
bond_unlock();
|
|
ifbond_release(ifb);
|
|
bondport_free(p);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_remove_interface(ifbond_ref ifb, struct ifnet * port_ifp)
|
|
{
|
|
int active_lag = 0;
|
|
int error = 0;
|
|
int event_code = 0;
|
|
bondport_ref head_port;
|
|
struct ifnet * ifp;
|
|
interface_filter_t filter;
|
|
int last = FALSE;
|
|
int new_link_address = FALSE;
|
|
bondport_ref p;
|
|
lacp_actor_partner_state s;
|
|
int was_distributing;
|
|
|
|
bond_assert_lock_held();
|
|
|
|
ifbond_retain(ifb);
|
|
ifbond_wait(ifb, "bond_remove_interface");
|
|
|
|
p = ifbond_lookup_port(ifb, port_ifp);
|
|
if (p == NULL) {
|
|
error = ENXIO;
|
|
/* it got removed by another thread */
|
|
goto signal_done;
|
|
}
|
|
|
|
/* de-select it and remove it from the lists */
|
|
was_distributing = bondport_flags_distributing(p);
|
|
bondport_disable_distributing(p);
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
active_lag = bondport_remove_from_LAG(p);
|
|
/* invalidate timers here while holding the bond_lock */
|
|
bondport_invalidate_timers(p);
|
|
|
|
/* announce that we're Individual now */
|
|
s = p->po_actor_state;
|
|
s = lacp_actor_partner_state_set_individual(s);
|
|
s = lacp_actor_partner_state_set_not_collecting(s);
|
|
s = lacp_actor_partner_state_set_not_distributing(s);
|
|
s = lacp_actor_partner_state_set_out_of_sync(s);
|
|
p->po_actor_state = s;
|
|
bondport_flags_set_ntt(p);
|
|
}
|
|
|
|
TAILQ_REMOVE(&ifb->ifb_port_list, p, po_port_list);
|
|
ifb->ifb_port_count--;
|
|
|
|
ifp = ifb->ifb_ifp;
|
|
head_port = TAILQ_FIRST(&ifb->ifb_port_list);
|
|
if (head_port == NULL) {
|
|
ifnet_set_flags(ifp, 0, IFF_RUNNING);
|
|
if (ifbond_flags_lladdr(ifb) == FALSE) {
|
|
last = TRUE;
|
|
}
|
|
ifnet_set_offload(ifp, 0);
|
|
ifnet_set_mtu(ifp, ETHERMTU);
|
|
ifb->ifb_altmtu = 0;
|
|
} else if (ifbond_flags_lladdr(ifb) == FALSE
|
|
&& bcmp(&p->po_saved_addr, IF_LLADDR(ifp),
|
|
ETHER_ADDR_LEN) == 0) {
|
|
new_link_address = TRUE;
|
|
}
|
|
/* check if we need to generate a link status event */
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
if (ifbond_selection(ifb) || active_lag) {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
ifb->ifb_last_link_event = event_code;
|
|
}
|
|
bondport_transmit_machine(p, LAEventStart,
|
|
TRANSMIT_MACHINE_TX_IMMEDIATE);
|
|
} else {
|
|
/* are we removing the last distributing interface? */
|
|
if (was_distributing && ifb->ifb_distributing_count == 0) {
|
|
ifb->ifb_last_link_event = event_code = KEV_DL_LINK_OFF;
|
|
}
|
|
}
|
|
filter = p->po_filter;
|
|
bond_unlock();
|
|
|
|
if (last) {
|
|
ifnet_set_lladdr_and_type(ifp, NULL, 0, IFT_IEEE8023ADLAG);
|
|
} else if (new_link_address) {
|
|
struct ifnet * scan_ifp;
|
|
bondport_ref scan_port;
|
|
|
|
/* ifbond_wait() allows port list traversal without holding the lock */
|
|
|
|
/* this port gave the bond its ethernet address, switch to new one */
|
|
ifnet_set_lladdr_and_type(ifp,
|
|
&head_port->po_saved_addr, ETHER_ADDR_LEN,
|
|
IFT_ETHER);
|
|
|
|
/* re-program each port with the new link address */
|
|
TAILQ_FOREACH(scan_port, &ifb->ifb_port_list, po_port_list) {
|
|
scan_ifp = scan_port->po_ifp;
|
|
|
|
if (!uint32_bit_is_set(scan_port->po_control_flags,
|
|
PORT_CONTROL_FLAGS_LLADDR_SET)) {
|
|
/* port doesn't support setting lladdr */
|
|
continue;
|
|
}
|
|
error = if_siflladdr(scan_ifp,
|
|
(const struct ether_addr *) IF_LLADDR(ifp));
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): "
|
|
"if_siflladdr (%s) failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p),
|
|
bondport_get_name(scan_port), error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* restore the port's ethernet address */
|
|
if (uint32_bit_is_set(p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_LLADDR_SET)) {
|
|
error = if_siflladdr(port_ifp, &p->po_saved_addr);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): if_siflladdr failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
}
|
|
}
|
|
|
|
/* disable promiscous mode (if we enabled it) */
|
|
if (uint32_bit_is_set(p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_PROMISCUOUS_SET)) {
|
|
error = ifnet_set_promiscuous(port_ifp, 0);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): disable promiscuous failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
}
|
|
}
|
|
|
|
/* disable promiscous mode from bond (if we enabled it) */
|
|
if (uint32_bit_is_set(p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET)) {
|
|
error = ifnet_set_promiscuous(port_ifp, 0);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): disable promiscuous failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
}
|
|
}
|
|
|
|
/* restore the port's MTU */
|
|
error = siocsifmtu(port_ifp, p->po_devmtu.ifdm_current);
|
|
if (error != 0) {
|
|
printf("%s(%s, %s): SIOCSIFMTU %d failed %d\n",
|
|
__func__,
|
|
ifb->ifb_name, bondport_get_name(p),
|
|
p->po_devmtu.ifdm_current, error);
|
|
}
|
|
|
|
/* remove the bond "protocol" */
|
|
bond_detach_protocol(port_ifp);
|
|
|
|
/* detach the filter */
|
|
if (filter != NULL) {
|
|
iflt_detach(filter);
|
|
}
|
|
|
|
/* generate link event */
|
|
if (event_code != 0) {
|
|
interface_link_event(ifp, event_code);
|
|
}
|
|
|
|
bond_lock();
|
|
bondport_free(p);
|
|
if_clear_eflags(port_ifp, IFEF_BOND);
|
|
/* release this bondport's reference to the ifbond */
|
|
ifbond_release(ifb);
|
|
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
ifbond_release(ifb);
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
bond_set_lacp_mode(ifbond_ref ifb)
|
|
{
|
|
bondport_ref p;
|
|
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
bondport_disable_distributing(p);
|
|
bondport_start(p);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_set_static_mode(ifbond_ref ifb)
|
|
{
|
|
bondport_ref p;
|
|
lacp_actor_partner_state s;
|
|
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
bondport_disable_distributing(p);
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
(void)bondport_remove_from_LAG(p);
|
|
bondport_cancel_timers(p);
|
|
|
|
/* announce that we're Individual now */
|
|
s = p->po_actor_state;
|
|
s = lacp_actor_partner_state_set_individual(s);
|
|
s = lacp_actor_partner_state_set_not_collecting(s);
|
|
s = lacp_actor_partner_state_set_not_distributing(s);
|
|
s = lacp_actor_partner_state_set_out_of_sync(s);
|
|
p->po_actor_state = s;
|
|
bondport_flags_set_ntt(p);
|
|
bondport_transmit_machine(p, LAEventStart,
|
|
TRANSMIT_MACHINE_TX_IMMEDIATE);
|
|
/* clear state */
|
|
p->po_actor_state = 0;
|
|
bzero(&p->po_partner_state, sizeof(p->po_partner_state));
|
|
|
|
if (media_active(&p->po_media_info)) {
|
|
bondport_enable_distributing(p);
|
|
} else {
|
|
bondport_disable_distributing(p);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int
|
|
bond_set_mode(struct ifnet * ifp, int mode)
|
|
{
|
|
int error = 0;
|
|
int event_code = 0;
|
|
ifbond_ref ifb;
|
|
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return (ifb == NULL) ? EOPNOTSUPP : EBUSY;
|
|
}
|
|
if (ifb->ifb_mode == mode) {
|
|
bond_unlock();
|
|
return 0;
|
|
}
|
|
|
|
ifbond_retain(ifb);
|
|
ifbond_wait(ifb, "bond_set_mode");
|
|
|
|
/* verify (again) that the mode is actually different */
|
|
if (ifb->ifb_mode == mode) {
|
|
/* nothing to do */
|
|
goto signal_done;
|
|
}
|
|
|
|
ifb->ifb_mode = mode;
|
|
if (mode == IF_BOND_MODE_LACP) {
|
|
bond_set_lacp_mode(ifb);
|
|
|
|
/* check if we need to generate a link status event */
|
|
if (ifbond_selection(ifb)) {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
}
|
|
} else {
|
|
bond_set_static_mode(ifb);
|
|
event_code = (ifb->ifb_distributing_count == 0)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
}
|
|
ifb->ifb_last_link_event = event_code;
|
|
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
bond_unlock();
|
|
ifbond_release(ifb);
|
|
|
|
if (event_code != 0) {
|
|
interface_link_event(ifp, event_code);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_get_status(ifbond_ref ifb, struct if_bond_req * ibr_p, user_addr_t datap)
|
|
{
|
|
int count;
|
|
user_addr_t dst;
|
|
int error = 0;
|
|
struct if_bond_status_req * ibsr;
|
|
struct if_bond_status ibs;
|
|
bondport_ref port;
|
|
|
|
ibsr = &(ibr_p->ibr_ibru.ibru_status);
|
|
if (ibsr->ibsr_version != IF_BOND_STATUS_REQ_VERSION) {
|
|
return EINVAL;
|
|
}
|
|
ibsr->ibsr_key = ifb->ifb_key;
|
|
ibsr->ibsr_mode = ifb->ifb_mode;
|
|
ibsr->ibsr_total = ifb->ifb_port_count;
|
|
dst = proc_is64bit(current_proc())
|
|
? ibsr->ibsr_ibsru.ibsru_buffer64
|
|
: CAST_USER_ADDR_T(ibsr->ibsr_ibsru.ibsru_buffer);
|
|
if (dst == USER_ADDR_NULL) {
|
|
/* just want to know how many there are */
|
|
goto done;
|
|
}
|
|
if (ibsr->ibsr_count < 0) {
|
|
return EINVAL;
|
|
}
|
|
count = (ifb->ifb_port_count < ibsr->ibsr_count)
|
|
? ifb->ifb_port_count : ibsr->ibsr_count;
|
|
TAILQ_FOREACH(port, &ifb->ifb_port_list, po_port_list) {
|
|
struct if_bond_partner_state * ibps_p;
|
|
partner_state_ref ps;
|
|
|
|
if (count == 0) {
|
|
break;
|
|
}
|
|
bzero(&ibs, sizeof(ibs));
|
|
strlcpy(ibs.ibs_if_name, port->po_name, sizeof(ibs.ibs_if_name));
|
|
ibs.ibs_port_priority = port->po_priority;
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
ibs.ibs_state = port->po_actor_state;
|
|
ibs.ibs_selected_state = port->po_selected;
|
|
ps = &port->po_partner_state;
|
|
ibps_p = &ibs.ibs_partner_state;
|
|
ibps_p->ibps_system = ps->ps_lag_info.li_system;
|
|
ibps_p->ibps_system_priority = ps->ps_lag_info.li_system_priority;
|
|
ibps_p->ibps_key = ps->ps_lag_info.li_key;
|
|
ibps_p->ibps_port = ps->ps_port;
|
|
ibps_p->ibps_port_priority = ps->ps_port_priority;
|
|
ibps_p->ibps_state = ps->ps_state;
|
|
} else {
|
|
/* fake the selected information */
|
|
ibs.ibs_selected_state = bondport_flags_distributing(port)
|
|
? SelectedState_SELECTED : SelectedState_UNSELECTED;
|
|
}
|
|
error = copyout(&ibs, dst, sizeof(ibs));
|
|
if (error != 0) {
|
|
break;
|
|
}
|
|
dst += sizeof(ibs);
|
|
count--;
|
|
}
|
|
|
|
done:
|
|
if (error == 0) {
|
|
error = copyout(ibr_p, datap, sizeof(*ibr_p));
|
|
} else {
|
|
(void)copyout(ibr_p, datap, sizeof(*ibr_p));
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_set_promisc(struct ifnet * ifp)
|
|
{
|
|
int error = 0;
|
|
ifbond_ref ifb;
|
|
bool is_promisc;
|
|
bondport_ref p;
|
|
int val;
|
|
|
|
is_promisc = (ifnet_flags(ifp) & IFF_PROMISC) != 0;
|
|
|
|
/* determine whether promiscuous state needs to be changed */
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL) {
|
|
bond_unlock();
|
|
error = EBUSY;
|
|
goto done;
|
|
}
|
|
if (is_promisc == ifbond_flags_promisc(ifb)) {
|
|
/* already in the right state */
|
|
bond_unlock();
|
|
goto done;
|
|
}
|
|
ifbond_retain(ifb);
|
|
ifbond_wait(ifb, __func__);
|
|
if (ifbond_flags_if_detaching(ifb)) {
|
|
/* someone destroyed the bond while we were waiting */
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
bond_unlock();
|
|
|
|
/* update the promiscuous state of each memeber */
|
|
val = is_promisc ? 1 : 0;
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
struct ifnet * port_ifp = p->po_ifp;
|
|
bool port_is_promisc;
|
|
|
|
port_is_promisc = uint32_bit_is_set(p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET);
|
|
if (port_is_promisc == is_promisc) {
|
|
/* already in the right state */
|
|
continue;
|
|
}
|
|
error = ifnet_set_promiscuous(port_ifp, val);
|
|
if (error != 0) {
|
|
printf("%s: ifnet_set_promiscuous(%s, %d): failed %d",
|
|
ifb->ifb_name, port_ifp->if_xname, val, error);
|
|
continue;
|
|
}
|
|
printf("%s: ifnet_set_promiscuous(%s, %d): succeeded",
|
|
ifb->ifb_name, port_ifp->if_xname, val);
|
|
if (is_promisc) {
|
|
/* remember that we set it */
|
|
uint32_bit_set(&p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET);
|
|
} else {
|
|
uint32_bit_clear(&p->po_control_flags,
|
|
PORT_CONTROL_FLAGS_BOND_PROMISCUOUS_SET);
|
|
}
|
|
}
|
|
|
|
/* assume that updating promiscuous state succeeded */
|
|
error = 0;
|
|
bond_lock();
|
|
|
|
/* update our internal state */
|
|
if (is_promisc) {
|
|
ifbond_flags_set_promisc(ifb);
|
|
} else {
|
|
ifbond_flags_clear_promisc(ifb);
|
|
}
|
|
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
bond_unlock();
|
|
ifbond_release(ifb);
|
|
|
|
done:
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
bond_get_mtu_values(ifbond_ref ifb, int * ret_min, int * ret_max)
|
|
{
|
|
int mtu_min = 0;
|
|
int mtu_max = 0;
|
|
bondport_ref p;
|
|
|
|
if (TAILQ_FIRST(&ifb->ifb_port_list) != NULL) {
|
|
mtu_min = IF_MINMTU;
|
|
}
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
struct ifdevmtu * devmtu_p = &p->po_devmtu;
|
|
|
|
if (devmtu_p->ifdm_min > mtu_min) {
|
|
mtu_min = devmtu_p->ifdm_min;
|
|
}
|
|
if (mtu_max == 0 || devmtu_p->ifdm_max < mtu_max) {
|
|
mtu_max = devmtu_p->ifdm_max;
|
|
}
|
|
}
|
|
*ret_min = mtu_min;
|
|
*ret_max = mtu_max;
|
|
return;
|
|
}
|
|
|
|
static int
|
|
bond_set_mtu_on_ports(ifbond_ref ifb, int mtu)
|
|
{
|
|
int error = 0;
|
|
bondport_ref p;
|
|
|
|
TAILQ_FOREACH(p, &ifb->ifb_port_list, po_port_list) {
|
|
error = siocsifmtu(p->po_ifp, mtu);
|
|
if (error != 0) {
|
|
printf("if_bond(%s): SIOCSIFMTU %s failed, %d\n",
|
|
ifb->ifb_name, bondport_get_name(p), error);
|
|
break;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_set_mtu(struct ifnet * ifp, int mtu, int isdevmtu)
|
|
{
|
|
int error = 0;
|
|
ifbond_ref ifb;
|
|
int mtu_min;
|
|
int mtu_max;
|
|
int new_max;
|
|
int old_max;
|
|
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
error = (ifb == NULL) ? EOPNOTSUPP : EBUSY;
|
|
goto done;
|
|
}
|
|
ifbond_retain(ifb);
|
|
ifbond_wait(ifb, "bond_set_mtu");
|
|
|
|
/* check again */
|
|
if (ifnet_softc(ifp) == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
error = EBUSY;
|
|
goto signal_done;
|
|
}
|
|
bond_get_mtu_values(ifb, &mtu_min, &mtu_max);
|
|
if (mtu > mtu_max) {
|
|
error = EINVAL;
|
|
goto signal_done;
|
|
}
|
|
if (mtu < mtu_min && (isdevmtu == 0 || mtu != 0)) {
|
|
/* allow SIOCSIFALTMTU to set the mtu to 0 */
|
|
error = EINVAL;
|
|
goto signal_done;
|
|
}
|
|
if (isdevmtu) {
|
|
new_max = (mtu > (int)ifnet_mtu(ifp)) ? mtu : (int)ifnet_mtu(ifp);
|
|
} else {
|
|
new_max = (mtu > ifb->ifb_altmtu) ? mtu : ifb->ifb_altmtu;
|
|
}
|
|
old_max = ((int)ifnet_mtu(ifp) > ifb->ifb_altmtu)
|
|
? (int)ifnet_mtu(ifp) : ifb->ifb_altmtu;
|
|
if (new_max != old_max) {
|
|
/* we can safely walk the list of port without the lock held */
|
|
bond_unlock();
|
|
error = bond_set_mtu_on_ports(ifb, new_max);
|
|
if (error != 0) {
|
|
/* try our best to back out of it */
|
|
(void)bond_set_mtu_on_ports(ifb, old_max);
|
|
}
|
|
bond_lock();
|
|
}
|
|
if (error == 0) {
|
|
if (isdevmtu) {
|
|
ifb->ifb_altmtu = mtu;
|
|
} else {
|
|
ifnet_set_mtu(ifp, mtu);
|
|
}
|
|
}
|
|
|
|
signal_done:
|
|
ifbond_signal(ifb, __func__);
|
|
ifbond_release(ifb);
|
|
|
|
done:
|
|
bond_unlock();
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
bond_ioctl(struct ifnet *ifp, u_long cmd, void * data)
|
|
{
|
|
int error = 0;
|
|
struct if_bond_req ibr;
|
|
struct ifaddr * ifa;
|
|
ifbond_ref ifb;
|
|
struct ifreq * ifr;
|
|
struct ifmediareq *ifmr;
|
|
struct ifnet * port_ifp = NULL;
|
|
user_addr_t user_addr;
|
|
|
|
if (ifnet_type(ifp) != IFT_IEEE8023ADLAG) {
|
|
return EOPNOTSUPP;
|
|
}
|
|
ifr = (struct ifreq *)data;
|
|
ifa = (struct ifaddr *)data;
|
|
|
|
switch (cmd) {
|
|
case SIOCSIFADDR:
|
|
ifnet_set_flags(ifp, IFF_UP, IFF_UP);
|
|
break;
|
|
|
|
case SIOCGIFMEDIA32:
|
|
case SIOCGIFMEDIA64:
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return ifb == NULL ? EOPNOTSUPP : EBUSY;
|
|
}
|
|
ifmr = (struct ifmediareq *)data;
|
|
ifmr->ifm_current = IFM_ETHER;
|
|
ifmr->ifm_mask = 0;
|
|
ifmr->ifm_status = IFM_AVALID;
|
|
ifmr->ifm_active = IFM_ETHER;
|
|
ifmr->ifm_count = 1;
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
if (ifb->ifb_active_lag != NULL) {
|
|
ifmr->ifm_active = ifb->ifb_active_lag->lag_active_media;
|
|
ifmr->ifm_status |= IFM_ACTIVE;
|
|
}
|
|
} else if (ifb->ifb_distributing_count > 0) {
|
|
ifmr->ifm_active
|
|
= ifb->ifb_distributing_array[0]->po_media_info.mi_active;
|
|
ifmr->ifm_status |= IFM_ACTIVE;
|
|
}
|
|
bond_unlock();
|
|
user_addr = (cmd == SIOCGIFMEDIA64) ?
|
|
((struct ifmediareq64 *)ifmr)->ifmu_ulist :
|
|
CAST_USER_ADDR_T(((struct ifmediareq32 *)ifmr)->ifmu_ulist);
|
|
if (user_addr != USER_ADDR_NULL) {
|
|
error = copyout(&ifmr->ifm_current,
|
|
user_addr,
|
|
sizeof(int));
|
|
}
|
|
break;
|
|
|
|
case SIOCSIFMEDIA:
|
|
/* XXX send the SIFMEDIA to all children? Or force autoselect? */
|
|
error = EINVAL;
|
|
break;
|
|
|
|
case SIOCGIFDEVMTU:
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
error = (ifb == NULL) ? EOPNOTSUPP : EBUSY;
|
|
break;
|
|
}
|
|
ifr->ifr_devmtu.ifdm_current = bond_device_mtu(ifp, ifb);
|
|
bond_get_mtu_values(ifb, &ifr->ifr_devmtu.ifdm_min,
|
|
&ifr->ifr_devmtu.ifdm_max);
|
|
bond_unlock();
|
|
break;
|
|
|
|
case SIOCGIFALTMTU:
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
error = (ifb == NULL) ? EOPNOTSUPP : EBUSY;
|
|
break;
|
|
}
|
|
ifr->ifr_mtu = ifb->ifb_altmtu;
|
|
bond_unlock();
|
|
break;
|
|
|
|
case SIOCSIFALTMTU:
|
|
error = bond_set_mtu(ifp, ifr->ifr_mtu, 1);
|
|
break;
|
|
|
|
case SIOCSIFMTU:
|
|
error = bond_set_mtu(ifp, ifr->ifr_mtu, 0);
|
|
break;
|
|
|
|
case SIOCSIFBOND:
|
|
user_addr = proc_is64bit(current_proc())
|
|
? ifr->ifr_data64 : CAST_USER_ADDR_T(ifr->ifr_data);
|
|
error = copyin(user_addr, &ibr, sizeof(ibr));
|
|
if (error) {
|
|
break;
|
|
}
|
|
switch (ibr.ibr_op) {
|
|
case IF_BOND_OP_ADD_INTERFACE:
|
|
case IF_BOND_OP_REMOVE_INTERFACE:
|
|
port_ifp = ifunit(ibr.ibr_ibru.ibru_if_name);
|
|
if (port_ifp == NULL) {
|
|
error = ENXIO;
|
|
break;
|
|
}
|
|
if (ifnet_type(port_ifp) != IFT_ETHER) {
|
|
error = EPROTONOSUPPORT;
|
|
break;
|
|
}
|
|
break;
|
|
case IF_BOND_OP_SET_VERBOSE:
|
|
case IF_BOND_OP_SET_MODE:
|
|
break;
|
|
default:
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
}
|
|
if (error != 0) {
|
|
break;
|
|
}
|
|
switch (ibr.ibr_op) {
|
|
case IF_BOND_OP_ADD_INTERFACE:
|
|
error = bond_add_interface(ifp, port_ifp);
|
|
break;
|
|
case IF_BOND_OP_REMOVE_INTERFACE:
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return ifb == NULL ? EOPNOTSUPP : EBUSY;
|
|
}
|
|
error = bond_remove_interface(ifb, port_ifp);
|
|
bond_unlock();
|
|
break;
|
|
case IF_BOND_OP_SET_VERBOSE:
|
|
bond_lock();
|
|
if_bond_debug = ibr.ibr_ibru.ibru_int_val;
|
|
bond_unlock();
|
|
break;
|
|
case IF_BOND_OP_SET_MODE:
|
|
switch (ibr.ibr_ibru.ibru_int_val) {
|
|
case IF_BOND_MODE_LACP:
|
|
case IF_BOND_MODE_STATIC:
|
|
break;
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
if (error != 0) {
|
|
break;
|
|
}
|
|
error = bond_set_mode(ifp, ibr.ibr_ibru.ibru_int_val);
|
|
break;
|
|
}
|
|
break; /* SIOCSIFBOND */
|
|
|
|
case SIOCGIFBOND:
|
|
user_addr = proc_is64bit(current_proc())
|
|
? ifr->ifr_data64 : CAST_USER_ADDR_T(ifr->ifr_data);
|
|
error = copyin(user_addr, &ibr, sizeof(ibr));
|
|
if (error) {
|
|
break;
|
|
}
|
|
switch (ibr.ibr_op) {
|
|
case IF_BOND_OP_GET_STATUS:
|
|
break;
|
|
default:
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
}
|
|
if (error != 0) {
|
|
break;
|
|
}
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL || ifbond_flags_if_detaching(ifb)) {
|
|
bond_unlock();
|
|
return ifb == NULL ? EOPNOTSUPP : EBUSY;
|
|
}
|
|
switch (ibr.ibr_op) {
|
|
case IF_BOND_OP_GET_STATUS:
|
|
error = bond_get_status(ifb, &ibr, user_addr);
|
|
break;
|
|
}
|
|
bond_unlock();
|
|
break; /* SIOCGIFBOND */
|
|
|
|
case SIOCSIFLLADDR:
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
/* enable promiscuous mode on members */
|
|
error = bond_set_promisc(ifp);
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI:
|
|
error = bond_setmulti(ifp);
|
|
break;
|
|
default:
|
|
error = EOPNOTSUPP;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
static void
|
|
bond_if_free(struct ifnet * ifp)
|
|
{
|
|
ifbond_ref ifb;
|
|
|
|
if (ifp == NULL) {
|
|
return;
|
|
}
|
|
bond_lock();
|
|
ifb = (ifbond_ref)ifnet_softc(ifp);
|
|
if (ifb == NULL) {
|
|
bond_unlock();
|
|
return;
|
|
}
|
|
ifbond_release(ifb);
|
|
bond_unlock();
|
|
ifnet_release(ifp);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_handle_event(struct ifnet * port_ifp, int event_code)
|
|
{
|
|
struct ifnet * bond_ifp = NULL;
|
|
ifbond_ref ifb;
|
|
int old_distributing_count;
|
|
bondport_ref p;
|
|
struct media_info media_info = { .mi_active = 0, .mi_status = 0 };
|
|
|
|
switch (event_code) {
|
|
case KEV_DL_IF_DETACHED:
|
|
case KEV_DL_IF_DETACHING:
|
|
break;
|
|
case KEV_DL_LINK_OFF:
|
|
case KEV_DL_LINK_ON:
|
|
media_info = interface_media_info(port_ifp);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
bond_lock();
|
|
p = bond_lookup_port(port_ifp);
|
|
if (p == NULL) {
|
|
bond_unlock();
|
|
return;
|
|
}
|
|
ifb = p->po_bond;
|
|
old_distributing_count = ifb->ifb_distributing_count;
|
|
switch (event_code) {
|
|
case KEV_DL_IF_DETACHED:
|
|
case KEV_DL_IF_DETACHING:
|
|
bond_remove_interface(ifb, p->po_ifp);
|
|
break;
|
|
case KEV_DL_LINK_OFF:
|
|
case KEV_DL_LINK_ON:
|
|
p->po_media_info = media_info;
|
|
if (p->po_enabled) {
|
|
bondport_link_status_changed(p);
|
|
}
|
|
break;
|
|
}
|
|
/* generate a link-event */
|
|
if (ifb->ifb_mode == IF_BOND_MODE_LACP) {
|
|
if (ifbond_selection(ifb)) {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
/* XXX need to take a reference on bond_ifp */
|
|
bond_ifp = ifb->ifb_ifp;
|
|
ifb->ifb_last_link_event = event_code;
|
|
} else {
|
|
event_code = (ifb->ifb_active_lag == NULL)
|
|
? KEV_DL_LINK_OFF
|
|
: KEV_DL_LINK_ON;
|
|
if (event_code != ifb->ifb_last_link_event) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("%s: (event) generating LINK event\n",
|
|
ifb->ifb_name);
|
|
}
|
|
bond_ifp = ifb->ifb_ifp;
|
|
ifb->ifb_last_link_event = event_code;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* if the distributing array membership changed from 0 <-> !0
|
|
* generate a link event
|
|
*/
|
|
if (old_distributing_count == 0
|
|
&& ifb->ifb_distributing_count != 0) {
|
|
event_code = KEV_DL_LINK_ON;
|
|
} else if (old_distributing_count != 0
|
|
&& ifb->ifb_distributing_count == 0) {
|
|
event_code = KEV_DL_LINK_OFF;
|
|
}
|
|
if (event_code != 0 && event_code != ifb->ifb_last_link_event) {
|
|
bond_ifp = ifb->ifb_ifp;
|
|
ifb->ifb_last_link_event = event_code;
|
|
}
|
|
}
|
|
|
|
bond_unlock();
|
|
if (bond_ifp != NULL) {
|
|
interface_link_event(bond_ifp, event_code);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_iff_event(__unused void *cookie, ifnet_t port_ifp,
|
|
__unused protocol_family_t protocol,
|
|
const struct kev_msg *event)
|
|
{
|
|
int event_code;
|
|
|
|
if (event->vendor_code != KEV_VENDOR_APPLE
|
|
|| event->kev_class != KEV_NETWORK_CLASS
|
|
|| event->kev_subclass != KEV_DL_SUBCLASS) {
|
|
return;
|
|
}
|
|
event_code = event->event_code;
|
|
switch (event_code) {
|
|
case KEV_DL_LINK_OFF:
|
|
case KEV_DL_LINK_ON:
|
|
case KEV_DL_IF_DETACHING:
|
|
case KEV_DL_IF_DETACHED:
|
|
bond_handle_event(port_ifp, event_code);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bond_iff_detached(__unused void *cookie, ifnet_t port_ifp)
|
|
{
|
|
bond_handle_event(port_ifp, KEV_DL_IF_DETACHED);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
interface_link_event(struct ifnet * ifp, u_int32_t event_code)
|
|
{
|
|
struct event {
|
|
u_int32_t ifnet_family;
|
|
u_int32_t unit;
|
|
char if_name[IFNAMSIZ];
|
|
};
|
|
_Alignas(struct kern_event_msg) char message[sizeof(struct kern_event_msg) + sizeof(struct event)] = { 0 };
|
|
struct kern_event_msg *header = (struct kern_event_msg*)message;
|
|
struct event *data = (struct event *)(header + 1);
|
|
|
|
header->total_size = sizeof(message);
|
|
header->vendor_code = KEV_VENDOR_APPLE;
|
|
header->kev_class = KEV_NETWORK_CLASS;
|
|
header->kev_subclass = KEV_DL_SUBCLASS;
|
|
header->event_code = event_code;
|
|
data->ifnet_family = ifnet_family(ifp);
|
|
data->unit = (u_int32_t)ifnet_unit(ifp);
|
|
strlcpy(data->if_name, ifnet_name(ifp), IFNAMSIZ);
|
|
ifnet_event(ifp, header);
|
|
}
|
|
|
|
static errno_t
|
|
bond_proto_input(ifnet_t ifp, protocol_family_t protocol, mbuf_t packet,
|
|
char *header)
|
|
{
|
|
#pragma unused(protocol, packet, header)
|
|
if (if_bond_debug != 0) {
|
|
printf("%s: unexpected packet from %s\n", __func__,
|
|
ifp->if_xname);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Function: bond_attach_protocol
|
|
* Purpose:
|
|
* Attach a DLIL protocol to the interface.
|
|
*
|
|
* The ethernet demux special cases to always return PF_BOND if the
|
|
* interface is bonded. That means we receive all traffic from that
|
|
* interface without passing any of the traffic to any other attached
|
|
* protocol.
|
|
*/
|
|
static int
|
|
bond_attach_protocol(struct ifnet *ifp)
|
|
{
|
|
int error;
|
|
struct ifnet_attach_proto_param reg;
|
|
|
|
bzero(®, sizeof(reg));
|
|
reg.input = bond_proto_input;
|
|
|
|
error = ifnet_attach_protocol(ifp, PF_BOND, ®);
|
|
if (error) {
|
|
printf("bond over %s%d: ifnet_attach_protocol failed, %d\n",
|
|
ifnet_name(ifp), ifnet_unit(ifp), error);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Function: bond_detach_protocol
|
|
* Purpose:
|
|
* Detach our DLIL protocol from an interface
|
|
*/
|
|
static int
|
|
bond_detach_protocol(struct ifnet *ifp)
|
|
{
|
|
int error;
|
|
|
|
error = ifnet_detach_protocol(ifp, PF_BOND);
|
|
if (error) {
|
|
printf("bond over %s%d: ifnet_detach_protocol failed, %d\n",
|
|
ifnet_name(ifp), ifnet_unit(ifp), error);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
/*
|
|
* Function: bond_attach_filter
|
|
* Purpose:
|
|
* Attach our DLIL interface filter.
|
|
*/
|
|
static int
|
|
bond_attach_filter(struct ifnet *ifp, interface_filter_t * filter_p)
|
|
{
|
|
int error;
|
|
struct iff_filter iff;
|
|
|
|
/*
|
|
* install an interface filter
|
|
*/
|
|
memset(&iff, 0, sizeof(struct iff_filter));
|
|
iff.iff_name = "com.apple.kernel.bsd.net.if_bond";
|
|
iff.iff_input = bond_iff_input;
|
|
iff.iff_event = bond_iff_event;
|
|
iff.iff_detached = bond_iff_detached;
|
|
error = dlil_attach_filter(ifp, &iff, filter_p,
|
|
DLIL_IFF_TSO | DLIL_IFF_INTERNAL);
|
|
if (error != 0) {
|
|
printf("%s: dlil_attach_filter failed %d\n", __func__, error);
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
/*
|
|
* DLIL interface family functions
|
|
*/
|
|
extern int ether_attach_inet(ifnet_t ifp, protocol_family_t protocol_family);
|
|
extern void ether_detach_inet(ifnet_t ifp, protocol_family_t protocol_family);
|
|
extern int ether_attach_inet6(ifnet_t ifp, protocol_family_t protocol_family);
|
|
extern void ether_detach_inet6(ifnet_t ifp, protocol_family_t protocol_family);
|
|
extern int ether_attach_at(ifnet_t ifp, protocol_family_t protocol_family);
|
|
extern void ether_detach_at(ifnet_t ifp, protocol_family_t protocol_family);
|
|
|
|
__private_extern__ int
|
|
bond_family_init(void)
|
|
{
|
|
int error = 0;
|
|
|
|
error = proto_register_plumber(PF_INET, APPLE_IF_FAM_BOND,
|
|
ether_attach_inet,
|
|
ether_detach_inet);
|
|
if (error != 0) {
|
|
printf("bond: proto_register_plumber failed for AF_INET error=%d\n",
|
|
error);
|
|
goto done;
|
|
}
|
|
error = proto_register_plumber(PF_INET6, APPLE_IF_FAM_BOND,
|
|
ether_attach_inet6,
|
|
ether_detach_inet6);
|
|
if (error != 0) {
|
|
printf("bond: proto_register_plumber failed for AF_INET6 error=%d\n",
|
|
error);
|
|
goto done;
|
|
}
|
|
error = bond_clone_attach();
|
|
if (error != 0) {
|
|
printf("bond: proto_register_plumber failed bond_clone_attach error=%d\n",
|
|
error);
|
|
goto done;
|
|
}
|
|
|
|
done:
|
|
return error;
|
|
}
|
|
/**
|
|
**
|
|
** LACP routines:
|
|
**
|
|
**/
|
|
|
|
/**
|
|
** LACP ifbond_list routines
|
|
**/
|
|
static bondport_ref
|
|
ifbond_list_find_moved_port(bondport_ref rx_port,
|
|
const lacp_actor_partner_tlv_ref atlv)
|
|
{
|
|
ifbond_ref bond;
|
|
bondport_ref p;
|
|
partner_state_ref ps;
|
|
LAG_info_ref ps_li;
|
|
|
|
TAILQ_FOREACH(bond, &g_bond->ifbond_list, ifb_bond_list) {
|
|
TAILQ_FOREACH(p, &bond->ifb_port_list, po_port_list) {
|
|
if (rx_port == p) {
|
|
/* no point in comparing against ourselves */
|
|
continue;
|
|
}
|
|
if (p->po_receive_state != ReceiveState_PORT_DISABLED) {
|
|
/* it's not clear that we should be checking this */
|
|
continue;
|
|
}
|
|
ps = &p->po_partner_state;
|
|
if (lacp_actor_partner_state_defaulted(ps->ps_state)) {
|
|
continue;
|
|
}
|
|
ps_li = &ps->ps_lag_info;
|
|
if (ps->ps_port == lacp_actor_partner_tlv_get_port(atlv)
|
|
&& bcmp(&ps_li->li_system, atlv->lap_system,
|
|
sizeof(ps_li->li_system)) == 0) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("System " EA_FORMAT
|
|
" Port 0x%x moved from %s to %s\n",
|
|
EA_LIST(&ps_li->li_system), ps->ps_port,
|
|
bondport_get_name(p),
|
|
bondport_get_name(rx_port));
|
|
}
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
** LACP ifbond, LAG routines
|
|
**/
|
|
|
|
static int
|
|
ifbond_selection(ifbond_ref bond)
|
|
{
|
|
int all_ports_ready = 0;
|
|
int active_media = 0;
|
|
LAG_ref lag = NULL;
|
|
int lag_changed = 0;
|
|
bondport_ref p;
|
|
int port_speed = 0;
|
|
|
|
lag = ifbond_find_best_LAG(bond, &active_media);
|
|
if (lag != bond->ifb_active_lag) {
|
|
if (bond->ifb_active_lag != NULL) {
|
|
ifbond_deactivate_LAG(bond, bond->ifb_active_lag);
|
|
bond->ifb_active_lag = NULL;
|
|
}
|
|
bond->ifb_active_lag = lag;
|
|
if (lag != NULL) {
|
|
ifbond_activate_LAG(bond, lag, active_media);
|
|
}
|
|
lag_changed = 1;
|
|
} else if (lag != NULL) {
|
|
if (lag->lag_active_media != active_media) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("LAG PORT SPEED CHANGED from %d to %d\n",
|
|
link_speed(lag->lag_active_media),
|
|
link_speed(active_media));
|
|
}
|
|
ifbond_deactivate_LAG(bond, lag);
|
|
ifbond_activate_LAG(bond, lag, active_media);
|
|
lag_changed = 1;
|
|
}
|
|
}
|
|
if (lag != NULL) {
|
|
port_speed = link_speed(active_media);
|
|
all_ports_ready = ifbond_all_ports_ready(bond);
|
|
}
|
|
TAILQ_FOREACH(p, &bond->ifb_port_list, po_port_list) {
|
|
if (lag != NULL && p->po_lag == lag
|
|
&& media_speed(&p->po_media_info) == port_speed
|
|
&& (p->po_mux_state == MuxState_DETACHED
|
|
|| p->po_selected == SelectedState_SELECTED
|
|
|| p->po_selected == SelectedState_STANDBY)
|
|
&& bondport_aggregatable(p)) {
|
|
if (bond->ifb_max_active > 0) {
|
|
if (lag->lag_selected_port_count < bond->ifb_max_active) {
|
|
if (p->po_selected == SelectedState_STANDBY
|
|
|| p->po_selected == SelectedState_UNSELECTED) {
|
|
bondport_set_selected(p, SelectedState_SELECTED);
|
|
}
|
|
} else if (p->po_selected == SelectedState_UNSELECTED) {
|
|
bondport_set_selected(p, SelectedState_STANDBY);
|
|
}
|
|
} else {
|
|
bondport_set_selected(p, SelectedState_SELECTED);
|
|
}
|
|
}
|
|
if (bondport_flags_selected_changed(p)) {
|
|
bondport_flags_clear_selected_changed(p);
|
|
bondport_mux_machine(p, LAEventSelectedChange, NULL);
|
|
}
|
|
if (all_ports_ready
|
|
&& bondport_flags_ready(p)
|
|
&& p->po_mux_state == MuxState_WAITING) {
|
|
bondport_mux_machine(p, LAEventReady, NULL);
|
|
}
|
|
bondport_transmit_machine(p, LAEventStart, NULL);
|
|
}
|
|
return lag_changed;
|
|
}
|
|
|
|
static LAG_ref
|
|
ifbond_find_best_LAG(ifbond_ref bond, int * active_media)
|
|
{
|
|
int best_active = 0;
|
|
LAG_ref best_lag = NULL;
|
|
int best_count = 0;
|
|
int best_speed = 0;
|
|
LAG_ref lag;
|
|
|
|
if (bond->ifb_active_lag != NULL) {
|
|
best_lag = bond->ifb_active_lag;
|
|
best_count = LAG_get_aggregatable_port_count(best_lag, &best_active);
|
|
if (bond->ifb_max_active > 0
|
|
&& best_count > bond->ifb_max_active) {
|
|
best_count = bond->ifb_max_active;
|
|
}
|
|
best_speed = link_speed(best_active);
|
|
}
|
|
TAILQ_FOREACH(lag, &bond->ifb_lag_list, lag_list) {
|
|
int active;
|
|
int count;
|
|
int speed;
|
|
|
|
if (lag == bond->ifb_active_lag) {
|
|
/* we've already computed it */
|
|
continue;
|
|
}
|
|
count = LAG_get_aggregatable_port_count(lag, &active);
|
|
if (count == 0) {
|
|
continue;
|
|
}
|
|
if (bond->ifb_max_active > 0
|
|
&& count > bond->ifb_max_active) {
|
|
/* if there's a limit, don't count extra links */
|
|
count = bond->ifb_max_active;
|
|
}
|
|
speed = link_speed(active);
|
|
if ((count * speed) > (best_count * best_speed)) {
|
|
best_count = count;
|
|
best_speed = speed;
|
|
best_active = active;
|
|
best_lag = lag;
|
|
}
|
|
}
|
|
if (best_count == 0) {
|
|
return NULL;
|
|
}
|
|
*active_media = best_active;
|
|
return best_lag;
|
|
}
|
|
|
|
static void
|
|
ifbond_deactivate_LAG(__unused ifbond_ref bond, LAG_ref lag)
|
|
{
|
|
bondport_ref p;
|
|
|
|
TAILQ_FOREACH(p, &lag->lag_port_list, po_lag_port_list) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
ifbond_activate_LAG(ifbond_ref bond, LAG_ref lag, int active_media)
|
|
{
|
|
int need = 0;
|
|
bondport_ref p;
|
|
|
|
if (bond->ifb_max_active > 0) {
|
|
need = bond->ifb_max_active;
|
|
}
|
|
lag->lag_active_media = active_media;
|
|
TAILQ_FOREACH(p, &lag->lag_port_list, po_lag_port_list) {
|
|
if (bondport_aggregatable(p) == 0) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
} else if (media_speed(&p->po_media_info) != link_speed(active_media)) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
} else if (p->po_mux_state == MuxState_DETACHED) {
|
|
if (bond->ifb_max_active > 0) {
|
|
if (need > 0) {
|
|
bondport_set_selected(p, SelectedState_SELECTED);
|
|
need--;
|
|
} else {
|
|
bondport_set_selected(p, SelectedState_STANDBY);
|
|
}
|
|
} else {
|
|
bondport_set_selected(p, SelectedState_SELECTED);
|
|
}
|
|
} else {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
ifbond_set_max_active(ifbond_ref bond, int max_active)
|
|
{
|
|
LAG_ref lag = bond->ifb_active_lag;
|
|
|
|
bond->ifb_max_active = max_active;
|
|
if (bond->ifb_max_active <= 0 || lag == NULL) {
|
|
return;
|
|
}
|
|
if (lag->lag_selected_port_count > bond->ifb_max_active) {
|
|
bondport_ref p;
|
|
int remove_count;
|
|
|
|
remove_count = lag->lag_selected_port_count - bond->ifb_max_active;
|
|
TAILQ_FOREACH(p, &lag->lag_port_list, po_lag_port_list) {
|
|
if (p->po_selected == SelectedState_SELECTED) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
remove_count--;
|
|
if (remove_count == 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
ifbond_all_ports_ready(ifbond_ref bond)
|
|
{
|
|
int ready = 0;
|
|
bondport_ref p;
|
|
|
|
if (bond->ifb_active_lag == NULL) {
|
|
return 0;
|
|
}
|
|
TAILQ_FOREACH(p, &bond->ifb_active_lag->lag_port_list, po_lag_port_list) {
|
|
if (p->po_mux_state == MuxState_WAITING
|
|
&& p->po_selected == SelectedState_SELECTED) {
|
|
if (bondport_flags_ready(p) == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
/* note that there was at least one ready port */
|
|
ready = 1;
|
|
}
|
|
return ready;
|
|
}
|
|
|
|
static int
|
|
ifbond_all_ports_attached(ifbond_ref bond, bondport_ref this_port)
|
|
{
|
|
bondport_ref p;
|
|
|
|
TAILQ_FOREACH(p, &bond->ifb_port_list, po_port_list) {
|
|
if (this_port == p) {
|
|
continue;
|
|
}
|
|
if (bondport_flags_mux_attached(p) == 0) {
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static LAG_ref
|
|
ifbond_get_LAG_matching_port(ifbond_ref bond, bondport_ref p)
|
|
{
|
|
LAG_ref lag;
|
|
|
|
TAILQ_FOREACH(lag, &bond->ifb_lag_list, lag_list) {
|
|
if (bcmp(&lag->lag_info, &p->po_partner_state.ps_lag_info,
|
|
sizeof(lag->lag_info)) == 0) {
|
|
return lag;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int
|
|
LAG_get_aggregatable_port_count(LAG_ref lag, int * active_media)
|
|
{
|
|
int active;
|
|
int count;
|
|
bondport_ref p;
|
|
int speed;
|
|
|
|
active = 0;
|
|
count = 0;
|
|
speed = 0;
|
|
TAILQ_FOREACH(p, &lag->lag_port_list, po_lag_port_list) {
|
|
if (bondport_aggregatable(p)) {
|
|
int this_speed;
|
|
|
|
this_speed = media_speed(&p->po_media_info);
|
|
if (this_speed == 0) {
|
|
continue;
|
|
}
|
|
if (this_speed > speed) {
|
|
active = p->po_media_info.mi_active;
|
|
speed = this_speed;
|
|
count = 1;
|
|
} else if (this_speed == speed) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
*active_media = active;
|
|
return count;
|
|
}
|
|
|
|
|
|
/**
|
|
** LACP bondport routines
|
|
**/
|
|
static void
|
|
bondport_link_status_changed(bondport_ref p)
|
|
{
|
|
ifbond_ref bond = p->po_bond;
|
|
|
|
if (if_bond_debug) {
|
|
if (media_active(&p->po_media_info)) {
|
|
const char * duplex_string;
|
|
|
|
if (media_full_duplex(&p->po_media_info)) {
|
|
duplex_string = "full";
|
|
} else if (media_type_unknown(&p->po_media_info)) {
|
|
duplex_string = "unknown";
|
|
} else {
|
|
duplex_string = "half";
|
|
}
|
|
timestamp_printf("[%s] Link UP %d Mbit/s %s duplex\n",
|
|
bondport_get_name(p),
|
|
media_speed(&p->po_media_info),
|
|
duplex_string);
|
|
} else {
|
|
timestamp_printf("[%s] Link DOWN\n",
|
|
bondport_get_name(p));
|
|
}
|
|
}
|
|
if (bond->ifb_mode == IF_BOND_MODE_LACP) {
|
|
if (media_active(&p->po_media_info)
|
|
&& bond->ifb_active_lag != NULL
|
|
&& p->po_lag == bond->ifb_active_lag
|
|
&& p->po_selected != SelectedState_UNSELECTED) {
|
|
if (media_speed(&p->po_media_info) != p->po_lag->lag_active_media) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Port speed %d differs from LAG %d\n",
|
|
bondport_get_name(p),
|
|
media_speed(&p->po_media_info),
|
|
link_speed(p->po_lag->lag_active_media));
|
|
}
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
}
|
|
}
|
|
bondport_receive_machine(p, LAEventMediaChange, NULL);
|
|
bondport_mux_machine(p, LAEventMediaChange, NULL);
|
|
bondport_periodic_transmit_machine(p, LAEventMediaChange, NULL);
|
|
} else {
|
|
if (media_active(&p->po_media_info)) {
|
|
bondport_enable_distributing(p);
|
|
} else {
|
|
bondport_disable_distributing(p);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static int
|
|
bondport_aggregatable(bondport_ref p)
|
|
{
|
|
partner_state_ref ps = &p->po_partner_state;
|
|
|
|
if (lacp_actor_partner_state_aggregatable(p->po_actor_state) == 0
|
|
|| lacp_actor_partner_state_aggregatable(ps->ps_state) == 0) {
|
|
/* we and/or our partner are individual */
|
|
return 0;
|
|
}
|
|
if (p->po_lag == NULL) {
|
|
return 0;
|
|
}
|
|
switch (p->po_receive_state) {
|
|
default:
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Port is not selectable\n",
|
|
bondport_get_name(p));
|
|
}
|
|
return 0;
|
|
case ReceiveState_CURRENT:
|
|
case ReceiveState_EXPIRED:
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
bondport_matches_LAG(bondport_ref p, LAG_ref lag)
|
|
{
|
|
LAG_info_ref lag_li;
|
|
partner_state_ref ps;
|
|
LAG_info_ref ps_li;
|
|
|
|
ps = &p->po_partner_state;
|
|
ps_li = &ps->ps_lag_info;
|
|
lag_li = &lag->lag_info;
|
|
if (ps_li->li_system_priority == lag_li->li_system_priority
|
|
&& ps_li->li_key == lag_li->li_key
|
|
&& (bcmp(&ps_li->li_system, &lag_li->li_system,
|
|
sizeof(lag_li->li_system))
|
|
== 0)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
bondport_remove_from_LAG(bondport_ref p)
|
|
{
|
|
int active_lag = 0;
|
|
ifbond_ref bond = p->po_bond;
|
|
LAG_ref lag = p->po_lag;
|
|
|
|
if (lag == NULL) {
|
|
return 0;
|
|
}
|
|
TAILQ_REMOVE(&lag->lag_port_list, p, po_lag_port_list);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Removed from LAG (0x%04x," EA_FORMAT
|
|
",0x%04x)\n",
|
|
bondport_get_name(p),
|
|
lag->lag_info.li_system_priority,
|
|
EA_LIST(&lag->lag_info.li_system),
|
|
lag->lag_info.li_key);
|
|
}
|
|
p->po_lag = NULL;
|
|
lag->lag_port_count--;
|
|
if (lag->lag_port_count > 0) {
|
|
return bond->ifb_active_lag == lag;
|
|
}
|
|
if (if_bond_debug) {
|
|
timestamp_printf("Key 0x%04x: LAG Released (%04x," EA_FORMAT
|
|
",0x%04x)\n",
|
|
bond->ifb_key,
|
|
lag->lag_info.li_system_priority,
|
|
EA_LIST(&lag->lag_info.li_system),
|
|
lag->lag_info.li_key);
|
|
}
|
|
TAILQ_REMOVE(&bond->ifb_lag_list, lag, lag_list);
|
|
if (bond->ifb_active_lag == lag) {
|
|
bond->ifb_active_lag = NULL;
|
|
active_lag = 1;
|
|
}
|
|
kfree_type(struct LAG_s, lag);
|
|
return active_lag;
|
|
}
|
|
|
|
static void
|
|
bondport_add_to_LAG(bondport_ref p, LAG_ref lag)
|
|
{
|
|
TAILQ_INSERT_TAIL(&lag->lag_port_list, p, po_lag_port_list);
|
|
p->po_lag = lag;
|
|
lag->lag_port_count++;
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Added to LAG (0x%04x," EA_FORMAT "0x%04x)\n",
|
|
bondport_get_name(p),
|
|
lag->lag_info.li_system_priority,
|
|
EA_LIST(&lag->lag_info.li_system),
|
|
lag->lag_info.li_key);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_assign_to_LAG(bondport_ref p)
|
|
{
|
|
ifbond_ref bond = p->po_bond;
|
|
LAG_ref lag;
|
|
|
|
if (lacp_actor_partner_state_defaulted(p->po_actor_state)) {
|
|
bondport_remove_from_LAG(p);
|
|
return;
|
|
}
|
|
lag = p->po_lag;
|
|
if (lag != NULL) {
|
|
if (bondport_matches_LAG(p, lag)) {
|
|
/* still OK */
|
|
return;
|
|
}
|
|
bondport_remove_from_LAG(p);
|
|
}
|
|
lag = ifbond_get_LAG_matching_port(bond, p);
|
|
if (lag != NULL) {
|
|
bondport_add_to_LAG(p, lag);
|
|
return;
|
|
}
|
|
lag = kalloc_type(struct LAG_s, Z_WAITOK);
|
|
TAILQ_INIT(&lag->lag_port_list);
|
|
lag->lag_port_count = 0;
|
|
lag->lag_selected_port_count = 0;
|
|
lag->lag_info = p->po_partner_state.ps_lag_info;
|
|
TAILQ_INSERT_TAIL(&bond->ifb_lag_list, lag, lag_list);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("Key 0x%04x: LAG Created (0x%04x," EA_FORMAT
|
|
",0x%04x)\n",
|
|
bond->ifb_key,
|
|
lag->lag_info.li_system_priority,
|
|
EA_LIST(&lag->lag_info.li_system),
|
|
lag->lag_info.li_key);
|
|
}
|
|
bondport_add_to_LAG(p, lag);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_lacpdu(bondport_ref p, lacpdu_ref in_lacpdu_p)
|
|
{
|
|
bondport_ref moved_port;
|
|
|
|
moved_port
|
|
= ifbond_list_find_moved_port(p, (const lacp_actor_partner_tlv_ref)
|
|
&in_lacpdu_p->la_actor_tlv);
|
|
if (moved_port != NULL) {
|
|
bondport_receive_machine(moved_port, LAEventPortMoved, NULL);
|
|
}
|
|
bondport_receive_machine(p, LAEventPacket, in_lacpdu_p);
|
|
bondport_mux_machine(p, LAEventPacket, in_lacpdu_p);
|
|
bondport_periodic_transmit_machine(p, LAEventPacket, in_lacpdu_p);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_set_selected(bondport_ref p, SelectedState s)
|
|
{
|
|
if (s != p->po_selected) {
|
|
ifbond_ref bond = p->po_bond;
|
|
LAG_ref lag = p->po_lag;
|
|
|
|
bondport_flags_set_selected_changed(p);
|
|
if (lag != NULL && bond->ifb_active_lag == lag) {
|
|
if (p->po_selected == SelectedState_SELECTED) {
|
|
lag->lag_selected_port_count--;
|
|
} else if (s == SelectedState_SELECTED) {
|
|
lag->lag_selected_port_count++;
|
|
}
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] SetSelected: %s (was %s)\n",
|
|
bondport_get_name(p),
|
|
SelectedStateString(s),
|
|
SelectedStateString(p->po_selected));
|
|
}
|
|
}
|
|
}
|
|
p->po_selected = s;
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Receive machine
|
|
**/
|
|
|
|
static void
|
|
bondport_UpdateDefaultSelected(bondport_ref p)
|
|
{
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_RecordDefault(bondport_ref p)
|
|
{
|
|
bzero(&p->po_partner_state, sizeof(p->po_partner_state));
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_defaulted(p->po_actor_state);
|
|
bondport_assign_to_LAG(p);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_UpdateSelected(bondport_ref p, lacpdu_ref lacpdu_p)
|
|
{
|
|
lacp_actor_partner_tlv_ref actor;
|
|
partner_state_ref ps;
|
|
LAG_info_ref ps_li;
|
|
|
|
/* compare the PDU's Actor information to our Partner state */
|
|
actor = (lacp_actor_partner_tlv_ref)lacpdu_p->la_actor_tlv;
|
|
ps = &p->po_partner_state;
|
|
ps_li = &ps->ps_lag_info;
|
|
if (lacp_actor_partner_tlv_get_port(actor) != ps->ps_port
|
|
|| (lacp_actor_partner_tlv_get_port_priority(actor)
|
|
!= ps->ps_port_priority)
|
|
|| bcmp(actor->lap_system, &ps_li->li_system, sizeof(ps_li->li_system))
|
|
|| (lacp_actor_partner_tlv_get_system_priority(actor)
|
|
!= ps_li->li_system_priority)
|
|
|| (lacp_actor_partner_tlv_get_key(actor) != ps_li->li_key)
|
|
|| (lacp_actor_partner_state_aggregatable(actor->lap_state)
|
|
!= lacp_actor_partner_state_aggregatable(ps->ps_state))) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] updateSelected UNSELECTED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_RecordPDU(bondport_ref p, lacpdu_ref lacpdu_p)
|
|
{
|
|
lacp_actor_partner_tlv_ref actor;
|
|
ifbond_ref bond = p->po_bond;
|
|
int lacp_maintain = 0;
|
|
partner_state_ref ps;
|
|
lacp_actor_partner_tlv_ref partner;
|
|
LAG_info_ref ps_li;
|
|
|
|
/* copy the PDU's Actor information into our Partner state */
|
|
actor = (lacp_actor_partner_tlv_ref)lacpdu_p->la_actor_tlv;
|
|
ps = &p->po_partner_state;
|
|
ps_li = &ps->ps_lag_info;
|
|
ps->ps_port = lacp_actor_partner_tlv_get_port(actor);
|
|
ps->ps_port_priority = lacp_actor_partner_tlv_get_port_priority(actor);
|
|
ps_li->li_system = *((lacp_system_ref)actor->lap_system);
|
|
ps_li->li_system_priority
|
|
= lacp_actor_partner_tlv_get_system_priority(actor);
|
|
ps_li->li_key = lacp_actor_partner_tlv_get_key(actor);
|
|
ps->ps_state = lacp_actor_partner_state_set_out_of_sync(actor->lap_state);
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_not_defaulted(p->po_actor_state);
|
|
|
|
/* compare the PDU's Partner information to our own information */
|
|
partner = (lacp_actor_partner_tlv_ref)lacpdu_p->la_partner_tlv;
|
|
|
|
if (lacp_actor_partner_state_active_lacp(ps->ps_state)
|
|
|| (lacp_actor_partner_state_active_lacp(p->po_actor_state)
|
|
&& lacp_actor_partner_state_active_lacp(partner->lap_state))) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] recordPDU: LACP will maintain\n",
|
|
bondport_get_name(p));
|
|
}
|
|
lacp_maintain = 1;
|
|
}
|
|
if ((lacp_actor_partner_tlv_get_port(partner)
|
|
== bondport_get_index(p))
|
|
&& lacp_actor_partner_tlv_get_port_priority(partner) == p->po_priority
|
|
&& bcmp(partner->lap_system, &g_bond->system,
|
|
sizeof(g_bond->system)) == 0
|
|
&& (lacp_actor_partner_tlv_get_system_priority(partner)
|
|
== g_bond->system_priority)
|
|
&& lacp_actor_partner_tlv_get_key(partner) == bond->ifb_key
|
|
&& (lacp_actor_partner_state_aggregatable(partner->lap_state)
|
|
== lacp_actor_partner_state_aggregatable(p->po_actor_state))
|
|
&& lacp_actor_partner_state_in_sync(actor->lap_state)
|
|
&& lacp_maintain) {
|
|
ps->ps_state = lacp_actor_partner_state_set_in_sync(ps->ps_state);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] recordPDU: LACP partner in sync\n",
|
|
bondport_get_name(p));
|
|
}
|
|
} else if (lacp_actor_partner_state_aggregatable(actor->lap_state) == 0
|
|
&& lacp_actor_partner_state_in_sync(actor->lap_state)
|
|
&& lacp_maintain) {
|
|
ps->ps_state = lacp_actor_partner_state_set_in_sync(ps->ps_state);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] recordPDU: LACP partner in sync (ind)\n",
|
|
bondport_get_name(p));
|
|
}
|
|
}
|
|
bondport_assign_to_LAG(p);
|
|
return;
|
|
}
|
|
|
|
static __inline__ lacp_actor_partner_state
|
|
updateNTTBits(lacp_actor_partner_state s)
|
|
{
|
|
return s & (LACP_ACTOR_PARTNER_STATE_LACP_ACTIVITY
|
|
| LACP_ACTOR_PARTNER_STATE_LACP_TIMEOUT
|
|
| LACP_ACTOR_PARTNER_STATE_AGGREGATION
|
|
| LACP_ACTOR_PARTNER_STATE_SYNCHRONIZATION);
|
|
}
|
|
|
|
static void
|
|
bondport_UpdateNTT(bondport_ref p, lacpdu_ref lacpdu_p)
|
|
{
|
|
ifbond_ref bond = p->po_bond;
|
|
lacp_actor_partner_tlv_ref partner;
|
|
|
|
/* compare the PDU's Actor information to our Partner state */
|
|
partner = (lacp_actor_partner_tlv_ref)lacpdu_p->la_partner_tlv;
|
|
if ((lacp_actor_partner_tlv_get_port(partner) != bondport_get_index(p))
|
|
|| lacp_actor_partner_tlv_get_port_priority(partner) != p->po_priority
|
|
|| bcmp(partner->lap_system, &g_bond->system, sizeof(g_bond->system))
|
|
|| (lacp_actor_partner_tlv_get_system_priority(partner)
|
|
!= g_bond->system_priority)
|
|
|| lacp_actor_partner_tlv_get_key(partner) != bond->ifb_key
|
|
|| (updateNTTBits(partner->lap_state)
|
|
!= updateNTTBits(p->po_actor_state))) {
|
|
bondport_flags_set_ntt(p);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] updateNTT: Need To Transmit\n",
|
|
bondport_get_name(p));
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_AttachMuxToAggregator(bondport_ref p)
|
|
{
|
|
if (bondport_flags_mux_attached(p) == 0) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Attached Mux To Aggregator\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_set_mux_attached(p);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_DetachMuxFromAggregator(bondport_ref p)
|
|
{
|
|
if (bondport_flags_mux_attached(p)) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Detached Mux From Aggregator\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_clear_mux_attached(p);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_enable_distributing(bondport_ref p)
|
|
{
|
|
if (bondport_flags_distributing(p) == 0) {
|
|
ifbond_ref bond = p->po_bond;
|
|
|
|
bond->ifb_distributing_array[bond->ifb_distributing_count++] = p;
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Distribution Enabled\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_set_distributing(p);
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_disable_distributing(bondport_ref p)
|
|
{
|
|
if (bondport_flags_distributing(p)) {
|
|
bondport_ref * array;
|
|
ifbond_ref bond;
|
|
int count;
|
|
int i;
|
|
|
|
bond = p->po_bond;
|
|
array = bond->ifb_distributing_array;
|
|
count = bond->ifb_distributing_count;
|
|
for (i = 0; i < count; i++) {
|
|
if (array[i] == p) {
|
|
int j;
|
|
|
|
for (j = i; j < (count - 1); j++) {
|
|
array[j] = array[j + 1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
bond->ifb_distributing_count--;
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Distribution Disabled\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_clear_distributing(p);
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Receive machine functions
|
|
**/
|
|
static void
|
|
bondport_receive_machine_initialize(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_receive_machine_port_disabled(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_receive_machine_expired(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_receive_machine_lacp_disabled(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_receive_machine_defaulted(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_receive_machine_current(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
static void
|
|
bondport_receive_machine_event(bondport_ref p, LAEvent event,
|
|
void * event_data)
|
|
{
|
|
switch (p->po_receive_state) {
|
|
case ReceiveState_none:
|
|
bondport_receive_machine_initialize(p, LAEventStart, NULL);
|
|
break;
|
|
case ReceiveState_INITIALIZE:
|
|
bondport_receive_machine_initialize(p, event, event_data);
|
|
break;
|
|
case ReceiveState_PORT_DISABLED:
|
|
bondport_receive_machine_port_disabled(p, event, event_data);
|
|
break;
|
|
case ReceiveState_EXPIRED:
|
|
bondport_receive_machine_expired(p, event, event_data);
|
|
break;
|
|
case ReceiveState_LACP_DISABLED:
|
|
bondport_receive_machine_lacp_disabled(p, event, event_data);
|
|
break;
|
|
case ReceiveState_DEFAULTED:
|
|
bondport_receive_machine_defaulted(p, event, event_data);
|
|
break;
|
|
case ReceiveState_CURRENT:
|
|
bondport_receive_machine_current(p, event, event_data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine(bondport_ref p, LAEvent event,
|
|
void * event_data)
|
|
{
|
|
switch (event) {
|
|
case LAEventPacket:
|
|
if (p->po_receive_state != ReceiveState_LACP_DISABLED) {
|
|
bondport_receive_machine_current(p, event, event_data);
|
|
}
|
|
break;
|
|
case LAEventMediaChange:
|
|
if (media_active(&p->po_media_info)) {
|
|
switch (p->po_receive_state) {
|
|
case ReceiveState_PORT_DISABLED:
|
|
case ReceiveState_LACP_DISABLED:
|
|
bondport_receive_machine_port_disabled(p, LAEventMediaChange, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else {
|
|
bondport_receive_machine_port_disabled(p, LAEventStart, NULL);
|
|
}
|
|
break;
|
|
default:
|
|
bondport_receive_machine_event(p, event, event_data);
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_initialize(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive INITIALIZE\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_INITIALIZE;
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
bondport_RecordDefault(p);
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_not_expired(p->po_actor_state);
|
|
bondport_receive_machine_port_disabled(p, LAEventStart, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_port_disabled(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
partner_state_ref ps;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive PORT_DISABLED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_PORT_DISABLED;
|
|
ps = &p->po_partner_state;
|
|
ps->ps_state = lacp_actor_partner_state_set_out_of_sync(ps->ps_state);
|
|
OS_FALLTHROUGH;
|
|
case LAEventMediaChange:
|
|
if (media_active(&p->po_media_info)) {
|
|
if (media_ok(&p->po_media_info)) {
|
|
bondport_receive_machine_expired(p, LAEventStart, NULL);
|
|
} else {
|
|
bondport_receive_machine_lacp_disabled(p, LAEventStart, NULL);
|
|
}
|
|
} else if (p->po_selected == SelectedState_SELECTED) {
|
|
struct timeval tv;
|
|
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive PORT_DISABLED: "
|
|
"link timer started\n",
|
|
bondport_get_name(p));
|
|
}
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
devtimer_set_relative(p->po_current_while_timer, tv,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_receive_machine_port_disabled,
|
|
(void *)LAEventTimeout, NULL);
|
|
} else if (p->po_selected == SelectedState_STANDBY) {
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
}
|
|
break;
|
|
case LAEventTimeout:
|
|
if (p->po_selected == SelectedState_SELECTED) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive PORT_DISABLED: "
|
|
"link timer completed, marking UNSELECTED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
}
|
|
break;
|
|
case LAEventPortMoved:
|
|
bondport_receive_machine_initialize(p, LAEventStart, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_expired(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
lacp_actor_partner_state s;
|
|
struct timeval tv;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive EXPIRED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_EXPIRED;
|
|
s = p->po_partner_state.ps_state;
|
|
s = lacp_actor_partner_state_set_out_of_sync(s);
|
|
s = lacp_actor_partner_state_set_short_timeout(s);
|
|
p->po_partner_state.ps_state = s;
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_expired(p->po_actor_state);
|
|
/* start current_while timer */
|
|
tv.tv_sec = LACP_SHORT_TIMEOUT_TIME;
|
|
tv.tv_usec = 0;
|
|
devtimer_set_relative(p->po_current_while_timer, tv,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_receive_machine_expired,
|
|
(void *)LAEventTimeout, NULL);
|
|
|
|
break;
|
|
case LAEventTimeout:
|
|
bondport_receive_machine_defaulted(p, LAEventStart, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_lacp_disabled(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
partner_state_ref ps;
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive LACP_DISABLED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_LACP_DISABLED;
|
|
bondport_set_selected(p, SelectedState_UNSELECTED);
|
|
bondport_RecordDefault(p);
|
|
ps = &p->po_partner_state;
|
|
ps->ps_state = lacp_actor_partner_state_set_individual(ps->ps_state);
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_not_expired(p->po_actor_state);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_defaulted(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive DEFAULTED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_DEFAULTED;
|
|
bondport_UpdateDefaultSelected(p);
|
|
bondport_RecordDefault(p);
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_not_expired(p->po_actor_state);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_receive_machine_current(bondport_ref p, LAEvent event,
|
|
void * event_data)
|
|
{
|
|
partner_state_ref ps;
|
|
struct timeval tv;
|
|
|
|
switch (event) {
|
|
case LAEventPacket:
|
|
devtimer_cancel(p->po_current_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Receive CURRENT\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_receive_state = ReceiveState_CURRENT;
|
|
bondport_UpdateSelected(p, event_data);
|
|
bondport_UpdateNTT(p, event_data);
|
|
bondport_RecordPDU(p, event_data);
|
|
p->po_actor_state
|
|
= lacp_actor_partner_state_set_not_expired(p->po_actor_state);
|
|
bondport_assign_to_LAG(p);
|
|
/* start current_while timer */
|
|
ps = &p->po_partner_state;
|
|
if (lacp_actor_partner_state_short_timeout(ps->ps_state)) {
|
|
tv.tv_sec = LACP_SHORT_TIMEOUT_TIME;
|
|
} else {
|
|
tv.tv_sec = LACP_LONG_TIMEOUT_TIME;
|
|
}
|
|
tv.tv_usec = 0;
|
|
devtimer_set_relative(p->po_current_while_timer, tv,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_receive_machine_current,
|
|
(void *)LAEventTimeout, NULL);
|
|
break;
|
|
case LAEventTimeout:
|
|
bondport_receive_machine_expired(p, LAEventStart, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Periodic Transmission machine
|
|
**/
|
|
|
|
static void
|
|
bondport_periodic_transmit_machine(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
int interval;
|
|
partner_state_ref ps;
|
|
struct timeval tv;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] periodic_transmit Start\n",
|
|
bondport_get_name(p));
|
|
}
|
|
OS_FALLTHROUGH;
|
|
case LAEventMediaChange:
|
|
devtimer_cancel(p->po_periodic_timer);
|
|
p->po_periodic_interval = 0;
|
|
if (media_active(&p->po_media_info) == 0
|
|
|| media_ok(&p->po_media_info) == 0) {
|
|
break;
|
|
}
|
|
OS_FALLTHROUGH;
|
|
case LAEventPacket:
|
|
/* Neither Partner nor Actor are LACP Active, no periodic tx */
|
|
ps = &p->po_partner_state;
|
|
if (lacp_actor_partner_state_active_lacp(p->po_actor_state) == 0
|
|
&& (lacp_actor_partner_state_active_lacp(ps->ps_state)
|
|
== 0)) {
|
|
devtimer_cancel(p->po_periodic_timer);
|
|
p->po_periodic_interval = 0;
|
|
break;
|
|
}
|
|
if (lacp_actor_partner_state_short_timeout(ps->ps_state)) {
|
|
interval = LACP_FAST_PERIODIC_TIME;
|
|
} else {
|
|
interval = LACP_SLOW_PERIODIC_TIME;
|
|
}
|
|
if (p->po_periodic_interval != interval) {
|
|
if (interval == LACP_FAST_PERIODIC_TIME
|
|
&& p->po_periodic_interval == LACP_SLOW_PERIODIC_TIME) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] periodic_transmit:"
|
|
" Need To Transmit\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_set_ntt(p);
|
|
}
|
|
p->po_periodic_interval = interval;
|
|
tv.tv_usec = 0;
|
|
tv.tv_sec = interval;
|
|
devtimer_set_relative(p->po_periodic_timer, tv,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_periodic_transmit_machine,
|
|
(void *)LAEventTimeout, NULL);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Periodic Transmission Timer: %d secs\n",
|
|
bondport_get_name(p),
|
|
p->po_periodic_interval);
|
|
}
|
|
}
|
|
break;
|
|
case LAEventTimeout:
|
|
bondport_flags_set_ntt(p);
|
|
tv.tv_sec = p->po_periodic_interval;
|
|
tv.tv_usec = 0;
|
|
devtimer_set_relative(p->po_periodic_timer, tv, (devtimer_timeout_func)(void (*)(void))
|
|
bondport_periodic_transmit_machine,
|
|
(void *)LAEventTimeout, NULL);
|
|
if (if_bond_debug > 1) {
|
|
timestamp_printf("[%s] Periodic Transmission Timer: %d secs\n",
|
|
bondport_get_name(p), p->po_periodic_interval);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Transmit machine
|
|
**/
|
|
static int
|
|
bondport_can_transmit(bondport_ref p, int32_t current_secs,
|
|
__darwin_time_t * next_secs)
|
|
{
|
|
if (p->po_last_transmit_secs != current_secs) {
|
|
p->po_last_transmit_secs = current_secs;
|
|
p->po_n_transmit = 0;
|
|
}
|
|
if (p->po_n_transmit < LACP_PACKET_RATE) {
|
|
p->po_n_transmit++;
|
|
return 1;
|
|
}
|
|
if (next_secs != NULL) {
|
|
*next_secs = current_secs + 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
bondport_transmit_machine(bondport_ref p, LAEvent event,
|
|
void * event_data)
|
|
{
|
|
lacp_actor_partner_tlv_ref aptlv;
|
|
lacp_collector_tlv_ref ctlv;
|
|
struct timeval next_tick_time = {.tv_sec = 0, .tv_usec = 0};
|
|
lacpdu_ref out_lacpdu_p;
|
|
packet_buffer_ref pkt;
|
|
partner_state_ref ps;
|
|
LAG_info_ref ps_li;
|
|
|
|
switch (event) {
|
|
case LAEventTimeout:
|
|
case LAEventStart:
|
|
if (p->po_periodic_interval == 0 || bondport_flags_ntt(p) == 0) {
|
|
break;
|
|
}
|
|
if (event_data == TRANSMIT_MACHINE_TX_IMMEDIATE) {
|
|
/* we're going away, transmit the packet no matter what */
|
|
} else if (bondport_can_transmit(p, devtimer_current_secs(),
|
|
&next_tick_time.tv_sec) == 0) {
|
|
if (devtimer_enabled(p->po_transmit_timer)) {
|
|
if (if_bond_debug > 0) {
|
|
timestamp_printf("[%s] Transmit Timer Already Set\n",
|
|
bondport_get_name(p));
|
|
}
|
|
} else {
|
|
devtimer_set_absolute(p->po_transmit_timer, next_tick_time,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_transmit_machine,
|
|
(void *)LAEventTimeout, NULL);
|
|
if (if_bond_debug > 0) {
|
|
timestamp_printf("[%s] Transmit Timer Deadline %d secs\n",
|
|
bondport_get_name(p),
|
|
(int)next_tick_time.tv_sec);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (if_bond_debug > 0) {
|
|
if (event == LAEventTimeout) {
|
|
timestamp_printf("[%s] Transmit Timer Complete\n",
|
|
bondport_get_name(p));
|
|
}
|
|
}
|
|
pkt = packet_buffer_allocate(sizeof(*out_lacpdu_p));
|
|
if (pkt == NULL) {
|
|
printf("[%s] Transmit: failed to allocate packet buffer\n",
|
|
bondport_get_name(p));
|
|
break;
|
|
}
|
|
out_lacpdu_p = (lacpdu_ref)packet_buffer_byteptr(pkt);
|
|
bzero(out_lacpdu_p, sizeof(*out_lacpdu_p));
|
|
out_lacpdu_p->la_subtype = IEEE8023AD_SLOW_PROTO_SUBTYPE_LACP;
|
|
out_lacpdu_p->la_version = LACPDU_VERSION_1;
|
|
|
|
/* Actor */
|
|
aptlv = (lacp_actor_partner_tlv_ref)out_lacpdu_p->la_actor_tlv;
|
|
aptlv->lap_tlv_type = LACPDU_TLV_TYPE_ACTOR;
|
|
aptlv->lap_length = LACPDU_ACTOR_TLV_LENGTH;
|
|
*((lacp_system_ref)aptlv->lap_system) = g_bond->system;
|
|
lacp_actor_partner_tlv_set_system_priority(aptlv,
|
|
g_bond->system_priority);
|
|
lacp_actor_partner_tlv_set_port_priority(aptlv, p->po_priority);
|
|
lacp_actor_partner_tlv_set_port(aptlv, bondport_get_index(p));
|
|
lacp_actor_partner_tlv_set_key(aptlv, p->po_bond->ifb_key);
|
|
aptlv->lap_state = p->po_actor_state;
|
|
|
|
/* Partner */
|
|
aptlv = (lacp_actor_partner_tlv_ref)out_lacpdu_p->la_partner_tlv;
|
|
aptlv->lap_tlv_type = LACPDU_TLV_TYPE_PARTNER;
|
|
aptlv->lap_length = LACPDU_PARTNER_TLV_LENGTH;
|
|
ps = &p->po_partner_state;
|
|
ps_li = &ps->ps_lag_info;
|
|
lacp_actor_partner_tlv_set_port(aptlv, ps->ps_port);
|
|
lacp_actor_partner_tlv_set_port_priority(aptlv, ps->ps_port_priority);
|
|
*((lacp_system_ref)aptlv->lap_system) = ps_li->li_system;
|
|
lacp_actor_partner_tlv_set_system_priority(aptlv,
|
|
ps_li->li_system_priority);
|
|
lacp_actor_partner_tlv_set_key(aptlv, ps_li->li_key);
|
|
aptlv->lap_state = ps->ps_state;
|
|
|
|
/* Collector */
|
|
ctlv = (lacp_collector_tlv_ref)out_lacpdu_p->la_collector_tlv;
|
|
ctlv->lac_tlv_type = LACPDU_TLV_TYPE_COLLECTOR;
|
|
ctlv->lac_length = LACPDU_COLLECTOR_TLV_LENGTH;
|
|
|
|
bondport_slow_proto_transmit(p, pkt);
|
|
bondport_flags_clear_ntt(p);
|
|
if (if_bond_debug > 0) {
|
|
timestamp_printf("[%s] Transmit Packet %d\n",
|
|
bondport_get_name(p), p->po_n_transmit);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
/**
|
|
** Mux machine functions
|
|
**/
|
|
|
|
static void
|
|
bondport_mux_machine_detached(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_mux_machine_waiting(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
static void
|
|
bondport_mux_machine_attached(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
static void
|
|
bondport_mux_machine_collecting_distributing(bondport_ref p, LAEvent event,
|
|
void * event_data);
|
|
|
|
static void
|
|
bondport_mux_machine(bondport_ref p, LAEvent event, void * event_data)
|
|
{
|
|
switch (p->po_mux_state) {
|
|
case MuxState_none:
|
|
bondport_mux_machine_detached(p, LAEventStart, NULL);
|
|
break;
|
|
case MuxState_DETACHED:
|
|
bondport_mux_machine_detached(p, event, event_data);
|
|
break;
|
|
case MuxState_WAITING:
|
|
bondport_mux_machine_waiting(p, event, event_data);
|
|
break;
|
|
case MuxState_ATTACHED:
|
|
bondport_mux_machine_attached(p, event, event_data);
|
|
break;
|
|
case MuxState_COLLECTING_DISTRIBUTING:
|
|
bondport_mux_machine_collecting_distributing(p, event, event_data);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_mux_machine_detached(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
lacp_actor_partner_state s;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux DETACHED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_mux_state = MuxState_DETACHED;
|
|
bondport_flags_clear_ready(p);
|
|
bondport_DetachMuxFromAggregator(p);
|
|
bondport_disable_distributing(p);
|
|
s = p->po_actor_state;
|
|
s = lacp_actor_partner_state_set_out_of_sync(s);
|
|
s = lacp_actor_partner_state_set_not_collecting(s);
|
|
s = lacp_actor_partner_state_set_not_distributing(s);
|
|
p->po_actor_state = s;
|
|
bondport_flags_set_ntt(p);
|
|
break;
|
|
case LAEventSelectedChange:
|
|
case LAEventPacket:
|
|
case LAEventMediaChange:
|
|
if (p->po_selected == SelectedState_SELECTED
|
|
|| p->po_selected == SelectedState_STANDBY) {
|
|
bondport_mux_machine_waiting(p, LAEventStart, NULL);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_mux_machine_waiting(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
struct timeval tv;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_mux_state = MuxState_WAITING;
|
|
OS_FALLTHROUGH;
|
|
default:
|
|
case LAEventSelectedChange:
|
|
if (p->po_selected == SelectedState_UNSELECTED) {
|
|
bondport_mux_machine_detached(p, LAEventStart, NULL);
|
|
break;
|
|
}
|
|
if (p->po_selected == SelectedState_STANDBY) {
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
/* wait until state changes to SELECTED */
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: Standby\n",
|
|
bondport_get_name(p));
|
|
}
|
|
break;
|
|
}
|
|
if (bondport_flags_ready(p)) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: Port is already ready\n",
|
|
bondport_get_name(p));
|
|
}
|
|
break;
|
|
}
|
|
if (devtimer_enabled(p->po_wait_while_timer)) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: Timer already set\n",
|
|
bondport_get_name(p));
|
|
}
|
|
break;
|
|
}
|
|
if (ifbond_all_ports_attached(p->po_bond, p)) {
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: No waiting\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_set_ready(p);
|
|
goto no_waiting;
|
|
}
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: 2 seconds\n",
|
|
bondport_get_name(p));
|
|
}
|
|
tv.tv_sec = LACP_AGGREGATE_WAIT_TIME;
|
|
tv.tv_usec = 0;
|
|
devtimer_set_relative(p->po_wait_while_timer, tv,
|
|
(devtimer_timeout_func)(void (*)(void))
|
|
bondport_mux_machine_waiting,
|
|
(void *)LAEventTimeout, NULL);
|
|
break;
|
|
case LAEventTimeout:
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: Ready\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_flags_set_ready(p);
|
|
break;
|
|
case LAEventReady:
|
|
no_waiting:
|
|
if (bondport_flags_ready(p)) {
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux WAITING: All Ports Ready\n",
|
|
bondport_get_name(p));
|
|
}
|
|
bondport_mux_machine_attached(p, LAEventStart, NULL);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_mux_machine_attached(bondport_ref p, LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
lacp_actor_partner_state s;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux ATTACHED\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_mux_state = MuxState_ATTACHED;
|
|
bondport_AttachMuxToAggregator(p);
|
|
s = p->po_actor_state;
|
|
s = lacp_actor_partner_state_set_in_sync(s);
|
|
s = lacp_actor_partner_state_set_not_collecting(s);
|
|
s = lacp_actor_partner_state_set_not_distributing(s);
|
|
bondport_disable_distributing(p);
|
|
p->po_actor_state = s;
|
|
bondport_flags_set_ntt(p);
|
|
OS_FALLTHROUGH;
|
|
default:
|
|
switch (p->po_selected) {
|
|
case SelectedState_SELECTED:
|
|
s = p->po_partner_state.ps_state;
|
|
if (lacp_actor_partner_state_in_sync(s)) {
|
|
bondport_mux_machine_collecting_distributing(p, LAEventStart,
|
|
NULL);
|
|
}
|
|
break;
|
|
default:
|
|
bondport_mux_machine_detached(p, LAEventStart, NULL);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
bondport_mux_machine_collecting_distributing(bondport_ref p,
|
|
LAEvent event,
|
|
__unused void * event_data)
|
|
{
|
|
lacp_actor_partner_state s;
|
|
|
|
switch (event) {
|
|
case LAEventStart:
|
|
devtimer_cancel(p->po_wait_while_timer);
|
|
if (if_bond_debug) {
|
|
timestamp_printf("[%s] Mux COLLECTING_DISTRIBUTING\n",
|
|
bondport_get_name(p));
|
|
}
|
|
p->po_mux_state = MuxState_COLLECTING_DISTRIBUTING;
|
|
bondport_enable_distributing(p);
|
|
s = p->po_actor_state;
|
|
s = lacp_actor_partner_state_set_collecting(s);
|
|
s = lacp_actor_partner_state_set_distributing(s);
|
|
p->po_actor_state = s;
|
|
bondport_flags_set_ntt(p);
|
|
OS_FALLTHROUGH;
|
|
default:
|
|
s = p->po_partner_state.ps_state;
|
|
if (lacp_actor_partner_state_in_sync(s) == 0) {
|
|
bondport_mux_machine_attached(p, LAEventStart, NULL);
|
|
break;
|
|
}
|
|
switch (p->po_selected) {
|
|
case SelectedState_UNSELECTED:
|
|
case SelectedState_STANDBY:
|
|
bondport_mux_machine_attached(p, LAEventStart, NULL);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|