2007-04-10 00:27:25 +00:00
|
|
|
/* $NetBSD: ieee8023ad_lacp.c,v 1.3 2005/12/11 12:24:54 christos Exp $ */
|
|
|
|
|
|
|
|
/*-
|
2017-11-27 15:23:17 +00:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause-NetBSD
|
|
|
|
*
|
2007-04-10 00:27:25 +00:00
|
|
|
* Copyright (c)2005 YAMAMOTO Takashi,
|
2008-03-16 19:25:30 +00:00
|
|
|
* Copyright (c)2008 Andrew Thompson <thompsa@FreeBSD.org>
|
2007-04-10 00:27:25 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without
|
|
|
|
* modification, are permitted provided that the following conditions
|
|
|
|
* are met:
|
|
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer.
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
|
|
* documentation and/or other materials provided with the distribution.
|
|
|
|
*
|
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
|
|
* SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <sys/cdefs.h>
|
|
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
|
Add kernel-side support for in-kernel TLS.
KTLS adds support for in-kernel framing and encryption of Transport
Layer Security (1.0-1.2) data on TCP sockets. KTLS only supports
offload of TLS for transmitted data. Key negotation must still be
performed in userland. Once completed, transmit session keys for a
connection are provided to the kernel via a new TCP_TXTLS_ENABLE
socket option. All subsequent data transmitted on the socket is
placed into TLS frames and encrypted using the supplied keys.
Any data written to a KTLS-enabled socket via write(2), aio_write(2),
or sendfile(2) is assumed to be application data and is encoded in TLS
frames with an application data type. Individual records can be sent
with a custom type (e.g. handshake messages) via sendmsg(2) with a new
control message (TLS_SET_RECORD_TYPE) specifying the record type.
At present, rekeying is not supported though the in-kernel framework
should support rekeying.
KTLS makes use of the recently added unmapped mbufs to store TLS
frames in the socket buffer. Each TLS frame is described by a single
ext_pgs mbuf. The ext_pgs structure contains the header of the TLS
record (and trailer for encrypted records) as well as references to
the associated TLS session.
KTLS supports two primary methods of encrypting TLS frames: software
TLS and ifnet TLS.
Software TLS marks mbufs holding socket data as not ready via
M_NOTREADY similar to sendfile(2) when TLS framing information is
added to an unmapped mbuf in ktls_frame(). ktls_enqueue() is then
called to schedule TLS frames for encryption. In the case of
sendfile_iodone() calls ktls_enqueue() instead of pru_ready() leaving
the mbufs marked M_NOTREADY until encryption is completed. For other
writes (vn_sendfile when pages are available, write(2), etc.), the
PRUS_NOTREADY is set when invoking pru_send() along with invoking
ktls_enqueue().
A pool of worker threads (the "KTLS" kernel process) encrypts TLS
frames queued via ktls_enqueue(). Each TLS frame is temporarily
mapped using the direct map and passed to a software encryption
backend to perform the actual encryption.
(Note: The use of PHYS_TO_DMAP could be replaced with sf_bufs if
someone wished to make this work on architectures without a direct
map.)
KTLS supports pluggable software encryption backends. Internally,
Netflix uses proprietary pure-software backends. This commit includes
a simple backend in a new ktls_ocf.ko module that uses the kernel's
OpenCrypto framework to provide AES-GCM encryption of TLS frames. As
a result, software TLS is now a bit of a misnomer as it can make use
of hardware crypto accelerators.
Once software encryption has finished, the TLS frame mbufs are marked
ready via pru_ready(). At this point, the encrypted data appears as
regular payload to the TCP stack stored in unmapped mbufs.
ifnet TLS permits a NIC to offload the TLS encryption and TCP
segmentation. In this mode, a new send tag type (IF_SND_TAG_TYPE_TLS)
is allocated on the interface a socket is routed over and associated
with a TLS session. TLS records for a TLS session using ifnet TLS are
not marked M_NOTREADY but are passed down the stack unencrypted. The
ip_output_send() and ip6_output_send() helper functions that apply
send tags to outbound IP packets verify that the send tag of the TLS
record matches the outbound interface. If so, the packet is tagged
with the TLS send tag and sent to the interface. The NIC device
driver must recognize packets with the TLS send tag and schedule them
for TLS encryption and TCP segmentation. If the the outbound
interface does not match the interface in the TLS send tag, the packet
is dropped. In addition, a task is scheduled to refresh the TLS send
tag for the TLS session. If a new TLS send tag cannot be allocated,
the connection is dropped. If a new TLS send tag is allocated,
however, subsequent packets will be tagged with the correct TLS send
tag. (This latter case has been tested by configuring both ports of a
Chelsio T6 in a lagg and failing over from one port to another. As
the connections migrated to the new port, new TLS send tags were
allocated for the new port and connections resumed without being
dropped.)
ifnet TLS can be enabled and disabled on supported network interfaces
via new '[-]txtls[46]' options to ifconfig(8). ifnet TLS is supported
across both vlan devices and lagg interfaces using failover, lacp with
flowid enabled, or lacp with flowid enabled.
Applications may request the current KTLS mode of a connection via a
new TCP_TXTLS_MODE socket option. They can also use this socket
option to toggle between software and ifnet TLS modes.
In addition, a testing tool is available in tools/tools/switch_tls.
This is modeled on tcpdrop and uses similar syntax. However, instead
of dropping connections, -s is used to force KTLS connections to
switch to software TLS and -i is used to switch to ifnet TLS.
Various sysctls and counters are available under the kern.ipc.tls
sysctl node. The kern.ipc.tls.enable node must be set to true to
enable KTLS (it is off by default). The use of unmapped mbufs must
also be enabled via kern.ipc.mb_use_ext_pgs to enable KTLS.
KTLS is enabled via the KERN_TLS kernel option.
This patch is the culmination of years of work by several folks
including Scott Long and Randall Stewart for the original design and
implementation; Drew Gallatin for several optimizations including the
use of ext_pgs mbufs, the M_NOTREADY mechanism for TLS records
awaiting software encryption, and pluggable software crypto backends;
and John Baldwin for modifications to support hardware TLS offload.
Reviewed by: gallatin, hselasky, rrs
Obtained from: Netflix
Sponsored by: Netflix, Chelsio Communications
Differential Revision: https://reviews.freebsd.org/D21277
2019-08-27 00:01:56 +00:00
|
|
|
#include "opt_kern_tls.h"
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
#include "opt_ratelimit.h"
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <sys/param.h>
|
|
|
|
#include <sys/callout.h>
|
2013-10-28 07:29:16 +00:00
|
|
|
#include <sys/eventhandler.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <sys/mbuf.h>
|
|
|
|
#include <sys/systm.h>
|
|
|
|
#include <sys/malloc.h>
|
|
|
|
#include <sys/kernel.h> /* hz */
|
|
|
|
#include <sys/socket.h> /* for net/if.h */
|
|
|
|
#include <sys/sockio.h>
|
2012-05-26 08:09:01 +00:00
|
|
|
#include <sys/sysctl.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <machine/stdarg.h>
|
2007-05-15 07:41:46 +00:00
|
|
|
#include <sys/lock.h>
|
|
|
|
#include <sys/rwlock.h>
|
2013-10-26 17:58:36 +00:00
|
|
|
#include <sys/taskqueue.h>
|
lacp: short timeout erroneously declares link-flapping
Panasas was seeing a higher-than-expected number of link-flap events.
After joint debugging with the switch vendor, we determined there were
problems on both sides; either of which might cause the occasional
event, but together caused lots of them.
On the switch side, an internal queuing issue was causing LACP PDUs --
which should be sent every second, in short-timeout mode -- to sometimes
be sent slightly later than they should have been. In some cases, two
successive PDUs were late, but we never saw three late PDUs in a row.
On the FreeBSD side, we saw a link-flap event every time there were two
late PDUs, while the spec says that it takes *three* seconds of downtime
to trigger that event. It turns out that if a PDU was received shortly
before the timer code was run, it would decrement less than a full
second after the PDU arrived. Then two delayed PDUs would cause two
additional decrements, causing it to reach zero less than three seconds
after the most-recent on-time PDU.
The solution is to note the time a PDU arrives, and only decrement if at
least a full second has elapsed since then.
Reported by: Greg Foster <gfoster@panasas.com>
Reviewed by: gallatin
Tested by: Greg Foster <gfoster@panasas.com>
MFC after: 3 days
Sponsored by: Panasas
Differential Revision: https://reviews.freebsd.org/D35070
2022-04-26 06:38:23 +00:00
|
|
|
#include <sys/time.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
#include <net/if.h>
|
2013-10-26 17:58:36 +00:00
|
|
|
#include <net/if_var.h>
|
2023-01-13 15:43:51 +00:00
|
|
|
#include <net/if_private.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <net/if_dl.h>
|
|
|
|
#include <net/ethernet.h>
|
2020-10-22 09:47:12 +00:00
|
|
|
#include <net/infiniband.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <net/if_media.h>
|
|
|
|
#include <net/if_types.h>
|
|
|
|
|
2007-04-17 00:35:11 +00:00
|
|
|
#include <net/if_lagg.h>
|
2007-04-10 00:27:25 +00:00
|
|
|
#include <net/ieee8023ad_lacp.h>
|
|
|
|
|
|
|
|
/*
|
|
|
|
* actor system priority and port priority.
|
|
|
|
* XXX should be configurable.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define LACP_SYSTEM_PRIO 0x8000
|
|
|
|
#define LACP_PORT_PRIO 0x8000
|
|
|
|
|
|
|
|
const uint8_t ethermulticastaddr_slowprotocols[ETHER_ADDR_LEN] =
|
|
|
|
{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x02 };
|
|
|
|
|
|
|
|
static const struct tlv_template lacp_info_tlv_template[] = {
|
|
|
|
{ LACP_TYPE_ACTORINFO,
|
|
|
|
sizeof(struct tlvhdr) + sizeof(struct lacp_peerinfo) },
|
|
|
|
{ LACP_TYPE_PARTNERINFO,
|
|
|
|
sizeof(struct tlvhdr) + sizeof(struct lacp_peerinfo) },
|
|
|
|
{ LACP_TYPE_COLLECTORINFO,
|
|
|
|
sizeof(struct tlvhdr) + sizeof(struct lacp_collectorinfo) },
|
|
|
|
{ 0, 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct tlv_template marker_info_tlv_template[] = {
|
2007-05-19 07:47:04 +00:00
|
|
|
{ MARKER_TYPE_INFO,
|
|
|
|
sizeof(struct tlvhdr) + sizeof(struct lacp_markerinfo) },
|
2007-04-10 00:27:25 +00:00
|
|
|
{ 0, 0 },
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct tlv_template marker_response_tlv_template[] = {
|
2007-05-19 07:47:04 +00:00
|
|
|
{ MARKER_TYPE_RESPONSE,
|
|
|
|
sizeof(struct tlvhdr) + sizeof(struct lacp_markerinfo) },
|
2007-04-10 00:27:25 +00:00
|
|
|
{ 0, 0 },
|
|
|
|
};
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
typedef void (*lacp_timer_func_t)(struct lacp_port *);
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
static void lacp_fill_actorinfo(struct lacp_port *, struct lacp_peerinfo *);
|
2007-05-19 07:47:04 +00:00
|
|
|
static void lacp_fill_markerinfo(struct lacp_port *,
|
|
|
|
struct lacp_markerinfo *);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
static uint64_t lacp_aggregator_bandwidth(struct lacp_aggregator *);
|
|
|
|
static void lacp_suppress_distributing(struct lacp_softc *,
|
|
|
|
struct lacp_aggregator *);
|
|
|
|
static void lacp_transit_expire(void *);
|
2008-03-16 19:25:30 +00:00
|
|
|
static void lacp_update_portmap(struct lacp_softc *);
|
2007-04-10 00:27:25 +00:00
|
|
|
static void lacp_select_active_aggregator(struct lacp_softc *);
|
|
|
|
static uint16_t lacp_compose_key(struct lacp_port *);
|
|
|
|
static int tlv_check(const void *, size_t, const struct tlvhdr *,
|
|
|
|
const struct tlv_template *, boolean_t);
|
|
|
|
static void lacp_tick(void *);
|
|
|
|
|
|
|
|
static void lacp_fill_aggregator_id(struct lacp_aggregator *,
|
|
|
|
const struct lacp_port *);
|
|
|
|
static void lacp_fill_aggregator_id_peer(struct lacp_peerinfo *,
|
|
|
|
const struct lacp_peerinfo *);
|
|
|
|
static int lacp_aggregator_is_compatible(const struct lacp_aggregator *,
|
|
|
|
const struct lacp_port *);
|
|
|
|
static int lacp_peerinfo_is_compatible(const struct lacp_peerinfo *,
|
|
|
|
const struct lacp_peerinfo *);
|
|
|
|
|
|
|
|
static struct lacp_aggregator *lacp_aggregator_get(struct lacp_softc *,
|
|
|
|
struct lacp_port *);
|
|
|
|
static void lacp_aggregator_addref(struct lacp_softc *,
|
|
|
|
struct lacp_aggregator *);
|
|
|
|
static void lacp_aggregator_delref(struct lacp_softc *,
|
|
|
|
struct lacp_aggregator *);
|
|
|
|
|
|
|
|
/* receive machine */
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
static int lacp_pdu_input(struct lacp_port *, struct mbuf *);
|
|
|
|
static int lacp_marker_input(struct lacp_port *, struct mbuf *);
|
2007-04-10 00:27:25 +00:00
|
|
|
static void lacp_sm_rx(struct lacp_port *, const struct lacpdu *);
|
|
|
|
static void lacp_sm_rx_timer(struct lacp_port *);
|
|
|
|
static void lacp_sm_rx_set_expired(struct lacp_port *);
|
|
|
|
static void lacp_sm_rx_update_ntt(struct lacp_port *,
|
|
|
|
const struct lacpdu *);
|
|
|
|
static void lacp_sm_rx_record_pdu(struct lacp_port *,
|
|
|
|
const struct lacpdu *);
|
|
|
|
static void lacp_sm_rx_update_selected(struct lacp_port *,
|
|
|
|
const struct lacpdu *);
|
|
|
|
static void lacp_sm_rx_record_default(struct lacp_port *);
|
|
|
|
static void lacp_sm_rx_update_default_selected(struct lacp_port *);
|
|
|
|
static void lacp_sm_rx_update_selected_from_peerinfo(struct lacp_port *,
|
|
|
|
const struct lacp_peerinfo *);
|
|
|
|
|
|
|
|
/* mux machine */
|
|
|
|
|
|
|
|
static void lacp_sm_mux(struct lacp_port *);
|
|
|
|
static void lacp_set_mux(struct lacp_port *, enum lacp_mux_state);
|
|
|
|
static void lacp_sm_mux_timer(struct lacp_port *);
|
|
|
|
|
|
|
|
/* periodic transmit machine */
|
|
|
|
|
|
|
|
static void lacp_sm_ptx_update_timeout(struct lacp_port *, uint8_t);
|
|
|
|
static void lacp_sm_ptx_tx_schedule(struct lacp_port *);
|
|
|
|
static void lacp_sm_ptx_timer(struct lacp_port *);
|
|
|
|
|
|
|
|
/* transmit machine */
|
|
|
|
|
|
|
|
static void lacp_sm_tx(struct lacp_port *);
|
|
|
|
static void lacp_sm_assert_ntt(struct lacp_port *);
|
|
|
|
|
|
|
|
static void lacp_run_timers(struct lacp_port *);
|
|
|
|
static int lacp_compare_peerinfo(const struct lacp_peerinfo *,
|
|
|
|
const struct lacp_peerinfo *);
|
|
|
|
static int lacp_compare_systemid(const struct lacp_systemid *,
|
|
|
|
const struct lacp_systemid *);
|
|
|
|
static void lacp_port_enable(struct lacp_port *);
|
|
|
|
static void lacp_port_disable(struct lacp_port *);
|
|
|
|
static void lacp_select(struct lacp_port *);
|
|
|
|
static void lacp_unselect(struct lacp_port *);
|
|
|
|
static void lacp_disable_collecting(struct lacp_port *);
|
|
|
|
static void lacp_enable_collecting(struct lacp_port *);
|
|
|
|
static void lacp_disable_distributing(struct lacp_port *);
|
|
|
|
static void lacp_enable_distributing(struct lacp_port *);
|
|
|
|
static int lacp_xmit_lacpdu(struct lacp_port *);
|
2007-05-19 07:47:04 +00:00
|
|
|
static int lacp_xmit_marker(struct lacp_port *);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
/* Debugging */
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
static void lacp_dump_lacpdu(const struct lacpdu *);
|
|
|
|
static const char *lacp_format_partner(const struct lacp_peerinfo *, char *,
|
|
|
|
size_t);
|
|
|
|
static const char *lacp_format_lagid(const struct lacp_peerinfo *,
|
|
|
|
const struct lacp_peerinfo *, char *, size_t);
|
|
|
|
static const char *lacp_format_lagid_aggregator(const struct lacp_aggregator *,
|
|
|
|
char *, size_t);
|
|
|
|
static const char *lacp_format_state(uint8_t, char *, size_t);
|
|
|
|
static const char *lacp_format_mac(const uint8_t *, char *, size_t);
|
|
|
|
static const char *lacp_format_systemid(const struct lacp_systemid *, char *,
|
|
|
|
size_t);
|
|
|
|
static const char *lacp_format_portid(const struct lacp_portid *, char *,
|
|
|
|
size_t);
|
|
|
|
static void lacp_dprintf(const struct lacp_port *, const char *, ...)
|
|
|
|
__attribute__((__format__(__printf__, 2, 3)));
|
2012-05-26 08:09:01 +00:00
|
|
|
|
2018-07-24 16:35:52 +00:00
|
|
|
VNET_DEFINE_STATIC(int, lacp_debug);
|
2014-10-01 21:37:32 +00:00
|
|
|
#define V_lacp_debug VNET(lacp_debug)
|
2020-02-26 14:26:36 +00:00
|
|
|
SYSCTL_NODE(_net_link_lagg, OID_AUTO, lacp, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
|
|
|
|
"ieee802.3ad");
|
2014-10-01 21:37:32 +00:00
|
|
|
SYSCTL_INT(_net_link_lagg_lacp, OID_AUTO, debug, CTLFLAG_RWTUN | CTLFLAG_VNET,
|
|
|
|
&VNET_NAME(lacp_debug), 0, "Enable LACP debug logging (1=debug, 2=trace)");
|
2012-05-26 08:09:01 +00:00
|
|
|
|
2018-07-24 16:35:52 +00:00
|
|
|
VNET_DEFINE_STATIC(int, lacp_default_strict_mode) = 1;
|
2018-01-24 10:13:14 +00:00
|
|
|
SYSCTL_INT(_net_link_lagg_lacp, OID_AUTO, default_strict_mode,
|
|
|
|
CTLFLAG_RWTUN | CTLFLAG_VNET, &VNET_NAME(lacp_default_strict_mode), 0,
|
2015-11-06 15:33:27 +00:00
|
|
|
"LACP strict protocol compliance default");
|
2014-10-01 21:37:32 +00:00
|
|
|
#define LACP_DPRINTF(a) if (V_lacp_debug & 0x01) { lacp_dprintf a ; }
|
|
|
|
#define LACP_TRACE(a) if (V_lacp_debug & 0x02) { lacp_dprintf(a,"%s\n",__func__); }
|
|
|
|
#define LACP_TPRINTF(a) if (V_lacp_debug & 0x04) { lacp_dprintf a ; }
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* partner administration variables.
|
|
|
|
* XXX should be configurable.
|
|
|
|
*/
|
|
|
|
|
2013-07-13 04:25:03 +00:00
|
|
|
static const struct lacp_peerinfo lacp_partner_admin_optimistic = {
|
2007-04-10 00:27:25 +00:00
|
|
|
.lip_systemid = { .lsi_prio = 0xffff },
|
|
|
|
.lip_portid = { .lpi_prio = 0xffff },
|
|
|
|
.lip_state = LACP_STATE_SYNC | LACP_STATE_AGGREGATION |
|
|
|
|
LACP_STATE_COLLECTING | LACP_STATE_DISTRIBUTING,
|
2013-07-13 04:25:03 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct lacp_peerinfo lacp_partner_admin_strict = {
|
|
|
|
.lip_systemid = { .lsi_prio = 0xffff },
|
|
|
|
.lip_portid = { .lpi_prio = 0xffff },
|
2007-04-10 00:27:25 +00:00
|
|
|
.lip_state = 0,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const lacp_timer_func_t lacp_timer_funcs[LACP_NTIMER] = {
|
|
|
|
[LACP_TIMER_CURRENT_WHILE] = lacp_sm_rx_timer,
|
|
|
|
[LACP_TIMER_PERIODIC] = lacp_sm_ptx_timer,
|
|
|
|
[LACP_TIMER_WAIT_WHILE] = lacp_sm_mux_timer,
|
|
|
|
};
|
|
|
|
|
2007-12-31 01:16:35 +00:00
|
|
|
struct mbuf *
|
2007-05-15 07:41:46 +00:00
|
|
|
lacp_input(struct lagg_port *lgp, struct mbuf *m)
|
|
|
|
{
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_port *lp = LACP_PORT(lgp);
|
2007-05-15 07:41:46 +00:00
|
|
|
uint8_t subtype;
|
|
|
|
|
|
|
|
if (m->m_pkthdr.len < sizeof(struct ether_header) + sizeof(subtype)) {
|
|
|
|
m_freem(m);
|
2007-12-31 01:16:35 +00:00
|
|
|
return (NULL);
|
2007-05-15 07:41:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
m_copydata(m, sizeof(struct ether_header), sizeof(subtype), &subtype);
|
|
|
|
switch (subtype) {
|
|
|
|
case SLOWPROTOCOLS_SUBTYPE_LACP:
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_pdu_input(lp, m);
|
|
|
|
return (NULL);
|
2007-05-15 07:41:46 +00:00
|
|
|
|
|
|
|
case SLOWPROTOCOLS_SUBTYPE_MARKER:
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_marker_input(lp, m);
|
|
|
|
return (NULL);
|
2007-05-15 07:41:46 +00:00
|
|
|
}
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
/* Not a subtype we are interested in */
|
|
|
|
return (m);
|
2007-05-15 07:41:46 +00:00
|
|
|
}
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
/*
|
2007-05-15 07:41:46 +00:00
|
|
|
* lacp_pdu_input: process lacpdu
|
2007-04-10 00:27:25 +00:00
|
|
|
*/
|
2007-05-15 07:41:46 +00:00
|
|
|
static int
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_pdu_input(struct lacp_port *lp, struct mbuf *m)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct lacpdu *du;
|
|
|
|
int error = 0;
|
|
|
|
|
|
|
|
if (m->m_pkthdr.len != sizeof(*du)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((m->m_flags & M_MCAST) == 0) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m->m_len < sizeof(*du)) {
|
|
|
|
m = m_pullup(m, sizeof(*du));
|
|
|
|
if (m == NULL) {
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
du = mtod(m, struct lacpdu *);
|
|
|
|
|
|
|
|
if (memcmp(&du->ldu_eh.ether_dhost,
|
|
|
|
ðermulticastaddr_slowprotocols, ETHER_ADDR_LEN)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ignore the version for compatibility with
|
|
|
|
* the future protocol revisions.
|
|
|
|
*/
|
|
|
|
#if 0
|
|
|
|
if (du->ldu_sph.sph_version != 1) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ignore tlv types for compatibility with
|
|
|
|
* the future protocol revisions.
|
|
|
|
*/
|
|
|
|
if (tlv_check(du, sizeof(*du), &du->ldu_tlv_actor,
|
|
|
|
lacp_info_tlv_template, FALSE)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
2014-10-01 21:37:32 +00:00
|
|
|
if (V_lacp_debug > 0) {
|
2012-05-26 08:09:01 +00:00
|
|
|
lacp_dprintf(lp, "lacpdu receive\n");
|
|
|
|
lacp_dump_lacpdu(du);
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
|
2013-07-26 19:41:13 +00:00
|
|
|
if ((1 << lp->lp_ifp->if_dunit) & lp->lp_lsc->lsc_debug.lsc_rx_test) {
|
2013-07-13 04:25:03 +00:00
|
|
|
LACP_TPRINTF((lp, "Dropping RX PDU\n"));
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_sm_rx(lp, du);
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
m_freem(m);
|
|
|
|
return (error);
|
|
|
|
|
|
|
|
bad:
|
|
|
|
m_freem(m);
|
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_fill_actorinfo(struct lacp_port *lp, struct lacp_peerinfo *info)
|
|
|
|
{
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lagg_softc *sc = lgp->lp_softc;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
info->lip_systemid.lsi_prio = htons(LACP_SYSTEM_PRIO);
|
|
|
|
memcpy(&info->lip_systemid.lsi_mac,
|
2007-06-12 07:29:11 +00:00
|
|
|
IF_LLADDR(sc->sc_ifp), ETHER_ADDR_LEN);
|
2007-04-10 00:27:25 +00:00
|
|
|
info->lip_portid.lpi_prio = htons(LACP_PORT_PRIO);
|
|
|
|
info->lip_portid.lpi_portno = htons(lp->lp_ifp->if_index);
|
|
|
|
info->lip_state = lp->lp_state;
|
|
|
|
}
|
|
|
|
|
2007-05-19 07:47:04 +00:00
|
|
|
static void
|
|
|
|
lacp_fill_markerinfo(struct lacp_port *lp, struct lacp_markerinfo *info)
|
|
|
|
{
|
|
|
|
struct ifnet *ifp = lp->lp_ifp;
|
|
|
|
|
|
|
|
/* Fill in the port index and system id (encoded as the MAC) */
|
|
|
|
info->mi_rq_port = htons(ifp->if_index);
|
|
|
|
memcpy(&info->mi_rq_system, lp->lp_systemid.lsi_mac, ETHER_ADDR_LEN);
|
|
|
|
info->mi_rq_xid = htonl(0);
|
|
|
|
}
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
static int
|
|
|
|
lacp_xmit_lacpdu(struct lacp_port *lp)
|
|
|
|
{
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct mbuf *m;
|
|
|
|
struct lacpdu *du;
|
|
|
|
int error;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_ASSERT(lp->lp_lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2012-12-05 08:04:20 +00:00
|
|
|
m = m_gethdr(M_NOWAIT, MT_DATA);
|
2007-04-10 00:27:25 +00:00
|
|
|
if (m == NULL) {
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
m->m_len = m->m_pkthdr.len = sizeof(*du);
|
|
|
|
|
|
|
|
du = mtod(m, struct lacpdu *);
|
|
|
|
memset(du, 0, sizeof(*du));
|
|
|
|
|
|
|
|
memcpy(&du->ldu_eh.ether_dhost, ethermulticastaddr_slowprotocols,
|
|
|
|
ETHER_ADDR_LEN);
|
2007-04-17 00:35:11 +00:00
|
|
|
memcpy(&du->ldu_eh.ether_shost, lgp->lp_lladdr, ETHER_ADDR_LEN);
|
2007-04-10 00:27:25 +00:00
|
|
|
du->ldu_eh.ether_type = htons(ETHERTYPE_SLOW);
|
|
|
|
|
|
|
|
du->ldu_sph.sph_subtype = SLOWPROTOCOLS_SUBTYPE_LACP;
|
|
|
|
du->ldu_sph.sph_version = 1;
|
|
|
|
|
|
|
|
TLV_SET(&du->ldu_tlv_actor, LACP_TYPE_ACTORINFO, sizeof(du->ldu_actor));
|
|
|
|
du->ldu_actor = lp->lp_actor;
|
|
|
|
|
|
|
|
TLV_SET(&du->ldu_tlv_partner, LACP_TYPE_PARTNERINFO,
|
|
|
|
sizeof(du->ldu_partner));
|
|
|
|
du->ldu_partner = lp->lp_partner;
|
|
|
|
|
|
|
|
TLV_SET(&du->ldu_tlv_collector, LACP_TYPE_COLLECTORINFO,
|
|
|
|
sizeof(du->ldu_collector));
|
|
|
|
du->ldu_collector.lci_maxdelay = 0;
|
|
|
|
|
2014-10-01 21:37:32 +00:00
|
|
|
if (V_lacp_debug > 0) {
|
2012-05-26 08:09:01 +00:00
|
|
|
lacp_dprintf(lp, "lacpdu transmit\n");
|
|
|
|
lacp_dump_lacpdu(du);
|
|
|
|
}
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
m->m_flags |= M_MCAST;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* XXX should use higher priority queue.
|
|
|
|
* otherwise network congestion can break aggregation.
|
|
|
|
*/
|
|
|
|
|
2007-04-17 00:35:11 +00:00
|
|
|
error = lagg_enqueue(lp->lp_ifp, m);
|
2007-04-10 00:27:25 +00:00
|
|
|
return (error);
|
|
|
|
}
|
|
|
|
|
2007-05-19 07:47:04 +00:00
|
|
|
static int
|
|
|
|
lacp_xmit_marker(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
|
|
|
struct mbuf *m;
|
|
|
|
struct markerdu *mdu;
|
|
|
|
int error;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_ASSERT(lp->lp_lsc);
|
2007-05-19 07:47:04 +00:00
|
|
|
|
2012-12-05 08:04:20 +00:00
|
|
|
m = m_gethdr(M_NOWAIT, MT_DATA);
|
2007-05-19 07:47:04 +00:00
|
|
|
if (m == NULL) {
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
m->m_len = m->m_pkthdr.len = sizeof(*mdu);
|
|
|
|
|
|
|
|
mdu = mtod(m, struct markerdu *);
|
|
|
|
memset(mdu, 0, sizeof(*mdu));
|
|
|
|
|
|
|
|
memcpy(&mdu->mdu_eh.ether_dhost, ethermulticastaddr_slowprotocols,
|
|
|
|
ETHER_ADDR_LEN);
|
|
|
|
memcpy(&mdu->mdu_eh.ether_shost, lgp->lp_lladdr, ETHER_ADDR_LEN);
|
|
|
|
mdu->mdu_eh.ether_type = htons(ETHERTYPE_SLOW);
|
|
|
|
|
|
|
|
mdu->mdu_sph.sph_subtype = SLOWPROTOCOLS_SUBTYPE_MARKER;
|
|
|
|
mdu->mdu_sph.sph_version = 1;
|
|
|
|
|
|
|
|
/* Bump the transaction id and copy over the marker info */
|
|
|
|
lp->lp_marker.mi_rq_xid = htonl(ntohl(lp->lp_marker.mi_rq_xid) + 1);
|
|
|
|
TLV_SET(&mdu->mdu_tlv, MARKER_TYPE_INFO, sizeof(mdu->mdu_info));
|
|
|
|
mdu->mdu_info = lp->lp_marker;
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "marker transmit, port=%u, sys=%6D, id=%u\n",
|
|
|
|
ntohs(mdu->mdu_info.mi_rq_port), mdu->mdu_info.mi_rq_system, ":",
|
|
|
|
ntohl(mdu->mdu_info.mi_rq_xid)));
|
|
|
|
|
|
|
|
m->m_flags |= M_MCAST;
|
|
|
|
error = lagg_enqueue(lp->lp_ifp, m);
|
|
|
|
return (error);
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
void
|
2007-04-17 00:35:11 +00:00
|
|
|
lacp_linkstate(struct lagg_port *lgp)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lacp_port *lp = LACP_PORT(lgp);
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2007-04-17 00:35:11 +00:00
|
|
|
struct ifnet *ifp = lgp->lp_ifp;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct ifmediareq ifmr;
|
|
|
|
int error = 0;
|
|
|
|
u_int media;
|
|
|
|
uint8_t old_state;
|
|
|
|
uint16_t old_key;
|
|
|
|
|
|
|
|
bzero((char *)&ifmr, sizeof(ifmr));
|
2017-09-06 14:36:35 +00:00
|
|
|
error = (*ifp->if_ioctl)(ifp, SIOCGIFXMEDIA, (caddr_t)&ifmr);
|
|
|
|
if (error != 0) {
|
|
|
|
bzero((char *)&ifmr, sizeof(ifmr));
|
|
|
|
error = (*ifp->if_ioctl)(ifp, SIOCGIFMEDIA, (caddr_t)&ifmr);
|
|
|
|
}
|
2007-04-10 00:27:25 +00:00
|
|
|
if (error != 0)
|
|
|
|
return;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
media = ifmr.ifm_active;
|
2007-05-03 08:56:20 +00:00
|
|
|
LACP_DPRINTF((lp, "media changed 0x%x -> 0x%x, ether = %d, fdx = %d, "
|
|
|
|
"link = %d\n", lp->lp_media, media, IFM_TYPE(media) == IFM_ETHER,
|
|
|
|
(media & IFM_FDX) != 0, ifp->if_link_state == LINK_STATE_UP));
|
2007-04-10 00:27:25 +00:00
|
|
|
old_state = lp->lp_state;
|
|
|
|
old_key = lp->lp_key;
|
|
|
|
|
|
|
|
lp->lp_media = media;
|
2007-06-12 07:29:11 +00:00
|
|
|
/*
|
2007-05-03 08:56:20 +00:00
|
|
|
* If the port is not an active full duplex Ethernet link then it can
|
|
|
|
* not be aggregated.
|
|
|
|
*/
|
|
|
|
if (IFM_TYPE(media) != IFM_ETHER || (media & IFM_FDX) == 0 ||
|
|
|
|
ifp->if_link_state != LINK_STATE_UP) {
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_port_disable(lp);
|
|
|
|
} else {
|
|
|
|
lacp_port_enable(lp);
|
|
|
|
}
|
|
|
|
lp->lp_key = lacp_compose_key(lp);
|
|
|
|
|
|
|
|
if (old_state != lp->lp_state || old_key != lp->lp_key) {
|
|
|
|
LACP_DPRINTF((lp, "-> UNSELECTED\n"));
|
|
|
|
lp->lp_selected = LACP_UNSELECTED;
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_tick(void *arg)
|
|
|
|
{
|
|
|
|
struct lacp_softc *lsc = arg;
|
|
|
|
struct lacp_port *lp;
|
|
|
|
|
|
|
|
LIST_FOREACH(lp, &lsc->lsc_ports, lp_next) {
|
|
|
|
if ((lp->lp_state & LACP_STATE_AGGREGATION) == 0)
|
|
|
|
continue;
|
|
|
|
|
2014-10-01 21:37:32 +00:00
|
|
|
CURVNET_SET(lp->lp_ifp->if_vnet);
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_run_timers(lp);
|
|
|
|
|
|
|
|
lacp_select(lp);
|
|
|
|
lacp_sm_mux(lp);
|
|
|
|
lacp_sm_tx(lp);
|
|
|
|
lacp_sm_ptx_tx_schedule(lp);
|
2014-10-01 21:37:32 +00:00
|
|
|
CURVNET_RESTORE();
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
callout_reset(&lsc->lsc_callout, hz, lacp_tick, lsc);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2007-04-17 00:35:11 +00:00
|
|
|
lacp_port_create(struct lagg_port *lgp)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lagg_softc *sc = lgp->lp_softc;
|
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
2007-04-10 00:27:25 +00:00
|
|
|
struct lacp_port *lp;
|
2007-04-17 00:35:11 +00:00
|
|
|
struct ifnet *ifp = lgp->lp_ifp;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct sockaddr_dl sdl;
|
|
|
|
struct ifmultiaddr *rifma = NULL;
|
|
|
|
int error;
|
|
|
|
|
2014-01-18 23:24:51 +00:00
|
|
|
link_init_sdl(ifp, (struct sockaddr *)&sdl, IFT_ETHER);
|
2007-04-10 00:27:25 +00:00
|
|
|
sdl.sdl_alen = ETHER_ADDR_LEN;
|
|
|
|
|
|
|
|
bcopy(ðermulticastaddr_slowprotocols,
|
|
|
|
LLADDR(&sdl), ETHER_ADDR_LEN);
|
|
|
|
error = if_addmulti(ifp, (struct sockaddr *)&sdl, &rifma);
|
|
|
|
if (error) {
|
2014-09-26 13:02:29 +00:00
|
|
|
printf("%s: ADDMULTI failed on %s\n", __func__,
|
|
|
|
lgp->lp_ifp->if_xname);
|
2007-04-10 00:27:25 +00:00
|
|
|
return (error);
|
|
|
|
}
|
|
|
|
|
|
|
|
lp = malloc(sizeof(struct lacp_port),
|
|
|
|
M_DEVBUF, M_NOWAIT|M_ZERO);
|
|
|
|
if (lp == NULL)
|
|
|
|
return (ENOMEM);
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2014-09-26 12:35:58 +00:00
|
|
|
lgp->lp_psc = lp;
|
2007-04-10 00:27:25 +00:00
|
|
|
lp->lp_ifp = ifp;
|
2007-04-17 00:35:11 +00:00
|
|
|
lp->lp_lagg = lgp;
|
2007-04-10 00:27:25 +00:00
|
|
|
lp->lp_lsc = lsc;
|
2007-05-07 00:18:56 +00:00
|
|
|
lp->lp_ifma = rifma;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
LIST_INSERT_HEAD(&lsc->lsc_ports, lp, lp_next);
|
|
|
|
|
|
|
|
lacp_fill_actorinfo(lp, &lp->lp_actor);
|
2007-05-19 07:47:04 +00:00
|
|
|
lacp_fill_markerinfo(lp, &lp->lp_marker);
|
2017-01-24 01:39:40 +00:00
|
|
|
lp->lp_state = LACP_STATE_ACTIVITY;
|
2007-04-10 00:27:25 +00:00
|
|
|
lp->lp_aggregator = NULL;
|
|
|
|
lacp_sm_rx_set_expired(lp);
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
|
|
|
lacp_linkstate(lgp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2007-04-17 00:35:11 +00:00
|
|
|
lacp_port_destroy(struct lagg_port *lgp)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lacp_port *lp = LACP_PORT(lgp);
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2007-05-07 00:18:56 +00:00
|
|
|
int i;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
for (i = 0; i < LACP_NTIMER; i++) {
|
|
|
|
LACP_TIMER_DISARM(lp, i);
|
|
|
|
}
|
|
|
|
|
|
|
|
lacp_disable_collecting(lp);
|
|
|
|
lacp_disable_distributing(lp);
|
|
|
|
lacp_unselect(lp);
|
|
|
|
|
2014-10-05 02:34:21 +00:00
|
|
|
LIST_REMOVE(lp, lp_next);
|
|
|
|
LACP_UNLOCK(lsc);
|
|
|
|
|
2007-05-07 00:28:55 +00:00
|
|
|
/* The address may have already been removed by if_purgemaddrs() */
|
|
|
|
if (!lgp->lp_detaching)
|
|
|
|
if_delmulti_ifma(lp->lp_ifma);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
free(lp, M_DEVBUF);
|
|
|
|
}
|
|
|
|
|
2007-07-05 09:18:57 +00:00
|
|
|
void
|
2014-09-26 12:35:58 +00:00
|
|
|
lacp_req(struct lagg_softc *sc, void *data)
|
2007-07-05 09:18:57 +00:00
|
|
|
{
|
|
|
|
struct lacp_opreq *req = (struct lacp_opreq *)data;
|
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
2014-05-02 16:24:09 +00:00
|
|
|
struct lacp_aggregator *la;
|
2007-07-05 09:18:57 +00:00
|
|
|
|
|
|
|
bzero(req, sizeof(struct lacp_opreq));
|
2020-09-01 21:19:14 +00:00
|
|
|
|
2014-09-26 12:35:58 +00:00
|
|
|
/*
|
2014-05-02 16:24:09 +00:00
|
|
|
* If the LACP softc is NULL, return with the opreq structure full of
|
|
|
|
* zeros. It is normal for the softc to be NULL while the lagg is
|
|
|
|
* being destroyed.
|
|
|
|
*/
|
|
|
|
if (NULL == lsc)
|
|
|
|
return;
|
|
|
|
|
|
|
|
la = lsc->lsc_active_aggregator;
|
|
|
|
LACP_LOCK(lsc);
|
2007-07-05 09:18:57 +00:00
|
|
|
if (la != NULL) {
|
|
|
|
req->actor_prio = ntohs(la->la_actor.lip_systemid.lsi_prio);
|
|
|
|
memcpy(&req->actor_mac, &la->la_actor.lip_systemid.lsi_mac,
|
|
|
|
ETHER_ADDR_LEN);
|
|
|
|
req->actor_key = ntohs(la->la_actor.lip_key);
|
|
|
|
req->actor_portprio = ntohs(la->la_actor.lip_portid.lpi_prio);
|
|
|
|
req->actor_portno = ntohs(la->la_actor.lip_portid.lpi_portno);
|
|
|
|
req->actor_state = la->la_actor.lip_state;
|
|
|
|
|
|
|
|
req->partner_prio = ntohs(la->la_partner.lip_systemid.lsi_prio);
|
|
|
|
memcpy(&req->partner_mac, &la->la_partner.lip_systemid.lsi_mac,
|
|
|
|
ETHER_ADDR_LEN);
|
|
|
|
req->partner_key = ntohs(la->la_partner.lip_key);
|
|
|
|
req->partner_portprio = ntohs(la->la_partner.lip_portid.lpi_prio);
|
|
|
|
req->partner_portno = ntohs(la->la_partner.lip_portid.lpi_portno);
|
|
|
|
req->partner_state = la->la_partner.lip_state;
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-07-05 09:18:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2014-09-26 12:35:58 +00:00
|
|
|
lacp_portreq(struct lagg_port *lgp, void *data)
|
2007-07-05 09:18:57 +00:00
|
|
|
{
|
|
|
|
struct lacp_opreq *req = (struct lacp_opreq *)data;
|
|
|
|
struct lacp_port *lp = LACP_PORT(lgp);
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2007-07-05 09:18:57 +00:00
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-07-05 09:18:57 +00:00
|
|
|
req->actor_prio = ntohs(lp->lp_actor.lip_systemid.lsi_prio);
|
|
|
|
memcpy(&req->actor_mac, &lp->lp_actor.lip_systemid.lsi_mac,
|
|
|
|
ETHER_ADDR_LEN);
|
|
|
|
req->actor_key = ntohs(lp->lp_actor.lip_key);
|
|
|
|
req->actor_portprio = ntohs(lp->lp_actor.lip_portid.lpi_prio);
|
|
|
|
req->actor_portno = ntohs(lp->lp_actor.lip_portid.lpi_portno);
|
|
|
|
req->actor_state = lp->lp_actor.lip_state;
|
|
|
|
|
|
|
|
req->partner_prio = ntohs(lp->lp_partner.lip_systemid.lsi_prio);
|
|
|
|
memcpy(&req->partner_mac, &lp->lp_partner.lip_systemid.lsi_mac,
|
|
|
|
ETHER_ADDR_LEN);
|
|
|
|
req->partner_key = ntohs(lp->lp_partner.lip_key);
|
|
|
|
req->partner_portprio = ntohs(lp->lp_partner.lip_portid.lpi_prio);
|
|
|
|
req->partner_portno = ntohs(lp->lp_partner.lip_portid.lpi_portno);
|
|
|
|
req->partner_state = lp->lp_partner.lip_state;
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-07-05 09:18:57 +00:00
|
|
|
}
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
static void
|
|
|
|
lacp_disable_collecting(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
LACP_DPRINTF((lp, "collecting disabled\n"));
|
|
|
|
lp->lp_state &= ~LACP_STATE_COLLECTING;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_enable_collecting(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
LACP_DPRINTF((lp, "collecting enabled\n"));
|
|
|
|
lp->lp_state |= LACP_STATE_COLLECTING;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_disable_distributing(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la = lp->lp_aggregator;
|
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2013-07-13 04:25:03 +00:00
|
|
|
struct lagg_softc *sc = lsc->lsc_softc;
|
2007-04-10 00:27:25 +00:00
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_ASSERT(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (la == NULL || (lp->lp_state & LACP_STATE_DISTRIBUTING) == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
KASSERT(!TAILQ_EMPTY(&la->la_ports), ("no aggregator ports"));
|
|
|
|
KASSERT(la->la_nports > 0, ("nports invalid (%d)", la->la_nports));
|
|
|
|
KASSERT(la->la_refcnt >= la->la_nports, ("aggregator refcnt invalid"));
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "disable distributing on aggregator %s, "
|
|
|
|
"nports %d -> %d\n",
|
|
|
|
lacp_format_lagid_aggregator(la, buf, sizeof(buf)),
|
|
|
|
la->la_nports, la->la_nports - 1));
|
|
|
|
|
|
|
|
TAILQ_REMOVE(&la->la_ports, lp, lp_dist_q);
|
|
|
|
la->la_nports--;
|
2013-07-13 04:25:03 +00:00
|
|
|
sc->sc_active = la->la_nports;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (lsc->lsc_active_aggregator == la) {
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_suppress_distributing(lsc, la);
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_select_active_aggregator(lsc);
|
2008-03-16 19:25:30 +00:00
|
|
|
/* regenerate the port map, the active aggregator has changed */
|
|
|
|
lacp_update_portmap(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
|
|
|
|
lp->lp_state &= ~LACP_STATE_DISTRIBUTING;
|
2018-08-13 14:13:25 +00:00
|
|
|
if_link_state_change(sc->sc_ifp,
|
|
|
|
sc->sc_active ? LINK_STATE_UP : LINK_STATE_DOWN);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_enable_distributing(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la = lp->lp_aggregator;
|
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2013-07-13 04:25:03 +00:00
|
|
|
struct lagg_softc *sc = lsc->lsc_softc;
|
2007-04-10 00:27:25 +00:00
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_ASSERT(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if ((lp->lp_state & LACP_STATE_DISTRIBUTING) != 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "enable distributing on aggregator %s, "
|
|
|
|
"nports %d -> %d\n",
|
|
|
|
lacp_format_lagid_aggregator(la, buf, sizeof(buf)),
|
|
|
|
la->la_nports, la->la_nports + 1));
|
|
|
|
|
|
|
|
KASSERT(la->la_refcnt > la->la_nports, ("aggregator refcnt invalid"));
|
|
|
|
TAILQ_INSERT_HEAD(&la->la_ports, lp, lp_dist_q);
|
|
|
|
la->la_nports++;
|
2013-07-13 04:25:03 +00:00
|
|
|
sc->sc_active = la->la_nports;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
lp->lp_state |= LACP_STATE_DISTRIBUTING;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
if (lsc->lsc_active_aggregator == la) {
|
|
|
|
lacp_suppress_distributing(lsc, la);
|
|
|
|
lacp_update_portmap(lsc);
|
|
|
|
} else
|
|
|
|
/* try to become the active aggregator */
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_select_active_aggregator(lsc);
|
2018-08-13 14:13:25 +00:00
|
|
|
|
|
|
|
if_link_state_change(sc->sc_ifp,
|
|
|
|
sc->sc_active ? LINK_STATE_UP : LINK_STATE_DOWN);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_transit_expire(void *vp)
|
|
|
|
{
|
|
|
|
struct lacp_softc *lsc = vp;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_ASSERT(lsc);
|
|
|
|
|
2014-10-05 02:34:21 +00:00
|
|
|
CURVNET_SET(lsc->lsc_softc->sc_ifp->if_vnet);
|
2012-05-26 08:09:01 +00:00
|
|
|
LACP_TRACE(NULL);
|
2014-10-05 02:34:21 +00:00
|
|
|
CURVNET_RESTORE();
|
2012-05-26 08:09:01 +00:00
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
lsc->lsc_suppress_distributing = FALSE;
|
|
|
|
}
|
|
|
|
|
2014-09-26 08:42:32 +00:00
|
|
|
void
|
2007-06-12 07:29:11 +00:00
|
|
|
lacp_attach(struct lagg_softc *sc)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
|
|
|
struct lacp_softc *lsc;
|
|
|
|
|
2014-09-26 08:42:32 +00:00
|
|
|
lsc = malloc(sizeof(struct lacp_softc), M_DEVBUF, M_WAITOK | M_ZERO);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2014-09-26 12:35:58 +00:00
|
|
|
sc->sc_psc = lsc;
|
2007-06-12 07:29:11 +00:00
|
|
|
lsc->lsc_softc = sc;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2015-03-11 16:02:24 +00:00
|
|
|
lsc->lsc_hashkey = m_ether_tcpip_hash_init();
|
2007-04-10 00:27:25 +00:00
|
|
|
lsc->lsc_active_aggregator = NULL;
|
2015-11-06 15:33:27 +00:00
|
|
|
lsc->lsc_strict_mode = VNET(lacp_default_strict_mode);
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_INIT(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
TAILQ_INIT(&lsc->lsc_aggregators);
|
|
|
|
LIST_INIT(&lsc->lsc_ports);
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
callout_init_mtx(&lsc->lsc_transit_callout, &lsc->lsc_mtx, 0);
|
|
|
|
callout_init_mtx(&lsc->lsc_callout, &lsc->lsc_mtx, 0);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2007-04-17 00:35:11 +00:00
|
|
|
/* if the lagg is already up then do the same */
|
2007-06-12 07:29:11 +00:00
|
|
|
if (sc->sc_ifp->if_drv_flags & IFF_DRV_RUNNING)
|
|
|
|
lacp_init(sc);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
2014-09-26 07:12:40 +00:00
|
|
|
void
|
2014-09-26 08:42:32 +00:00
|
|
|
lacp_detach(void *psc)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2014-09-26 08:42:32 +00:00
|
|
|
struct lacp_softc *lsc = (struct lacp_softc *)psc;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
KASSERT(TAILQ_EMPTY(&lsc->lsc_aggregators),
|
|
|
|
("aggregators still active"));
|
|
|
|
KASSERT(lsc->lsc_active_aggregator == NULL,
|
|
|
|
("aggregator still attached"));
|
|
|
|
|
|
|
|
callout_drain(&lsc->lsc_transit_callout);
|
|
|
|
callout_drain(&lsc->lsc_callout);
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK_DESTROY(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
free(lsc, M_DEVBUF);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2007-06-12 07:29:11 +00:00
|
|
|
lacp_init(struct lagg_softc *sc)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
callout_reset(&lsc->lsc_callout, hz, lacp_tick, lsc);
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2007-06-12 07:29:11 +00:00
|
|
|
lacp_stop(struct lagg_softc *sc)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
callout_stop(&lsc->lsc_transit_callout);
|
|
|
|
callout_stop(&lsc->lsc_callout);
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lagg_port *
|
2020-11-18 14:55:49 +00:00
|
|
|
lacp_select_tx_port_by_hash(struct lagg_softc *sc, uint32_t hash,
|
|
|
|
uint8_t numa_domain, int *err)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_portmap *pm;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct lacp_port *lp;
|
2019-05-03 14:43:21 +00:00
|
|
|
struct lacp_port **map;
|
|
|
|
int count;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (__predict_false(lsc->lsc_suppress_distributing)) {
|
|
|
|
LACP_DPRINTF((NULL, "%s: waiting transit\n", __func__));
|
2020-11-18 14:55:49 +00:00
|
|
|
*err = ENOBUFS;
|
2007-04-10 00:27:25 +00:00
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
pm = &lsc->lsc_pmap[lsc->lsc_activemap];
|
|
|
|
if (pm->pm_count == 0) {
|
2007-04-10 00:27:25 +00:00
|
|
|
LACP_DPRINTF((NULL, "%s: no active aggregator\n", __func__));
|
2020-11-18 14:55:49 +00:00
|
|
|
*err = ENETDOWN;
|
2007-04-10 00:27:25 +00:00
|
|
|
return (NULL);
|
|
|
|
}
|
|
|
|
|
2019-05-03 14:43:21 +00:00
|
|
|
#ifdef NUMA
|
|
|
|
if ((sc->sc_opts & LAGG_OPT_USE_NUMA) &&
|
2020-03-09 13:44:51 +00:00
|
|
|
pm->pm_num_dom > 1 && numa_domain < MAXMEMDOM) {
|
|
|
|
count = pm->pm_numa[numa_domain].count;
|
2019-05-03 14:43:21 +00:00
|
|
|
if (count > 0) {
|
2020-03-09 13:44:51 +00:00
|
|
|
map = pm->pm_numa[numa_domain].map;
|
2019-05-03 14:43:21 +00:00
|
|
|
} else {
|
|
|
|
/* No ports on this domain; use global hash. */
|
|
|
|
map = pm->pm_map;
|
|
|
|
count = pm->pm_count;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
map = pm->pm_map;
|
|
|
|
count = pm->pm_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
hash %= count;
|
|
|
|
lp = map[hash];
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2007-04-17 00:35:11 +00:00
|
|
|
return (lp->lp_lagg);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
|
|
|
|
struct lagg_port *
|
2020-11-18 14:55:49 +00:00
|
|
|
lacp_select_tx_port(struct lagg_softc *sc, struct mbuf *m, int *err)
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
{
|
|
|
|
struct lacp_softc *lsc = LACP_SOFTC(sc);
|
|
|
|
uint32_t hash;
|
2020-03-09 13:44:51 +00:00
|
|
|
uint8_t numa_domain;
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
|
2020-03-09 13:44:51 +00:00
|
|
|
if ((sc->sc_opts & LAGG_OPT_USE_FLOWID) &&
|
|
|
|
M_HASHTYPE_GET(m) != M_HASHTYPE_NONE)
|
|
|
|
hash = m->m_pkthdr.flowid >> sc->flowid_shift;
|
|
|
|
else
|
|
|
|
hash = m_ether_tcpip_hash(sc->sc_flags, m, lsc->lsc_hashkey);
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
|
2020-03-09 13:44:51 +00:00
|
|
|
numa_domain = m->m_pkthdr.numa_domain;
|
2020-11-18 14:55:49 +00:00
|
|
|
return (lacp_select_tx_port_by_hash(sc, hash, numa_domain, err));
|
Implement kernel support for hardware rate limited sockets.
- Add RATELIMIT kernel configuration keyword which must be set to
enable the new functionality.
- Add support for hardware driven, Receive Side Scaling, RSS aware, rate
limited sendqueues and expose the functionality through the already
established SO_MAX_PACING_RATE setsockopt(). The API support rates in
the range from 1 to 4Gbytes/s which are suitable for regular TCP and
UDP streams. The setsockopt(2) manual page has been updated.
- Add rate limit function callback API to "struct ifnet" which supports
the following operations: if_snd_tag_alloc(), if_snd_tag_modify(),
if_snd_tag_query() and if_snd_tag_free().
- Add support to ifconfig to view, set and clear the IFCAP_TXRTLMT
flag, which tells if a network driver supports rate limiting or not.
- This patch also adds support for rate limiting through VLAN and LAGG
intermediate network devices.
- How rate limiting works:
1) The userspace application calls setsockopt() after accepting or
making a new connection to set the rate which is then stored in the
socket structure in the kernel. Later on when packets are transmitted
a check is made in the transmit path for rate changes. A rate change
implies a non-blocking ifp->if_snd_tag_alloc() call will be made to the
destination network interface, which then sets up a custom sendqueue
with the given rate limitation parameter. A "struct m_snd_tag" pointer is
returned which serves as a "snd_tag" hint in the m_pkthdr for the
subsequently transmitted mbufs.
2) When the network driver sees the "m->m_pkthdr.snd_tag" different
from NULL, it will move the packets into a designated rate limited sendqueue
given by the snd_tag pointer. It is up to the individual drivers how the rate
limited traffic will be rate limited.
3) Route changes are detected by the NIC drivers in the ifp->if_transmit()
routine when the ifnet pointer in the incoming snd_tag mismatches the
one of the network interface. The network adapter frees the mbuf and
returns EAGAIN which causes the ip_output() to release and clear the send
tag. Upon next ip_output() a new "snd_tag" will be tried allocated.
4) When the PCB is detached the custom sendqueue will be released by a
non-blocking ifp->if_snd_tag_free() call to the currently bound network
interface.
Reviewed by: wblock (manpages), adrian, gallatin, scottl (network)
Differential Revision: https://reviews.freebsd.org/D3687
Sponsored by: Mellanox Technologies
MFC after: 3 months
2017-01-18 13:31:17 +00:00
|
|
|
}
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
/*
|
|
|
|
* lacp_suppress_distributing: drop transmit packets for a while
|
|
|
|
* to preserve packet ordering.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_suppress_distributing(struct lacp_softc *lsc, struct lacp_aggregator *la)
|
|
|
|
{
|
2007-05-19 07:47:04 +00:00
|
|
|
struct lacp_port *lp;
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
if (lsc->lsc_active_aggregator != la) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
LACP_TRACE(NULL);
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
lsc->lsc_suppress_distributing = TRUE;
|
2007-05-19 07:47:04 +00:00
|
|
|
|
|
|
|
/* send a marker frame down each port to verify the queues are empty */
|
|
|
|
LIST_FOREACH(lp, &lsc->lsc_ports, lp_next) {
|
|
|
|
lp->lp_flags |= LACP_PORT_MARK;
|
2021-09-23 08:57:11 +00:00
|
|
|
if (lacp_xmit_marker(lp) != 0)
|
|
|
|
lp->lp_flags &= ~LACP_PORT_MARK;
|
2007-05-19 07:47:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* set a timeout for the marker frames */
|
2007-04-10 00:27:25 +00:00
|
|
|
callout_reset(&lsc->lsc_transit_callout,
|
|
|
|
LACP_TRANSIT_DELAY * hz / 1000, lacp_transit_expire, lsc);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
lacp_compare_peerinfo(const struct lacp_peerinfo *a,
|
|
|
|
const struct lacp_peerinfo *b)
|
|
|
|
{
|
|
|
|
return (memcmp(a, b, offsetof(struct lacp_peerinfo, lip_state)));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
lacp_compare_systemid(const struct lacp_systemid *a,
|
|
|
|
const struct lacp_systemid *b)
|
|
|
|
{
|
|
|
|
return (memcmp(a, b, sizeof(*a)));
|
|
|
|
}
|
|
|
|
|
|
|
|
#if 0 /* unused */
|
|
|
|
static int
|
|
|
|
lacp_compare_portid(const struct lacp_portid *a,
|
|
|
|
const struct lacp_portid *b)
|
|
|
|
{
|
|
|
|
return (memcmp(a, b, sizeof(*a)));
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static uint64_t
|
|
|
|
lacp_aggregator_bandwidth(struct lacp_aggregator *la)
|
|
|
|
{
|
|
|
|
struct lacp_port *lp;
|
|
|
|
uint64_t speed;
|
|
|
|
|
|
|
|
lp = TAILQ_FIRST(&la->la_ports);
|
|
|
|
if (lp == NULL) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
speed = ifmedia_baudrate(lp->lp_media);
|
|
|
|
speed *= la->la_nports;
|
|
|
|
if (speed == 0) {
|
|
|
|
LACP_DPRINTF((lp, "speed 0? media=0x%x nports=%d\n",
|
|
|
|
lp->lp_media, la->la_nports));
|
|
|
|
}
|
|
|
|
|
|
|
|
return (speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_select_active_aggregator: select an aggregator to be used to transmit
|
2007-04-17 00:35:11 +00:00
|
|
|
* packets from lagg(4) interface.
|
2007-04-10 00:27:25 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_select_active_aggregator(struct lacp_softc *lsc)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la;
|
|
|
|
struct lacp_aggregator *best_la = NULL;
|
|
|
|
uint64_t best_speed = 0;
|
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
LACP_TRACE(NULL);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
TAILQ_FOREACH(la, &lsc->lsc_aggregators, la_q) {
|
|
|
|
uint64_t speed;
|
|
|
|
|
|
|
|
if (la->la_nports == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
speed = lacp_aggregator_bandwidth(la);
|
|
|
|
LACP_DPRINTF((NULL, "%s, speed=%jd, nports=%d\n",
|
|
|
|
lacp_format_lagid_aggregator(la, buf, sizeof(buf)),
|
|
|
|
speed, la->la_nports));
|
2007-05-19 09:37:04 +00:00
|
|
|
|
2014-09-26 12:35:58 +00:00
|
|
|
/*
|
|
|
|
* This aggregator is chosen if the partner has a better
|
|
|
|
* system priority or, the total aggregated speed is higher
|
|
|
|
* or, it is already the chosen aggregator
|
2007-05-19 09:37:04 +00:00
|
|
|
*/
|
|
|
|
if ((best_la != NULL && LACP_SYS_PRI(la->la_partner) <
|
2014-09-26 12:35:58 +00:00
|
|
|
LACP_SYS_PRI(best_la->la_partner)) ||
|
2007-05-19 09:37:04 +00:00
|
|
|
speed > best_speed ||
|
2007-04-10 00:27:25 +00:00
|
|
|
(speed == best_speed &&
|
|
|
|
la == lsc->lsc_active_aggregator)) {
|
|
|
|
best_la = la;
|
|
|
|
best_speed = speed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
KASSERT(best_la == NULL || best_la->la_nports > 0,
|
|
|
|
("invalid aggregator refcnt"));
|
|
|
|
KASSERT(best_la == NULL || !TAILQ_EMPTY(&best_la->la_ports),
|
|
|
|
("invalid aggregator list"));
|
|
|
|
|
|
|
|
if (lsc->lsc_active_aggregator != best_la) {
|
|
|
|
LACP_DPRINTF((NULL, "active aggregator changed\n"));
|
|
|
|
LACP_DPRINTF((NULL, "old %s\n",
|
|
|
|
lacp_format_lagid_aggregator(lsc->lsc_active_aggregator,
|
|
|
|
buf, sizeof(buf))));
|
|
|
|
} else {
|
|
|
|
LACP_DPRINTF((NULL, "active aggregator not changed\n"));
|
|
|
|
}
|
|
|
|
LACP_DPRINTF((NULL, "new %s\n",
|
|
|
|
lacp_format_lagid_aggregator(best_la, buf, sizeof(buf))));
|
|
|
|
|
|
|
|
if (lsc->lsc_active_aggregator != best_la) {
|
|
|
|
lsc->lsc_active_aggregator = best_la;
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_update_portmap(lsc);
|
2007-04-10 00:27:25 +00:00
|
|
|
if (best_la) {
|
|
|
|
lacp_suppress_distributing(lsc, best_la);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
/*
|
|
|
|
* Updated the inactive portmap array with the new list of ports and
|
|
|
|
* make it live.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
lacp_update_portmap(struct lacp_softc *lsc)
|
|
|
|
{
|
2013-07-13 04:25:03 +00:00
|
|
|
struct lagg_softc *sc = lsc->lsc_softc;
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lacp_aggregator *la;
|
|
|
|
struct lacp_portmap *p;
|
|
|
|
struct lacp_port *lp;
|
2013-07-13 04:25:03 +00:00
|
|
|
uint64_t speed;
|
2008-03-16 19:25:30 +00:00
|
|
|
u_int newmap;
|
|
|
|
int i;
|
2019-05-03 14:43:21 +00:00
|
|
|
#ifdef NUMA
|
|
|
|
int count;
|
|
|
|
uint8_t domain;
|
|
|
|
#endif
|
2008-03-16 19:25:30 +00:00
|
|
|
|
|
|
|
newmap = lsc->lsc_activemap == 0 ? 1 : 0;
|
|
|
|
p = &lsc->lsc_pmap[newmap];
|
|
|
|
la = lsc->lsc_active_aggregator;
|
2013-07-13 04:25:03 +00:00
|
|
|
speed = 0;
|
2008-03-16 19:25:30 +00:00
|
|
|
bzero(p, sizeof(struct lacp_portmap));
|
|
|
|
|
|
|
|
if (la != NULL && la->la_nports > 0) {
|
|
|
|
p->pm_count = la->la_nports;
|
|
|
|
i = 0;
|
2019-05-03 14:43:21 +00:00
|
|
|
TAILQ_FOREACH(lp, &la->la_ports, lp_dist_q) {
|
2008-03-16 19:25:30 +00:00
|
|
|
p->pm_map[i++] = lp;
|
2019-05-03 14:43:21 +00:00
|
|
|
#ifdef NUMA
|
|
|
|
domain = lp->lp_ifp->if_numa_domain;
|
|
|
|
if (domain >= MAXMEMDOM)
|
|
|
|
continue;
|
|
|
|
count = p->pm_numa[domain].count;
|
|
|
|
p->pm_numa[domain].map[count] = lp;
|
|
|
|
p->pm_numa[domain].count++;
|
|
|
|
#endif
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
KASSERT(i == p->pm_count, ("Invalid port count"));
|
2019-05-03 14:43:21 +00:00
|
|
|
|
|
|
|
#ifdef NUMA
|
|
|
|
for (i = 0; i < MAXMEMDOM; i++) {
|
|
|
|
if (p->pm_numa[i].count != 0)
|
|
|
|
p->pm_num_dom++;
|
|
|
|
}
|
|
|
|
#endif
|
2013-07-13 04:25:03 +00:00
|
|
|
speed = lacp_aggregator_bandwidth(la);
|
2008-03-16 19:25:30 +00:00
|
|
|
}
|
2013-07-13 04:25:03 +00:00
|
|
|
sc->sc_ifp->if_baudrate = speed;
|
2022-05-19 06:02:27 +00:00
|
|
|
EVENTHANDLER_INVOKE(ifnet_event, sc->sc_ifp,
|
|
|
|
IFNET_EVENT_UPDATE_BAUDRATE);
|
2008-03-16 19:25:30 +00:00
|
|
|
|
|
|
|
/* switch the active portmap over */
|
|
|
|
atomic_store_rel_int(&lsc->lsc_activemap, newmap);
|
|
|
|
LACP_DPRINTF((NULL, "Set table %d with %d ports\n",
|
|
|
|
lsc->lsc_activemap,
|
|
|
|
lsc->lsc_pmap[lsc->lsc_activemap].pm_count));
|
|
|
|
}
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
static uint16_t
|
|
|
|
lacp_compose_key(struct lacp_port *lp)
|
|
|
|
{
|
2007-04-17 00:35:11 +00:00
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
2007-06-12 07:29:11 +00:00
|
|
|
struct lagg_softc *sc = lgp->lp_softc;
|
2007-04-10 00:27:25 +00:00
|
|
|
u_int media = lp->lp_media;
|
|
|
|
uint16_t key;
|
|
|
|
|
|
|
|
if ((lp->lp_state & LACP_STATE_AGGREGATION) == 0) {
|
|
|
|
/*
|
|
|
|
* non-aggregatable links should have unique keys.
|
|
|
|
*
|
|
|
|
* XXX this isn't really unique as if_index is 16 bit.
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* bit 0..14: (some bits of) if_index of this port */
|
|
|
|
key = lp->lp_ifp->if_index;
|
|
|
|
/* bit 15: 1 */
|
|
|
|
key |= 0x8000;
|
|
|
|
} else {
|
|
|
|
u_int subtype = IFM_SUBTYPE(media);
|
|
|
|
|
2007-05-03 08:56:20 +00:00
|
|
|
KASSERT(IFM_TYPE(media) == IFM_ETHER, ("invalid media type"));
|
|
|
|
KASSERT((media & IFM_FDX) != 0, ("aggregating HDX interface"));
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2013-10-17 15:14:58 +00:00
|
|
|
/* bit 0..4: IFM_SUBTYPE modulo speed */
|
|
|
|
switch (subtype) {
|
|
|
|
case IFM_10_T:
|
|
|
|
case IFM_10_2:
|
|
|
|
case IFM_10_5:
|
|
|
|
case IFM_10_STP:
|
|
|
|
case IFM_10_FL:
|
|
|
|
key = IFM_10_T;
|
|
|
|
break;
|
|
|
|
case IFM_100_TX:
|
|
|
|
case IFM_100_FX:
|
|
|
|
case IFM_100_T4:
|
|
|
|
case IFM_100_VG:
|
|
|
|
case IFM_100_T2:
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_100_T:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_100_SGMII:
|
2013-10-17 15:14:58 +00:00
|
|
|
key = IFM_100_TX;
|
|
|
|
break;
|
|
|
|
case IFM_1000_SX:
|
|
|
|
case IFM_1000_LX:
|
|
|
|
case IFM_1000_CX:
|
|
|
|
case IFM_1000_T:
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_1000_KX:
|
|
|
|
case IFM_1000_SGMII:
|
|
|
|
case IFM_1000_CX_SGMII:
|
2013-10-17 15:14:58 +00:00
|
|
|
key = IFM_1000_SX;
|
|
|
|
break;
|
|
|
|
case IFM_10G_LR:
|
|
|
|
case IFM_10G_SR:
|
|
|
|
case IFM_10G_CX4:
|
|
|
|
case IFM_10G_TWINAX:
|
|
|
|
case IFM_10G_TWINAX_LONG:
|
|
|
|
case IFM_10G_LRM:
|
|
|
|
case IFM_10G_T:
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_10G_KX4:
|
|
|
|
case IFM_10G_KR:
|
|
|
|
case IFM_10G_CR1:
|
|
|
|
case IFM_10G_ER:
|
|
|
|
case IFM_10G_SFI:
|
2017-05-10 18:33:40 +00:00
|
|
|
case IFM_10G_AOC:
|
2013-10-17 15:14:58 +00:00
|
|
|
key = IFM_10G_LR;
|
|
|
|
break;
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_20G_KR2:
|
|
|
|
key = IFM_20G_KR2;
|
|
|
|
break;
|
|
|
|
case IFM_2500_KX:
|
|
|
|
case IFM_2500_T:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_2500_X:
|
2015-04-07 21:31:17 +00:00
|
|
|
key = IFM_2500_KX;
|
|
|
|
break;
|
|
|
|
case IFM_5000_T:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_5000_KR:
|
|
|
|
case IFM_5000_KR_S:
|
|
|
|
case IFM_5000_KR1:
|
2015-04-07 21:31:17 +00:00
|
|
|
key = IFM_5000_T;
|
|
|
|
break;
|
|
|
|
case IFM_50G_PCIE:
|
|
|
|
case IFM_50G_CR2:
|
|
|
|
case IFM_50G_KR2:
|
2020-02-11 18:03:45 +00:00
|
|
|
case IFM_50G_KR4:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_50G_SR2:
|
|
|
|
case IFM_50G_LR2:
|
|
|
|
case IFM_50G_LAUI2_AC:
|
|
|
|
case IFM_50G_LAUI2:
|
|
|
|
case IFM_50G_AUI2_AC:
|
|
|
|
case IFM_50G_AUI2:
|
|
|
|
case IFM_50G_CP:
|
|
|
|
case IFM_50G_SR:
|
|
|
|
case IFM_50G_LR:
|
|
|
|
case IFM_50G_FR:
|
|
|
|
case IFM_50G_KR_PAM4:
|
|
|
|
case IFM_50G_AUI1_AC:
|
|
|
|
case IFM_50G_AUI1:
|
2015-04-07 21:31:17 +00:00
|
|
|
key = IFM_50G_PCIE;
|
|
|
|
break;
|
|
|
|
case IFM_56G_R4:
|
|
|
|
key = IFM_56G_R4;
|
|
|
|
break;
|
|
|
|
case IFM_25G_PCIE:
|
|
|
|
case IFM_25G_CR:
|
|
|
|
case IFM_25G_KR:
|
|
|
|
case IFM_25G_SR:
|
2017-05-10 18:33:40 +00:00
|
|
|
case IFM_25G_LR:
|
|
|
|
case IFM_25G_ACC:
|
|
|
|
case IFM_25G_AOC:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_25G_T:
|
|
|
|
case IFM_25G_CR_S:
|
|
|
|
case IFM_25G_CR1:
|
|
|
|
case IFM_25G_KR_S:
|
|
|
|
case IFM_25G_AUI:
|
|
|
|
case IFM_25G_KR1:
|
2015-04-07 21:31:17 +00:00
|
|
|
key = IFM_25G_PCIE;
|
|
|
|
break;
|
2013-10-17 15:14:58 +00:00
|
|
|
case IFM_40G_CR4:
|
|
|
|
case IFM_40G_SR4:
|
|
|
|
case IFM_40G_LR4:
|
2020-09-16 14:45:16 +00:00
|
|
|
case IFM_40G_LM4:
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_40G_XLPPI:
|
|
|
|
case IFM_40G_KR4:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_40G_XLAUI:
|
|
|
|
case IFM_40G_XLAUI_AC:
|
|
|
|
case IFM_40G_ER4:
|
2013-10-17 15:14:58 +00:00
|
|
|
key = IFM_40G_CR4;
|
|
|
|
break;
|
2015-04-07 21:31:17 +00:00
|
|
|
case IFM_100G_CR4:
|
|
|
|
case IFM_100G_SR4:
|
|
|
|
case IFM_100G_KR4:
|
|
|
|
case IFM_100G_LR4:
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_100G_CAUI4_AC:
|
|
|
|
case IFM_100G_CAUI4:
|
|
|
|
case IFM_100G_AUI4_AC:
|
|
|
|
case IFM_100G_AUI4:
|
|
|
|
case IFM_100G_CR_PAM4:
|
|
|
|
case IFM_100G_KR_PAM4:
|
|
|
|
case IFM_100G_CP2:
|
|
|
|
case IFM_100G_SR2:
|
|
|
|
case IFM_100G_DR:
|
|
|
|
case IFM_100G_KR2_PAM4:
|
|
|
|
case IFM_100G_CAUI2_AC:
|
|
|
|
case IFM_100G_CAUI2:
|
|
|
|
case IFM_100G_AUI2_AC:
|
|
|
|
case IFM_100G_AUI2:
|
2015-04-07 21:31:17 +00:00
|
|
|
key = IFM_100G_CR4;
|
|
|
|
break;
|
2018-08-22 18:19:56 +00:00
|
|
|
case IFM_200G_CR4_PAM4:
|
|
|
|
case IFM_200G_SR4:
|
|
|
|
case IFM_200G_FR4:
|
|
|
|
case IFM_200G_LR4:
|
|
|
|
case IFM_200G_DR4:
|
|
|
|
case IFM_200G_KR4_PAM4:
|
|
|
|
case IFM_200G_AUI4_AC:
|
|
|
|
case IFM_200G_AUI4:
|
|
|
|
case IFM_200G_AUI8_AC:
|
|
|
|
case IFM_200G_AUI8:
|
|
|
|
key = IFM_200G_CR4_PAM4;
|
|
|
|
break;
|
|
|
|
case IFM_400G_FR8:
|
|
|
|
case IFM_400G_LR8:
|
|
|
|
case IFM_400G_DR4:
|
|
|
|
case IFM_400G_AUI8_AC:
|
|
|
|
case IFM_400G_AUI8:
|
|
|
|
key = IFM_400G_FR8;
|
|
|
|
break;
|
2013-10-17 15:14:58 +00:00
|
|
|
default:
|
|
|
|
key = subtype;
|
2015-04-07 21:31:17 +00:00
|
|
|
break;
|
2013-10-17 15:14:58 +00:00
|
|
|
}
|
2007-04-17 00:35:11 +00:00
|
|
|
/* bit 5..14: (some bits of) if_index of lagg device */
|
2007-06-12 07:29:11 +00:00
|
|
|
key |= 0x7fe0 & ((sc->sc_ifp->if_index) << 5);
|
2007-04-10 00:27:25 +00:00
|
|
|
/* bit 15: 0 */
|
|
|
|
}
|
|
|
|
return (htons(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_aggregator_addref(struct lacp_softc *lsc, struct lacp_aggregator *la)
|
|
|
|
{
|
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
|
|
|
LACP_DPRINTF((NULL, "%s: lagid=%s, refcnt %d -> %d\n",
|
|
|
|
__func__,
|
|
|
|
lacp_format_lagid(&la->la_actor, &la->la_partner,
|
|
|
|
buf, sizeof(buf)),
|
|
|
|
la->la_refcnt, la->la_refcnt + 1));
|
|
|
|
|
|
|
|
KASSERT(la->la_refcnt > 0, ("refcount <= 0"));
|
|
|
|
la->la_refcnt++;
|
|
|
|
KASSERT(la->la_refcnt > la->la_nports, ("invalid refcount"));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_aggregator_delref(struct lacp_softc *lsc, struct lacp_aggregator *la)
|
|
|
|
{
|
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
|
|
|
LACP_DPRINTF((NULL, "%s: lagid=%s, refcnt %d -> %d\n",
|
|
|
|
__func__,
|
|
|
|
lacp_format_lagid(&la->la_actor, &la->la_partner,
|
|
|
|
buf, sizeof(buf)),
|
|
|
|
la->la_refcnt, la->la_refcnt - 1));
|
|
|
|
|
|
|
|
KASSERT(la->la_refcnt > la->la_nports, ("invalid refcnt"));
|
|
|
|
la->la_refcnt--;
|
|
|
|
if (la->la_refcnt > 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
KASSERT(la->la_refcnt == 0, ("refcount not zero"));
|
|
|
|
KASSERT(lsc->lsc_active_aggregator != la, ("aggregator active"));
|
|
|
|
|
|
|
|
TAILQ_REMOVE(&lsc->lsc_aggregators, la, la_q);
|
|
|
|
|
|
|
|
free(la, M_DEVBUF);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_aggregator_get: allocate an aggregator.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static struct lacp_aggregator *
|
|
|
|
lacp_aggregator_get(struct lacp_softc *lsc, struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la;
|
|
|
|
|
|
|
|
la = malloc(sizeof(*la), M_DEVBUF, M_NOWAIT);
|
|
|
|
if (la) {
|
|
|
|
la->la_refcnt = 1;
|
|
|
|
la->la_nports = 0;
|
|
|
|
TAILQ_INIT(&la->la_ports);
|
|
|
|
la->la_pending = 0;
|
|
|
|
TAILQ_INSERT_TAIL(&lsc->lsc_aggregators, la, la_q);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (la);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_fill_aggregator_id: setup a newly allocated aggregator from a port.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_fill_aggregator_id(struct lacp_aggregator *la, const struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
lacp_fill_aggregator_id_peer(&la->la_partner, &lp->lp_partner);
|
|
|
|
lacp_fill_aggregator_id_peer(&la->la_actor, &lp->lp_actor);
|
|
|
|
|
|
|
|
la->la_actor.lip_state = lp->lp_state & LACP_STATE_AGGREGATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_fill_aggregator_id_peer(struct lacp_peerinfo *lpi_aggr,
|
|
|
|
const struct lacp_peerinfo *lpi_port)
|
|
|
|
{
|
|
|
|
memset(lpi_aggr, 0, sizeof(*lpi_aggr));
|
|
|
|
lpi_aggr->lip_systemid = lpi_port->lip_systemid;
|
|
|
|
lpi_aggr->lip_key = lpi_port->lip_key;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_aggregator_is_compatible: check if a port can join to an aggregator.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int
|
|
|
|
lacp_aggregator_is_compatible(const struct lacp_aggregator *la,
|
|
|
|
const struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
if (!(lp->lp_state & LACP_STATE_AGGREGATION) ||
|
|
|
|
!(lp->lp_partner.lip_state & LACP_STATE_AGGREGATION)) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(la->la_actor.lip_state & LACP_STATE_AGGREGATION)) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lacp_peerinfo_is_compatible(&la->la_partner, &lp->lp_partner)) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!lacp_peerinfo_is_compatible(&la->la_actor, &lp->lp_actor)) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
lacp_peerinfo_is_compatible(const struct lacp_peerinfo *a,
|
|
|
|
const struct lacp_peerinfo *b)
|
|
|
|
{
|
|
|
|
if (memcmp(&a->lip_systemid, &b->lip_systemid,
|
|
|
|
sizeof(a->lip_systemid))) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (memcmp(&a->lip_key, &b->lip_key, sizeof(a->lip_key))) {
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_port_enable(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
lp->lp_state |= LACP_STATE_AGGREGATION;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_port_disable(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
lacp_set_mux(lp, LACP_MUX_DETACHED);
|
|
|
|
|
|
|
|
lp->lp_state &= ~LACP_STATE_AGGREGATION;
|
|
|
|
lp->lp_selected = LACP_UNSELECTED;
|
|
|
|
lacp_sm_rx_record_default(lp);
|
|
|
|
lp->lp_partner.lip_state &= ~LACP_STATE_AGGREGATION;
|
|
|
|
lp->lp_state &= ~LACP_STATE_EXPIRED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_select: select an aggregator. create one if necessary.
|
|
|
|
*/
|
|
|
|
static void
|
|
|
|
lacp_select(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
|
|
|
struct lacp_aggregator *la;
|
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
|
|
|
if (lp->lp_aggregator) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
Do some minimal work to better conform to the 802.3ad (LACP) standard.
In particular, don't set the synchronized bit for the peer unless it truly
appears to be synchronized to us. Also, don't set our own synchronized bit
unless we have actually seen a remote system.
Prior to this change, we were seeing some strange behavior, such as:
1. We send an advertisement with the Activity, Aggregation, and Default
flags, followed by an advertisement with the Activity, Aggregation,
Synchronization, and Default flags. However, we hadn't seen an
advertisement from another peer and were still advertising the default
(NULL) peer. A closer examination of the in-kernel data structures (using
kgdb) showed that the system had added the default (NULL) peer as a valid
aggregator for the segment.
2. We were receiving an advertisement from a peer that included the
default (NULL) peer instead of including our system information. However,
we responded with an advertisement that included the Synchronization flag
for both our system and the peer. (Since the peer's advertisement did not
include our system information, we shouldn't add the synchronization bit
for the peer.)
This patch corrects those two items.
Reviewed by: smh
MFC after: 2 weeks
Sponsored by: Netflix
Differential Revision: https://reviews.freebsd.org/D9485
2017-02-26 00:19:02 +00:00
|
|
|
/* If we haven't heard from our peer, skip this step. */
|
|
|
|
if (lp->lp_state & LACP_STATE_DEFAULTED)
|
|
|
|
return;
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
KASSERT(!LACP_TIMER_ISARMED(lp, LACP_TIMER_WAIT_WHILE),
|
|
|
|
("timer_wait_while still active"));
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "port lagid=%s\n",
|
|
|
|
lacp_format_lagid(&lp->lp_actor, &lp->lp_partner,
|
|
|
|
buf, sizeof(buf))));
|
|
|
|
|
|
|
|
TAILQ_FOREACH(la, &lsc->lsc_aggregators, la_q) {
|
|
|
|
if (lacp_aggregator_is_compatible(la, lp)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (la == NULL) {
|
|
|
|
la = lacp_aggregator_get(lsc, lp);
|
|
|
|
if (la == NULL) {
|
|
|
|
LACP_DPRINTF((lp, "aggregator creation failed\n"));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* will retry on the next tick.
|
|
|
|
*/
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
lacp_fill_aggregator_id(la, lp);
|
|
|
|
LACP_DPRINTF((lp, "aggregator created\n"));
|
|
|
|
} else {
|
|
|
|
LACP_DPRINTF((lp, "compatible aggregator found\n"));
|
2008-03-16 19:25:30 +00:00
|
|
|
if (la->la_refcnt == LACP_MAX_PORTS)
|
|
|
|
return;
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_aggregator_addref(lsc, la);
|
|
|
|
}
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "aggregator lagid=%s\n",
|
|
|
|
lacp_format_lagid(&la->la_actor, &la->la_partner,
|
|
|
|
buf, sizeof(buf))));
|
|
|
|
|
|
|
|
lp->lp_aggregator = la;
|
|
|
|
lp->lp_selected = LACP_SELECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* lacp_unselect: finish unselect/detach process.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_unselect(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
|
|
|
struct lacp_aggregator *la = lp->lp_aggregator;
|
|
|
|
|
|
|
|
KASSERT(!LACP_TIMER_ISARMED(lp, LACP_TIMER_WAIT_WHILE),
|
|
|
|
("timer_wait_while still active"));
|
|
|
|
|
|
|
|
if (la == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lp->lp_aggregator = NULL;
|
|
|
|
lacp_aggregator_delref(lsc, la);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* mux machine */
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_mux(struct lacp_port *lp)
|
|
|
|
{
|
2013-07-13 04:25:03 +00:00
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
|
|
|
struct lagg_softc *sc = lgp->lp_softc;
|
2007-04-10 00:27:25 +00:00
|
|
|
enum lacp_mux_state new_state;
|
|
|
|
boolean_t p_sync =
|
|
|
|
(lp->lp_partner.lip_state & LACP_STATE_SYNC) != 0;
|
|
|
|
boolean_t p_collecting =
|
|
|
|
(lp->lp_partner.lip_state & LACP_STATE_COLLECTING) != 0;
|
|
|
|
enum lacp_selected selected = lp->lp_selected;
|
|
|
|
struct lacp_aggregator *la;
|
|
|
|
|
2014-10-01 21:37:32 +00:00
|
|
|
if (V_lacp_debug > 1)
|
2013-07-13 04:25:03 +00:00
|
|
|
lacp_dprintf(lp, "%s: state= 0x%x, selected= 0x%x, "
|
|
|
|
"p_sync= 0x%x, p_collecting= 0x%x\n", __func__,
|
|
|
|
lp->lp_mux_state, selected, p_sync, p_collecting);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
re_eval:
|
|
|
|
la = lp->lp_aggregator;
|
|
|
|
KASSERT(lp->lp_mux_state == LACP_MUX_DETACHED || la != NULL,
|
|
|
|
("MUX not detached"));
|
|
|
|
new_state = lp->lp_mux_state;
|
|
|
|
switch (lp->lp_mux_state) {
|
|
|
|
case LACP_MUX_DETACHED:
|
|
|
|
if (selected != LACP_UNSELECTED) {
|
|
|
|
new_state = LACP_MUX_WAITING;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LACP_MUX_WAITING:
|
|
|
|
KASSERT(la->la_pending > 0 ||
|
|
|
|
!LACP_TIMER_ISARMED(lp, LACP_TIMER_WAIT_WHILE),
|
|
|
|
("timer_wait_while still active"));
|
|
|
|
if (selected == LACP_SELECTED && la->la_pending == 0) {
|
|
|
|
new_state = LACP_MUX_ATTACHED;
|
|
|
|
} else if (selected == LACP_UNSELECTED) {
|
|
|
|
new_state = LACP_MUX_DETACHED;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LACP_MUX_ATTACHED:
|
|
|
|
if (selected == LACP_SELECTED && p_sync) {
|
|
|
|
new_state = LACP_MUX_COLLECTING;
|
|
|
|
} else if (selected != LACP_SELECTED) {
|
|
|
|
new_state = LACP_MUX_DETACHED;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LACP_MUX_COLLECTING:
|
|
|
|
if (selected == LACP_SELECTED && p_sync && p_collecting) {
|
|
|
|
new_state = LACP_MUX_DISTRIBUTING;
|
|
|
|
} else if (selected != LACP_SELECTED || !p_sync) {
|
|
|
|
new_state = LACP_MUX_ATTACHED;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case LACP_MUX_DISTRIBUTING:
|
|
|
|
if (selected != LACP_SELECTED || !p_sync || !p_collecting) {
|
|
|
|
new_state = LACP_MUX_COLLECTING;
|
2013-07-25 19:10:23 +00:00
|
|
|
lacp_dprintf(lp, "Interface stopped DISTRIBUTING, possible flapping\n");
|
2013-07-13 04:25:03 +00:00
|
|
|
sc->sc_flapping++;
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("%s: unknown state", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lp->lp_mux_state == new_state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lacp_set_mux(lp, new_state);
|
|
|
|
goto re_eval;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_set_mux(struct lacp_port *lp, enum lacp_mux_state new_state)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la = lp->lp_aggregator;
|
|
|
|
|
|
|
|
if (lp->lp_mux_state == new_state) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (new_state) {
|
|
|
|
case LACP_MUX_DETACHED:
|
|
|
|
lp->lp_state &= ~LACP_STATE_SYNC;
|
|
|
|
lacp_disable_distributing(lp);
|
|
|
|
lacp_disable_collecting(lp);
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
/* cancel timer */
|
|
|
|
if (LACP_TIMER_ISARMED(lp, LACP_TIMER_WAIT_WHILE)) {
|
|
|
|
KASSERT(la->la_pending > 0,
|
|
|
|
("timer_wait_while not active"));
|
|
|
|
la->la_pending--;
|
|
|
|
}
|
|
|
|
LACP_TIMER_DISARM(lp, LACP_TIMER_WAIT_WHILE);
|
|
|
|
lacp_unselect(lp);
|
|
|
|
break;
|
|
|
|
case LACP_MUX_WAITING:
|
|
|
|
LACP_TIMER_ARM(lp, LACP_TIMER_WAIT_WHILE,
|
|
|
|
LACP_AGGREGATE_WAIT_TIME);
|
|
|
|
la->la_pending++;
|
|
|
|
break;
|
|
|
|
case LACP_MUX_ATTACHED:
|
|
|
|
lp->lp_state |= LACP_STATE_SYNC;
|
|
|
|
lacp_disable_collecting(lp);
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
break;
|
|
|
|
case LACP_MUX_COLLECTING:
|
|
|
|
lacp_enable_collecting(lp);
|
|
|
|
lacp_disable_distributing(lp);
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
break;
|
|
|
|
case LACP_MUX_DISTRIBUTING:
|
|
|
|
lacp_enable_distributing(lp);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
panic("%s: unknown state", __func__);
|
|
|
|
}
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "mux_state %d -> %d\n", lp->lp_mux_state, new_state));
|
|
|
|
|
|
|
|
lp->lp_mux_state = new_state;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_mux_timer(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
struct lacp_aggregator *la = lp->lp_aggregator;
|
|
|
|
char buf[LACP_LAGIDSTR_MAX+1];
|
|
|
|
|
|
|
|
KASSERT(la->la_pending > 0, ("no pending event"));
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "%s: aggregator %s, pending %d -> %d\n", __func__,
|
|
|
|
lacp_format_lagid(&la->la_actor, &la->la_partner,
|
|
|
|
buf, sizeof(buf)),
|
|
|
|
la->la_pending, la->la_pending - 1));
|
|
|
|
|
|
|
|
la->la_pending--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* periodic transmit machine */
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_ptx_update_timeout(struct lacp_port *lp, uint8_t oldpstate)
|
|
|
|
{
|
|
|
|
if (LACP_STATE_EQ(oldpstate, lp->lp_partner.lip_state,
|
|
|
|
LACP_STATE_TIMEOUT)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
LACP_DPRINTF((lp, "partner timeout changed\n"));
|
|
|
|
|
|
|
|
/*
|
|
|
|
* FAST_PERIODIC -> SLOW_PERIODIC
|
|
|
|
* or
|
|
|
|
* SLOW_PERIODIC (-> PERIODIC_TX) -> FAST_PERIODIC
|
|
|
|
*
|
|
|
|
* let lacp_sm_ptx_tx_schedule to update timeout.
|
|
|
|
*/
|
|
|
|
|
|
|
|
LACP_TIMER_DISARM(lp, LACP_TIMER_PERIODIC);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* if timeout has been shortened, assert NTT.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if ((lp->lp_partner.lip_state & LACP_STATE_TIMEOUT)) {
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_ptx_tx_schedule(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
int timeout;
|
|
|
|
|
|
|
|
if (!(lp->lp_state & LACP_STATE_ACTIVITY) &&
|
|
|
|
!(lp->lp_partner.lip_state & LACP_STATE_ACTIVITY)) {
|
|
|
|
/*
|
|
|
|
* NO_PERIODIC
|
|
|
|
*/
|
|
|
|
|
|
|
|
LACP_TIMER_DISARM(lp, LACP_TIMER_PERIODIC);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (LACP_TIMER_ISARMED(lp, LACP_TIMER_PERIODIC)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
timeout = (lp->lp_partner.lip_state & LACP_STATE_TIMEOUT) ?
|
|
|
|
LACP_FAST_PERIODIC_TIME : LACP_SLOW_PERIODIC_TIME;
|
|
|
|
|
|
|
|
LACP_TIMER_ARM(lp, LACP_TIMER_PERIODIC, timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_ptx_timer(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx(struct lacp_port *lp, const struct lacpdu *du)
|
|
|
|
{
|
|
|
|
int timeout;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check LACP_DISABLED first
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!(lp->lp_state & LACP_STATE_AGGREGATION)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* check loopback condition.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (!lacp_compare_systemid(&du->ldu_actor.lip_systemid,
|
|
|
|
&lp->lp_actor.lip_systemid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* EXPIRED, DEFAULTED, CURRENT -> CURRENT
|
|
|
|
*/
|
|
|
|
|
lacp: short timeout erroneously declares link-flapping
Panasas was seeing a higher-than-expected number of link-flap events.
After joint debugging with the switch vendor, we determined there were
problems on both sides; either of which might cause the occasional
event, but together caused lots of them.
On the switch side, an internal queuing issue was causing LACP PDUs --
which should be sent every second, in short-timeout mode -- to sometimes
be sent slightly later than they should have been. In some cases, two
successive PDUs were late, but we never saw three late PDUs in a row.
On the FreeBSD side, we saw a link-flap event every time there were two
late PDUs, while the spec says that it takes *three* seconds of downtime
to trigger that event. It turns out that if a PDU was received shortly
before the timer code was run, it would decrement less than a full
second after the PDU arrived. Then two delayed PDUs would cause two
additional decrements, causing it to reach zero less than three seconds
after the most-recent on-time PDU.
The solution is to note the time a PDU arrives, and only decrement if at
least a full second has elapsed since then.
Reported by: Greg Foster <gfoster@panasas.com>
Reviewed by: gallatin
Tested by: Greg Foster <gfoster@panasas.com>
MFC after: 3 days
Sponsored by: Panasas
Differential Revision: https://reviews.freebsd.org/D35070
2022-04-26 06:38:23 +00:00
|
|
|
microuptime(&lp->lp_last_lacpdu_rx);
|
2007-04-10 00:27:25 +00:00
|
|
|
lacp_sm_rx_update_selected(lp, du);
|
|
|
|
lacp_sm_rx_update_ntt(lp, du);
|
|
|
|
lacp_sm_rx_record_pdu(lp, du);
|
|
|
|
|
|
|
|
timeout = (lp->lp_state & LACP_STATE_TIMEOUT) ?
|
|
|
|
LACP_SHORT_TIMEOUT_TIME : LACP_LONG_TIMEOUT_TIME;
|
|
|
|
LACP_TIMER_ARM(lp, LACP_TIMER_CURRENT_WHILE, timeout);
|
|
|
|
|
|
|
|
lp->lp_state &= ~LACP_STATE_EXPIRED;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* kick transmit machine without waiting the next tick.
|
|
|
|
*/
|
|
|
|
|
|
|
|
lacp_sm_tx(lp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_set_expired(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
lp->lp_partner.lip_state &= ~LACP_STATE_SYNC;
|
|
|
|
lp->lp_partner.lip_state |= LACP_STATE_TIMEOUT;
|
|
|
|
LACP_TIMER_ARM(lp, LACP_TIMER_CURRENT_WHILE, LACP_SHORT_TIMEOUT_TIME);
|
|
|
|
lp->lp_state |= LACP_STATE_EXPIRED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_timer(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
if ((lp->lp_state & LACP_STATE_EXPIRED) == 0) {
|
|
|
|
/* CURRENT -> EXPIRED */
|
|
|
|
LACP_DPRINTF((lp, "%s: CURRENT -> EXPIRED\n", __func__));
|
|
|
|
lacp_sm_rx_set_expired(lp);
|
|
|
|
} else {
|
|
|
|
/* EXPIRED -> DEFAULTED */
|
|
|
|
LACP_DPRINTF((lp, "%s: EXPIRED -> DEFAULTED\n", __func__));
|
|
|
|
lacp_sm_rx_update_default_selected(lp);
|
|
|
|
lacp_sm_rx_record_default(lp);
|
|
|
|
lp->lp_state &= ~LACP_STATE_EXPIRED;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_record_pdu(struct lacp_port *lp, const struct lacpdu *du)
|
|
|
|
{
|
|
|
|
boolean_t active;
|
|
|
|
uint8_t oldpstate;
|
|
|
|
char buf[LACP_STATESTR_MAX+1];
|
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
oldpstate = lp->lp_partner.lip_state;
|
|
|
|
|
|
|
|
active = (du->ldu_actor.lip_state & LACP_STATE_ACTIVITY)
|
|
|
|
|| ((lp->lp_state & LACP_STATE_ACTIVITY) &&
|
|
|
|
(du->ldu_partner.lip_state & LACP_STATE_ACTIVITY));
|
|
|
|
|
|
|
|
lp->lp_partner = du->ldu_actor;
|
|
|
|
if (active &&
|
|
|
|
((LACP_STATE_EQ(lp->lp_state, du->ldu_partner.lip_state,
|
|
|
|
LACP_STATE_AGGREGATION) &&
|
|
|
|
!lacp_compare_peerinfo(&lp->lp_actor, &du->ldu_partner))
|
|
|
|
|| (du->ldu_partner.lip_state & LACP_STATE_AGGREGATION) == 0)) {
|
Do some minimal work to better conform to the 802.3ad (LACP) standard.
In particular, don't set the synchronized bit for the peer unless it truly
appears to be synchronized to us. Also, don't set our own synchronized bit
unless we have actually seen a remote system.
Prior to this change, we were seeing some strange behavior, such as:
1. We send an advertisement with the Activity, Aggregation, and Default
flags, followed by an advertisement with the Activity, Aggregation,
Synchronization, and Default flags. However, we hadn't seen an
advertisement from another peer and were still advertising the default
(NULL) peer. A closer examination of the in-kernel data structures (using
kgdb) showed that the system had added the default (NULL) peer as a valid
aggregator for the segment.
2. We were receiving an advertisement from a peer that included the
default (NULL) peer instead of including our system information. However,
we responded with an advertisement that included the Synchronization flag
for both our system and the peer. (Since the peer's advertisement did not
include our system information, we shouldn't add the synchronization bit
for the peer.)
This patch corrects those two items.
Reviewed by: smh
MFC after: 2 weeks
Sponsored by: Netflix
Differential Revision: https://reviews.freebsd.org/D9485
2017-02-26 00:19:02 +00:00
|
|
|
/*
|
|
|
|
* XXX Maintain legacy behavior of leaving the
|
|
|
|
* LACP_STATE_SYNC bit unchanged from the partner's
|
|
|
|
* advertisement if lsc_strict_mode is false.
|
|
|
|
* TODO: We should re-examine the concept of the "strict mode"
|
|
|
|
* to ensure it makes sense to maintain a non-strict mode.
|
|
|
|
*/
|
|
|
|
if (lp->lp_lsc->lsc_strict_mode)
|
|
|
|
lp->lp_partner.lip_state |= LACP_STATE_SYNC;
|
2007-04-10 00:27:25 +00:00
|
|
|
} else {
|
|
|
|
lp->lp_partner.lip_state &= ~LACP_STATE_SYNC;
|
|
|
|
}
|
|
|
|
|
|
|
|
lp->lp_state &= ~LACP_STATE_DEFAULTED;
|
|
|
|
|
|
|
|
if (oldpstate != lp->lp_partner.lip_state) {
|
|
|
|
LACP_DPRINTF((lp, "old pstate %s\n",
|
|
|
|
lacp_format_state(oldpstate, buf, sizeof(buf))));
|
|
|
|
LACP_DPRINTF((lp, "new pstate %s\n",
|
|
|
|
lacp_format_state(lp->lp_partner.lip_state, buf,
|
|
|
|
sizeof(buf))));
|
|
|
|
}
|
|
|
|
|
|
|
|
lacp_sm_ptx_update_timeout(lp, oldpstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_update_ntt(struct lacp_port *lp, const struct lacpdu *du)
|
|
|
|
{
|
2012-05-26 08:09:01 +00:00
|
|
|
|
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (lacp_compare_peerinfo(&lp->lp_actor, &du->ldu_partner) ||
|
|
|
|
!LACP_STATE_EQ(lp->lp_state, du->ldu_partner.lip_state,
|
|
|
|
LACP_STATE_ACTIVITY | LACP_STATE_SYNC | LACP_STATE_AGGREGATION)) {
|
|
|
|
LACP_DPRINTF((lp, "%s: assert ntt\n", __func__));
|
|
|
|
lacp_sm_assert_ntt(lp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_record_default(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
uint8_t oldpstate;
|
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
oldpstate = lp->lp_partner.lip_state;
|
2013-07-26 19:41:13 +00:00
|
|
|
if (lp->lp_lsc->lsc_strict_mode)
|
2013-07-13 04:25:03 +00:00
|
|
|
lp->lp_partner = lacp_partner_admin_strict;
|
|
|
|
else
|
2015-11-06 15:33:27 +00:00
|
|
|
lp->lp_partner = lacp_partner_admin_optimistic;
|
2007-04-10 00:27:25 +00:00
|
|
|
lp->lp_state |= LACP_STATE_DEFAULTED;
|
|
|
|
lacp_sm_ptx_update_timeout(lp, oldpstate);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_update_selected_from_peerinfo(struct lacp_port *lp,
|
|
|
|
const struct lacp_peerinfo *info)
|
|
|
|
{
|
2012-05-26 08:09:01 +00:00
|
|
|
|
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (lacp_compare_peerinfo(&lp->lp_partner, info) ||
|
|
|
|
!LACP_STATE_EQ(lp->lp_partner.lip_state, info->lip_state,
|
|
|
|
LACP_STATE_AGGREGATION)) {
|
|
|
|
lp->lp_selected = LACP_UNSELECTED;
|
|
|
|
/* mux machine will clean up lp->lp_aggregator */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_update_selected(struct lacp_port *lp, const struct lacpdu *du)
|
|
|
|
{
|
2012-05-26 08:09:01 +00:00
|
|
|
|
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
lacp_sm_rx_update_selected_from_peerinfo(lp, &du->ldu_actor);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_rx_update_default_selected(struct lacp_port *lp)
|
|
|
|
{
|
2012-05-26 08:09:01 +00:00
|
|
|
|
|
|
|
LACP_TRACE(lp);
|
2007-04-10 00:27:25 +00:00
|
|
|
|
2013-07-26 19:41:13 +00:00
|
|
|
if (lp->lp_lsc->lsc_strict_mode)
|
2013-07-13 04:25:03 +00:00
|
|
|
lacp_sm_rx_update_selected_from_peerinfo(lp,
|
|
|
|
&lacp_partner_admin_strict);
|
|
|
|
else
|
|
|
|
lacp_sm_rx_update_selected_from_peerinfo(lp,
|
|
|
|
&lacp_partner_admin_optimistic);
|
2007-04-10 00:27:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* transmit machine */
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_tx(struct lacp_port *lp)
|
|
|
|
{
|
2013-07-13 04:25:03 +00:00
|
|
|
int error = 0;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (!(lp->lp_state & LACP_STATE_AGGREGATION)
|
|
|
|
#if 1
|
|
|
|
|| (!(lp->lp_state & LACP_STATE_ACTIVITY)
|
|
|
|
&& !(lp->lp_partner.lip_state & LACP_STATE_ACTIVITY))
|
|
|
|
#endif
|
|
|
|
) {
|
|
|
|
lp->lp_flags &= ~LACP_PORT_NTT;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(lp->lp_flags & LACP_PORT_NTT)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Rate limit to 3 PDUs per LACP_FAST_PERIODIC_TIME */
|
|
|
|
if (ppsratecheck(&lp->lp_last_lacpdu, &lp->lp_lacpdu_sent,
|
|
|
|
(3 / LACP_FAST_PERIODIC_TIME)) == 0) {
|
|
|
|
LACP_DPRINTF((lp, "rate limited pdu\n"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-07-26 19:41:13 +00:00
|
|
|
if (((1 << lp->lp_ifp->if_dunit) & lp->lp_lsc->lsc_debug.lsc_tx_test) == 0) {
|
2013-07-13 04:25:03 +00:00
|
|
|
error = lacp_xmit_lacpdu(lp);
|
2013-07-26 19:41:13 +00:00
|
|
|
} else {
|
2013-07-13 04:25:03 +00:00
|
|
|
LACP_TPRINTF((lp, "Dropping TX PDU\n"));
|
2013-07-26 19:41:13 +00:00
|
|
|
}
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (error == 0) {
|
|
|
|
lp->lp_flags &= ~LACP_PORT_NTT;
|
|
|
|
} else {
|
|
|
|
LACP_DPRINTF((lp, "lacpdu transmit failure, error %d\n",
|
|
|
|
error));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_sm_assert_ntt(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
|
|
|
|
lp->lp_flags |= LACP_PORT_NTT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_run_timers(struct lacp_port *lp)
|
|
|
|
{
|
|
|
|
int i;
|
lacp: short timeout erroneously declares link-flapping
Panasas was seeing a higher-than-expected number of link-flap events.
After joint debugging with the switch vendor, we determined there were
problems on both sides; either of which might cause the occasional
event, but together caused lots of them.
On the switch side, an internal queuing issue was causing LACP PDUs --
which should be sent every second, in short-timeout mode -- to sometimes
be sent slightly later than they should have been. In some cases, two
successive PDUs were late, but we never saw three late PDUs in a row.
On the FreeBSD side, we saw a link-flap event every time there were two
late PDUs, while the spec says that it takes *three* seconds of downtime
to trigger that event. It turns out that if a PDU was received shortly
before the timer code was run, it would decrement less than a full
second after the PDU arrived. Then two delayed PDUs would cause two
additional decrements, causing it to reach zero less than three seconds
after the most-recent on-time PDU.
The solution is to note the time a PDU arrives, and only decrement if at
least a full second has elapsed since then.
Reported by: Greg Foster <gfoster@panasas.com>
Reviewed by: gallatin
Tested by: Greg Foster <gfoster@panasas.com>
MFC after: 3 days
Sponsored by: Panasas
Differential Revision: https://reviews.freebsd.org/D35070
2022-04-26 06:38:23 +00:00
|
|
|
struct timeval time_diff;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
for (i = 0; i < LACP_NTIMER; i++) {
|
|
|
|
KASSERT(lp->lp_timer[i] >= 0,
|
|
|
|
("invalid timer value %d", lp->lp_timer[i]));
|
|
|
|
if (lp->lp_timer[i] == 0) {
|
|
|
|
continue;
|
lacp: short timeout erroneously declares link-flapping
Panasas was seeing a higher-than-expected number of link-flap events.
After joint debugging with the switch vendor, we determined there were
problems on both sides; either of which might cause the occasional
event, but together caused lots of them.
On the switch side, an internal queuing issue was causing LACP PDUs --
which should be sent every second, in short-timeout mode -- to sometimes
be sent slightly later than they should have been. In some cases, two
successive PDUs were late, but we never saw three late PDUs in a row.
On the FreeBSD side, we saw a link-flap event every time there were two
late PDUs, while the spec says that it takes *three* seconds of downtime
to trigger that event. It turns out that if a PDU was received shortly
before the timer code was run, it would decrement less than a full
second after the PDU arrived. Then two delayed PDUs would cause two
additional decrements, causing it to reach zero less than three seconds
after the most-recent on-time PDU.
The solution is to note the time a PDU arrives, and only decrement if at
least a full second has elapsed since then.
Reported by: Greg Foster <gfoster@panasas.com>
Reviewed by: gallatin
Tested by: Greg Foster <gfoster@panasas.com>
MFC after: 3 days
Sponsored by: Panasas
Differential Revision: https://reviews.freebsd.org/D35070
2022-04-26 06:38:23 +00:00
|
|
|
} else {
|
|
|
|
if (i == LACP_TIMER_CURRENT_WHILE) {
|
|
|
|
microuptime(&time_diff);
|
|
|
|
timevalsub(&time_diff, &lp->lp_last_lacpdu_rx);
|
|
|
|
if (time_diff.tv_sec) {
|
|
|
|
/* At least one sec has elapsed since last LACP packet. */
|
|
|
|
--lp->lp_timer[i];
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
--lp->lp_timer[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((lp->lp_timer[i] <= 0) && (lacp_timer_funcs[i])) {
|
2007-04-10 00:27:25 +00:00
|
|
|
(*lacp_timer_funcs[i])(lp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
2008-03-16 19:25:30 +00:00
|
|
|
lacp_marker_input(struct lacp_port *lp, struct mbuf *m)
|
2007-04-10 00:27:25 +00:00
|
|
|
{
|
2007-05-19 07:47:04 +00:00
|
|
|
struct lacp_softc *lsc = lp->lp_lsc;
|
2008-03-16 19:25:30 +00:00
|
|
|
struct lagg_port *lgp = lp->lp_lagg;
|
|
|
|
struct lacp_port *lp2;
|
2007-04-10 00:27:25 +00:00
|
|
|
struct markerdu *mdu;
|
|
|
|
int error = 0;
|
2007-05-19 07:47:04 +00:00
|
|
|
int pending = 0;
|
2007-04-10 00:27:25 +00:00
|
|
|
|
|
|
|
if (m->m_pkthdr.len != sizeof(*mdu)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((m->m_flags & M_MCAST) == 0) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m->m_len < sizeof(*mdu)) {
|
|
|
|
m = m_pullup(m, sizeof(*mdu));
|
|
|
|
if (m == NULL) {
|
|
|
|
return (ENOMEM);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mdu = mtod(m, struct markerdu *);
|
|
|
|
|
|
|
|
if (memcmp(&mdu->mdu_eh.ether_dhost,
|
|
|
|
ðermulticastaddr_slowprotocols, ETHER_ADDR_LEN)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mdu->mdu_sph.sph_version != 1) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (mdu->mdu_tlv.tlv_type) {
|
|
|
|
case MARKER_TYPE_INFO:
|
|
|
|
if (tlv_check(mdu, sizeof(*mdu), &mdu->mdu_tlv,
|
|
|
|
marker_info_tlv_template, TRUE)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
mdu->mdu_tlv.tlv_type = MARKER_TYPE_RESPONSE;
|
|
|
|
memcpy(&mdu->mdu_eh.ether_dhost,
|
|
|
|
ðermulticastaddr_slowprotocols, ETHER_ADDR_LEN);
|
|
|
|
memcpy(&mdu->mdu_eh.ether_shost,
|
2007-04-17 00:35:11 +00:00
|
|
|
lgp->lp_lladdr, ETHER_ADDR_LEN);
|
|
|
|
error = lagg_enqueue(lp->lp_ifp, m);
|
2007-04-10 00:27:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case MARKER_TYPE_RESPONSE:
|
|
|
|
if (tlv_check(mdu, sizeof(*mdu), &mdu->mdu_tlv,
|
|
|
|
marker_response_tlv_template, TRUE)) {
|
|
|
|
goto bad;
|
|
|
|
}
|
2007-05-19 07:47:04 +00:00
|
|
|
LACP_DPRINTF((lp, "marker response, port=%u, sys=%6D, id=%u\n",
|
|
|
|
ntohs(mdu->mdu_info.mi_rq_port), mdu->mdu_info.mi_rq_system,
|
|
|
|
":", ntohl(mdu->mdu_info.mi_rq_xid)));
|
|
|
|
|
|
|
|
/* Verify that it is the last marker we sent out */
|
|
|
|
if (memcmp(&mdu->mdu_info, &lp->lp_marker,
|
|
|
|
sizeof(struct lacp_markerinfo)))
|
|
|
|
goto bad;
|
|
|
|
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_LOCK(lsc);
|
2007-05-19 07:47:04 +00:00
|
|
|
lp->lp_flags &= ~LACP_PORT_MARK;
|
|
|
|
|
|
|
|
if (lsc->lsc_suppress_distributing) {
|
|
|
|
/* Check if any ports are waiting for a response */
|
|
|
|
LIST_FOREACH(lp2, &lsc->lsc_ports, lp_next) {
|
|
|
|
if (lp2->lp_flags & LACP_PORT_MARK) {
|
|
|
|
pending = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pending == 0) {
|
|
|
|
/* All interface queues are clear */
|
|
|
|
LACP_DPRINTF((NULL, "queue flush complete\n"));
|
|
|
|
lsc->lsc_suppress_distributing = FALSE;
|
|
|
|
}
|
|
|
|
}
|
2008-03-16 19:25:30 +00:00
|
|
|
LACP_UNLOCK(lsc);
|
2007-05-19 07:47:04 +00:00
|
|
|
m_freem(m);
|
|
|
|
break;
|
|
|
|
|
2007-04-10 00:27:25 +00:00
|
|
|
default:
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (error);
|
|
|
|
|
|
|
|
bad:
|
2007-05-19 07:47:04 +00:00
|
|
|
LACP_DPRINTF((lp, "bad marker frame\n"));
|
2007-04-10 00:27:25 +00:00
|
|
|
m_freem(m);
|
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
tlv_check(const void *p, size_t size, const struct tlvhdr *tlv,
|
|
|
|
const struct tlv_template *tmpl, boolean_t check_type)
|
|
|
|
{
|
|
|
|
while (/* CONSTCOND */ 1) {
|
|
|
|
if ((const char *)tlv - (const char *)p + sizeof(*tlv) > size) {
|
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
if ((check_type && tlv->tlv_type != tmpl->tmpl_type) ||
|
|
|
|
tlv->tlv_length != tmpl->tmpl_length) {
|
|
|
|
return (EINVAL);
|
|
|
|
}
|
|
|
|
if (tmpl->tmpl_type == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
tlv = (const struct tlvhdr *)
|
|
|
|
((const char *)tlv + tlv->tlv_length);
|
|
|
|
tmpl++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (0);
|
|
|
|
}
|
|
|
|
|
2012-05-26 08:09:01 +00:00
|
|
|
/* Debugging */
|
2007-04-10 00:27:25 +00:00
|
|
|
const char *
|
|
|
|
lacp_format_mac(const uint8_t *mac, char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
snprintf(buf, buflen, "%02X-%02X-%02X-%02X-%02X-%02X",
|
|
|
|
(int)mac[0],
|
|
|
|
(int)mac[1],
|
|
|
|
(int)mac[2],
|
|
|
|
(int)mac[3],
|
|
|
|
(int)mac[4],
|
|
|
|
(int)mac[5]);
|
|
|
|
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_systemid(const struct lacp_systemid *sysid,
|
|
|
|
char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
char macbuf[LACP_MACSTR_MAX+1];
|
|
|
|
|
|
|
|
snprintf(buf, buflen, "%04X,%s",
|
|
|
|
ntohs(sysid->lsi_prio),
|
|
|
|
lacp_format_mac(sysid->lsi_mac, macbuf, sizeof(macbuf)));
|
|
|
|
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_portid(const struct lacp_portid *portid, char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
snprintf(buf, buflen, "%04X,%04X",
|
|
|
|
ntohs(portid->lpi_prio),
|
|
|
|
ntohs(portid->lpi_portno));
|
|
|
|
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_partner(const struct lacp_peerinfo *peer, char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
char sysid[LACP_SYSTEMIDSTR_MAX+1];
|
|
|
|
char portid[LACP_PORTIDSTR_MAX+1];
|
|
|
|
|
|
|
|
snprintf(buf, buflen, "(%s,%04X,%s)",
|
|
|
|
lacp_format_systemid(&peer->lip_systemid, sysid, sizeof(sysid)),
|
|
|
|
ntohs(peer->lip_key),
|
|
|
|
lacp_format_portid(&peer->lip_portid, portid, sizeof(portid)));
|
|
|
|
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_lagid(const struct lacp_peerinfo *a,
|
|
|
|
const struct lacp_peerinfo *b, char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
char astr[LACP_PARTNERSTR_MAX+1];
|
|
|
|
char bstr[LACP_PARTNERSTR_MAX+1];
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
/*
|
|
|
|
* there's a convention to display small numbered peer
|
|
|
|
* in the left.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (lacp_compare_peerinfo(a, b) > 0) {
|
|
|
|
const struct lacp_peerinfo *t;
|
|
|
|
|
|
|
|
t = a;
|
|
|
|
a = b;
|
|
|
|
b = t;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
snprintf(buf, buflen, "[%s,%s]",
|
|
|
|
lacp_format_partner(a, astr, sizeof(astr)),
|
|
|
|
lacp_format_partner(b, bstr, sizeof(bstr)));
|
|
|
|
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_lagid_aggregator(const struct lacp_aggregator *la,
|
|
|
|
char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
if (la == NULL) {
|
|
|
|
return ("(none)");
|
|
|
|
}
|
|
|
|
|
|
|
|
return (lacp_format_lagid(&la->la_actor, &la->la_partner, buf, buflen));
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *
|
|
|
|
lacp_format_state(uint8_t state, char *buf, size_t buflen)
|
|
|
|
{
|
|
|
|
snprintf(buf, buflen, "%b", state, LACP_STATE_BITS);
|
|
|
|
return (buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_dump_lacpdu(const struct lacpdu *du)
|
|
|
|
{
|
|
|
|
char buf[LACP_PARTNERSTR_MAX+1];
|
|
|
|
char buf2[LACP_STATESTR_MAX+1];
|
|
|
|
|
|
|
|
printf("actor=%s\n",
|
|
|
|
lacp_format_partner(&du->ldu_actor, buf, sizeof(buf)));
|
|
|
|
printf("actor.state=%s\n",
|
|
|
|
lacp_format_state(du->ldu_actor.lip_state, buf2, sizeof(buf2)));
|
|
|
|
printf("partner=%s\n",
|
|
|
|
lacp_format_partner(&du->ldu_partner, buf, sizeof(buf)));
|
|
|
|
printf("partner.state=%s\n",
|
|
|
|
lacp_format_state(du->ldu_partner.lip_state, buf2, sizeof(buf2)));
|
|
|
|
|
|
|
|
printf("maxdelay=%d\n", ntohs(du->ldu_collector.lci_maxdelay));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
lacp_dprintf(const struct lacp_port *lp, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list va;
|
|
|
|
|
|
|
|
if (lp) {
|
|
|
|
printf("%s: ", lp->lp_ifp->if_xname);
|
|
|
|
}
|
|
|
|
|
|
|
|
va_start(va, fmt);
|
|
|
|
vprintf(fmt, va);
|
|
|
|
va_end(va);
|
|
|
|
}
|