diff --git a/distrib/sets/lists/base/shl.mi b/distrib/sets/lists/base/shl.mi index 60d12cc21a7..00c1d021e26 100644 --- a/distrib/sets/lists/base/shl.mi +++ b/distrib/sets/lists/base/shl.mi @@ -780,6 +780,9 @@ ./usr/lib/librumpnet_l2tp.so base-rump-shlib rump ./usr/lib/librumpnet_l2tp.so.0 base-rump-shlib rump ./usr/lib/librumpnet_l2tp.so.0.0 base-rump-shlib rump +./usr/lib/librumpnet_lagg.so base-rump-shlib rump +./usr/lib/librumpnet_lagg.so.0 base-rump-shlib rump +./usr/lib/librumpnet_lagg.so.0.0 base-rump-shlib rump ./usr/lib/librumpnet_local.so base-rump-shlib rump ./usr/lib/librumpnet_local.so.0 base-rump-shlib rump ./usr/lib/librumpnet_local.so.0.0 base-rump-shlib rump diff --git a/distrib/sets/lists/comp/mi b/distrib/sets/lists/comp/mi index 880092afe3f..d00da52bf8d 100644 --- a/distrib/sets/lists/comp/mi +++ b/distrib/sets/lists/comp/mi @@ -2409,6 +2409,7 @@ ./usr/include/net/if_ieee80211.h comp-obsolete obsolete ./usr/include/net/if_ipsec.h comp-c-include ./usr/include/net/if_l2tp.h comp-c-include +./usr/include/net/if_lagg.h comp-c-include ./usr/include/net/if_llc.h comp-c-include ./usr/include/net/if_media.h comp-c-include ./usr/include/net/if_mpls.h comp-c-include @@ -3864,6 +3865,8 @@ ./usr/lib/librumpnet_ipsec_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_l2tp.a comp-c-lib rump ./usr/lib/librumpnet_l2tp_p.a comp-c-proflib rump,profile +./usr/lib/librumpnet_lagg.a comp-c-lib rump +./usr/lib/librumpnet_lagg_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_local.a comp-c-lib rump ./usr/lib/librumpnet_local_p.a comp-c-proflib rump,profile ./usr/lib/librumpnet_net.a comp-c-lib rump diff --git a/distrib/sets/lists/comp/shl.mi b/distrib/sets/lists/comp/shl.mi index f89d38e33d0..7014b68b48e 100644 --- a/distrib/sets/lists/comp/shl.mi +++ b/distrib/sets/lists/comp/shl.mi @@ -230,6 +230,7 @@ ./usr/lib/librumpnet_gif_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_ipsec_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_l2tp_pic.a comp-c-piclib picinstall,rump +./usr/lib/librumpnet_lagg_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_local_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_net80211_pic.a comp-c-piclib picinstall,rump ./usr/lib/librumpnet_net_pic.a comp-c-piclib picinstall,rump diff --git a/distrib/sets/lists/debug/mi b/distrib/sets/lists/debug/mi index 11fc60bd429..90c262025ad 100644 --- a/distrib/sets/lists/debug/mi +++ b/distrib/sets/lists/debug/mi @@ -221,6 +221,7 @@ ./usr/lib/librumpnet_gif_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_ipsec_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_l2tp_g.a comp-c-debuglib debuglib,rump +./usr/lib/librumpnet_lagg_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_local_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_net80211_g.a comp-c-debuglib debuglib,rump ./usr/lib/librumpnet_net_g.a comp-c-debuglib debuglib,rump diff --git a/distrib/sets/lists/debug/module.mi b/distrib/sets/lists/debug/module.mi index c684a1b3950..6f9376367f3 100644 --- a/distrib/sets/lists/debug/module.mi +++ b/distrib/sets/lists/debug/module.mi @@ -186,6 +186,8 @@ ./usr/libdata/debug/@MODULEDIR@/if_kue/if_kue.kmod.debug modules-base-kernel kmod,debug ./usr/libdata/debug/@MODULEDIR@/if_l2tp modules-base-kernel kmod,debug ./usr/libdata/debug/@MODULEDIR@/if_l2tp/if_l2tp.kmod.debug modules-base-kernel kmod,debug +./usr/libdata/debug/@MODULEDIR@/if_lagg modules-base-kernel kmod,debug +./usr/libdata/debug/@MODULEDIR@/if_lagg/if_lagg.kmod.debug modules-base-kernel kmod,debug ./usr/libdata/debug/@MODULEDIR@/if_loop modules-base-kernel kmod,debug ./usr/libdata/debug/@MODULEDIR@/if_loop/if_loop.kmod.debug modules-base-kernel kmod,debug ./usr/libdata/debug/@MODULEDIR@/if_mpls modules-base-kernel kmod,debug diff --git a/distrib/sets/lists/debug/shl.mi b/distrib/sets/lists/debug/shl.mi index d58ecb8de02..864bf54a5b8 100644 --- a/distrib/sets/lists/debug/shl.mi +++ b/distrib/sets/lists/debug/shl.mi @@ -277,6 +277,7 @@ ./usr/libdata/debug/usr/lib/librumpnet_gif.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_ipsec.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_l2tp.so.0.0.debug comp-rump-debug debug,rump +./usr/libdata/debug/usr/lib/librumpnet_lagg.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_local.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_net.so.0.0.debug comp-rump-debug debug,rump ./usr/libdata/debug/usr/lib/librumpnet_net80211.so.0.0.debug comp-rump-debug debug,rump diff --git a/distrib/sets/lists/man/mi b/distrib/sets/lists/man/mi index da0f80d8734..a1986b635d3 100644 --- a/distrib/sets/lists/man/mi +++ b/distrib/sets/lists/man/mi @@ -1411,6 +1411,7 @@ ./usr/share/man/cat4/kttcp.0 man-sys-catman .cat ./usr/share/man/cat4/kue.0 man-sys-catman .cat ./usr/share/man/cat4/l2tp.0 man-sys-catman .cat +./usr/share/man/cat4/lagg.0 man-sys-catman .cat ./usr/share/man/cat4/lc.0 man-sys-catman .cat ./usr/share/man/cat4/ld.0 man-sys-catman .cat ./usr/share/man/cat4/le.0 man-sys-catman .cat @@ -4606,6 +4607,7 @@ ./usr/share/man/html4/kttcp.html man-sys-htmlman html ./usr/share/man/html4/kue.html man-sys-htmlman html ./usr/share/man/html4/l2tp.html man-sys-htmlman html +./usr/share/man/html4/lagg.html man-sys-htmlman html ./usr/share/man/html4/lc.html man-sys-htmlman html ./usr/share/man/html4/ld.html man-sys-htmlman html ./usr/share/man/html4/le.html man-sys-htmlman html @@ -7642,6 +7644,7 @@ ./usr/share/man/man4/kttcp.4 man-sys-man .man ./usr/share/man/man4/kue.4 man-sys-man .man ./usr/share/man/man4/l2tp.4 man-sys-man .man +./usr/share/man/man4/lagg.4 man-sys-man .man ./usr/share/man/man4/lc.4 man-sys-man .man ./usr/share/man/man4/ld.4 man-sys-man .man ./usr/share/man/man4/le.4 man-sys-man .man diff --git a/distrib/sets/lists/modules/mi b/distrib/sets/lists/modules/mi index e5111baa97d..25a34463755 100644 --- a/distrib/sets/lists/modules/mi +++ b/distrib/sets/lists/modules/mi @@ -209,6 +209,8 @@ ./@MODULEDIR@/if_kue/if_kue.kmod modules-base-kernel kmod ./@MODULEDIR@/if_l2tp modules-base-kernel kmod ./@MODULEDIR@/if_l2tp/if_l2tp.kmod modules-base-kernel kmod +./@MODULEDIR@/if_lagg modules-base-kernel kmod +./@MODULEDIR@/if_lagg/if_lagg.kmod modules-base-kernel kmod ./@MODULEDIR@/if_loop modules-base-kernel kmod ./@MODULEDIR@/if_loop/if_loop.kmod modules-base-kernel kmod ./@MODULEDIR@/if_mpls modules-base-kernel kmod diff --git a/distrib/sets/lists/tests/mi b/distrib/sets/lists/tests/mi index f2f06a64dd0..d5152f23f5c 100644 --- a/distrib/sets/lists/tests/mi +++ b/distrib/sets/lists/tests/mi @@ -4106,6 +4106,10 @@ ./usr/tests/net/if_l2tp/Atffile tests-net-tests atf,rump ./usr/tests/net/if_l2tp/Kyuafile tests-net-tests atf,rump,kyua ./usr/tests/net/if_l2tp/t_l2tp tests-net-tests atf,rump +./usr/tests/net/if_lagg tests-net-tests compattestfile,atf +./usr/tests/net/if_lagg/Atffile tests-net-tests atf,rump +./usr/tests/net/if_lagg/Kyuafile tests-net-tests atf,rump,kyua +./usr/tests/net/if_lagg/t_lagg tests-net-tests atf,rump ./usr/tests/net/if_loop tests-net-tests compattestfile,atf ./usr/tests/net/if_loop/Atffile tests-net-tests atf,rump ./usr/tests/net/if_loop/Kyuafile tests-net-tests atf,rump,kyua diff --git a/etc/mtree/NetBSD.dist.tests b/etc/mtree/NetBSD.dist.tests index 55ff148dfc7..003a961884f 100644 --- a/etc/mtree/NetBSD.dist.tests +++ b/etc/mtree/NetBSD.dist.tests @@ -358,6 +358,7 @@ ./usr/tests/net/if_gif ./usr/tests/net/if_ipsec ./usr/tests/net/if_l2tp +./usr/tests/net/if_lagg ./usr/tests/net/if_loop ./usr/tests/net/if_pppoe ./usr/tests/net/if_tap diff --git a/sbin/ifconfig/Makefile.common b/sbin/ifconfig/Makefile.common index 0a72e2a3cca..042fc7d54f8 100644 --- a/sbin/ifconfig/Makefile.common +++ b/sbin/ifconfig/Makefile.common @@ -17,5 +17,5 @@ SRCS+= af_inet.c af_inetany.c env.c ether.c \ SRCS+= ieee80211.c .endif .ifndef SMALLPROG -SRCS+= agr.c l2tp.c +SRCS+= agr.c l2tp.c lagg.c .endif diff --git a/sbin/ifconfig/lagg.c b/sbin/ifconfig/lagg.c new file mode 100644 index 00000000000..a19b46a2107 --- /dev/null +++ b/sbin/ifconfig/lagg.c @@ -0,0 +1,676 @@ +/* $NetBSD: $ */ + +/* + * Copyright (c) 2021 Internet Initiative Japan Inc. + * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 +#if !defined(lint) +__RCSID("$NetBSD: $"); +#endif /* !defined(lint) */ + +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "env.h" +#include "extern.h" +#include "util.h" + +static status_func_t status; +static usage_func_t usage; +static cmdloop_branch_t branch; + +static void lagg_constructor(void) __attribute__((constructor)); +static void lagg_status(prop_dictionary_t, prop_dictionary_t); +static void lagg_usage(prop_dictionary_t); + +static int setlaggproto(prop_dictionary_t, prop_dictionary_t); +static int setlaggport(prop_dictionary_t, prop_dictionary_t); +static int setlagglacp(prop_dictionary_t, prop_dictionary_t); +static int setlagglacpmaxports(prop_dictionary_t, prop_dictionary_t); +static int setlaggfail(prop_dictionary_t, prop_dictionary_t); +static void lagg_status_proto(lagg_proto, struct laggreqproto *); +static void lagg_status_port(lagg_proto, struct laggreqport *); + +#ifdef LAGG_DEBUG +static const bool lagg_debug = true; +#else +static const bool lagg_debug = false; +#endif + +#define LAGG_RETRY_MAX 10 + +static const char *laggprotostr[LAGG_PROTO_MAX] = { + [LAGG_PROTO_NONE] = "none", + [LAGG_PROTO_LACP] = "lacp", + [LAGG_PROTO_FAILOVER] = "failover", + [LAGG_PROTO_LOADBALANCE] = "loadbalance", +}; + +enum laggportcmd { + LAGGPORT_NOCMD = 1, + LAGGPORT_ADD, + LAGGPORT_DEL, +}; + +enum lagglacpcmd { + LAGGLACP_ADD = 1, + LAGGLACP_DEL, +}; + +enum lagglacpopt { + LAGGLACPOPT_DUMPDU = 1, + LAGGLACPOPT_STOPDU, + LAGGLACPOPT_OPTIMISTIC, + LAGGLACPOPT_MULTILS, +}; + +enum laggfailopt { + LAGGFAILOPT_RXALL = 1, +}; + +struct pbranch laggport_root; +struct pbranch lagglacp_root; +struct pbranch laggfail_root; + +struct pstr laggproto = PSTR_INITIALIZER(&laggproto, "lagg-protocol", + setlaggproto, "laggproto", &command_root.pb_parser); +struct piface laggaddport = PIFACE_INITIALIZER(&laggaddport, + "lagg-port-interface", setlaggport, "laggport", + &laggport_root.pb_parser); +struct piface laggdelport = PIFACE_INITIALIZER(&laggdelport, + "lagg-port-interface", setlaggport, "laggport", + &command_root.pb_parser); +struct pinteger laggportoptpri = PINTEGER_INITIALIZER1(&laggportoptpri, + "lagg-port-priority", 0, UINT16_MAX, 10, + setlaggport, "laggportpri", &laggport_root.pb_parser); +struct pinteger lagglacpmaxports = PINTEGER_INITIALIZER1(&lagglacpmaxports, + "lagg-lacp-maxports", 1, UINT32_MAX, 10, + setlagglacpmaxports, "lacpmaxports", + &lagglacp_root.pb_parser); +struct pinteger laggportpri_num = PINTEGER_INITIALIZER1(&laggportpri_num, + "lagg-port-priority", 0, UINT16_MAX, 10, + setlaggport, "laggportpri", &command_root.pb_parser); +struct piface laggportpri_if = PIFACE_INITIALIZER(&laggportpri_if, + "lagg-port-interface", NULL, "laggport", + &laggportpri_num.pi_parser); + +static const struct kwinst lagglacpkw[] = { + {.k_word = "dumpdu", .k_key = "lacpdumpdu", + .k_type = KW_T_INT, .k_int = LAGGLACPOPT_DUMPDU} + , {.k_word = "-dumpdu", .k_key = "lacpdumpdu", + .k_type = KW_T_INT, .k_int = -LAGGLACPOPT_DUMPDU} + , {.k_word = "stopdu", .k_key = "lacpstopdu", + .k_type = KW_T_INT, .k_int = LAGGLACPOPT_STOPDU} + , {.k_word = "-stopdu", .k_key = "lacpstopdu", + .k_type = KW_T_INT, .k_int = -LAGGLACPOPT_STOPDU} + , {.k_word = "optimistic", .k_key = "lacpoptimistic", + .k_type = KW_T_INT, .k_int = LAGGLACPOPT_OPTIMISTIC} + , {.k_word = "-optimistic", .k_key = "lacpoptimistic", + .k_type = KW_T_INT, .k_int = -LAGGLACPOPT_OPTIMISTIC} + , {.k_word = "maxports", .k_nextparser = &lagglacpmaxports.pi_parser} + , {.k_word = "-maxports", .k_key = "lacpmaxports", + .k_type = KW_T_INT, .k_int = 0} + , {.k_word = "multi-linkspeed", .k_key = "lacpmultils", + .k_type = KW_T_INT, .k_int = LAGGLACPOPT_MULTILS} + , {.k_word = "-multi-linkspeed", .k_key = "lacpmultils", + .k_type = KW_T_INT, .k_int = -LAGGLACPOPT_MULTILS} +}; +struct pkw lagglacp = PKW_INITIALIZER(&lagglacp, "lagg-lacp-option", + setlagglacp, NULL, lagglacpkw, __arraycount(lagglacpkw), + &lagglacp_root.pb_parser); + +static const struct kwinst laggfailkw[] = { + {.k_word = "rx-all", .k_key = "failrxall", + .k_type = KW_T_INT, .k_int = LAGGFAILOPT_RXALL} + , {.k_word = "-rx-all", .k_key = "failrxall", + .k_type = KW_T_INT, .k_int = -LAGGFAILOPT_RXALL} +}; +struct pkw laggfail = PKW_INITIALIZER(&laggfail, "lagg-failover-option", + setlaggfail, NULL, laggfailkw, __arraycount(laggfailkw), + &laggfail_root.pb_parser); + +static const struct kwinst laggkw[] = { + {.k_word = "laggproto", .k_nextparser = &laggproto.ps_parser} + , {.k_word = "-laggproto", .k_key = "laggproto", .k_type = KW_T_STR, + .k_str = "none", .k_exec = setlaggproto} + , {.k_word = "laggport", .k_key = "laggportcmd", .k_type = KW_T_INT, + .k_int = LAGGPORT_ADD, .k_nextparser = &laggaddport.pif_parser} + , {.k_word = "-laggport", .k_key = "laggportcmd", .k_type = KW_T_INT, + .k_int = LAGGPORT_DEL, .k_nextparser = &laggdelport.pif_parser} + , {.k_word = "lagglacp", .k_nextparser = &lagglacp.pk_parser} + , {.k_word = "laggportpri", .k_nextparser = &laggportpri_if.pif_parser} + , {.k_word = "laggfailover", .k_nextparser = &laggfail.pk_parser} +}; +struct pkw lagg = PKW_INITIALIZER(&lagg, "lagg", NULL, NULL, + laggkw, __arraycount(laggkw), NULL); + +static const struct kwinst laggportkw[] = { + {.k_word = "pri", .k_nextparser = &laggportoptpri.pi_parser} +}; +struct pkw laggportopt = PKW_INITIALIZER(&laggportopt, "lagg-port-option", + NULL, NULL, laggportkw, __arraycount(laggportkw), NULL); + +struct branch laggport_brs[] = { + {.b_nextparser = &laggportopt.pk_parser} + , {.b_nextparser = &command_root.pb_parser} +}; +struct branch lagglacp_brs[] = { + {.b_nextparser = &lagglacp.pk_parser} + , {.b_nextparser = &command_root.pb_parser} +}; +struct branch laggfail_brs[] = { + {.b_nextparser = &laggfail.pk_parser} + , {.b_nextparser = &command_root.pb_parser} +}; + +static void +lagg_constructor(void) +{ + struct pbranch _laggport_root = PBRANCH_INITIALIZER(&laggport_root, + "laggport-root", laggport_brs, __arraycount(laggport_brs), true); + struct pbranch _lagglacp_root = PBRANCH_INITIALIZER(&lagglacp_root, + "lagglacp-root", lagglacp_brs, __arraycount(lagglacp_brs), true); + struct pbranch _laggfail_root = PBRANCH_INITIALIZER(&laggfail_root, + "laggfail-root", laggfail_brs, __arraycount(laggfail_brs), true); + + laggport_root = _laggport_root; + lagglacp_root = _lagglacp_root; + laggfail_root = _laggfail_root; + + cmdloop_branch_init(&branch, &lagg.pk_parser); + status_func_init(&status, lagg_status); + usage_func_init(&usage, lagg_usage); + + register_cmdloop_branch(&branch); + register_status(&status); + register_usage(&usage); +} + +static int +is_laggif(prop_dictionary_t env) +{ + const char *ifname; + size_t i, len; + + if ((ifname = getifname(env)) == NULL) + return 0; + + if (strncmp(ifname, "lagg", 4) != 0) + return 0; + + len = strlen(ifname); + for (i = 4; i < len; i++) { + if (!isdigit((unsigned char)ifname[i])) + return 0; + } + + return 1; +} + +static struct lagg_req * +getlagg(prop_dictionary_t env) +{ + struct lagg_req *req = NULL, *p; + size_t nports, bufsiz; + int i; + + if (!is_laggif(env)) { + if (lagg_debug) + warnx("valid only with lagg(4) interfaces"); + goto done; + } + + for (i = 0, nports = 0; i < LAGG_RETRY_MAX; i++) { + bufsiz = sizeof(*req); + bufsiz += sizeof(req->lrq_reqports[0]) * nports; + p = realloc(req, bufsiz); + if (p == NULL) + break; + + req = p; + memset(req, 0, bufsiz); + req->lrq_nports = nports; + if (indirect_ioctl(env, SIOCGLAGG, req) == 0) + goto done; + + if (errno != ENOMEM) + break; + nports = req->lrq_nports + 3; /* 3: additional space */ + } + + if (req != NULL) { + free(req); + req = NULL; + } + +done: + return req; +} + +static void +freelagg(struct lagg_req *req) +{ + + free(req); +} + +static void +lagg_status(prop_dictionary_t env, prop_dictionary_t oenv) +{ + struct lagg_req *req; + struct laggreqport *port; + const char *proto; + char str[256]; + size_t i; + + req = getlagg(env); + if (req == NULL) + return; + + if (req->lrq_proto >= LAGG_PROTO_MAX || + (proto = laggprotostr[req->lrq_proto]) == NULL) { + proto = "unknown"; + } + + printf("\tlaggproto %s", proto); + if (vflag) + lagg_status_proto(req->lrq_proto, &req->lrq_reqproto); + putchar('\n'); + + if (req->lrq_nports > 0) { + printf("\tlaggport:\n"); + for (i = 0; i < req->lrq_nports; i++) { + port = &req->lrq_reqports[i]; + snprintb(str, sizeof(str), + LAGG_PORT_BITS, port->rp_flags); + + printf("\t\t%.*s pri=%u flags=%s", + IFNAMSIZ, port->rp_portname, + (unsigned int)port->rp_prio, + str); + if (vflag) + lagg_status_port(req->lrq_proto, port); + putchar('\n'); + } + } + + freelagg(req); +} + +static int +setlaggproto(prop_dictionary_t env, prop_dictionary_t oenv) +{ + prop_object_t obj; + struct lagg_req req; + const char *proto; + size_t i, proto_len; + + memset(&req, 0, sizeof(req)); + + obj = prop_dictionary_get(env, "laggproto"); + if (obj == NULL) { + errno = ENOENT; + return -1; + } + + switch (prop_object_type(obj)) { + case PROP_TYPE_DATA: + proto = prop_data_value(obj); + proto_len = prop_data_size(obj); + break; + case PROP_TYPE_STRING: + proto = prop_string_value(obj); + proto_len = prop_string_size(obj); + break; + default: + errno = EFAULT; + return -1; + } + + for (i = 0; i < LAGG_PROTO_MAX; i++) { + if (strncmp(proto, laggprotostr[i], proto_len) == 0) + break; + } + + if (i >= LAGG_PROTO_MAX) { + errno = EPROTONOSUPPORT; + return -1; + } + + req.lrq_ioctl = LAGGIOC_SETPROTO; + req.lrq_proto = i; + + if (indirect_ioctl(env, SIOCSLAGG, &req) == -1) + return -1; + + return 0; +} + +static int +setlaggport(prop_dictionary_t env, prop_dictionary_t oenv __unused) +{ + struct lagg_req req; + struct laggreqport *rp; + const char *ifname; + enum lagg_ioctl ioc; + int64_t lpcmd, pri; + + if (!prop_dictionary_get_cstring_nocopy(env, "laggport", &ifname)) { + if (lagg_debug) + warnx("%s.%d", __func__, __LINE__); + errno = ENOENT; + return -1; + } + + memset(&req, 0, sizeof(req)); + req.lrq_nports = 1; + rp = &req.lrq_reqports[0]; + strlcpy(rp->rp_portname, ifname, sizeof(rp->rp_portname)); + ioc = LAGGIOC_NOCMD; + + if (prop_dictionary_get_int64(env, "laggportcmd", &lpcmd)) { + if (lpcmd == LAGGPORT_ADD) { + ioc = LAGGIOC_ADDPORT; + } else { + ioc = LAGGIOC_DELPORT; + } + } + + if (prop_dictionary_get_int64(env, "laggportpri", &pri)) { + ioc = LAGGIOC_SETPORTPRI; + rp->rp_prio = (uint32_t)pri; + } + + if (ioc != LAGGIOC_NOCMD) { + req.lrq_ioctl = ioc; + if (indirect_ioctl(env, SIOCSLAGG, &req) == -1) { + if (lagg_debug) { + warn("cmd=%d", ioc); + } + return -1; + } + } + + return 0; +} + +static int +setlagglacp(prop_dictionary_t env, prop_dictionary_t oenv __unused) +{ + struct lagg_req req_add, req_del; + struct laggreq_lacp *add_lacp, *del_lacp; + int64_t v; + + memset(&req_add, 0, sizeof(req_add)); + memset(&req_del, 0, sizeof(req_del)); + + req_add.lrq_proto = req_del.lrq_proto = LAGG_PROTO_LACP; + req_add.lrq_ioctl = req_del.lrq_ioctl = LAGGIOC_SETPROTOOPT; + add_lacp = &req_add.lrq_reqproto.rp_lacp; + del_lacp = &req_del.lrq_reqproto.rp_lacp; + + add_lacp->command = LAGGIOC_LACPSETFLAGS; + del_lacp->command = LAGGIOC_LACPCLRFLAGS; + + if (prop_dictionary_get_int64(env, "lacpdumpdu", &v)) { + if (v == LAGGLACPOPT_DUMPDU) { + add_lacp->flags |= LAGGREQLACP_DUMPDU; + } else { + del_lacp->flags |= LAGGREQLACP_DUMPDU; + } + } + + if (prop_dictionary_get_int64(env, "lacpstopdu", &v)) { + if (v == LAGGLACPOPT_STOPDU) { + add_lacp->flags |= LAGGREQLACP_STOPDU; + } else { + del_lacp->flags |= LAGGREQLACP_STOPDU; + } + } + + if (prop_dictionary_get_int64(env, "lacpoptimistic", &v)) { + if (v == LAGGLACPOPT_OPTIMISTIC) { + add_lacp->flags |= LAGGREQLACP_OPTIMISTIC; + } else { + del_lacp->flags |= LAGGREQLACP_OPTIMISTIC; + } + } + + if (prop_dictionary_get_int64(env, "lacpmultils", &v)) { + if (v == LAGGLACPOPT_MULTILS) { + add_lacp->flags |= LAGGREQLACP_MULTILS; + } else { + del_lacp->flags |= LAGGREQLACP_MULTILS; + } + } + + if (del_lacp->flags != 0) { + if (indirect_ioctl(env, SIOCSLAGG, &req_del) == -1) { + if (lagg_debug) { + warn("cmd=%d, pcmd=%d", + req_del.lrq_ioctl, + del_lacp->command); + } + return -1; + } + } + + if (add_lacp->flags != 0) { + if (indirect_ioctl(env, SIOCSLAGG, &req_add) == -1) { + if (lagg_debug) { + warn("cmd=%d, pcmd=%d", + req_add.lrq_ioctl, + add_lacp->command); + } + return -1; + } + } + + return 0; +} + +static int +setlagglacpmaxports(prop_dictionary_t env, + prop_dictionary_t oenv __unused) +{ + struct lagg_req req; + struct laggreq_lacp *lrq_lacp; + int64_t v; + + memset(&req, 0, sizeof(req)); + req.lrq_proto = LAGG_PROTO_LACP; + req.lrq_ioctl = LAGGIOC_SETPROTOOPT; + lrq_lacp = &req.lrq_reqproto.rp_lacp; + + if (!prop_dictionary_get_int64(env, "lacpmaxports", &v)) { + if (lagg_debug) + warnx("%s.%d", __func__, __LINE__); + errno = ENOENT; + return -1; + } + + if (v <= 0) { + lrq_lacp->command = LAGGIOC_LACPCLRMAXPORTS; + } else if (v > 0){ + lrq_lacp->command = LAGGIOC_LACPSETMAXPORTS; + lrq_lacp->maxports = (size_t)v; + } + + if (indirect_ioctl(env, SIOCSLAGG, &req) == -1) { + err(EXIT_FAILURE, "SIOCSLAGGPROTO"); + } + + return 0; +} + +static int +setlaggfail(prop_dictionary_t env, + prop_dictionary_t oenv __unused) +{ + struct lagg_req req_add, req_del; + struct laggreq_fail *add_fail, *del_fail; + int64_t v; + + memset(&req_add, 0, sizeof(req_add)); + memset(&req_del, 0, sizeof(req_del)); + + req_add.lrq_proto = req_del.lrq_proto = LAGG_PROTO_FAILOVER; + req_add.lrq_ioctl = req_del.lrq_ioctl = LAGGIOC_SETPROTOOPT; + add_fail = &req_add.lrq_reqproto.rp_fail; + del_fail = &req_del.lrq_reqproto.rp_fail; + + add_fail->command = LAGGIOC_FAILSETFLAGS; + del_fail->command = LAGGIOC_FAILCLRFLAGS; + + if (prop_dictionary_get_int64(env, "failrxall", &v)) { + if (v == LAGGFAILOPT_RXALL) { + add_fail->flags |= LAGGREQFAIL_RXALL; + } else { + del_fail->flags |= LAGGREQFAIL_RXALL; + } + } + + if (del_fail->flags != 0) { + if (indirect_ioctl(env, SIOCSLAGG, &req_del) == -1) { + if (lagg_debug) { + warn("cmd=%d, pcmd=%d", + req_del.lrq_ioctl, + del_fail->command); + } + return -1; + } + } + + if (add_fail->flags != 0) { + if (indirect_ioctl(env, SIOCSLAGG, &req_add) == -1) { + if (lagg_debug) { + warn("cmd=%d, pcmd=%d", + req_add.lrq_ioctl, + add_fail->command); + } + return -1; + } + } + + return 0; +} + +static void +lagg_usage(prop_dictionary_t env __unused) +{ + + fprintf(stderr, "\t[ laggproto p ]\n"); + fprintf(stderr, "\t[ laggport i [ pri n ] ] " + "[ -laggport i ]\n"); + fprintf(stderr, "\t[ laggportpri i [ pri n]]\n"); + fprintf(stderr, "\t[ lagglacp [ dumpdu | -dumpdu ] " + "[ stopdu | -stopdu ]\n" + "\t\t[ maxports n | -maxports ] [ optimistic | -optimistic ] ]\n"); + fprintf(stderr, "\t[ laggfailover] [ rx-all | -rx-all ]\n"); +} +static void +lacp_format_id(char *buf, size_t len, + uint16_t system_prio, uint8_t *system_mac, uint16_t system_key) +{ + + snprintf(buf, len, "[%04X,%02X-%02X-%02X-%02X-%02X-%02X," + "%04X]", + system_prio, + (unsigned int)system_mac[0],(unsigned int)system_mac[1], + (unsigned int)system_mac[2],(unsigned int)system_mac[3], + (unsigned int)system_mac[4],(unsigned int)system_mac[5], + system_key); +} + +static void +lagg_status_proto(lagg_proto pr, struct laggreqproto *req) +{ + struct laggreq_lacp *lacp; + char str[256]; + + switch (pr) { + case LAGG_PROTO_LACP: + lacp = &req->rp_lacp; + + printf("\n"); + snprintb(str, sizeof(str), LAGGREQLACP_BITS, + lacp->flags); + printf("\t\tmax ports=%zu, flags=%s\n", + lacp->maxports, str); + + lacp_format_id(str, sizeof(str), lacp->actor_prio, + lacp->actor_mac, lacp->actor_key); + printf("\t\tactor=%s\n", str); + + lacp_format_id(str, sizeof(str), lacp->partner_prio, + lacp->partner_mac, lacp->partner_key); + printf("\t\tpartner=%s", str); + break; + default: + break; + } +} + +static void +lagg_status_port(lagg_proto pr, struct laggreqport *req) +{ + struct laggreq_lacpport *lacp; + char str[256]; + + switch (pr) { + case LAGG_PROTO_LACP: + lacp = &req->rp_lacpport; + + putchar('\n'); + + snprintb(str, sizeof(str), LACP_STATE_BITS, lacp->actor_state); + printf("\t\t\tactor: state=%s\n",str); + + lacp_format_id(str, sizeof(str), lacp->partner_prio, + lacp->partner_mac, lacp->partner_key); + printf("\t\t\tpartner=%s\n", str); + snprintb(str, sizeof(str), LACP_STATE_BITS, + lacp->partner_state); + printf("\t\t\tpartner: port=%04X prio=%04X state=%s", + lacp->partner_portno, lacp->partner_portprio, str); + break; + default: + break; + } +} diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 555be236d32..07fa91df401 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -37,7 +37,7 @@ MAN= aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \ ixl.4 ixpide.4 ixv.4 \ jme.4 jmide.4 jmphy.4 joy.4 \ kcov.4 kloader.4 kse.4 ksyms.4 kttcp.4 \ - l2tp.4 lc.4 ld.4 lii.4 lo.4 lua.4 lxtphy.4 \ + l2tp.4 lagg.4 lc.4 ld.4 lii.4 lo.4 lua.4 lxtphy.4 \ mainbus.4 makphy.4 malo.4 mbe.4 mca.4 mcclock.4 mcx.4 md.4 mfb.4 \ mfi.4 mfii.4 mhzc.4 \ micphy.4 midi.4 mii.4 mk48txx.4 mlx.4 mly.4 mpls.4 mpii.4 mpt.4 \ diff --git a/share/man/man4/lagg.4 b/share/man/man4/lagg.4 new file mode 100644 index 00000000000..81e6df5f71d --- /dev/null +++ b/share/man/man4/lagg.4 @@ -0,0 +1,200 @@ + +.\" $NetBSD: $ +.\" +.\" Copyright (c) 2005, 2006 Reyk Floeter +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" +.\" Copyright (C) 2021 Internet Initiative Japan Inc. +.\" 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. +.\" 3. Neither the name of the project nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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. +.\" +.Dd April 2, 2020 +.Dt LAGG 4 +.Os +.Sh NAME +.Nm lagg +.Nd link aggregation and link failover interface +.Sh SYNOPSIS +.Cd "pseudo-device lagg" +.Sh DESCRIPTION +The +.Nm +interface allows aggregation of multiple network interfaces as one virtual +.Nm +interface for the purpose of providing fault-tolerance and high-speed links. +.Pp +A +.Nm +interface can be created using the +.Ic ifconfig lagg Ns Ar N Ic create +command. +It can use different link aggregation protocols specified +using the +.Ic laggproto Ar proto +option. +Child interfaces can be added using the +.Ic laggport Ar child-iface +option and removed using the +.Ic -laggport Ar child-iface +option. +A priority of each child interface can be configured using the +.Ic laggport Ar child-iface pri Ar N +or +.Ic laggportpri Ar child-iface Ar N +option. +The interface preferentially uses the child interface that is +the smallest numeric in the priority. +.Pp +The driver currently supports the aggregation protocols +.Ic failover, +.Ic loadbalance , +.Ic lacp , +and +.Ic none +(the default). +The protocols determine which ports are used for outgoing traffic +and whether a specific port accepts incoming traffic. +The interface link state is used to validate if the port is active or +not. +.Bl -tag -width loadbalance +.It Ic failover +Sends traffic only through the active port that is the highest priority. +When the same priority is configured, +The first interface added is used for sending traffic. +If the link-state of the sending port becomes down, +The next priority port is used. +.Pp +Received traffic is accepted through all active port +if +.Ic laggfailover Nm rx-all +option is enabled. +The option is enabled by default, and it can be +disabled by +.Ic laggfailover Nm -rx-all +option. +If the option is disabled, received traffic is only accepted +through the sending port. +.It Ic loadbalance +Balances outgoing traffic across the active ports based on hashed +protocol header information and accepts incoming traffic from +any active port. +This is a static setup and does not negotiate aggregation with the peer or +exchange frames to monitor the link. +The hash includes the Ethernet source and destination address, and, if +available, the VLAN tag, and the IP source and destination address. +.It Ic lacp +Supports the IEEE 802.1AX (formerly 802.3ad) Link Aggregation Control Protocol +(LACP) and the Marker Protocol. +LACP will nagotiate a set of aggregable links wit the peer in to a Link +Aggregated Group. +The LAG is composed of ports of the diffrent speed, set to full-duplex operation, +if +.Ic lagglacp Nm multi-speed +option is configured. +The function can be disabled by +.Ic lagglacp Nm \-multi-speed +option. +Outgoing traffic across the distributing ports based on hashed +protocol header information and accepts incoming traffic from +any collecting port. +The maximum number of active ports in a LAG can be configured by +.Ic lagglacp Nm maxports Ar N +option. +.It Ic none +THis protocol is intended to do nothing: it disables any traffic without +disabling the +.Nm +interface itself. +.El +.Pp +Each +.Nm +interface is created at runtime using interface cloing. +This is +most easily done with the +.Xr ifconfig 8 +.Cm create +command. +.Pp +The MTU of the first interface to be added is used as the lagg MTU. +All additional interfaces are required to have exactly the same value. +.Pp +.Sh EXAMPLES +Create a link aggregation using LACP with two +.Xr wm 4 +Gigabit Ethernet interfaces: +.Bd -literal -offset indent +# ifconfig wm0 up +# ifconfig wm1 up +# ifconfig lagg0 create +# ifconfig lagg0 laggproto lacp laggport wm0 laggport wm1 \e + 192.168.1.1 netmask 255.255.255.0 +.Ed +.Pp +Create a link aggregation using FAILOVER with two +.Xr wm 4 +Gigabit Ethernet interfaces and set each priority: +.Bd -literal -offset indent +# ifconfig wm0 up +# ifconfig wm1 up +# ifconfig lagg0 create +# ifconfig lagg0 laggproto failover \e + laggport wm0 pri 1000 laggport wm1 pri 2000 \e + 192.168.1.1 netmask 255.255.255.0 +.Ed +.Pp +.Sh SEE ALSO +.Xr ifconfig 8 +.Sh HISTORY +The +.Nm +device first appeared in +.Nx 10.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written under the name +.Nm trunk +by +.An Reyk Floeter Aq Mt reyk@openbsd.org . +.Sh BUGS +There is no way to configure LACP administrative variables, including system +priority. +The current implementation always performs active-mode LACP and uses 0x8000 as +system priority. diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index 79d8e4d4347..43f60e30c82 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1176,6 +1176,7 @@ pseudo-device bridge # simple inter-network bridging pseudo-device vether # Virtual Ethernet for bridge pseudo-device agr # IEEE 802.3ad link aggregation pseudo-device l2tp # L2TPv3 interface +pseudo-device lagg # Link aggregation interface pseudo-device npf # NPF packet filter #pseudo-device canloop # CAN loopback interface diff --git a/sys/arch/i386/conf/GENERIC b/sys/arch/i386/conf/GENERIC index 67c4a659d90..5151e37236e 100644 --- a/sys/arch/i386/conf/GENERIC +++ b/sys/arch/i386/conf/GENERIC @@ -1471,6 +1471,7 @@ pseudo-device bridge # simple inter-network bridging pseudo-device vether # Virtual Ethernet for bridge pseudo-device agr # IEEE 802.3ad link aggregation pseudo-device l2tp # L2TPv3 interface +pseudo-device lagg # Link Aggregation interface pseudo-device npf # NPF packet filter # srt is EXPERIMENTAL #pseudo-device srt # source-address-based routing diff --git a/sys/conf/files b/sys/conf/files index 0aac873ca50..61e30b13692 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1435,6 +1435,7 @@ defpseudodev l2tp: ifnet, ether, arp defpseudo canloop: ifnet defpseudo ipsecif: ifnet # avoid to confuse ipsec itself option defpseudo wg: ifnet, blake2s, libsodium +defpseudo lagg: ifnet, ether defpseudo sequencer defpseudo clockctl diff --git a/sys/modules/Makefile b/sys/modules/Makefile index c228482365a..1fa8ac61310 100644 --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -81,6 +81,7 @@ SUBDIR+= if_gif SUBDIR+= if_gre SUBDIR+= if_kue SUBDIR+= if_l2tp +SUBDIR+= if_lagg SUBDIR+= if_loop SUBDIR+= if_mpls SUBDIR+= if_mue diff --git a/sys/modules/if_lagg/Makefile b/sys/modules/if_lagg/Makefile new file mode 100644 index 00000000000..5c0dfa44671 --- /dev/null +++ b/sys/modules/if_lagg/Makefile @@ -0,0 +1,20 @@ +# $NetBSD: $ + +.include "../Makefile.inc" + +.PATH: ${S}/net/lagg + +KMOD= if_lagg +IOCONF= lagg.ioconf +SRCS= if_lagg.c \ + if_laggproto.c \ + if_lagg_lacp.c + +CPPFLAGS+= -DINET +CPPFLAGS+= -DINET6 +#CPPFLAGS+= -DLAGG_DEBUG +#CPPFLAGS+= -DLACP_DEBUG + +WARNS= 5 + +.include diff --git a/sys/modules/if_lagg/lagg.ioconf b/sys/modules/if_lagg/lagg.ioconf new file mode 100644 index 00000000000..451a016330c --- /dev/null +++ b/sys/modules/if_lagg/lagg.ioconf @@ -0,0 +1,7 @@ +# $NetBSD: $ + +ioconf lagg + +include "conf/files" + +pseudo-device lagg diff --git a/sys/net/Makefile b/sys/net/Makefile index 2ef99d2d8ba..32aa06cc984 100644 --- a/sys/net/Makefile +++ b/sys/net/Makefile @@ -11,7 +11,7 @@ INCS= bpf.h bpfjit.h bpfdesc.h dlt.h ethertypes.h if.h if_arc.h if_arp.h \ pfil.h pfkeyv2.h pfvar.h ppp-comp.h ppp_defs.h radix.h \ raw_cb.h route.h slcompress.h slip.h zlib.h -SUBDIR= agr npf +SUBDIR= agr npf lagg .include diff --git a/sys/net/files.net b/sys/net/files.net index 5f19cc32988..c96fd3345dd 100644 --- a/sys/net/files.net +++ b/sys/net/files.net @@ -67,3 +67,4 @@ file netinet6/in6_gif.c gif & inet6 file netinet6/in6_l2tp.c l2tp & inet6 include "net/agr/files.agr" +include "net/lagg/files.lagg" diff --git a/sys/net/if.c b/sys/net/if.c index ea23121d817..f3d94584489 100644 --- a/sys/net/if.c +++ b/sys/net/if.c @@ -160,6 +160,11 @@ __KERNEL_RCSID(0, "$NetBSD: if.c,v 1.484 2020/10/15 10:20:44 roy Exp $"); #include #endif +#include "lagg.h" +#if NLAGG > 0 +#include +#endif + #include MALLOC_DEFINE(M_IFADDR, "ifaddr", "interface address"); @@ -2421,6 +2426,11 @@ if_link_state_change_process(struct ifnet *ifp, int link_state) bridge_calc_link_state(ifp->if_bridge); #endif +#if NLAGG > 0 + if (ifp->if_lagg != NULL) + lagg_linkstate_changed(ifp); +#endif + DOMAIN_FOREACH(dp) { if (dp->dom_if_link_state_change != NULL) dp->dom_if_link_state_change(ifp, link_state); diff --git a/sys/net/if.h b/sys/net/if.h index 00e37bf08d7..0fd74de16aa 100644 --- a/sys/net/if.h +++ b/sys/net/if.h @@ -381,6 +381,7 @@ typedef struct ifnet { struct mowner *if_mowner; /* ?: who owns mbufs for this interface */ void *if_agrprivate; /* ?: used only when #if NAGR > 0 */ + void *if_lagg; /* ?: used only when #if NLAGG > 0 */ void *if_npf_private;/* ?: associated NPF context */ /* diff --git a/sys/net/if_ethersubr.c b/sys/net/if_ethersubr.c index 4749b5218b8..42d30ad6048 100644 --- a/sys/net/if_ethersubr.c +++ b/sys/net/if_ethersubr.c @@ -78,6 +78,7 @@ __KERNEL_RCSID(0, "$NetBSD: if_ethersubr.c,v 1.292 2021/02/14 19:35:37 roy Exp $ #include "bridge.h" #include "arp.h" #include "agr.h" +#include "lagg.h" #include #include @@ -124,6 +125,8 @@ __KERNEL_RCSID(0, "$NetBSD: if_ethersubr.c,v 1.292 2021/02/14 19:35:37 roy Exp $ #include #endif +#include + #if NBRIDGE > 0 #include #endif @@ -180,6 +183,9 @@ const uint8_t ethermulticastaddr_slowprotocols[ETHER_ADDR_LEN] = { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x02 }; #define senderr(e) { error = (e); goto bad;} +/* if_lagg(4) support */ +struct mbuf *(*lagg_input_ethernet_p)(struct ifnet *, struct mbuf *); + static int ether_output(struct ifnet *, struct mbuf *, const struct sockaddr *, const struct rtentry *); @@ -755,6 +761,14 @@ ether_input(struct ifnet *ifp, struct mbuf *m) } #endif + /* Handle input from a lagg(4) port */ + if (ifp->if_type == IFT_IEEE8023ADLAG) { + KASSERT(lagg_input_ethernet_p != NULL); + m = (*lagg_input_ethernet_p)(ifp, m); + if (m == NULL) + return; + } + /* * If VLANs are configured on the interface, check to * see if the device performed the decapsulation and @@ -1057,6 +1071,11 @@ ether_ifdetach(struct ifnet *ifp) vlan_ifdetach(ifp); #endif +#if NLAGG > 0 + if (ifp->if_lagg) + lagg_ifdetach(ifp); +#endif + ETHER_LOCK(ec); KASSERT(ec->ec_nvlans == 0); while ((enm = LIST_FIRST(&ec->ec_multiaddrs)) != NULL) { diff --git a/sys/net/lagg/Makefile b/sys/net/lagg/Makefile new file mode 100644 index 00000000000..acdd73bed1f --- /dev/null +++ b/sys/net/lagg/Makefile @@ -0,0 +1,8 @@ +# $NetBSD: $ +# + +INCSDIR= /usr/include/net + +INCS= if_lagg.h + +.include diff --git a/sys/net/lagg/files.lagg b/sys/net/lagg/files.lagg new file mode 100644 index 00000000000..136458c6304 --- /dev/null +++ b/sys/net/lagg/files.lagg @@ -0,0 +1,7 @@ +# $NetBSD: $ + +file net/lagg/if_lagg.c lagg & ether needs-flag +file net/lagg/if_lagg_lacp.c lagg & ether +file net/lagg/if_laggproto.c lagg + +defflag opt_lagg.h LAGG_DEBUG LACP_DEBUG diff --git a/sys/net/lagg/if_lagg.c b/sys/net/lagg/if_lagg.c new file mode 100644 index 00000000000..9a1b84f8413 --- /dev/null +++ b/sys/net/lagg/if_lagg.c @@ -0,0 +1,2436 @@ +/* $NetBSD: $ */ + +#include +__KERNEL_RCSID(0, "$NetBSD:$"); + +#ifdef _KERNEL_OPT +#include "opt_inet.h" +#include "opt_lagg.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(INET) || defined(INET6) +#include +#endif + +#ifdef INET6 +#include +#include +#endif + +#include +#include +#include + +#include "ioconf.h" + +enum lagg_portctrl { + LAGG_PORTCTRL_ALLOC, + LAGG_PORTCTRL_FREE, + LAGG_PORTCTRL_START, + LAGG_PORTCTRL_STOP +}; + +enum lagg_iftypes { + LAGG_IF_TYPE_ETHERNET, +}; + +static const struct lagg_proto lagg_protos[] = { + [LAGG_PROTO_NONE] = { + .pr_num = LAGG_PROTO_NONE, + .pr_attach = lagg_none_attach, + .pr_up = lagg_none_up, + }, + [LAGG_PROTO_LACP] = { + .pr_num = LAGG_PROTO_LACP, + .pr_attach = lacp_attach, + .pr_detach = lacp_detach, + .pr_up = lacp_up, + .pr_down = lacp_down, + .pr_transmit = lacp_transmit, + .pr_input = lacp_input, + .pr_allocport = lacp_allocport, + .pr_freeport = lacp_freeport, + .pr_startport = lacp_startport, + .pr_stopport = lacp_stopport, + .pr_protostat = lacp_protostat, + .pr_portstat = lacp_portstat, + .pr_linkstate = lacp_linkstate, + .pr_ioctl = lacp_ioctl, + }, + [LAGG_PROTO_FAILOVER] = { + .pr_num = LAGG_PROTO_FAILOVER, + .pr_attach = lagg_fail_attach, + .pr_detach = lagg_common_detach, + .pr_transmit = lagg_fail_transmit, + .pr_input = lagg_fail_input, + .pr_allocport = lagg_common_allocport, + .pr_freeport = lagg_common_freeport, + .pr_startport = lagg_common_startport, + .pr_stopport = lagg_common_stopport, + .pr_portstat = lagg_fail_portstat, + .pr_linkstate = lagg_common_linkstate, + .pr_ioctl = lagg_fail_ioctl, + }, + [LAGG_PROTO_LOADBALANCE] = { + .pr_num = LAGG_PROTO_LOADBALANCE, + .pr_attach = lagg_lb_attach, + .pr_detach = lagg_common_detach, + .pr_transmit = lagg_lb_transmit, + .pr_input = lagg_lb_input, + .pr_allocport = lagg_common_allocport, + .pr_freeport = lagg_common_freeport, + .pr_startport = lagg_lb_startport, + .pr_stopport = lagg_lb_stopport, + .pr_portstat = lagg_lb_portstat, + .pr_linkstate = lagg_common_linkstate, + }, +}; + +static struct mbuf * + lagg_input_ethernet(struct ifnet *, struct mbuf *); +static int lagg_clone_create(struct if_clone *, int); +static int lagg_clone_destroy(struct ifnet *); +static int lagg_init(struct ifnet *); +static int lagg_init_locked(struct lagg_softc *); +static void lagg_stop(struct ifnet *, int); +static void lagg_stop_locked(struct lagg_softc *); +static int lagg_ioctl(struct ifnet *, u_long, void *); +static int lagg_transmit(struct ifnet *, struct mbuf *); +static void lagg_start(struct ifnet *); +static int lagg_media_change(struct ifnet *); +static void lagg_media_status(struct ifnet *, struct ifmediareq *); +static int lagg_vlan_cb(struct ethercom *, uint16_t, bool); +static struct lagg_softc * + lagg_softc_alloc(enum lagg_iftypes); +static void lagg_softc_free(struct lagg_softc *); +static int lagg_setup_sysctls(struct lagg_softc *); +static void lagg_teardown_sysctls(struct lagg_softc *); +static int lagg_proto_attach(struct lagg_softc *, lagg_proto, + struct lagg_proto_softc **); +static void lagg_proto_detach(struct lagg_variant *); +static int lagg_proto_up(struct lagg_softc *); +static void lagg_proto_down(struct lagg_softc *); +static int lagg_proto_allocport(struct lagg_softc *, struct lagg_port *); +static void lagg_proto_freeport(struct lagg_softc *, struct lagg_port *); +static void lagg_proto_startport(struct lagg_softc *, + struct lagg_port *); +static void lagg_proto_stopport(struct lagg_softc *, + struct lagg_port *); +static struct mbuf * + lagg_proto_input(struct lagg_softc *, struct lagg_port *, + struct mbuf *); +static void lagg_proto_linkstate(struct lagg_softc *, struct lagg_port *); +static int lagg_proto_ioctl(struct lagg_softc *, struct lagg_req *); +static int lagg_get_stats(struct lagg_softc *, struct lagg_req *, size_t); +static int lagg_pr_attach(struct lagg_softc *, lagg_proto); +static void lagg_pr_detach(struct lagg_softc *); +static int lagg_addport(struct lagg_softc *, struct ifnet *); +static int lagg_delport(struct lagg_softc *, struct ifnet *); +static void lagg_delport_all(struct lagg_softc *); +static int lagg_port_ioctl(struct ifnet *, u_long, void *); +static int lagg_port_output(struct ifnet *, struct mbuf *, + const struct sockaddr *, const struct rtentry *); +static int lagg_config_promisc(struct lagg_softc *, struct lagg_port *); +static struct lagg_variant * + lagg_variant_getref(struct lagg_softc *, struct psref *); +static void lagg_variant_putref(struct lagg_variant *, struct psref *); +static int lagg_ether_addmulti(struct lagg_softc *, struct ifreq *); +static int lagg_ether_delmulti(struct lagg_softc *, struct ifreq *); +static void lagg_port_syncmulti(struct lagg_softc *, struct lagg_port *); +static void lagg_port_purgemulti(struct lagg_softc *, struct lagg_port *); +static void lagg_port_syncvlan(struct lagg_softc *, struct lagg_port *); +static void lagg_port_purgevlan(struct lagg_softc *, struct lagg_port *); + +static struct if_clone lagg_cloner = + IF_CLONE_INITIALIZER("lagg", lagg_clone_create, lagg_clone_destroy); +static unsigned int lagg_count; +static struct psref_class + *lagg_psref_class __read_mostly; +static struct psref_class + *lagg_port_psref_class __read_mostly; + +static enum lagg_iftypes + lagg_iftype = LAGG_IF_TYPE_ETHERNET; + +#ifdef LAGG_DEBUG +#define LAGG_DPRINTF(_sc, _fmt, _args...) do { \ + printf("%s: " _fmt, (_sc) != NULL ? \ + sc->sc_if.if_xname : "lagg", ##_args); \ +} while (0) +#else +#define LAGG_DPRINTF(_sc, _fmt, _args...) __nothing +#endif + +static inline size_t +lagg_sizeof_softc(enum lagg_iftypes ift) +{ + struct lagg_softc *_dummy = NULL; + size_t s; + + s = sizeof(*_dummy) - sizeof(_dummy->sc_if); + + switch (ift) { + case LAGG_IF_TYPE_ETHERNET: + s += sizeof(struct ethercom); + break; + default: + s += sizeof(struct ifnet); + break; + } + + return s; +} + +static inline bool +lagg_debug_enable(struct lagg_softc *sc) +{ + if (__predict_false(ISSET(sc->sc_if.if_flags, IFF_DEBUG))) + return true; + + return false; +} + +static inline void +lagg_evcnt_attach(struct lagg_softc *sc, + struct evcnt *ev, const char *name) +{ + + evcnt_attach_dynamic(ev, EVCNT_TYPE_MISC, NULL, + sc->sc_evgroup, name); +} + +static inline void +lagg_in6_ifattach(struct ifnet *ifp) +{ + +#ifdef INET6 + KERNEL_LOCK_UNLESS_NET_MPSAFE(); + if (in6_present) { + if (ISSET(ifp->if_flags, IFF_UP)) + in6_ifattach(ifp, NULL); + } + KERNEL_UNLOCK_UNLESS_NET_MPSAFE(); +#endif +} + +static inline void +lagg_in6_ifdetach(struct ifnet *ifp) +{ + +#ifdef INET6 + KERNEL_LOCK_UNLESS_NET_MPSAFE(); + if (in6_present) { + in6_ifdetach(ifp); + } + KERNEL_UNLOCK_UNLESS_NET_MPSAFE(); +#endif +} + +static inline int +lagg_lp_ioctl(struct lagg_port *lp, u_long cmd, void *data) +{ + struct ifnet *ifp_port; + int error; + + if (lp->lp_ioctl == NULL) + return EINVAL; + + ifp_port = lp->lp_ifp; + IFNET_LOCK(ifp_port); + error = lp->lp_ioctl(ifp_port, cmd, data); + IFNET_UNLOCK(ifp_port); + + return error; +} + +void +laggattach(int n) +{ + + /* + * Nothing to do here, initialization is handled by the + * module initialization code in lagginit() below). + */ +} + +static void +lagginit(void) +{ + size_t i; + + lagg_psref_class = psref_class_create("laggvariant", IPL_SOFTNET); + lagg_port_psref_class = psref_class_create("laggport", IPL_SOFTNET); + + for (i = 0; i < LAGG_PROTO_MAX; i++) { + if (lagg_protos[i].pr_init != NULL) + lagg_protos[i].pr_init(); + } + + lagg_input_ethernet_p = lagg_input_ethernet; + if_clone_attach(&lagg_cloner); +} + +static int +laggdetach(void) +{ + size_t i; + + if (lagg_count > 0) + return EBUSY; + + if_clone_detach(&lagg_cloner); + lagg_input_ethernet_p = NULL; + + for (i = 0; i < LAGG_PROTO_MAX; i++) { + if (lagg_protos[i].pr_fini != NULL) + lagg_protos[i].pr_fini(); + } + + psref_class_destroy(lagg_port_psref_class); + psref_class_destroy(lagg_psref_class); + + return 0; +} + +static int +lagg_clone_create(struct if_clone *ifc, int unit) +{ + struct lagg_softc *sc; + struct ifnet *ifp; + int error; + + sc = lagg_softc_alloc(lagg_iftype); + ifp = &sc->sc_if; + + mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_SOFTNET); + sc->sc_psz = pserialize_create(); + SIMPLEQ_INIT(&sc->sc_ports); + LIST_INIT(&sc->sc_mclist); + TAILQ_INIT(&sc->sc_vtags); + sc->sc_hash_mac = true; + sc->sc_hash_ipaddr = true; + sc->sc_hash_ip6addr = true; + sc->sc_hash_tcp = true; + sc->sc_hash_udp = true; + + if_initname(ifp, ifc->ifc_name, unit); + ifp->if_softc = sc; + ifp->if_init = lagg_init; + ifp->if_stop = lagg_stop; + ifp->if_ioctl = lagg_ioctl; + ifp->if_flags = IFF_SIMPLEX | IFF_BROADCAST | IFF_MULTICAST; + ifp->if_extflags = IFEF_MPSAFE; + ifp->if_transmit = lagg_transmit; + ifp->if_start = lagg_start; + IFQ_SET_READY(&ifq->if_send); + + error = lagg_setup_sysctls(sc); + if (error != 0) + goto destroy_psz; + + /*XXX dependent on ethernet */ + ifmedia_init_with_lock(&sc->sc_media, 0, lagg_media_change, + lagg_media_status, &sc->sc_lock); + ifmedia_add(&sc->sc_media, IFM_ETHER | IFM_AUTO, 0, NULL); + ifmedia_set(&sc->sc_media, IFM_ETHER | IFM_AUTO); + + error = if_initialize(ifp); + if (error != 0) + goto cleanup_ifmedia; + + switch (lagg_iftype) { + case LAGG_IF_TYPE_ETHERNET: + cprng_fast(sc->sc_lladdr, sizeof(sc->sc_lladdr)); + ether_set_vlan_cb((struct ethercom *)ifp, lagg_vlan_cb); + ether_ifattach(ifp, sc->sc_lladdr); + break; + default: + panic("unknown if type"); + } + + snprintf(sc->sc_evgroup, sizeof(sc->sc_evgroup), + "%s", ifp->if_xname); + lagg_evcnt_attach(sc, &sc->sc_novar, "no lagg variant"); + if_link_state_change(&sc->sc_if, LINK_STATE_DOWN); + lagg_setup_sysctls(sc); + (void)lagg_pr_attach(sc, LAGG_PROTO_NONE); + if_register(ifp); + lagg_count++; + + return 0; + +cleanup_ifmedia: + ifmedia_fini(&sc->sc_media); + lagg_teardown_sysctls(sc); +destroy_psz: + pserialize_destroy(sc->sc_psz); + mutex_destroy(&sc->sc_lock); + lagg_softc_free(sc); + + return error; +} + +static int +lagg_clone_destroy(struct ifnet *ifp) +{ + struct lagg_softc *sc = (struct lagg_softc *)ifp->if_softc; + + lagg_stop(ifp, 1); + + IFNET_LOCK(ifp); + lagg_delport_all(sc); + IFNET_UNLOCK(ifp); + + switch (ifp->if_type) { + case IFT_ETHER: + ether_ifdetach(ifp); + KASSERT(TAILQ_EMPTY(&sc->sc_vtags)); + break; + } + + if_detach(ifp); + ifmedia_fini(&sc->sc_media); + lagg_pr_detach(sc); + evcnt_detach(&sc->sc_novar); + lagg_teardown_sysctls(sc); + + pserialize_destroy(sc->sc_psz); + mutex_destroy(&sc->sc_lock); + lagg_softc_free(sc); + + if (lagg_count > 0) + lagg_count--; + + return 0; +} + +static int +lagg_init(struct ifnet *ifp) +{ + struct lagg_softc *sc; + int rv; + + sc = ifp->if_softc; + LAGG_LOCK(sc); + rv = lagg_init_locked(sc); + LAGG_UNLOCK(sc); + + return rv; +} + +static int +lagg_init_locked(struct lagg_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + int rv; + + KASSERT(LAGG_LOCKED(sc)); + + if (ISSET(ifp->if_flags, IFF_RUNNING)) + lagg_stop_locked(sc); + + SET(ifp->if_flags, IFF_RUNNING); + + rv = lagg_proto_up(sc); + if (rv != 0) + lagg_stop_locked(sc); + + return rv; +} + +static void +lagg_stop(struct ifnet *ifp, int disable __unused) +{ + struct lagg_softc *sc; + + sc = ifp->if_softc; + LAGG_LOCK(sc); + lagg_stop_locked(sc); + LAGG_UNLOCK(sc); +} + +static void +lagg_stop_locked(struct lagg_softc *sc) +{ + struct ifnet *ifp = &sc->sc_if; + + KASSERT(LAGG_LOCKED(sc)); + + if (!ISSET(ifp->if_flags, IFF_RUNNING)) + return; + + CLR(ifp->if_flags, IFF_RUNNING); + lagg_proto_down(sc); + +} + +static int +lagg_config(struct lagg_softc *sc, struct lagg_req *lrq) +{ + struct ifnet *ifp_port; + struct laggreqport *rp; + struct lagg_port *lp; + struct psref psref; + size_t i; + int error, bound; + + error = 0; + bound = curlwp_bind(); + + switch (lrq->lrq_ioctl) { + case LAGGIOC_SETPROTO: + if (lrq->lrq_proto >= LAGG_PROTO_MAX) { + error = EPROTONOSUPPORT; + break; + } + + lagg_delport_all(sc); + error = lagg_pr_attach(sc, lrq->lrq_proto); + if (error != 0) + break; + + for (i = 0; i < lrq->lrq_nports; i++) { + rp = &lrq->lrq_reqports[i]; + ifp_port = if_get(rp->rp_portname, &psref); + if (ifp_port == NULL) { + error = ENOENT; + break; /* break for */ + } + + error = lagg_addport(sc, ifp_port); + if_put(ifp_port, &psref); + + if (error != 0) + break; /* break for */ + } + break; /* break switch */ + case LAGGIOC_ADDPORT: + rp = &lrq->lrq_reqports[0]; + ifp_port = if_get(rp->rp_portname, &psref); + if (ifp_port == NULL) { + error = ENOENT; + break; + } + + error = lagg_addport(sc, ifp_port); + if_put(ifp_port, &psref); + break; + case LAGGIOC_DELPORT: + rp = &lrq->lrq_reqports[0]; + ifp_port = if_get(rp->rp_portname, &psref); + if (ifp_port == NULL) { + error = ENOENT; + break; + } + + error = lagg_delport(sc, ifp_port); + if_put(ifp_port, &psref); + break; + case LAGGIOC_SETPORTPRI: + rp = &lrq->lrq_reqports[0]; + ifp_port = if_get(rp->rp_portname, &psref); + if (ifp_port == NULL) { + error = ENOENT; + break; + } + + lp = ifp_port->if_lagg; + if (lp == NULL || lp->lp_softc != sc) { + if_put(ifp_port, &psref); + error = ENOENT; + break; + } + + lp->lp_prio = rp->rp_prio; + + /* restart protocol */ + LAGG_LOCK(sc); + lagg_proto_stopport(sc, lp); + lagg_proto_startport(sc, lp); + LAGG_UNLOCK(sc); + if_put(ifp_port, &psref); + break; + case LAGGIOC_SETPROTOOPT: + error = lagg_proto_ioctl(sc, lrq); + break; + default: + error = ENOTTY; + } + + curlwp_bindx(bound); + return error; +} + +static int +lagg_ioctl(struct ifnet *ifp, u_long cmd, void *data) +{ + struct lagg_softc *sc; + struct ifreq *ifr = (struct ifreq *)data; + struct lagg_req laggreq, *laggresp; + struct lagg_port *lp; + size_t allocsiz, outlen, nports; + char *outbuf; + void *buf; + int error = 0, rv; + + sc = ifp->if_softc; + + switch (cmd) { + case SIOCGLAGG: + error = copyin(ifr->ifr_data, &laggreq, sizeof(laggreq)); + if (error != 0) + break; + + nports = sc->sc_nports; + nports = MIN(nports, laggreq.lrq_nports); + + allocsiz = sizeof(*laggresp) + + sizeof(laggresp->lrq_reqports[0]) * nports; + laggresp = kmem_zalloc(allocsiz, KM_SLEEP); + + rv = lagg_get_stats(sc, laggresp, nports); + + outbuf = (char *)laggresp; + + nports = MIN(laggresp->lrq_nports, nports); + outlen = sizeof(*laggresp) + + sizeof(laggresp->lrq_reqports[0]) * nports; + + error = copyout(outbuf, ifr->ifr_data, outlen); + kmem_free(outbuf, allocsiz); + + if (error == 0 && rv != 0) + error = rv; + + break; + case SIOCSLAGG: + error = copyin(ifr->ifr_data, &laggreq, sizeof(laggreq)); + if (error != 0) + break; + + nports = laggreq.lrq_nports; + if (nports > 1) { + allocsiz = sizeof(struct lagg_req) + + sizeof(struct laggreqport) * nports; + buf = kmem_alloc(allocsiz, KM_SLEEP); + + error = copyin(ifr->ifr_data, buf, allocsiz); + if (error != 0) { + kmem_free(buf, allocsiz); + break; + } + } else { + buf = (void *)&laggreq; + allocsiz = 0; + } + + error = lagg_config(sc, buf); + if (allocsiz > 0) + kmem_free(buf, allocsiz); + break; + case SIOCSIFFLAGS: + error = ifioctl_common(ifp, cmd, data); + if (error != 0) + break; + + switch (ifp->if_flags & (IFF_UP | IFF_RUNNING)) { + case IFF_RUNNING: + ifp->if_stop(ifp, 1); + break; + case IFF_UP: + case IFF_UP | IFF_RUNNING: + error = ifp->if_init(ifp); + break; + } + + if (error != 0) + break; + + /* Set flags on ports too */ + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + (void)lagg_config_promisc(sc, lp); + } + LAGG_UNLOCK(sc); + break; + case SIOCSIFMTU: + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + error = lagg_lp_ioctl(lp, cmd, (void *)ifr); + + if (error != 0) { + lagg_log(sc, LOG_ERR, + "failed to change MTU to %d on port %s, " + "reverting all ports to original MTU(%d)\n", + ifr->ifr_mtu, lp->lp_ifp->if_xname, + ifp->if_mtu); + break; + } + } + + if (error == 0) { + ifp->if_mtu = ifr->ifr_mtu; + } else { + /* set every port back to the original MTU */ + ifr->ifr_mtu = ifp->if_mtu; + LAGG_PORTS_FOREACH(sc, lp) { + if (lp->lp_ioctl != NULL) { + lagg_lp_ioctl(lp, cmd, (void *)ifr); + } + } + } + LAGG_UNLOCK(sc); + break; + case SIOCADDMULTI: + if (sc->sc_if.if_type == IFT_ETHER) { + error = lagg_ether_addmulti(sc, ifr); + } else { + error = EPROTONOSUPPORT; + } + break; + case SIOCDELMULTI: + if (sc->sc_if.if_type == IFT_ETHER) { + error = lagg_ether_delmulti(sc, ifr); + } else { + error = EPROTONOSUPPORT; + } + break; + default: + error = ether_ioctl(ifp, cmd, data); + } + return error; +} + +static int +lagg_setup_sysctls(struct lagg_softc *sc) +{ + struct sysctllog **log; + const struct sysctlnode **rnode, *hashnode; + const char *ifname; + int error; + + log = &sc->sc_sysctllog; + rnode = &sc->sc_sysctlnode; + ifname = sc->sc_if.if_xname; + + error = sysctl_createv(log, 0, NULL, rnode, + CTLFLAG_PERMANENT, CTLTYPE_NODE, ifname, + SYSCTL_DESCR("lagg information and settings"), + NULL, 0, NULL, 0, CTL_NET, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, rnode, &hashnode, + CTLFLAG_PERMANENT, CTLTYPE_NODE, "hash", + SYSCTL_DESCR("hash calculation settings"), + NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, &hashnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "macaddr", + SYSCTL_DESCR("use src/dst mac addresses"), + NULL, 0, &sc->sc_hash_mac, 0, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, &hashnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "ipaddr", + SYSCTL_DESCR("use src/dst IPv4 addresses"), + NULL, 0, &sc->sc_hash_ipaddr, 0, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, &hashnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "ip6addr", + SYSCTL_DESCR("use src/dst IPv6 addresses"), + NULL, 0, &sc->sc_hash_ip6addr, 0, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, &hashnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "tcp", + SYSCTL_DESCR("use TCP src/dst port"), + NULL, 0, &sc->sc_hash_tcp, 0, CTL_CREATE, CTL_EOL); + if (error != 0) + goto done; + + error = sysctl_createv(log, 0, &hashnode, NULL, + CTLFLAG_READWRITE, CTLTYPE_BOOL, "udp", + SYSCTL_DESCR("use UDP src/dst port"), + NULL, 0, &sc->sc_hash_udp, 0, CTL_CREATE, CTL_EOL); +done: + if (error != 0) { + lagg_log(sc, LOG_ERR, "unable to create sysctl node\n"); + sysctl_teardown(log); + } + + return error; +} + +static void +lagg_teardown_sysctls(struct lagg_softc *sc) +{ + + sc->sc_sysctlnode = NULL; + sysctl_teardown(&sc->sc_sysctllog); +} + +uint32_t +lagg_hashmbuf(struct lagg_softc *sc, struct mbuf *m) +{ + union { + struct ether_header _eh; + struct ether_vlan_header _evl; + struct ip _ip; + struct ip6_hdr _ip6; + struct tcphdr _th; + struct udphdr _uh; + } buf; + const struct ether_header *eh; + const struct ether_vlan_header *evl; + const struct ip *ip; + const struct ip6_hdr *ip6; + const struct tcphdr *th; + const struct udphdr *uh; + uint32_t hash = HASH32_BUF_INIT; + uint32_t flowlabel; + uint16_t etype, vlantag; + uint8_t proto; + size_t off; + + KASSERT(ISSET(m->m_flags, M_PKTHDR)); + + eh = lagg_m_extract(m, 0, sizeof(*eh), &buf); + if (eh == NULL) { + return hash; + } + off = ETHER_HDR_LEN; + etype = ntohs(eh->ether_type); + + if (etype == ETHERTYPE_VLAN) { + evl = lagg_m_extract(m, 0, sizeof(*evl), &buf); + if (evl == NULL) { + return hash; + } + + vlantag = ntohs(evl->evl_tag); + etype = ntohs(evl->evl_proto); + off += ETHER_VLAN_ENCAP_LEN; + } else if (vlan_has_tag(m)) { + vlantag = vlan_get_tag(m); + } else { + vlantag = 0; + } + + if (sc->sc_hash_mac) { + hash = hash32_buf(&eh->ether_dhost, + sizeof(eh->ether_dhost), hash); + hash = hash32_buf(&eh->ether_shost, + sizeof(eh->ether_shost), hash); + hash = hash32_buf(&vlantag, sizeof(vlantag), hash); + } + + switch (etype) { + case ETHERTYPE_IP: + ip = lagg_m_extract(m, off, sizeof(*ip), &buf); + if (ip == NULL) { + return hash; + } + + if (sc->sc_hash_ipaddr) { + hash = hash32_buf(&ip->ip_src, + sizeof(ip->ip_src), hash); + hash = hash32_buf(&ip->ip_dst, + sizeof(ip->ip_dst), hash); + hash = hash32_buf(&ip->ip_p, sizeof(ip->ip_p), hash); + } + off += ip->ip_hl << 2; + proto = ip->ip_p; + break; + case ETHERTYPE_IPV6: + ip6 = lagg_m_extract(m, off, sizeof(*ip6), &buf); + if (ip6 == NULL) { + return hash; + } + + if (sc->sc_hash_ip6addr) { + hash = hash32_buf(&ip6->ip6_src, + sizeof(ip6->ip6_src), hash); + hash = hash32_buf(&ip6->ip6_dst, + sizeof(ip6->ip6_dst), hash); + flowlabel = ip6->ip6_flow & IPV6_FLOWLABEL_MASK; + hash = hash32_buf(&flowlabel, sizeof(flowlabel), hash); + } + proto = ip6->ip6_nxt; + off += sizeof(*ip6); + + /* L4 header is not supported */ + return hash; + default: + return hash; + } + + switch (proto) { + case IPPROTO_TCP: + th = lagg_m_extract(m, off, sizeof(*th), &buf); + if (th == NULL) { + return hash; + } + + if (sc->sc_hash_tcp) { + hash = hash32_buf(&th->th_sport, + sizeof(th->th_sport), hash); + hash = hash32_buf(&th->th_dport, + sizeof(th->th_dport), hash); + } + break; + case IPPROTO_UDP: + uh = lagg_m_extract(m, off, sizeof(*uh), &buf); + if (uh == NULL) { + return hash; + } + + if (sc->sc_hash_udp) { + hash = hash32_buf(&uh->uh_sport, + sizeof(uh->uh_sport), hash); + hash = hash32_buf(&uh->uh_dport, + sizeof(uh->uh_dport), hash); + } + break; + } + + return hash; +} + +static int +lagg_tx_common(struct ifnet *ifp, struct mbuf *m) +{ + struct lagg_variant *var; + lagg_proto pr; + struct psref psref; + int error; + + var = lagg_variant_getref(ifp->if_softc, &psref); + + if (__predict_false(var == NULL)) { + m_freem(m); + if_statinc(ifp, if_oerrors); + return ENOENT; + } + + pr = var->lv_proto; + if (__predict_true(lagg_protos[pr].pr_transmit != NULL)) { + error = lagg_protos[pr].pr_transmit(var->lv_psc, m); + /* mbuf is already freed */ + } else { + m_freem(m); + if_statinc(ifp, if_oerrors); + error = ENOBUFS; + } + + lagg_variant_putref(var, &psref); + + return error; +} + +static int +lagg_transmit(struct ifnet *ifp, struct mbuf *m) +{ + + return lagg_tx_common(ifp, m); +} + +static void +lagg_start(struct ifnet *ifp) +{ + struct mbuf *m; + + for (;;) { + IFQ_DEQUEUE(&ifp->if_snd, m); + if (m == NULL) + break; + + (void)lagg_tx_common(ifp, m); + } +} + +void +lagg_enqueue(struct lagg_softc *sc, struct lagg_port *lp, struct mbuf *m) +{ + struct ifnet *ifp; + int len, error; + short mflags; + + ifp = &sc->sc_if; + len = m->m_pkthdr.len; + mflags = m->m_flags; + + error = lagg_port_xmit(lp, m); + if (error) { + /* mbuf is already freed */ + if_statinc(ifp, if_oerrors); + } + + net_stat_ref_t nsr = IF_STAT_GETREF(ifp); + if_statinc_ref(nsr, if_opackets); + if_statadd_ref(nsr, if_obytes, len); + if (mflags & M_MCAST) + if_statinc_ref(nsr, if_omcasts); + IF_STAT_PUTREF(ifp); +} + +static struct mbuf * +lagg_proto_input(struct lagg_softc *sc, struct lagg_port *lp, struct mbuf *m) +{ + struct psref psref; + struct lagg_variant *var; + lagg_proto pr; + + var = lagg_variant_getref(sc, &psref); + + if (var == NULL) { + sc->sc_novar.ev_count++; + m_freem(m); + return NULL; + } + + bpf_mtap((struct ifnet *)&sc->sc_if, m, BPF_D_IN); + pr = var->lv_proto; + + if (lagg_protos[pr].pr_input != NULL) { + m = lagg_protos[pr].pr_input(var->lv_psc, lp, m); + } else { + m_freem(m); + m = NULL; + } + + lagg_variant_putref(var, &psref); + + return m; +} + +static struct mbuf * +lagg_input_ethernet(struct ifnet *ifp_port, struct mbuf *m) +{ + struct ifnet *ifp; + struct psref psref; + struct lagg_port *lp; + int s; + + /* sanity check */ + s = pserialize_read_enter(); + lp = atomic_load_consume(&ifp_port->if_lagg); + if (lp == NULL) { + /* This interface is not a member of lagg */ + pserialize_read_exit(s); + return m; + } + lagg_port_getref(lp, &psref); + pserialize_read_exit(s); + + if (pfil_run_hooks(ifp_port->if_pfil, &m, + ifp_port, PFIL_IN) != 0) { + goto out; + } + + m = lagg_proto_input(lp->lp_softc, lp, m); + if (m != NULL) { + ifp = &lp->lp_softc->sc_if; + m_set_rcvif(m, ifp); + if_input(ifp, m); + m = NULL; + } + +out: + lagg_port_putref(lp, &psref); + + return m; +} + +static int +lagg_media_change(struct ifnet *ifp) +{ + + if (ISSET(ifp->if_flags, IFF_DEBUG)) + printf("%s: ignore media change\n", ifp->if_xname); + + return 0; +} + +static void +lagg_media_status(struct ifnet *ifp, struct ifmediareq *imr) +{ + struct lagg_softc *sc; + struct lagg_port *lp; + + sc = ifp->if_softc; + + imr->ifm_status = IFM_AVALID; + imr->ifm_active = IFM_ETHER | IFM_AUTO; + + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + if (lagg_portactive(lp)) + imr->ifm_status |= IFM_ACTIVE; + } + LAGG_UNLOCK(sc); +} + +static int +lagg_port_vlan_cb(struct lagg_port *lp, + struct lagg_vlantag *lvt, bool set) +{ + struct ifnet *ifp_port; + struct ethercom *ec_port; + int error; + + if (lp->lp_ifp->if_type != IFT_ETHER) + return 0; + + error = 0; + ifp_port = lp->lp_ifp; + ec_port = (struct ethercom *)ifp_port; + + if (set) { + ec_port->ec_nvlans++; + if (ec_port->ec_nvlans == 1) { + IFNET_LOCK(ifp_port); + error = ether_enable_vlan_mtu(ifp_port); + IFNET_UNLOCK(ifp_port); + + if (error == -1) { + error = 0; + } else if (error != 0) { + ec_port->ec_nvlans--; + goto done; + } + + if (ec_port->ec_vlan_cb != NULL) { + error = ec_port->ec_vlan_cb(ec_port, + lvt->lvt_vtag, set); + if (error != 0) { + ec_port->ec_nvlans--; + IFNET_LOCK(ifp_port); + ether_disable_vlan_mtu(ifp_port); + IFNET_UNLOCK(ifp_port); + goto done; + } + } + } + } else { + if (ec_port->ec_nvlans == 0) { + error = ENOENT; + goto done; + } + + if (ec_port->ec_vlan_cb != NULL) { + (void)ec_port->ec_vlan_cb(ec_port, + lvt->lvt_vtag, set); + } + + ec_port->ec_nvlans--; + if (ec_port->ec_nvlans == 0) { + IFNET_LOCK(ifp_port); + (void)ether_disable_vlan_mtu(ifp_port); + IFNET_UNLOCK(ifp_port); + } + } + +done: + return error; +} + +static int +lagg_vlan_cb(struct ethercom *ec, uint16_t vtag, bool set) +{ + struct ifnet *ifp; + struct lagg_softc *sc; + struct lagg_vlantag *lvt, *lvt0; + struct lagg_port *lp; + int error; + + ifp = (struct ifnet *)ec; + sc = ifp->if_softc; + + if (set) { + lvt = kmem_zalloc(sizeof(*lvt), KM_SLEEP); + lvt->lvt_vtag = vtag; + TAILQ_INSERT_TAIL(&sc->sc_vtags, lvt, lvt_entry); + } else { + TAILQ_FOREACH_SAFE(lvt, &sc->sc_vtags, lvt_entry, lvt0) { + if (lvt->lvt_vtag == vtag) { + TAILQ_REMOVE(&sc->sc_vtags, lvt, lvt_entry); + break; + } + } + + if (lvt == NULL) + return ENOENT; + } + + KASSERT(lvt != NULL); + LAGG_PORTS_FOREACH(sc, lp) { + error = lagg_port_vlan_cb(lp, lvt, set); + if (error != 0) { + lagg_log(sc, LOG_WARNING, + "%s failed to configure vlan on %d\n", + lp->lp_ifp->if_xname, error); + } + } + + return 0; +} + +static struct lagg_softc * +lagg_softc_alloc(enum lagg_iftypes ift) +{ + struct lagg_softc *sc; + size_t s; + + s = lagg_sizeof_softc(ift); + KASSERT(s > 0); + + sc = kmem_zalloc(s, KM_SLEEP); + KASSERT(sc != NULL); + + return sc; +} + +static void +lagg_softc_free(struct lagg_softc *sc) +{ + + kmem_free(sc, + lagg_sizeof_softc(sc->sc_iftype)); +} + +static void +lagg_variant_update(struct lagg_softc *sc, struct lagg_variant *newvar) +{ + struct lagg_variant *oldvar; + + KASSERT(LAGG_LOCKED(sc)); + + psref_target_init(&newvar->lv_psref, lagg_psref_class); + + oldvar = sc->sc_var; + atomic_store_release(&sc->sc_var, newvar); + pserialize_perform(sc->sc_psz); + + if (__predict_true(oldvar != NULL)) + psref_target_destroy(&oldvar->lv_psref, lagg_psref_class); +} + +static struct lagg_variant * +lagg_variant_getref(struct lagg_softc *sc, struct psref *psref) +{ + struct lagg_variant *var; + int s; + + s = pserialize_read_enter(); + var = atomic_load_consume(&sc->sc_var); + if (var == NULL) { + pserialize_read_exit(s); + return NULL; + } + + psref_acquire(psref, &var->lv_psref, lagg_psref_class); + pserialize_read_exit(s); + + return var; +} + +static void +lagg_variant_putref(struct lagg_variant *var, struct psref *psref) +{ + + if (__predict_false(var == NULL)) + return; + psref_release(psref, &var->lv_psref, lagg_psref_class); +} + +static int +lagg_proto_attach(struct lagg_softc *sc, lagg_proto pr, + struct lagg_proto_softc **psc) +{ + + KASSERT(lagg_protos[pr].pr_attach != NULL); + return lagg_protos[pr].pr_attach(sc, psc); +} + +static void +lagg_proto_detach(struct lagg_variant *oldvar) +{ + lagg_proto pr; + + pr = oldvar->lv_proto; + + if (lagg_protos[pr].pr_detach == NULL) + return; + + lagg_protos[pr].pr_detach(oldvar->lv_psc); +} + +static int +lagg_proto_updown(struct lagg_softc *sc, bool is_up) +{ + struct lagg_variant *var; + struct psref psref; + lagg_proto pr; + int error, bound; + + error = 0; + bound = curlwp_bind(); + + var = lagg_variant_getref(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + return ENXIO; + } + + pr = var->lv_proto; + + if (is_up) { + if (lagg_protos[pr].pr_up != NULL) + error = lagg_protos[pr].pr_up(var->lv_psc); + else + error = 0; + } else { + if (lagg_protos[pr].pr_down != NULL) + lagg_protos[pr].pr_down(var->lv_psc); + else + error = 0; + } + + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); + + return error; +} + +static int +lagg_proto_up(struct lagg_softc *sc) +{ + + return lagg_proto_updown(sc, true); +} + +static void +lagg_proto_down(struct lagg_softc *sc) +{ + + (void)lagg_proto_updown(sc, false); +} + +static int +lagg_proto_portctrl(struct lagg_softc *sc, struct lagg_port *lp, + enum lagg_portctrl ctrl) +{ + struct lagg_variant *var; + struct psref psref; + lagg_proto pr; + int error, bound; + + error = 0; + bound = curlwp_bind(); + + var = lagg_variant_getref(sc, &psref); + if (var == NULL) { + curlwp_bindx(bound); + return ENXIO; + } + + pr = var->lv_proto; + + error = EPROTONOSUPPORT; + switch (ctrl) { + case LAGG_PORTCTRL_ALLOC: + if (lagg_protos[pr].pr_allocport != NULL) + error = lagg_protos[pr].pr_allocport(var->lv_psc, lp); + break; + case LAGG_PORTCTRL_FREE: + if (lagg_protos[pr].pr_freeport != NULL) { + lagg_protos[pr].pr_freeport(var->lv_psc, lp); + error = 0; + } + break; + case LAGG_PORTCTRL_START: + if (lagg_protos[pr].pr_startport != NULL) { + lagg_protos[pr].pr_startport(var->lv_psc, lp); + error = 0; + } + break; + case LAGG_PORTCTRL_STOP: + if (lagg_protos[pr].pr_stopport != NULL) { + lagg_protos[pr].pr_stopport(var->lv_psc, lp); + error = 0; + } + break; + } + + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); + return error; +} + +static int +lagg_proto_allocport(struct lagg_softc *sc, struct lagg_port *lp) +{ + + return lagg_proto_portctrl(sc, lp, LAGG_PORTCTRL_ALLOC); +} + +static void +lagg_proto_freeport(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_proto_portctrl(sc, lp, LAGG_PORTCTRL_FREE); +} + +static void +lagg_proto_startport(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_proto_portctrl(sc, lp, LAGG_PORTCTRL_START); +} + +static void +lagg_proto_stopport(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_proto_portctrl(sc, lp, LAGG_PORTCTRL_STOP); +} + +static void +lagg_proto_linkstate(struct lagg_softc *sc, struct lagg_port *lp) +{ + struct lagg_variant *var; + struct psref psref; + lagg_proto pr; + int bound; + + bound = curlwp_bind(); + var = lagg_variant_getref(sc, &psref); + + if (var == NULL) { + curlwp_bindx(bound); + return; + } + + pr = var->lv_proto; + + if (lagg_protos[pr].pr_linkstate) + lagg_protos[pr].pr_linkstate(var->lv_psc, lp); + + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); +} + +static void +lagg_proto_stat(struct lagg_variant *var, struct laggreqproto *resp) +{ + lagg_proto pr; + + pr = var->lv_proto; + + if (lagg_protos[pr].pr_protostat != NULL) + lagg_protos[pr].pr_protostat(var->lv_psc, resp); +} + +static void +lagg_proto_portstat(struct lagg_variant *var, struct lagg_port *lp, + struct laggreqport *resp) +{ + lagg_proto pr; + + pr = var->lv_proto; + + if (lagg_protos[pr].pr_portstat != NULL) + lagg_protos[pr].pr_portstat(var->lv_psc, lp, resp); +} + +static int +lagg_proto_ioctl(struct lagg_softc *sc, struct lagg_req *lreq) +{ + struct lagg_variant *var; + struct psref psref; + lagg_proto pr; + int bound, error; + + error = ENOTTY; + bound = curlwp_bind(); + var = lagg_variant_getref(sc, &psref); + + if (var == NULL) { + error = ENXIO; + goto done; + } + + pr = var->lv_proto; + if (pr != lreq->lrq_proto) { + error = EBUSY; + goto done; + } + + if (lagg_protos[pr].pr_ioctl != NULL) { + error = lagg_protos[pr].pr_ioctl(var->lv_psc, + &lreq->lrq_reqproto); + } + +done: + if (var != NULL) + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); + return error; +} + +static int +lagg_pr_attach(struct lagg_softc *sc, lagg_proto pr) +{ + struct lagg_variant *newvar, *oldvar; + struct lagg_proto_softc *psc; + bool cleanup_oldvar; + int error; + + error = 0; + cleanup_oldvar = false; + newvar = kmem_alloc(sizeof(*newvar), KM_SLEEP); + + LAGG_LOCK(sc); + oldvar = sc->sc_var; + + if (oldvar != NULL && oldvar->lv_proto == pr) { + error = 0; + goto done; + } + + error = lagg_proto_attach(sc, pr, &psc); + if (error != 0) + goto done; + + newvar->lv_proto = pr; + newvar->lv_psc = psc; + + lagg_variant_update(sc, newvar); + newvar = NULL; + + if (oldvar != NULL) { + lagg_proto_detach(oldvar); + cleanup_oldvar = true; + } +done: + LAGG_UNLOCK(sc); + + if (newvar != NULL) + kmem_free(newvar, sizeof(*newvar)); + if (cleanup_oldvar) + kmem_free(oldvar, sizeof(*oldvar)); + + return error; +} + +static void +lagg_pr_detach(struct lagg_softc *sc) +{ + struct lagg_variant *var; + + LAGG_LOCK(sc); + + var = sc->sc_var; + atomic_store_release(&sc->sc_var, NULL); + pserialize_perform(sc->sc_psz); + + if (var != NULL) + lagg_proto_detach(var); + + LAGG_UNLOCK(sc); + + if (var != NULL) + kmem_free(var, sizeof(*var)); +} + +static void +lagg_lladdr_set(struct lagg_softc *sc, struct lagg_port *lp, u_long if_type) +{ + struct ifnet *ifp, *ifp_port; + const uint8_t *lladdr; + + KASSERT(IFNET_LOCKED(&sc->sc_if)); + KASSERT(LAGG_LOCKED(sc)); + + ifp = &sc->sc_if; + ifp_port = lp->lp_ifp; + + if (ifp->if_type != IFT_ETHER) + return; + + switch (lp->lp_iftype) { + case IFT_ETHER: + memcpy(lp->lp_lladdr, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN); + + if (SIMPLEQ_EMPTY(&sc->sc_ports)) { + if_set_sadl(ifp, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN, 0); + LAGG_UNLOCK(sc); + /* apply new IPv6LLA */ + lagg_in6_ifdetach(&sc->sc_if); + lagg_in6_ifattach(&sc->sc_if); + LAGG_LOCK(sc); + } + + lladdr = CLLADDR(ifp->if_sadl); + + if (lp->lp_iftype != if_type || + memcmp(lp->lp_lladdr, lladdr, ETHER_ADDR_LEN) != 0) { + IFNET_LOCK(ifp_port); + if_set_sadl(ifp_port, lladdr, ETHER_ADDR_LEN, false); + IFNET_UNLOCK(ifp_port); + } + break; + } +} + +static void +lagg_lladdr_update(struct lagg_softc *sc) +{ + struct lagg_port *lp; + struct ifnet *ifp_port; + const uint8_t *lladdr; + bool stopped; + int error; + + KASSERT(LAGG_LOCKED(sc)); + KASSERT(sc->sc_if.if_type == IFT_ETHER); + + lladdr = CLLADDR(sc->sc_if.if_sadl); + + LAGG_PORTS_FOREACH(sc, lp) { + ifp_port = lp->lp_ifp; + + if (memcmp(lladdr, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN) == 0) { + continue; + } + + IFNET_LOCK(ifp_port); + if (ISSET(ifp_port->if_flags, IFF_RUNNING)) { + ifp_port->if_stop(ifp_port, 0); + stopped = true; + } else { + stopped = false; + } + + if_set_sadl(ifp_port, lladdr, ETHER_ADDR_LEN, false); + + if (stopped) { + error = ifp_port->if_init(ifp_port); + + if (error != 0) { + lagg_log(sc, LOG_WARNING, + "%s failed to if_init() on %d\n", + ifp_port->if_xname, error); + } + } + + IFNET_UNLOCK(ifp_port); + } +} + +static void +lagg_lladdr_unset(struct lagg_softc *sc, struct lagg_port *lp, u_int if_type) +{ + struct ifnet *ifp, *ifp_port; + struct lagg_port *lp0; + + KASSERT(IFNET_LOCKED(&sc->sc_if)); + KASSERT(LAGG_LOCKED(sc)); + + ifp = &sc->sc_if; + ifp_port = lp->lp_ifp; + + if (ifp->if_type != IFT_ETHER) + return; + + switch (lp->lp_iftype) { + case IFT_ETHER: + if (memcmp(lp->lp_lladdr, CLLADDR(ifp->if_sadl), + ETHER_ADDR_LEN) == 0) { + lp0 = SIMPLEQ_FIRST(&sc->sc_ports); + if (lp0 == NULL) { + if_set_sadl(ifp, sc->sc_lladdr, + ETHER_ADDR_LEN, 0); + } else { + if_set_sadl(ifp, lp0->lp_lladdr, + ETHER_ADDR_LEN, 0); + } + + LAGG_UNLOCK(sc); + lagg_in6_ifdetach(ifp); + lagg_in6_ifattach(ifp); + LAGG_LOCK(sc); + + lagg_lladdr_update(sc); + } + + if (lp->lp_iftype != if_type || + memcmp(lp->lp_lladdr, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN) != 0) { + IFNET_LOCK(ifp_port); + if_set_sadl(ifp_port, lp->lp_lladdr, + ETHER_ADDR_LEN, false); + IFNET_UNLOCK(ifp_port); + } + break; + } +} + +static int +lagg_ether_addmulti(struct lagg_softc *sc, struct ifreq *ifr) +{ + struct lagg_port *lp; + struct lagg_mc_entry *mc; + struct ethercom *ec; + const struct sockaddr *sa; + uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN]; + int error; + + if (sc->sc_if.if_type != IFT_ETHER) + return EPROTONOSUPPORT; + + ec = (struct ethercom *)&sc->sc_if; + sa = ifreq_getaddr(SIOCADDMULTI, ifr); + + error = ether_addmulti(sa, ec); + if (error != ENETRESET) + return error; + + error = ether_multiaddr(sa, addrlo, addrhi); + KASSERT(error == 0); + + mc = kmem_zalloc(sizeof(*mc), KM_SLEEP); + + ETHER_LOCK(ec); + mc->mc_enm = ether_lookup_multi(addrlo, addrhi, ec); + ETHER_UNLOCK(ec); + + KASSERT(mc->mc_enm != NULL); + + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + (void)lagg_lp_ioctl(lp, SIOCADDMULTI, (void *)ifr); + } + LAGG_UNLOCK(sc); + + KASSERT(sa->sa_len <= sizeof(mc->mc_addr)); + memcpy(&mc->mc_addr, sa, sa->sa_len); + LIST_INSERT_HEAD(&sc->sc_mclist, mc, mc_entry); + + return 0; +} + +static int +lagg_ether_delmulti(struct lagg_softc *sc, struct ifreq *ifr) +{ + struct lagg_port *lp; + struct lagg_mc_entry *mc; + const struct sockaddr *sa; + struct ethercom *ec; + struct ether_multi *enm; + uint8_t addrlo[ETHER_ADDR_LEN], addrhi[ETHER_ADDR_LEN]; + int error; + + ec = (struct ethercom *)&sc->sc_if; + sa = ifreq_getaddr(SIOCDELMULTI, ifr); + error = ether_multiaddr(sa, addrlo, addrhi); + if (error != 0) + return error; + + ETHER_LOCK(ec); + enm = ether_lookup_multi(addrlo, addrhi, ec); + ETHER_UNLOCK(ec); + + if (enm == NULL) + return ENOENT; + + LIST_FOREACH(mc, &sc->sc_mclist, mc_entry) { + if (mc->mc_enm == enm) + break; + } + + if (mc == NULL) + return ENOENT; + + error = ether_delmulti(sa, ec); + if (error != ENETRESET) + return error; + + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + (void)lagg_lp_ioctl(lp, SIOCDELMULTI, (void *)ifr); + } + LAGG_UNLOCK(sc); + + LIST_REMOVE(mc, mc_entry); + kmem_free(mc, sizeof(*mc)); + + return 0; +} + +static void +lagg_port_multi(struct lagg_softc *sc, struct lagg_port *lp, + u_long cmd) +{ + struct lagg_mc_entry *mc; + struct ifreq ifr; + struct ifnet *ifp_port; + const struct sockaddr *sa; + + ifp_port = lp->lp_ifp; + + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp_port->if_xname, sizeof(ifr.ifr_name)); + + LIST_FOREACH(mc, &sc->sc_mclist, mc_entry) { + sa = (struct sockaddr *)&mc->mc_addr; + KASSERT(sizeof(ifr.ifr_space) >= sa->sa_len); + memcpy(&ifr.ifr_addr, sa, sa->sa_len); + (void)lagg_lp_ioctl(lp, cmd, (void *)&ifr); + } + +} + +static void +lagg_port_syncmulti(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_port_multi(sc, lp, SIOCADDMULTI); +} + +static void +lagg_port_purgemulti(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_port_multi(sc, lp, SIOCDELMULTI); +} + +static void +lagg_port_vlan(struct lagg_softc *sc, struct lagg_port *lp, + bool set) +{ + struct lagg_vlantag *lvt; + int error; + + TAILQ_FOREACH(lvt, &sc->sc_vtags, lvt_entry) { + error = lagg_port_vlan_cb(lp, lvt, set); + if (error != 0) { + lagg_log(sc, LOG_WARNING, + "%s failed to configure vlan on %d\n", + lp->lp_ifp->if_xname, error); + } + } +} + +static void +lagg_port_syncvlan(struct lagg_softc *sc, struct lagg_port *lp) + +{ + lagg_port_vlan(sc, lp, true); +} + +static void +lagg_port_purgevlan(struct lagg_softc *sc, struct lagg_port *lp) +{ + + lagg_port_vlan(sc, lp, false); +} + +static int +lagg_addport_locked(struct lagg_softc *sc, struct lagg_port *lp) +{ + struct ifnet *ifp_port; + struct ifreq ifr; + u_char if_type; + uint64_t mtu_port; + int error; + bool stopped; + + KASSERT(IFNET_LOCKED(&sc->sc_if)); + KASSERT(LAGG_LOCKED(sc)); + KASSERT(lp != NULL); + + stopped = false; + ifp_port = lp->lp_ifp; + if (&sc->sc_if == ifp_port) { + LAGG_DPRINTF(sc, "cannot add a lagg to itself as a port\n"); + return EINVAL; + } + + if (sc->sc_nports > LAGG_MAX_PORTS) { + return ENOSPC; + } + + if (ifp_port->if_lagg != NULL) { + lp = (struct lagg_port *)ifp_port->if_lagg; + if (lp->lp_softc == sc) + return EEXIST; + return EBUSY; + } + + switch (ifp_port->if_type) { + case IFT_ETHER: + case IFT_L2VLAN: + if_type = IFT_IEEE8023ADLAG; + break; + default: + return ENOTSUP; + } + + mtu_port = ifp_port->if_mtu; + if (SIMPLEQ_EMPTY(&sc->sc_ports)) { + sc->sc_if.if_mtu = mtu_port; + } else if (sc->sc_if.if_mtu != mtu_port) { + if (ifp_port->if_ioctl == NULL) { + LAGG_DPRINTF(sc, "cannot change MTU for %s\n", + ifp_port->if_xname); + return EINVAL; + } + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp_port->if_xname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = sc->sc_if.if_mtu; + + IFNET_LOCK(ifp_port); + error = ifp_port->if_ioctl(ifp_port, SIOCSIFMTU, (void *)&ifr); + IFNET_UNLOCK(ifp_port); + + if (error != 0) { + LAGG_DPRINTF(sc, "invalid MTU for %s\n", + ifp_port->if_xname); + return error; + } + } + + IFNET_LOCK(ifp_port); + if (ISSET(ifp_port->if_flags, IFF_RUNNING)) { + ifp_port->if_stop(ifp_port, 0); + stopped = true; + } + /* to delete ipv6 link local address */ + lagg_in6_ifdetach(ifp_port); + IFNET_UNLOCK(ifp_port); + + lp->lp_softc = sc; + lp->lp_iftype = ifp_port->if_type; + lp->lp_ioctl = ifp_port->if_ioctl; + lp->lp_output = ifp_port->if_output; + lp->lp_ifcapenable = ifp_port->if_capenable; + lp->lp_mtu = mtu_port; + lp->lp_prio = LAGG_PORT_PRIO; + psref_target_init(&lp->lp_psref, lagg_port_psref_class); + + IFNET_LOCK(ifp_port); + ifp_port->if_type = if_type; + ifp_port->if_ioctl = lagg_port_ioctl; + ifp_port->if_output = lagg_port_output; + IFNET_UNLOCK(ifp_port); + + lagg_lladdr_set(sc, lp, if_type); + + error = lagg_proto_allocport(sc, lp); + if (error != 0) + goto restore_lladdr; + + lagg_port_syncmulti(sc, lp); + lagg_port_syncvlan(sc, lp); + + atomic_store_release(&ifp_port->if_lagg, (void *)lp); + SIMPLEQ_INSERT_TAIL(&sc->sc_ports, lp, lp_entry); + sc->sc_nports++; + + if (stopped) { + error = ifp_port->if_init(ifp_port); + if (error != 0) + goto remove_port; + } + + lagg_proto_startport(sc, lp); + + return 0; + +remove_port: + SIMPLEQ_REMOVE(&sc->sc_ports, lp, lagg_port, lp_entry); + sc->sc_nports--; + atomic_store_release(&ifp_port->if_lagg, NULL); + pserialize_perform(sc->sc_psz); + lagg_port_purgemulti(sc, lp); + lagg_port_purgevlan(sc, lp); + +restore_lladdr: + lagg_lladdr_unset(sc, lp, if_type); + psref_target_destroy(&lp->lp_psref, lagg_port_psref_class); + + IFNET_LOCK(ifp_port); + ifp_port->if_type = lp->lp_iftype; + ifp_port->if_ioctl = lp->lp_ioctl; + ifp_port->if_output = lp->lp_output; + if (stopped) { + (void)ifp_port->if_init(ifp_port); + } + lagg_in6_ifattach(ifp_port); + IFNET_UNLOCK(ifp_port); + + return error; +} + +static int +lagg_addport(struct lagg_softc *sc, struct ifnet *ifp_port) +{ + struct lagg_port *lp; + int error; + + lp = kmem_zalloc(sizeof(*lp), KM_SLEEP); + lp->lp_ifp = ifp_port; + + LAGG_LOCK(sc); + error = lagg_addport_locked(sc, lp); + LAGG_UNLOCK(sc); + + if (error != 0) + kmem_free(lp, sizeof(*lp)); + + return error; +} + +static void +lagg_delport_locked(struct lagg_softc *sc, struct lagg_port *lp) +{ + struct ifnet *ifp_port; + struct ifreq ifr; + u_long if_type; + bool stopped, detaching; + + KASSERT(LAGG_LOCKED(sc)); + + ifp_port = lp->lp_ifp; + stopped = false; + + SIMPLEQ_REMOVE(&sc->sc_ports, lp, lagg_port, lp_entry); + sc->sc_nports--; + atomic_store_release(&ifp_port->if_lagg, NULL); + pserialize_perform(sc->sc_psz); + + lagg_proto_stopport(sc, lp); + psref_target_destroy(&lp->lp_psref, lagg_port_psref_class); + + if (ISSET(ifp_port->if_flags, IFF_RUNNING)) { + ifp_port->if_stop(ifp_port, 0); + stopped = true; + } + + if (lp->lp_mtu != ifp_port->if_mtu) { + memset(&ifr, 0, sizeof(ifr)); + strlcpy(ifr.ifr_name, ifp_port->if_xname, sizeof(ifr.ifr_name)); + ifr.ifr_mtu = lp->lp_mtu; + (void)lagg_lp_ioctl(lp, SIOCSIFMTU, &ifr); + } + + if (SIMPLEQ_EMPTY(&sc->sc_ports)) { + sc->sc_if.if_mtu = 0; + } + + lagg_port_purgemulti(sc, lp); + lagg_port_purgevlan(sc, lp); + + detaching = lp->lp_detaching; + IFNET_LOCK(ifp_port); + if_type = ifp_port->if_type; + ifp_port->if_type = lp->lp_iftype; + ifp_port->if_ioctl = lp->lp_ioctl; + ifp_port->if_output = lp->lp_output; + ifp_port->if_capenable = lp->lp_ifcapenable; + IFNET_UNLOCK(ifp_port); + + lagg_lladdr_unset(sc, lp, if_type); + + lagg_proto_freeport(sc, lp); + kmem_free(lp, sizeof(*lp)); + + if (stopped) { + ifp_port->if_init(ifp_port); + } + + if (!detaching) { + IFNET_LOCK(ifp_port); + lagg_in6_ifattach(ifp_port); + IFNET_UNLOCK(ifp_port); + } +} + +static void +lagg_delport_all(struct lagg_softc *sc) +{ + struct lagg_port *lp, *lp0; + + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH_SAFE(sc, lp, lp0) { + lagg_delport_locked(sc, lp); + } + LAGG_UNLOCK(sc); +} + +static int +lagg_delport(struct lagg_softc *sc, struct ifnet *ifp_port) +{ + struct lagg_port *lp; + + KASSERT(IFNET_LOCKED(&sc->sc_if)); + + lp = ifp_port->if_lagg; + if (lp == NULL || lp->lp_softc != sc) + return ENOENT; + + LAGG_LOCK(sc); + lagg_delport_locked(sc, lp); + LAGG_UNLOCK(sc); + + return 0; +} + +static int +lagg_get_stats(struct lagg_softc *sc, struct lagg_req *resp, + size_t nports) +{ + struct lagg_variant *var; + struct lagg_port *lp; + struct laggreqport *port; + struct psref psref; + struct ifnet *ifp; + int bound; + size_t n; + + bound = curlwp_bind(); + var = lagg_variant_getref(sc, &psref); + + resp->lrq_proto = var->lv_proto; + + lagg_proto_stat(var, &resp->lrq_reqproto); + + n = 0; + LAGG_LOCK(sc); + LAGG_PORTS_FOREACH(sc, lp) { + if (n < nports) { + port = &resp->lrq_reqports[n]; + + ifp = lp->lp_ifp; + strlcpy(port->rp_portname, ifp->if_xname, + sizeof(port->rp_portname)); + + port->rp_prio = lp->lp_prio; + port->rp_flags = lp->lp_flags; + lagg_proto_portstat(var, lp, port); + } + n++; + } + LAGG_UNLOCK(sc); + + resp->lrq_nports = n; + + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); + + if (resp->lrq_nports > nports) { + return ENOMEM; + } + return 0; +} + +static int +lagg_config_promisc(struct lagg_softc *sc, struct lagg_port *lp) +{ + struct ifnet *ifp; + int error; + int status; + + ifp = &sc->sc_if; + status = ISSET(ifp->if_flags, IFF_PROMISC) ? 1 : 0; + + error = ifpromisc(lp->lp_ifp, status); + + return error; +} + +static int +lagg_port_ioctl(struct ifnet *ifp, u_long cmd, void *data) +{ + struct ifreq *ifr = (struct ifreq *)data; + struct lagg_softc *sc; + struct lagg_variant *var; + struct lagg_port *lp; + struct lagg_req laggreq; + struct laggreqport *rp; + struct psref psref; + int error = 0; + int bound; + u_int ifflags; + + if ((lp = ifp->if_lagg) == NULL || + (sc = lp->lp_softc) == NULL) { + goto fallback; + } + + switch (cmd) { + case SIOCGLAGGPORT: + error = copyin(ifr->ifr_data, &laggreq, sizeof(laggreq)); + if (error != 0) + break; + + if (laggreq.lrq_nports != 1) { + error = EINVAL; + break; + } + + rp = &laggreq.lrq_reqports[0]; + + if (rp->rp_portname[0] == '\0' || + ifunit(rp->rp_portname) != ifp) { + error = EINVAL; + break; + } + + bound = curlwp_bind(); + var = lagg_variant_getref(sc, &psref); + + rp->rp_prio = lp->lp_prio; + rp->rp_flags = lp->lp_flags; + lagg_proto_portstat(var, lp, rp); + lagg_variant_putref(var, &psref); + curlwp_bindx(bound); + break; + case SIOCSIFCAP: + case SIOCSIFMTU: + /* Do not allow the setting to be cahanged once joined */ + error = EINVAL; + break; + case SIOCSIFFLAGS: + ifflags = ifp->if_flags; + error = LAGG_PORT_IOCTL(lp, cmd, data); + ifflags ^= ifp->if_flags; + + if (ISSET(ifflags, IFF_RUNNING)) { + lagg_proto_linkstate(sc, lp); + } + break; + default: + goto fallback; + } + + return error; +fallback: + if (lp != NULL) { + error = LAGG_PORT_IOCTL(lp, cmd, data); + } else { + error = ENOTTY; + } + + return error; +} + +static int +lagg_port_output(struct ifnet *ifp, struct mbuf *m, + const struct sockaddr *dst, const struct rtentry *rt) +{ + struct lagg_port *lp = ifp->if_lagg; + int error = 0; + + switch (dst->sa_family) { + case pseudo_AF_HDRCMPLT: + case AF_UNSPEC: + if (lp != NULL) + error = lp->lp_output(ifp, m, dst, rt); + else + error = ENETDOWN; + break; + default: + m_freem(m); + error = ENETDOWN; + } + + return error; +} + +void +lagg_ifdetach(struct ifnet *ifp_port) +{ + struct lagg_port *lp; + struct lagg_softc *sc; + struct psref psref; + int s; + + IFNET_ASSERT_UNLOCKED(ifp_port); + + s = pserialize_read_enter(); + lp = atomic_load_consume(&ifp_port->if_lagg); + if (lp == NULL) { + pserialize_read_exit(s); + return; + } + lagg_port_getref(lp, &psref); + pserialize_read_exit(s); + + sc = lp->lp_softc; + if (sc != NULL) { + IFNET_LOCK(&sc->sc_if); + LAGG_LOCK(sc); + lagg_port_putref(lp, &psref); + } else { + lagg_port_putref(lp, &psref); + return; + } + + lp->lp_detaching = true; + lagg_delport_locked(sc, lp); + LAGG_UNLOCK(sc); + IFNET_UNLOCK(&sc->sc_if); +} + +void +lagg_linkstate_changed(struct ifnet *ifp) +{ + struct lagg_port *lp; + struct psref psref; + int s, bound; + + s = pserialize_read_enter(); + lp = atomic_load_consume(&ifp->if_lagg); + if (lp != NULL) { + bound = curlwp_bind(); + lagg_port_getref(lp, &psref); + } else { + pserialize_read_exit(s); + return; + } + pserialize_read_exit(s); + + lagg_proto_linkstate(lp->lp_softc, lp); + lagg_port_putref(lp, &psref); + curlwp_bindx(bound); +} + +void +lagg_port_getref(struct lagg_port *lp, struct psref *psref) +{ + + psref_acquire(psref, &lp->lp_psref, lagg_port_psref_class); +} + +void +lagg_port_putref(struct lagg_port *lp, struct psref *psref) +{ + + psref_release(psref, &lp->lp_psref, lagg_port_psref_class); +} + +void +lagg_log(struct lagg_softc *sc, int lvl, const char *fmt, ...) +{ + va_list ap; + + if (lvl == LOG_DEBUG && !lagg_debug_enable(sc)) + return; + + log(lvl, "%s: ", sc->sc_if.if_xname); + va_start(ap, fmt); + vlog(lvl, fmt, ap); + va_end(ap); +} + +static void +lagg_workq_work(struct work *wk, void *context) +{ + struct lagg_work *lw; + + lw = container_of(wk, struct lagg_work, lw_cookie); + + atomic_cas_uint(&lw->lw_state, LAGG_WORK_ENQUEUED, LAGG_WORK_IDLE); + lw->lw_func(lw, lw->lw_arg); +} + +struct workqueue * +lagg_workq_create(const char *name, pri_t prio, int ipl, int flags) +{ + struct workqueue *wq; + int error; + + error = workqueue_create(&wq, name, lagg_workq_work, + NULL, prio, ipl, flags); + + if (error) + return NULL; + + return wq; +} + +void +lagg_workq_destroy(struct workqueue *wq) +{ + + workqueue_destroy(wq); +} + +void +lagg_workq_add(struct workqueue *wq, struct lagg_work *lw) +{ + + if (atomic_cas_uint(&lw->lw_state, LAGG_WORK_IDLE, + LAGG_WORK_ENQUEUED) != LAGG_WORK_IDLE) + return; + + KASSERT(lw->lw_func != NULL); + kpreempt_disable(); + workqueue_enqueue(wq, &lw->lw_cookie, NULL); + kpreempt_enable(); +} + +void +lagg_workq_wait(struct workqueue *wq, struct lagg_work *lw) +{ + + atomic_swap_uint(&lw->lw_state, LAGG_WORK_STOPPING); + workqueue_wait(wq, &lw->lw_cookie); +} + +/* + * Module infrastructure + */ +#include + +IF_MODULE(MODULE_CLASS_DRIVER, lagg, NULL) diff --git a/sys/net/lagg/if_lagg.h b/sys/net/lagg/if_lagg.h new file mode 100644 index 00000000000..897908ff8a2 --- /dev/null +++ b/sys/net/lagg/if_lagg.h @@ -0,0 +1,149 @@ +/* $NetBSD: $ */ + +#ifndef _NET_LAGG_IF_LAGG_H_ +#define _NET_LAGG_IF_LAGG_H_ + +typedef enum { + LAGG_PROTO_NONE = 0, + LAGG_PROTO_LACP, /* 802.1ax lacp */ + LAGG_PROTO_FAILOVER, + LAGG_PROTO_LOADBALANCE, + LAGG_PROTO_MAX, +} lagg_proto; + +/* IEEE802.3ad LACP protocol definitions.*/ +#define LACP_STATE_ACTIVITY __BIT(0) +#define LACP_STATE_TIMEOUT __BIT(1) +#define LACP_STATE_AGGREGATION __BIT(2) +#define LACP_STATE_SYNC __BIT(3) +#define LACP_STATE_COLLECTING __BIT(4) +#define LACP_STATE_DISTRIBUTING __BIT(5) +#define LACP_STATE_DEFAULTED __BIT(6) +#define LACP_STATE_EXPIRED __BIT(7) +#define LACP_STATE_BITS \ + "\020" \ + "\001ACTIVITY" \ + "\002TIMEOUT" \ + "\003AGGREGATION" \ + "\004SYNC" \ + "\005COLLECTING" \ + "\006DISTRIBUTING" \ + "\007DEFAULTED" \ + "\010EXPIRED" +#define LACP_STATESTR_LEN 256 +#define LACP_MAC_LEN ETHER_ADDR_LEN + +enum lagg_ioctl_lacp { + LAGGIOC_LACPSETFLAGS = 1, + LAGGIOC_LACPCLRFLAGS, + LAGGIOC_LACPSETMAXPORTS, + LAGGIOC_LACPCLRMAXPORTS, +}; + +#define LAGGREQLACP_OPTIMISTIC __BIT(0) +#define LAGGREQLACP_DUMPDU __BIT(1) +#define LAGGREQLACP_STOPDU __BIT(2) +#define LAGGREQLACP_MULTILS __BIT(3) +#define LAGGREQLACP_BITS \ + "\020" \ + "\001OPTIMISTIC" \ + "\002DUMPDU" \ + "\003STOPDU" \ + "\004MULTILS" + +struct laggreq_lacp { + uint32_t command; + uint32_t flags; + size_t maxports; + + uint16_t actor_prio; + uint8_t actor_mac[LACP_MAC_LEN]; + uint16_t actor_key; + uint16_t partner_prio; + uint8_t partner_mac[LACP_MAC_LEN]; + uint16_t partner_key; +}; + +enum lagg_ioctl_fail { + LAGGIOC_FAILSETFLAGS = 1, + LAGGIOC_FAILCLRFLAGS +}; + +#define LAGGREQFAIL_RXALL __BIT(0) + +struct laggreq_fail { + uint32_t command; + uint32_t flags; +}; + +struct laggreqproto { + union { + struct laggreq_lacp proto_lacp; + struct laggreq_fail proto_fail; + } rp_proto; +#define rp_lacp rp_proto.proto_lacp +#define rp_fail rp_proto.proto_fail +}; + +#define LAGG_PORT_SLAVE 0 +#define LAGG_PORT_MASTER __BIT(0) +#define LAGG_PORT_STACK __BIT(1) +#define LAGG_PORT_ACTIVE __BIT(2) +#define LAGG_PORT_COLLECTING __BIT(3) +#define LAGG_PORT_DISTRIBUTING __BIT(4) +#define LAGG_PORT_STANDBY __BIT(5) +#define LAGG_PORT_BITS \ + "\020" \ + "\001MASTER" \ + "\002STACK" \ + "\003ACTIVE" \ + "\004COLLECTING" \ + "\005DISTRIBUTING" \ + "\006STANDBY" +#define LACP_PORTSTR_LEN 256 + +struct laggreq_lacpport { + uint16_t partner_prio; + uint8_t partner_mac[LACP_MAC_LEN]; + uint16_t partner_key; + + uint16_t actor_portprio; + uint16_t actor_portno; + uint8_t actor_state; + uint16_t partner_portprio; + uint16_t partner_portno; + uint8_t partner_state; +}; + +struct laggreqport { + char rp_portname[IFNAMSIZ]; + uint32_t rp_prio; + uint32_t rp_flags; + + union { + struct laggreq_lacpport port_lacp; + } rp_port; +#define rp_lacpport rp_port.port_lacp +}; + +enum lagg_ioctl { + LAGGIOC_NOCMD, + LAGGIOC_SETPROTO = 1, + LAGGIOC_SETPROTOOPT, + LAGGIOC_ADDPORT, + LAGGIOC_DELPORT, + LAGGIOC_SETPORTPRI, +}; + +struct lagg_req { + uint32_t lrq_ioctl; + lagg_proto lrq_proto; + size_t lrq_nports; + struct laggreqproto lrq_reqproto; + struct laggreqport lrq_reqports[1]; +}; + +#define SIOCGLAGG SIOCGIFGENERIC +#define SIOCSLAGG SIOCSIFGENERIC +#define SIOCGLAGGPORT SIOCGIFGENERIC +#endif diff --git a/sys/net/lagg/if_lagg_lacp.c b/sys/net/lagg/if_lagg_lacp.c new file mode 100644 index 00000000000..5bea3ac7703 --- /dev/null +++ b/sys/net/lagg/if_lagg_lacp.c @@ -0,0 +1,2621 @@ +/* $NetBSD: $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c)2005 YAMAMOTO Takashi, + * Copyright (c)2008 Andrew Thompson + * Copyright (c)2021 Internet Initiative Japan, Inc. + * 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 +__KERNEL_RCSID(0, "$NetBSD:$"); + +#ifdef _KERNEL_OPT +#include "opt_lagg.h" +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#define LACP_SYSTEMIDSTR_LEN 32 + +enum { + LACP_TIMER_CURRENT_WHILE = 0, + LACP_TIMER_PERIODIC, + LACP_TIMER_WAIT_WHILE, + LACP_NTIMER +}; + +enum { + LACP_PTIMER_DISTRIBUTING = 0, + LACP_NPTIMER +}; + +enum lacp_selected { + LACP_UNSELECTED, + LACP_STANDBY, + LACP_SELECTED, +}; + +enum lacp_mux_state { + LACP_MUX_DETACHED, + LACP_MUX_WAITING, + LACP_MUX_ATTACHED, + LACP_MUX_COLLECTING, + LACP_MUX_DISTRIBUTING, + LACP_MUX_INIT, +}; + +struct lacp_aggregator_systemid { + uint16_t sid_prio; + uint16_t sid_key; + uint8_t sid_mac[LACP_MAC_LEN]; +}; + +struct lacp_aggregator { + TAILQ_ENTRY(lacp_aggregator) + la_q; + LIST_HEAD(, lacp_port) + la_ports; + ssize_t la_attached_port; + + struct lacp_aggregator_systemid + la_sid; +}; + +struct lacp_portinfo { + uint8_t lpi_state; + uint16_t lpi_portno; +#define LACP_PORTNO_NONE 0 + uint16_t lpi_portprio; +}; + +struct lacp_port { + struct lagg_port *lp_laggport; + int lp_timer[LACP_NTIMER]; + uint32_t lp_marker_xid; + enum lacp_selected lp_selected; + enum lacp_mux_state lp_mux_state; + + struct lacp_portinfo lp_actor; + struct lacp_portinfo lp_partner; + struct lacp_aggregator *lp_aggregator; + struct lacp_aggregator_systemid + lp_aggregator_sidbuf; + uint32_t lp_media; + int lp_pending; + LIST_ENTRY(lacp_port) lp_entry_la; + struct timeval lp_last_lacpdu; + int lp_lacpdu_sent; + bool lp_collector; + + unsigned int lp_flags; +#define LACP_PORT_NTT __BIT(0) +#define LACP_PORT_MARK __BIT(1) + + struct lagg_work lp_work_smtx; + struct lagg_work lp_work_marker; +}; + +struct lacp_portmap { + size_t pm_count; + struct lagg_port *pm_ports[LACP_MAX_PORTS]; +}; + +struct lacp_softc { + struct lagg_softc *lsc_softc; + kmutex_t lsc_lock; + pserialize_t lsc_psz; + bool lsc_running; + bool lsc_suppress_distributing; + int lsc_timer[LACP_NPTIMER]; + uint8_t lsc_system_mac[LACP_MAC_LEN]; + uint16_t lsc_system_prio; + uint16_t lsc_key; + size_t lsc_max_ports; + size_t lsc_activemap; + struct lacp_portmap lsc_portmaps[2]; /* active & idle */ + struct lacp_aggregator *lsc_aggregator; + TAILQ_HEAD(, lacp_aggregator) + lsc_aggregators; + struct workqueue *lsc_workq; + struct lagg_work lsc_work_tick; + callout_t lsc_tick; + + char lsc_evgroup[32]; + struct evcnt lsc_mgethdr_failed; + struct evcnt lsc_mpullup_failed; + struct evcnt lsc_badlacpdu; + struct evcnt lsc_badmarkerdu; + + bool lsc_optimistic; + bool lsc_stop_lacpdu; + bool lsc_dump_du; + bool lsc_multi_linkspeed; +}; + +/* + * Locking notes: + * - Items in struct lacp_softc are protected by + * lsc_lock (an adaptive mutex) + * - lsc_activemap is protected by pserialize (lsc_psz) + * - Items of struct lagg_port in lsc_portmaps are protected by + * protected by both pserialize (lsc_psz) and psref (lp_psref) + * - Updates for lsc_activemap and lsc_portmaps is serialized by + * sc_lock in struct lagg_softc + * - Other locking notes are described in if_laggproto.h + */ + +static void lacp_dprintf(const struct lacp_softc *, + const struct lacp_port *, const char *, ...) + __attribute__((__format__(__printf__, 3, 4))); + +#ifdef LACP_DEBUG +#define LACP_DPRINTF(a) do { lacp_dprintf a; } while (/*CONSTCOND*/ 0) +#define LACP_PEERINFO_IDSTR(_pi, _b, _bs) \ + lacp_peerinfo_idstr(_pi, _b, _bs) +#define LACP_STATE_STR(_s, _b, _bs) lacp_state_str(_s, _b, _bs) +#define LACP_AGGREGATOR_STR(_a, _b, _bs) \ + lacp_aggregator_str(_a, _b, _bs) +#define __LACPDEBUGUSED +#else +#define LACP_DPRINTF(a) __nothing +#define LACP_PEERINFO_IDSTR(_pi, _b, _bs) __nothing +#define LACP_STATE_STR(_s, _b, _bs) __nothing +#define LACP_AGGREGATOR_STR(_a, _b, _bs) __nothing +#define __LACPDEBUGUSED __unused +#endif + +#define LACP_LOCK(_sc) mutex_enter(&(_sc)->lsc_lock) +#define LACP_UNLOCK(_sc) mutex_exit(&(_sc)->lsc_lock) +#define LACP_LOCKED(_sc) mutex_owned(&(_sc)->lsc_lock) +#define LACP_LINKSTATE(_sc, _lp) \ + lacp_linkstate((struct lagg_proto_softc *)(_sc), (_lp)) +#define LACP_TIMER_ARM(_lacpp, _timer, _val) \ + (_lacpp)->lp_timer[(_timer)] = (_val) +#define LACP_TIMER_DISARM(_lacpp, _timer) \ + LACP_TIMER_ARM((_lacpp), (_timer), 0) +#define LACP_TIMER_ISARMED(_lacpp, _timer) \ + ((_lacpp)->lp_timer[(_timer)] > 0) +#define LACP_PTIMER_ARM(_sc, _timer, _val) \ + (_sc)->lsc_timer[(_timer)] = (_val) +#define LACP_PTIMER_DISARM(_sc, _timer) \ + LACP_PTIMER_ARM((_sc), (_timer), 0) +#define LACP_PTIMER_ISARMED(_sc, _timer) \ + ((_sc)->lsc_timer[(_timer)] > 0) +#define LACP_STATE_EQ(_s1, _s2, _mask) (!ISSET((_s1) ^ (_s2), (_mask))) +#define LACP_PORT_XNAME(_lacpp) (_lacpp != NULL) ? \ + (_lacpp)->lp_laggport->lp_ifp->if_xname : "(unknown)" +#define LACP_ISDUMPING(_sc) (_sc)->lsc_dump_du +#define LACP_PORTMAP_ACTIVE(_sc) \ + atomic_load_consume(&(_sc)->lsc_activemap) +#define LACP_PORTMAP_NEXT(_sc) \ + (((LACP_PORTMAP_ACTIVE((_sc))) ^ 0x01) &0x01) +#define LACP_SYS_PRI(_la) ntohs((_la)->la_sid.sid_prio) +#define LACP_TLV_PARSE(_du, _st, _name, _tlvlist) \ + tlv_parse(&(_du)->_name, \ + sizeof(_st) - offsetof(_st, _name), \ + (_tlvlist)) + +static void lacp_tick(void *); +static void lacp_tick_work(struct lagg_work *, void *); +static uint32_t lacp_ifmedia2lacpmedia(u_int); +static void lacp_port_disable(struct lacp_softc *, struct lacp_port *); +static void lacp_port_enable(struct lacp_softc *, struct lacp_port *); +static void lacp_peerinfo_actor(struct lacp_softc *, struct lacp_port *, + struct lacpdu_peerinfo *); +static void lacp_peerinfo_partner(struct lacp_port *, + struct lacpdu_peerinfo *); +static struct lagg_port * + lacp_select_tx_port(struct lacp_softc *, struct mbuf *, + struct psref *); +static void lacp_suppress_distributing(struct lacp_softc *); +static void lacp_distributing_timer(struct lacp_softc *); + +static void lacp_select(struct lacp_softc *, struct lacp_port *); +static void lacp_unselect(struct lacp_softc *, struct lacp_port *); +static void lacp_selected_update(struct lacp_softc *, + struct lacp_aggregator *); +static void lacp_sm_port_init(struct lacp_softc *, + struct lacp_port *, struct lagg_port *); +static int lacp_set_mux(struct lacp_softc *, + struct lacp_port *, enum lacp_mux_state); +static void lacp_sm_mux(struct lacp_softc *, struct lacp_port *); +static void lacp_sm_mux_timer(struct lacp_softc *, struct lacp_port *); +static void lacp_sm_rx(struct lacp_softc *, struct lacp_port *, + struct lacpdu_peerinfo *, struct lacpdu_peerinfo *); +static void lacp_sm_rx_set_expired(struct lacp_port *); +static void lacp_sm_rx_timer(struct lacp_softc *, struct lacp_port *); +static void lacp_sm_rx_record_default(struct lacp_softc *, + struct lacp_port *); + +static void lacp_sm_tx(struct lacp_softc *, struct lacp_port *); +static void lacp_sm_tx_work(struct lagg_work *, void *); +static void lacp_sm_ptx_timer(struct lacp_softc *, struct lacp_port *); +static void lacp_sm_ptx_schedule(struct lacp_port *); +static void lacp_sm_ptx_update_timeout(struct lacp_port *, uint8_t); + +static void lacp_marker_work(struct lagg_work *, void *); +static void lacp_dump_lacpdutlv(const struct lacpdu_peerinfo *, + const struct lacpdu_peerinfo *, + const struct lacpdu_collectorinfo *); +static void lacp_dump_markertlv(const struct markerdu_info *, + const struct markerdu_info *); + +typedef void (*lacp_timer_func_t)(struct lacp_softc *, struct lacp_port *); +static const lacp_timer_func_t lacp_timer_funcs[] = { + [LACP_TIMER_CURRENT_WHILE] = lacp_sm_rx_timer, + [LACP_TIMER_PERIODIC] = lacp_sm_ptx_timer, + [LACP_TIMER_WAIT_WHILE] = lacp_sm_mux_timer, +}; +typedef void (*lacp_prototimer_func_t)(struct lacp_softc *); +static const lacp_prototimer_func_t lacp_ptimer_funcs[] = { + [LACP_PTIMER_DISTRIBUTING] = lacp_distributing_timer, +}; + +static void +lacp_dprintf(const struct lacp_softc *lsc, const struct lacp_port *lacpp, + const char *fmt, ...) +{ + struct lagg_softc *sc; + va_list va; + + if (lsc != NULL && lsc->lsc_softc != NULL) { + sc = lsc->lsc_softc; + printf("%s", sc->sc_if.if_xname); + } else { + printf("lacp"); + } + + if (lacpp != NULL) + printf("(%s)", LACP_PORT_XNAME(lacpp)); + + printf(": "); + + va_start(va, fmt); + vprintf(fmt, va); + va_end(va); +} + +static inline void +lacp_evcnt_attach(struct lacp_softc *lsc, + struct evcnt *ev, const char *name) +{ + + evcnt_attach_dynamic(ev, EVCNT_TYPE_MISC, NULL, + lsc->lsc_evgroup, name); +} + +static inline bool +lacp_iscollecting(struct lacp_port *lacpp) +{ + + return atomic_load_relaxed(&lacpp->lp_collector); +} + +static inline bool +lacp_isdistributing(struct lacp_port *lacpp) +{ + + return ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING); +} + +static inline bool +lacp_isactive(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + if (lacpp->lp_selected != LACP_SELECTED) + return false; + + if (lacpp->lp_aggregator == NULL) + return false; + + if (lacpp->lp_aggregator != lsc->lsc_aggregator) + return false; + + return true; +} + +static inline void +lacp_mcastaddr(struct ifreq *ifr, const char *if_xname) +{ + static const uint8_t addr[ETHER_ADDR_LEN] = + { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x02 }; + + memset(ifr, 0, sizeof(*ifr)); + + strlcpy(ifr->ifr_name, if_xname, + sizeof(ifr->ifr_name)); + ifr->ifr_addr.sa_len = sizeof(ifr->ifr_addr); + ifr->ifr_addr.sa_family = AF_UNSPEC; + + KASSERT(sizeof(ifr->ifr_addr) >= sizeof(addr)); + memcpy(&ifr->ifr_addr.sa_data, addr, sizeof(addr)); +} + +static inline u_int +lacp_portmap_linkstate(struct lacp_portmap *pm) +{ + + if (pm->pm_count == 0) + return LINK_STATE_DOWN; + + return LINK_STATE_UP; +} + +static inline struct lacp_port * +lacp_port_priority_max(struct lacp_port *a, struct lacp_port *b) +{ + uint16_t pri_a, pri_b; + + pri_a = ntohs(a->lp_actor.lpi_portprio); + pri_b = ntohs(b->lp_actor.lpi_portprio); + + if (pri_a < pri_b) + return a; + if (pri_b < pri_a) + return b; + + pri_a = ntohs(a->lp_partner.lpi_portprio); + pri_b = ntohs(b->lp_partner.lpi_portprio); + + if (pri_a < pri_b) + return a; + if (pri_b < pri_a) + return b; + + if (a->lp_media > b->lp_media) + return a; + if (b->lp_media > a->lp_media) + return b; + + return a; +} + +static void +tlv_parse(void *vp, size_t len, struct tlv *list) +{ + struct tlvhdr *th; + uint8_t *p; + size_t l, i; + + th = (struct tlvhdr *)vp; + p = (uint8_t *)vp; + + for (l = 0; l < len; l += th->tlv_length) { + th = (struct tlvhdr *)(p + l); + + if (th->tlv_type == TLV_TYPE_TERMINATE) + break; + + for (i = 0; list[i].tlv_t != TLV_TYPE_TERMINATE; i++) { + if (th->tlv_type != list[i].tlv_t) + continue; + + if (th->tlv_length - sizeof(*th) != list[i].tlv_l) + break; + + if (list[i].tlv_v == NULL) { + list[i].tlv_v = + (void *)((uint8_t *)th + sizeof(*th)); + } + + break; + } + } +} + +int +lacp_attach(struct lagg_softc *sc, struct lagg_proto_softc **lscp) +{ + struct lacp_softc *lsc; + char xnamebuf[MAXCOMLEN]; + int error; + + KASSERT(LAGG_LOCKED(sc)); + + lsc = kmem_zalloc(sizeof(*lsc), KM_NOSLEEP); + if (lsc == NULL) + return ENOMEM; + + mutex_init(&lsc->lsc_lock, MUTEX_DEFAULT, IPL_SOFTNET); + lsc->lsc_softc = sc; + lsc->lsc_key = htons(if_get_index(&sc->sc_if)); + lsc->lsc_system_prio = htons(LACP_SYSTEM_PRIO); + lsc->lsc_running = false; + lsc->lsc_max_ports = LACP_MAX_PORTS; + lsc->lsc_multi_linkspeed = true; + TAILQ_INIT(&lsc->lsc_aggregators); + + lagg_work_set(&lsc->lsc_work_tick, lacp_tick_work, lsc); + snprintf(xnamebuf, sizeof(xnamebuf), "%s.lacp", + sc->sc_if.if_xname); + lsc->lsc_workq = lagg_workq_create(xnamebuf, + PRI_SOFTNET, IPL_SOFTNET, WQ_MPSAFE); + if (lsc->lsc_workq == NULL) { + lagg_log(sc, LOG_ERR, "workqueue create failed\n"); + error = ENOMEM; + goto destroy_lock; + } + + lsc->lsc_psz = pserialize_create(); + + callout_init(&lsc->lsc_tick, CALLOUT_MPSAFE); + callout_setfunc(&lsc->lsc_tick, lacp_tick, lsc); + + snprintf(lsc->lsc_evgroup, sizeof(lsc->lsc_evgroup), + "%s-lacp", sc->sc_if.if_xname); + lacp_evcnt_attach(lsc, &lsc->lsc_mgethdr_failed, "MGETHDR failed"); + lacp_evcnt_attach(lsc, &lsc->lsc_mpullup_failed, "m_pullup failed"); + lacp_evcnt_attach(lsc, &lsc->lsc_badlacpdu, "Bad LACPDU recieved"); + lacp_evcnt_attach(lsc, &lsc->lsc_badmarkerdu, "Bad MarkerDU recieved"); + + if_link_state_change(&sc->sc_if, LINK_STATE_DOWN); + + *lscp = (struct lagg_proto_softc *)lsc; + return 0; + +destroy_lock: + mutex_destroy(&lsc->lsc_lock); + + return error; +} + +void +lacp_detach(struct lagg_proto_softc *xlsc) +{ + struct lacp_softc *lsc = (struct lacp_softc *)xlsc; + struct lagg_softc *sc = lsc->lsc_softc; + + KASSERT(LAGG_LOCKED(lsc->lsc_softc)); + KASSERT(TAILQ_EMPTY(&lsc->lsc_aggregators)); + KASSERT(SIMPLEQ_EMPTY(&sc->sc_ports)); + + lacp_down(xlsc); + + evcnt_detach(&lsc->lsc_mgethdr_failed); + evcnt_detach(&lsc->lsc_mpullup_failed); + evcnt_detach(&lsc->lsc_badlacpdu); + evcnt_detach(&lsc->lsc_badmarkerdu); + lagg_workq_destroy(lsc->lsc_workq); + pserialize_destroy(lsc->lsc_psz); + mutex_destroy(&lsc->lsc_lock); + kmem_free(lsc, sizeof(*lsc)); +} + +int +lacp_up(struct lagg_proto_softc *xlsc) +{ + struct lagg_softc *sc; + struct lagg_port *lp; + struct lacp_softc *lsc; + + lsc = (struct lacp_softc *)xlsc; + sc = lsc->lsc_softc; + + KASSERT(LAGG_LOCKED(sc)); + + LACP_LOCK(lsc); + lsc->lsc_running = true; + callout_schedule(&lsc->lsc_tick, hz); + LACP_UNLOCK(lsc); + + LAGG_PORTS_FOREACH(sc, lp) { + lacp_linkstate(xlsc, lp); + } + + LACP_DPRINTF((lsc, NULL, "lacp start\n")); + + return 0; +} + +static void +lacp_down_locked(struct lacp_softc *lsc) +{ + struct lagg_softc *sc; + struct lagg_port *lp; + + sc = lsc->lsc_softc; + + KASSERT(LAGG_LOCKED(sc)); + KASSERT(LACP_LOCKED(lsc)); + + lsc->lsc_running = false; + callout_halt(&lsc->lsc_tick, &lsc->lsc_lock); + + LAGG_PORTS_FOREACH(sc, lp) { + lacp_port_disable(lsc, lp->lp_proto_ctx); + } + + LACP_DPRINTF((lsc, NULL, "lacp stopped\n")); +} + +void +lacp_down(struct lagg_proto_softc *xlsc) +{ + struct lacp_softc *lsc; + + lsc = (struct lacp_softc *)xlsc; + + KASSERT(LAGG_LOCKED(lsc->lsc_softc)); + + LACP_LOCK(lsc); + lacp_down_locked(lsc); + LACP_UNLOCK(lsc); +} + +int +lacp_transmit(struct lagg_proto_softc *xlsc, struct mbuf *m) +{ + struct lacp_softc *lsc; + struct lagg_port *lp; + struct ifnet *ifp; + struct psref psref; + + lsc = (struct lacp_softc *)xlsc; + + if (__predict_false(lsc->lsc_suppress_distributing)) { + LACP_DPRINTF((lsc, NULL, "waiting transit\n")); + m_freem(m); + return ENOBUFS; + } + + lp = lacp_select_tx_port(lsc, m, &psref); + if (__predict_false(lp == NULL)) { + LACP_DPRINTF((lsc, NULL, "no distributing port\n")); + ifp = &lsc->lsc_softc->sc_if; + if_statinc(ifp, if_oerrors); + m_freem(m); + return ENOENT; + } + + lagg_enqueue(lsc->lsc_softc, lp, m); + lagg_port_putref(lp, &psref); + + return 0; +} + +int +lacp_allocport(struct lagg_proto_softc *xlsc, struct lagg_port *lp) +{ + struct lagg_softc *sc; + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct ifreq ifr; + int error; + + lsc = (struct lacp_softc *)xlsc; + sc = lsc->lsc_softc; + + KASSERT(LAGG_LOCKED(sc)); + + IFNET_LOCK(lp->lp_ifp); + lacp_mcastaddr(&ifr, lp->lp_ifp->if_xname); + error = lp->lp_ioctl(lp->lp_ifp, SIOCADDMULTI, (void *)&ifr); + IFNET_UNLOCK(lp->lp_ifp); + + if (error != 0) { + lagg_log(sc, LOG_ERR, "SIOCADDMULTI failed on %s\n", + lp->lp_ifp->if_xname); + return error; + } + + lacpp = kmem_zalloc(sizeof(*lacpp), KM_NOSLEEP); + if (lacpp == NULL) + return ENOMEM; + + lagg_work_set(&lacpp->lp_work_smtx, lacp_sm_tx_work, lsc); + lagg_work_set(&lacpp->lp_work_marker, lacp_marker_work, lsc); + + LACP_LOCK(lsc); + if (LAGG_PORTS_EMPTY(sc)) { + memcpy(lsc->lsc_system_mac, LAGG_CLLADDR(sc), + sizeof(lsc->lsc_system_mac)); + } + lacp_sm_port_init(lsc, lacpp, lp); + LACP_UNLOCK(lsc); + + lp->lp_proto_ctx = (void *)lacpp; + lp->lp_prio = ntohs(lacpp->lp_actor.lpi_portprio); + LACP_LINKSTATE(lsc, lp); + + return 0; +} + +void +lacp_startport(struct lagg_proto_softc *xlsc, struct lagg_port *lp) +{ + struct lacp_port *lacpp; + uint16_t prio; + + lacpp = lp->lp_proto_ctx; + + prio = (uint16_t)MIN(lp->lp_prio, UINT16_MAX); + lacpp->lp_actor.lpi_portprio = htons(prio); + + LACP_LINKSTATE((struct lacp_softc *)xlsc, lp); +} + +void +lacp_stopport(struct lagg_proto_softc *xlsc, struct lagg_port *lp) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp; + int i; + + lsc = (struct lacp_softc *)xlsc; + lacpp = lp->lp_proto_ctx; + + KASSERT(LAGG_LOCKED(lsc->lsc_softc)); + + LACP_LOCK(lsc); + for (i = 0; i < LACP_NTIMER; i++) { + LACP_TIMER_DISARM(lacpp, i); + } + + lacp_port_disable(lsc, lacpp); + LACP_UNLOCK(lsc); +} + +void +lacp_freeport(struct lagg_proto_softc *xlsc, struct lagg_port *lp) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp, *lacpp0; + struct lagg_softc *sc; + struct lagg_port *lp0; + struct ifreq ifr; + + lsc = (struct lacp_softc *)xlsc; + sc = lsc->lsc_softc; + lacpp = lp->lp_proto_ctx; + + KASSERT(LAGG_LOCKED(sc)); + + lagg_workq_wait(lsc->lsc_workq, &lacpp->lp_work_smtx); + lagg_workq_wait(lsc->lsc_workq, &lacpp->lp_work_marker); + + lacp_mcastaddr(&ifr, LACP_PORT_XNAME(lacpp)); + + IFNET_LOCK(lp->lp_ifp); + (void)lp->lp_ioctl(lp->lp_ifp, SIOCDELMULTI, (void *)&ifr); + IFNET_UNLOCK(lp->lp_ifp); + + LACP_LOCK(lsc); + if (LAGG_PORTS_EMPTY(sc)) { + memset(lsc->lsc_system_mac, 0, + sizeof(lsc->lsc_system_mac)); + } else if (memcmp(LAGG_CLLADDR(sc), lsc->lsc_system_mac, + sizeof(lsc->lsc_system_mac)) != 0) { + memcpy(lsc->lsc_system_mac, LAGG_CLLADDR(sc), + sizeof(lsc->lsc_system_mac)); + LAGG_PORTS_FOREACH(sc, lp0) { + lacpp0 = lp0->lp_proto_ctx; + lacpp0->lp_selected = LACP_UNSELECTED; + /* reset state of the port at lacp_set_mux() */ + } + } + LACP_UNLOCK(lsc); + + lp->lp_proto_ctx = NULL; + kmem_free(lacpp, sizeof(*lacpp)); +} + +void +lacp_protostat(struct lagg_proto_softc *xlsc, struct laggreqproto *resp) +{ + struct laggreq_lacp *rplacp; + struct lacp_softc *lsc; + struct lacp_aggregator *la; + struct lacp_aggregator_systemid *sid; + + lsc = (struct lacp_softc *)xlsc; + + LACP_LOCK(lsc); + la = lsc->lsc_aggregator; + rplacp = &resp->rp_lacp; + + if (lsc->lsc_optimistic) + SET(rplacp->flags, LAGGREQLACP_OPTIMISTIC); + if (lsc->lsc_dump_du) + SET(rplacp->flags, LAGGREQLACP_DUMPDU); + if (lsc->lsc_stop_lacpdu) + SET(rplacp->flags, LAGGREQLACP_STOPDU); + if (lsc->lsc_multi_linkspeed) + SET(rplacp->flags, LAGGREQLACP_MULTILS); + + rplacp->maxports = lsc->lsc_max_ports; + rplacp->actor_prio = ntohs(lsc->lsc_system_prio); + memcpy(rplacp->actor_mac, lsc->lsc_system_mac, + sizeof(rplacp->actor_mac)); + rplacp->actor_key = ntohs(lsc->lsc_key); + + if (la != NULL) { + sid = &la->la_sid; + rplacp->partner_prio = ntohs(sid->sid_prio); + memcpy(rplacp->partner_mac, sid->sid_mac, + sizeof(rplacp->partner_mac)); + rplacp->partner_key = ntohs(sid->sid_key); + } + LACP_UNLOCK(lsc); +} + +void +lacp_portstat(struct lagg_proto_softc *xlsc, struct lagg_port *lp, + struct laggreqport *resp) +{ + struct laggreq_lacpport *llp; + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct lacp_aggregator *la; + struct lacp_aggregator_systemid *sid; + + lsc = (struct lacp_softc *)xlsc; + lacpp = lp->lp_proto_ctx; + la = lacpp->lp_aggregator; + llp = &resp->rp_lacpport; + + if (lacp_isactive(lsc, lacpp)) + SET(resp->rp_flags, LAGG_PORT_ACTIVE); + if (lacp_iscollecting(lacpp)) + SET(resp->rp_flags, LAGG_PORT_COLLECTING); + if (lacp_isdistributing(lacpp)) + SET(resp->rp_flags, LAGG_PORT_DISTRIBUTING); + if (lacpp->lp_selected == LACP_STANDBY) + SET(resp->rp_flags, LAGG_PORT_STANDBY); + + if (la != NULL) { + sid = &la->la_sid; + llp->partner_prio = ntohs(sid->sid_prio); + memcpy(llp->partner_mac, sid->sid_mac, + sizeof(llp->partner_mac)); + llp->partner_key = ntohs(sid->sid_key); + } + + llp->actor_portprio = ntohs(lacpp->lp_actor.lpi_portprio); + llp->actor_portno = ntohs(lacpp->lp_actor.lpi_portno); + llp->actor_state = lacpp->lp_actor.lpi_state; + + llp->partner_portprio = ntohs(lacpp->lp_partner.lpi_portprio); + llp->partner_portno = ntohs(lacpp->lp_partner.lpi_portno); + llp->partner_state = lacpp->lp_partner.lpi_state; +} + +void +lacp_linkstate(struct lagg_proto_softc *xlsc, struct lagg_port *lp) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct ifmediareq ifmr; + struct ifnet *ifp_port; + uint8_t old_state; + uint32_t media, old_media; + int error; + + lsc = (struct lacp_softc *)xlsc; + + ifp_port = lp->lp_ifp; + lacpp = lp->lp_proto_ctx; + media = LACP_MEDIA_DEFAULT; + + memset(&ifmr, 0, sizeof(ifmr)); + ifmr.ifm_count = 0; + error = ifp_port->if_ioctl(ifp_port, SIOCGIFMEDIA, (void *)&ifmr); + if (error == 0) { + media = lacp_ifmedia2lacpmedia(ifmr.ifm_active); + } else if (error != ENOTTY){ + LACP_DPRINTF((lsc, lacpp, + "SIOCGIFMEDIA failed (%d)\n", error)); + return; + } + + LACP_LOCK(lsc); + if (lsc->lsc_running) { + old_media = lacpp->lp_media; + old_state = lacpp->lp_actor.lpi_state; + + if (lacpp->lp_media != media) { + LACP_DPRINTF((lsc, lacpp, + "media changed 0x%"PRIx32"->0x%"PRIx32", " + "ether = %d, fdx = %d, link = %d, running = %d\n", + lacpp->lp_media, media, + ISSET(media, LACP_MEDIA_ETHER) != 0, + ISSET(media, LACP_MEDIA_FDX) != 0, + ifp_port->if_link_state != LINK_STATE_DOWN, + ISSET(ifp_port->if_flags, IFF_RUNNING) != 0)); + lacpp->lp_media = media; + } + + if (ISSET(media, LACP_MEDIA_ETHER) && + ISSET(media, LACP_MEDIA_FDX) && + ifp_port->if_link_state != LINK_STATE_DOWN && + ISSET(ifp_port->if_flags, IFF_RUNNING)) { + lacp_port_enable(lsc, lacpp); + } else { + lacp_port_disable(lsc, lacpp); + } + + if (old_state != lacpp->lp_actor.lpi_state || + old_media != media) { + LACP_DPRINTF((lsc, lacpp, + "state changed to UNSELECTED\n")); + lacpp->lp_selected = LACP_UNSELECTED; + } + } else { + LACP_DPRINTF((lsc, lacpp, + "LACP is inactive, skip linkstate\n")); + } + + LACP_UNLOCK(lsc); +} + +int +lacp_ioctl(struct lagg_proto_softc *xlsc, struct laggreqproto *lreq) +{ + struct lacp_softc *lsc; + struct laggreq_lacp *rplacp; + struct lacp_aggregator *la; + int error; + size_t maxports; + bool set; + + lsc = (struct lacp_softc *)xlsc; + rplacp = &lreq->rp_lacp; + error = 0; + + switch (rplacp->command) { + case LAGGIOC_LACPSETFLAGS: + case LAGGIOC_LACPCLRFLAGS: + set = (rplacp->command == LAGGIOC_LACPSETFLAGS) ? + true : false; + + LACP_LOCK(lsc); + + if (ISSET(rplacp->flags, LAGGREQLACP_OPTIMISTIC)) + lsc->lsc_optimistic = set; + if (ISSET(rplacp->flags, LAGGREQLACP_DUMPDU)) + lsc->lsc_dump_du = set; + if (ISSET(rplacp->flags, LAGGREQLACP_STOPDU)) + lsc->lsc_stop_lacpdu = set; + if (ISSET(rplacp->flags, LAGGREQLACP_MULTILS)) + lsc->lsc_multi_linkspeed = set; + + LACP_UNLOCK(lsc); + break; + case LAGGIOC_LACPSETMAXPORTS: + case LAGGIOC_LACPCLRMAXPORTS: + maxports = (rplacp->command == LAGGIOC_LACPSETMAXPORTS) ? + rplacp->maxports : LACP_MAX_PORTS; + if (0 == maxports || LACP_MAX_PORTS < maxports) { + error = ERANGE; + break; + } + + LACP_LOCK(lsc); + if (lsc->lsc_max_ports != maxports) { + lsc->lsc_max_ports = maxports; + TAILQ_FOREACH(la, &lsc->lsc_aggregators, la_q) { + lacp_selected_update(lsc, la); + } + } + LACP_UNLOCK(lsc); + break; + default: + error = ENOTTY; + } + + return error; +} + +static int +lacp_pdu_input(struct lacp_softc *lsc, struct lacp_port *lacpp, struct mbuf *m) +{ + enum { + LACP_TLV_ACTOR = 0, + LACP_TLV_PARTNER, + LACP_TLV_COLLECTOR, + LACP_TLV_TERM, + LACP_TLV_NUM + }; + + struct lacpdu *du; + struct lacpdu_peerinfo *pi_actor, *pi_partner; + struct lacpdu_collectorinfo *lci; + struct tlv tlvlist_lacp[LACP_TLV_NUM] = { + [LACP_TLV_ACTOR] = { + .tlv_t = LACP_TYPE_ACTORINFO, + .tlv_l = sizeof(*pi_actor)}, + [LACP_TLV_PARTNER] = { + .tlv_t = LACP_TYPE_PARTNERINFO, + .tlv_l = sizeof(*pi_partner)}, + [LACP_TLV_COLLECTOR] = { + .tlv_t = LACP_TYPE_COLLECTORINFO, + .tlv_l = sizeof(*lci)}, + [LACP_TLV_TERM] = { + .tlv_t = TLV_TYPE_TERMINATE, + .tlv_l = 0}, + }; + + if (m->m_pkthdr.len != sizeof(*du)) + goto bad; + + if ((m->m_flags & M_MCAST) == 0) + goto bad; + + if (m->m_len < (int)sizeof(*du)) { + m = m_pullup(m, sizeof(*du)); + if (m == NULL) { + lsc->lsc_mpullup_failed.ev_count++; + return ENOMEM; + } + } + + du = mtod(m, struct lacpdu *); + + if (memcmp(&du->ldu_eh.ether_dhost, + ethermulticastaddr_slowprotocols, ETHER_ADDR_LEN) != 0) + goto bad; + + LACP_TLV_PARSE(du, struct lacpdu, ldu_tlv_actor, + tlvlist_lacp); + + pi_actor = tlvlist_lacp[LACP_TLV_ACTOR].tlv_v; + pi_partner = tlvlist_lacp[LACP_TLV_PARTNER].tlv_v; + lci = tlvlist_lacp[LACP_TLV_COLLECTOR].tlv_v; + + if (pi_actor == NULL || pi_partner == NULL) + goto bad; + + if (LACP_ISDUMPING(lsc)) { + lacp_dprintf(lsc, lacpp, "lacpdu received\n"); + lacp_dump_lacpdutlv(pi_actor, pi_partner, lci); + } + + LACP_LOCK(lsc); + lacp_sm_rx(lsc, lacpp, pi_partner, pi_actor); + LACP_UNLOCK(lsc); + + m_freem(m); + return 0; +bad: + lsc->lsc_badlacpdu.ev_count++; + m_freem(m); + return EINVAL; +} + +static int +marker_cmp(struct markerdu_info *mi, + struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + KASSERT(LACP_LOCKED(lsc)); + + if (mi->mi_rq_port != lacpp->lp_actor.lpi_portno) + return -1; + + if (ntohl(mi->mi_rq_xid) != lacpp->lp_marker_xid) + return -1; + + return memcmp(mi->mi_rq_system, lsc->lsc_system_mac, + LACP_MAC_LEN); +} + +static void +lacp_marker_reply(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct mbuf *m_info) +{ + struct lagg_port *lp; + struct markerdu *mdu; + struct ifnet *ifp_port; + struct psref psref; + + LACP_LOCK(lsc); + lp = lacpp->lp_laggport; + lagg_port_getref(lp, &psref); + LACP_UNLOCK(lsc); + + ifp_port = lp->lp_ifp; + mdu = mtod(m_info, struct markerdu *); + + mdu->mdu_tlv_info.tlv_type = MARKER_TYPE_RESPONSE; + /* ether_dhost is already equals to multicast address */ + memcpy(mdu->mdu_eh.ether_shost, + CLLADDR(ifp_port->if_sadl), ETHER_ADDR_LEN); + + if (LACP_ISDUMPING(lsc)) { + lacp_dprintf(lsc, lacpp, "markerdu reply\n"); + lacp_dump_markertlv(NULL, &mdu->mdu_info); + } + + lagg_port_xmit(lp, m_info); + lagg_port_putref(lp, &psref); +} + +static int +lacp_marker_recv_response(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct markerdu_info *mi_res) +{ + struct lagg_softc *sc; + struct lagg_port *lp0; + struct lacp_port *lacpp0; + bool pending; + + sc = lsc->lsc_softc; + + LACP_LOCK(lsc); + if (marker_cmp(mi_res, lsc, lacpp) != 0) { + LACP_UNLOCK(lsc); + return -1; + } + CLR(lacpp->lp_flags, LACP_PORT_MARK); + LACP_UNLOCK(lsc); + + LAGG_LOCK(sc); + LACP_LOCK(lsc); + + if (lsc->lsc_suppress_distributing) { + pending = false; + LAGG_PORTS_FOREACH(sc, lp0) { + lacpp0 = lp0->lp_proto_ctx; + if (ISSET(lacpp0->lp_flags, LACP_PORT_MARK)) { + pending = true; + break; + } + } + + if (!pending) { + LACP_DPRINTF((lsc, NULL, "queue flush complete\n")); + LACP_PTIMER_DISARM(lsc, LACP_PTIMER_DISTRIBUTING); + lsc->lsc_suppress_distributing = false; + } + } + + LACP_UNLOCK(lsc); + LAGG_UNLOCK(sc); + + return 0; +} + +static int +lacp_marker_input(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct mbuf *m) +{ + enum { + MARKER_TLV_INFO = 0, + MARKER_TLV_RESPONSE, + MARKER_TLV_TERM, + MARKER_TLV_NUM + }; + + struct markerdu *mdu; + struct markerdu_info *mi_info, *mi_res; + int error; + struct tlv tlvlist_marker[MARKER_TLV_NUM] = { + [MARKER_TLV_INFO] = { + .tlv_t = MARKER_TYPE_INFO, + .tlv_l = sizeof(*mi_info)}, + [MARKER_TLV_RESPONSE] = { + .tlv_t = MARKER_TYPE_RESPONSE, + .tlv_l = sizeof(*mi_res)}, + [MARKER_TLV_TERM] = { + .tlv_t = TLV_TYPE_TERMINATE, + .tlv_l = 0}, + }; + + if (m->m_pkthdr.len != sizeof(*mdu)) + goto bad; + + if ((m->m_flags & M_MCAST) == 0) + goto bad; + + if (m->m_len < (int)sizeof(*mdu)) { + m = m_pullup(m, sizeof(*mdu)); + if (m == NULL) { + lsc->lsc_mpullup_failed.ev_count++; + return ENOMEM; + } + } + + mdu = mtod(m, struct markerdu *); + + if (memcmp(mdu->mdu_eh.ether_dhost, + ethermulticastaddr_slowprotocols, ETHER_ADDR_LEN) != 0) + goto bad; + + LACP_TLV_PARSE(mdu, struct markerdu, mdu_tlv_info, + tlvlist_marker); + + mi_info = tlvlist_marker[MARKER_TLV_INFO].tlv_v; + mi_res = tlvlist_marker[MARKER_TLV_RESPONSE].tlv_v; + + if (LACP_ISDUMPING(lsc)) { + lacp_dprintf(lsc, lacpp, "markerdu received\n"); + lacp_dump_markertlv(mi_info, mi_res); + } + + if (mi_info != NULL && mi_res == NULL) { + lacp_marker_reply(lsc, lacpp, m); + } else if (mi_info == NULL && mi_res != NULL) { + error = lacp_marker_recv_response(lsc, lacpp, + mi_res); + if (error != 0) { + goto bad; + } else { + m_freem(m); + } + } else { + goto bad; + } + + return 0; +bad: + lsc->lsc_badmarkerdu.ev_count++; + m_freem(m); + return EINVAL; +} + +struct mbuf * +lacp_input(struct lagg_proto_softc *xlsc, struct lagg_port *lp, struct mbuf *m) +{ + struct ifnet *ifp; + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct ether_header *eh; + uint8_t subtype; + + eh = mtod(m, struct ether_header *); + lsc = (struct lacp_softc *)xlsc; + ifp = &lsc->lsc_softc->sc_if; + lacpp = lp->lp_proto_ctx; + + if (!vlan_has_tag(m) && + eh->ether_type == htons(ETHERTYPE_SLOWPROTOCOLS)) { + if (m->m_pkthdr.len < (int)(sizeof(*eh) + sizeof(subtype))) { + m_freem(m); + return NULL; + } + + m_copydata(m, sizeof(struct ether_header), + sizeof(subtype), &subtype); + switch (subtype) { + case SLOWPROTOCOLS_SUBTYPE_LACP: + (void)lacp_pdu_input(lsc, lacpp, m); + return NULL; + case SLOWPROTOCOLS_SUBTYPE_MARKER: + (void)lacp_marker_input(lsc, lacpp, m); + return NULL; + } + } + + if (!lacp_iscollecting(lacpp) || !lacp_isactive(lsc, lacpp)) { + if_statinc(ifp, if_ierrors); + m_freem(m); + return NULL; + } + + return m; +} + +static bool +lacp_port_need_to_tell(struct lacp_port *lacpp) +{ + + if (!ISSET(lacpp->lp_actor.lpi_state, + LACP_STATE_AGGREGATION)) { + return false; + } + + if (!ISSET(lacpp->lp_actor.lpi_state, + LACP_STATE_ACTIVITY) + && !ISSET(lacpp->lp_partner.lpi_state, + LACP_STATE_ACTIVITY)) { + return false; + } + + if (!ISSET(lacpp->lp_flags, LACP_PORT_NTT)) + return false; + + if (ppsratecheck(&lacpp->lp_last_lacpdu, &lacpp->lp_lacpdu_sent, + (3 / LACP_FAST_PERIODIC_TIME)) == 0) + return false; + + return true; +} + +static void +lacp_sm_assert_ntt(struct lacp_port *lacpp) +{ + + SET(lacpp->lp_flags, LACP_PORT_NTT); +} + +static void +lacp_sm_negate_ntt(struct lacp_port *lacpp) +{ + + CLR(lacpp->lp_flags, LACP_PORT_NTT); +} + +static struct mbuf * +lacp_lacpdu_mbuf(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct ifnet *ifp_port; + struct mbuf *m; + struct lacpdu *du; + + KASSERT(LACP_LOCKED(lsc)); + + ifp_port = lacpp->lp_laggport->lp_ifp; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + lsc->lsc_mgethdr_failed.ev_count++; + return NULL; + } + + m->m_pkthdr.len = m->m_len = sizeof(*du); + + du = mtod(m, struct lacpdu *); + memset(du, 0, sizeof(*du)); + + m->m_flags |= M_MCAST; + memcpy(du->ldu_eh.ether_dhost, ethermulticastaddr_slowprotocols, + ETHER_ADDR_LEN); + memcpy(du->ldu_eh.ether_shost, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN); + du->ldu_eh.ether_type = htons(ETHERTYPE_SLOWPROTOCOLS); + 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)); + lacp_peerinfo_actor(lsc, lacpp, &du->ldu_actor); + + tlv_set(&du->ldu_tlv_partner, LACP_TYPE_PARTNERINFO, + sizeof(du->ldu_partner)); + lacp_peerinfo_partner(lacpp, &du->ldu_partner); + + tlv_set(&du->ldu_tlv_collector, LACP_TYPE_COLLECTORINFO, + sizeof(du->ldu_collector)); + du->ldu_collector.lci_maxdelay = 0; + + du->ldu_tlv_term.tlv_type = LACP_TYPE_TERMINATE; + du->ldu_tlv_term.tlv_length = 0; + + return m; +} + +static void +lacp_sm_tx_work(struct lagg_work *lw, void *xlsc) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct lagg_port *lp; + struct lacpdu *du; + struct mbuf *m; + struct psref psref; + int bound; + + lsc = xlsc; + lacpp = container_of(lw, struct lacp_port, lp_work_smtx); + + if (lsc->lsc_stop_lacpdu) + return; + + LACP_LOCK(lsc); + m = lacp_lacpdu_mbuf(lsc, lacpp); + if (m == NULL) { + LACP_UNLOCK(lsc); + return; + } + lacp_sm_negate_ntt(lacpp); + lp = lacpp->lp_laggport; + bound = curlwp_bind(); + lagg_port_getref(lp, &psref); + LACP_UNLOCK(lsc); + + if (LACP_ISDUMPING(lsc)) { + lacp_dprintf(lsc, lacpp, "lacpdu transmit\n"); + du = mtod(m, struct lacpdu *); + lacp_dump_lacpdutlv(&du->ldu_actor, + &du->ldu_partner, &du->ldu_collector); + } + + lagg_port_xmit(lp, m); + lagg_port_putref(lp, &psref); + curlwp_bindx(bound); +} + +static void +lacp_sm_tx(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + if (!lacp_port_need_to_tell(lacpp)) + return; + + lagg_workq_add(lsc->lsc_workq, &lacpp->lp_work_smtx); +} + +static void +lacp_tick(void *xlsc) +{ + struct lacp_softc *lsc; + + lsc = xlsc; + + lagg_workq_add(lsc->lsc_workq, &lsc->lsc_work_tick); + + LACP_LOCK(lsc); + callout_schedule(&lsc->lsc_tick, hz); + LACP_UNLOCK(lsc); +} + +static void +lacp_run_timers(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + size_t i; + + for (i = 0; i < LACP_NTIMER; i++) { + KASSERT(lacpp->lp_timer[i] >= 0); + + if (lacpp->lp_timer[i] == 0) + continue; + if (--lacpp->lp_timer[i] > 0) + continue; + + KASSERT(lacp_timer_funcs[i] != NULL); + lacp_timer_funcs[i](lsc, lacpp); + } +} + +static void +lacp_run_prototimers(struct lacp_softc *lsc) +{ + size_t i; + + for (i = 0; i < LACP_NPTIMER; i++) { + KASSERT(lsc->lsc_timer[i] >= 0); + + if (lsc->lsc_timer[i] == 0) + continue; + if (--lsc->lsc_timer[i] > 0) + continue; + + KASSERT(lacp_ptimer_funcs[i] != NULL); + lacp_ptimer_funcs[i](lsc); + } +} + +static void +lacp_tick_work(struct lagg_work *lw __unused, void *xlsc) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct lagg_softc *sc; + struct lagg_port *lp; + + lsc = xlsc; + sc = lsc->lsc_softc; + + LACP_LOCK(lsc); + lacp_run_prototimers(lsc); + LACP_UNLOCK(lsc); + + LAGG_LOCK(sc); + LACP_LOCK(lsc); + LAGG_PORTS_FOREACH(sc, lp) { + lacpp = lp->lp_proto_ctx; + if (!ISSET(lacpp->lp_actor.lpi_state, + LACP_STATE_AGGREGATION)) { + continue; + } + + lacp_run_timers(lsc, lacpp); + lacp_select(lsc, lacpp); + lacp_sm_mux(lsc, lacpp); + lacp_sm_tx(lsc, lacpp); + lacp_sm_ptx_schedule(lacpp); + } + + LACP_UNLOCK(lsc); + LAGG_UNLOCK(sc); +} + +static void +lacp_systemid_str(char *buf, size_t buflen, + uint16_t prio, const uint8_t *mac, uint16_t key) +{ + + snprintf(buf, buflen, + "%04X," + "%02X-%02X-%02X-%02X-%02X-%02X," + "%04X", + (unsigned int)ntohs(prio), + (int)mac[0], (int)mac[1], (int)mac[2], + (int)mac[3], (int)mac[4], (int)mac[5], + (unsigned int)htons(key)); + +} + +__LACPDEBUGUSED static void +lacp_aggregator_str(struct lacp_aggregator *la, char *buf, size_t buflen) +{ + + lacp_systemid_str(buf, buflen, la->la_sid.sid_prio, + la->la_sid.sid_mac, la->la_sid.sid_key); +} + +static void +lacp_peerinfo_idstr(const struct lacpdu_peerinfo *pi, + char *buf, size_t buflen) +{ + + lacp_systemid_str(buf, buflen, pi->lpi_system_prio, + pi->lpi_system_mac, pi->lpi_key); +} + +static void +lacp_state_str(uint8_t state, char *buf, size_t buflen) +{ + + snprintb(buf, buflen, LACP_STATE_BITS, state); +} + +static struct lagg_port * +lacp_select_tx_port(struct lacp_softc *lsc, struct mbuf *m, + struct psref *psref) +{ + struct lacp_portmap *pm; + struct lagg_port *lp; + uint32_t hash; + size_t act; + int s; + + hash = lagg_hashmbuf(lsc->lsc_softc, m); + + s = pserialize_read_enter(); + act = LACP_PORTMAP_ACTIVE(lsc); + pm = &lsc->lsc_portmaps[act]; + + if (pm->pm_count == 0) { + pserialize_read_exit(s); + return NULL; + } + + hash %= pm->pm_count; + lp = pm->pm_ports[hash]; + lagg_port_getref(lp, psref); + + pserialize_read_exit(s); + + return lp; +} + +static void +lacp_peerinfo_actor(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *dst) +{ + + memcpy(dst->lpi_system_mac, lsc->lsc_system_mac, LACP_MAC_LEN); + dst->lpi_system_prio = lsc->lsc_system_prio; + dst->lpi_key = lsc->lsc_key; + dst->lpi_port_no = lacpp->lp_actor.lpi_portno; + dst->lpi_port_prio = lacpp->lp_actor.lpi_portprio; + dst->lpi_state = lacpp->lp_actor.lpi_state; +} + +static void +lacp_peerinfo_partner(struct lacp_port *lacpp, struct lacpdu_peerinfo *dst) +{ + struct lacp_aggregator *la; + + la = lacpp->lp_aggregator; + + if (la != NULL) { + memcpy(dst->lpi_system_mac, la->la_sid.sid_mac, LACP_MAC_LEN); + dst->lpi_system_prio = la->la_sid.sid_prio; + dst->lpi_key = la->la_sid.sid_key; + } else { + memset(dst->lpi_system_mac, 0, LACP_MAC_LEN); + dst->lpi_system_prio = 0; + dst->lpi_key = 0; + } + dst->lpi_port_no = lacpp->lp_partner.lpi_portno; + dst->lpi_port_prio = lacpp->lp_partner.lpi_portprio; + dst->lpi_state = lacpp->lp_partner.lpi_state; +} + +static int +lacp_compare_peerinfo(struct lacpdu_peerinfo *a, struct lacpdu_peerinfo *b) +{ + + return memcmp(a, b, offsetof(struct lacpdu_peerinfo, lpi_state)); +} + +static void +lacp_sm_rx_record_default(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + uint8_t oldpstate; + struct lacp_portinfo *pi; + char buf[LACP_STATESTR_LEN] __LACPDEBUGUSED; + + pi = &lacpp->lp_partner; + + oldpstate = pi->lpi_state; + pi->lpi_portno = htons(LACP_PORTNO_NONE); + pi->lpi_portprio = htons(0xffff); + + if (lsc->lsc_optimistic) + pi->lpi_state = LACP_PARTNER_ADMIN_OPTIMISTIC; + else + pi->lpi_state = LACP_PARTNER_ADMIN_STRICT; + + SET(lacpp->lp_actor.lpi_state, LACP_STATE_DEFAULTED); + + if (oldpstate != pi->lpi_state) { + LACP_STATE_STR(oldpstate, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "oldpstate %s\n", buf)); + + LACP_STATE_STR(pi->lpi_state, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "newpstate %s\n", buf)); + } +} + +static inline bool +lacp_port_is_synced(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *my_pi, struct lacpdu_peerinfo *peer_pi) +{ + struct lacpdu_peerinfo actor; + + if (!ISSET(peer_pi->lpi_state, LACP_STATE_ACTIVITY) && + (!ISSET(my_pi->lpi_state, LACP_STATE_ACTIVITY) || + !ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_ACTIVITY))) + return false; + + if (!ISSET(peer_pi->lpi_state, LACP_STATE_AGGREGATION)) + return false; + + lacp_peerinfo_actor(lsc, lacpp, &actor); + if (lacp_compare_peerinfo(&actor, my_pi) != 0) + return false; + + if (!LACP_STATE_EQ(actor.lpi_state, my_pi->lpi_state, + LACP_STATE_AGGREGATION)) { + return false; + } + + return true; +} + +static void +lacp_sm_rx_record_peerinfo(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *my_pi, struct lacpdu_peerinfo *peer_pi) +{ + char buf[LACP_STATESTR_LEN] __LACPDEBUGUSED; + uint8_t oldpstate; + struct lacp_portinfo *pi; + struct lacp_aggregator_systemid *sid; + + pi = &lacpp->lp_partner; + sid = &lacpp->lp_aggregator_sidbuf; + + oldpstate = lacpp->lp_partner.lpi_state; + + sid->sid_prio = peer_pi->lpi_system_prio; + sid->sid_key = peer_pi->lpi_key; + memcpy(sid->sid_mac, peer_pi->lpi_system_mac, + sizeof(sid->sid_mac)); + + pi->lpi_portno = peer_pi->lpi_port_no; + pi->lpi_portprio = peer_pi->lpi_port_prio; + pi->lpi_state = peer_pi->lpi_state; + + if (lacp_port_is_synced(lsc, lacpp, my_pi, peer_pi)) { + if (lsc->lsc_optimistic) + SET(lacpp->lp_partner.lpi_state, LACP_STATE_SYNC); + } else { + CLR(lacpp->lp_partner.lpi_state, LACP_STATE_SYNC); + } + + CLR(lacpp->lp_actor.lpi_state, LACP_STATE_DEFAULTED); + + if (oldpstate != lacpp->lp_partner.lpi_state) { + LACP_STATE_STR(oldpstate, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "oldpstate %s\n", buf)); + + LACP_STATE_STR(lacpp->lp_partner.lpi_state, + buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "newpstate %s\n", buf)); + } + + lacp_sm_ptx_update_timeout(lacpp, oldpstate); +} + +static void +lacp_sm_rx_set_expired(struct lacp_port *lacpp) +{ + + CLR(lacpp->lp_partner.lpi_state, LACP_STATE_SYNC); + SET(lacpp->lp_partner.lpi_state, LACP_STATE_TIMEOUT); + LACP_TIMER_ARM(lacpp, LACP_TIMER_CURRENT_WHILE, + LACP_SHORT_TIMEOUT_TIME); + SET(lacpp->lp_actor.lpi_state, LACP_STATE_EXPIRED); +} + +static void +lacp_sm_port_init(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lagg_port *lp) +{ + + KASSERT(LACP_LOCKED(lsc)); + + lacpp->lp_laggport = lp; + lacpp->lp_actor.lpi_state = LACP_STATE_ACTIVITY; + lacpp->lp_actor.lpi_portno = htons(if_get_index(lp->lp_ifp)); + lacpp->lp_actor.lpi_portprio = htons(LACP_PORT_PRIO); + lacpp->lp_partner.lpi_state = LACP_STATE_TIMEOUT; + lacpp->lp_aggregator = NULL; + lacpp->lp_marker_xid = 0; + lacpp->lp_mux_state = LACP_MUX_INIT; + + lacp_set_mux(lsc, lacpp, LACP_MUX_DETACHED); + lacp_sm_rx_record_default(lsc, lacpp); +} + +static void +lacp_port_disable(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + if (ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION)) + LACP_DPRINTF((lsc, lacpp, "enable -> disable\n")); + + lacp_set_mux(lsc, lacpp, LACP_MUX_DETACHED); + lacp_sm_rx_record_default(lsc, lacpp); + CLR(lacpp->lp_actor.lpi_state, + LACP_STATE_AGGREGATION | LACP_STATE_EXPIRED); + CLR(lacpp->lp_partner.lpi_state, LACP_STATE_AGGREGATION); +} + +static void +lacp_port_enable(struct lacp_softc *lsc __LACPDEBUGUSED, + struct lacp_port *lacpp) +{ + + if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION)) + LACP_DPRINTF((lsc, lacpp, "disable -> enable\n")); + + SET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION); + lacp_sm_rx_set_expired(lacpp); +} + +static void +lacp_sm_rx_timer(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_EXPIRED)) { + /* CURRENT -> EXPIRED */ + LACP_DPRINTF((lsc, lacpp, "CURRENT -> EXPIRED\n")); + lacp_sm_rx_set_expired(lacpp); + } else { + LACP_DPRINTF((lsc, lacpp, "EXPIRED -> DEFAULTED\n")); + lacp_set_mux(lsc, lacpp, LACP_MUX_DETACHED); + lacp_sm_rx_record_default(lsc, lacpp); + } +} + +static void +lacp_sm_ptx_timer(struct lacp_softc *lsc __unused, struct lacp_port *lacpp) +{ + + lacp_sm_assert_ntt(lacpp); +} + +static void +lacp_sm_ptx_schedule(struct lacp_port *lacpp) +{ + int timeout; + + /* no periodic */ + if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_ACTIVITY) && + !ISSET(lacpp->lp_partner.lpi_state, LACP_STATE_ACTIVITY)) { + LACP_TIMER_DISARM(lacpp, LACP_TIMER_PERIODIC); + return; + } + + if (LACP_TIMER_ISARMED(lacpp, LACP_TIMER_PERIODIC)) + return; + + timeout = ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_TIMEOUT) ? + LACP_FAST_PERIODIC_TIME : LACP_SLOW_PERIODIC_TIME; + + LACP_TIMER_ARM(lacpp, LACP_TIMER_PERIODIC, timeout); +} + +static void +lacp_sm_ptx_update_timeout(struct lacp_port *lacpp, uint8_t oldpstate) +{ + + if (LACP_STATE_EQ(oldpstate, lacpp->lp_partner.lpi_state, + LACP_STATE_TIMEOUT)) + return; + + LACP_DPRINTF((NULL, lacpp, "partner timeout changed\n")); + + LACP_TIMER_DISARM(lacpp, LACP_TIMER_PERIODIC); + + /* if timeout has been shorted, assert NTT */ + if (ISSET(lacpp->lp_partner.lpi_state, LACP_STATE_TIMEOUT)) + lacp_sm_assert_ntt(lacpp); +} + +static void +lacp_sm_mux_timer(struct lacp_softc *lsc __LACPDEBUGUSED, + struct lacp_port *lacpp) +{ + char buf[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + + KASSERT(lacpp->lp_pending > 0); + + LACP_AGGREGATOR_STR(lacpp->lp_aggregator, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "aggregator %s, pending %d -> %d\n", + buf, lacpp->lp_pending, lacpp->lp_pending -1)); + + lacpp->lp_pending--; +} + +static void +lacp_sm_rx_update_selected(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *peer_pi) +{ + struct lacpdu_peerinfo partner; + char str0[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + char str1[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + + if (lacpp->lp_aggregator == NULL) + return; + + lacp_peerinfo_partner(lacpp, &partner); + if (lacp_compare_peerinfo(peer_pi, &partner) != 0) { + LACP_PEERINFO_IDSTR(&partner, str0, sizeof(str0)); + LACP_PEERINFO_IDSTR(peer_pi, str1, sizeof(str1)); + LACP_DPRINTF((lsc, lacpp, + "different peerinfo, %s vs %s\n", str0, str1)); + goto do_unselect; + } + + if (!LACP_STATE_EQ(lacpp->lp_partner.lpi_state, + peer_pi->lpi_state, LACP_STATE_AGGREGATION)) { + LACP_DPRINTF((lsc, lacpp, + "STATE_AGGREGATION changed %d -> %d\n", + ISSET(lacpp->lp_partner.lpi_state, + LACP_STATE_AGGREGATION) != 0, + ISSET(peer_pi->lpi_state, LACP_STATE_AGGREGATION) != 0)); + goto do_unselect; + } + + return; + +do_unselect: + lacpp->lp_selected = LACP_UNSELECTED; + /* lacpp->lp_aggregator will be released at lacp_set_mux() */ +} + +static void +lacp_sm_rx_update_ntt(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *my_pi) +{ + struct lacpdu_peerinfo actor; + + lacp_peerinfo_actor(lsc, lacpp, &actor); + + if (lacp_compare_peerinfo(&actor, my_pi) != 0 || + !LACP_STATE_EQ(lacpp->lp_actor.lpi_state, my_pi->lpi_state, + LACP_STATE_ACTIVITY | LACP_STATE_SYNC | LACP_STATE_AGGREGATION)) { + LACP_DPRINTF((lsc, lacpp, "assert ntt\n")); + lacp_sm_assert_ntt(lacpp); + } +} + +static void +lacp_sm_rx(struct lacp_softc *lsc, struct lacp_port *lacpp, + struct lacpdu_peerinfo *my_pi, struct lacpdu_peerinfo *peer_pi) +{ + int timeout; + + KASSERT(LACP_LOCKED(lsc)); + + /* check LACP disabled first */ + if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_AGGREGATION)) + return; + + /* check loopback condition */ + if (memcmp(lsc->lsc_system_mac, peer_pi->lpi_system_mac, + LACP_MAC_LEN) == 0 && + lsc->lsc_system_prio == peer_pi->lpi_system_prio) + return; + + lacp_sm_rx_update_selected(lsc, lacpp, peer_pi); + lacp_sm_rx_update_ntt(lsc, lacpp, my_pi); + lacp_sm_rx_record_peerinfo(lsc, lacpp, my_pi, peer_pi); + + timeout = ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_TIMEOUT) ? + LACP_SHORT_TIMEOUT_TIME : LACP_LONG_TIMEOUT_TIME; + LACP_TIMER_ARM(lacpp, LACP_TIMER_CURRENT_WHILE, timeout); + + CLR(lacpp->lp_actor.lpi_state, LACP_STATE_EXPIRED); + + /* kick transmit machine without timeout. */ + lacp_sm_tx(lsc, lacpp); +} + +static void +lacp_disable_collecting(struct lacp_port *lacpp) +{ + + LACP_DPRINTF((NULL, lacpp, "collecting disabled\n")); + CLR(lacpp->lp_actor.lpi_state, LACP_STATE_COLLECTING); + atomic_store_relaxed(&lacpp->lp_collector, false); +} + +static void +lacp_enable_collecting(struct lacp_port *lacpp) +{ + LACP_DPRINTF((NULL, lacpp, "collecting enabled\n")); + SET(lacpp->lp_actor.lpi_state, LACP_STATE_COLLECTING); + atomic_store_relaxed(&lacpp->lp_collector, true); +} + +static void +lacp_update_portmap(struct lacp_softc *lsc) +{ + struct lagg_softc *sc; + struct lacp_aggregator *la; + struct lacp_portmap *pm_act, *pm_next; + struct lacp_port *lacpp; + size_t pmap, n; + u_int link; + + KASSERT(LACP_LOCKED(lsc)); + + la = lsc->lsc_aggregator; + + pmap = LACP_PORTMAP_ACTIVE(lsc); + pm_act = &lsc->lsc_portmaps[pmap]; + + pmap = LACP_PORTMAP_NEXT(lsc); + pm_next = &lsc->lsc_portmaps[pmap]; + + n = 0; + if (la != NULL) { + LIST_FOREACH(lacpp, &la->la_ports, lp_entry_la) { + if (!ISSET(lacpp->lp_actor.lpi_state, + LACP_STATE_DISTRIBUTING)) { + continue; + } + + pm_next->pm_ports[n] = lacpp->lp_laggport; + n++; + + if (n >= LACP_MAX_PORTS) + break; + } + } + pm_next->pm_count = n; + + atomic_store_release(&lsc->lsc_activemap, pmap); + pserialize_perform(lsc->lsc_psz); + + LACP_DPRINTF((lsc, NULL, "portmap count updated (%zu -> %zu)\n", + pm_act->pm_count, pm_next->pm_count)); + + link = lacp_portmap_linkstate(pm_next); + if (link != lacp_portmap_linkstate(pm_act)) { + sc = lsc->lsc_softc; + if_link_state_change(&sc->sc_if, link); + } + + /* cleanup */ + pm_act->pm_count = 0; + memset(pm_act->pm_ports, 0, sizeof(pm_act->pm_ports)); +} + +static void +lacp_disable_distributing(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_portmap *pm; + bool do_update; + size_t act, i; + int s; + + KASSERT(LACP_LOCKED(lsc)); + + LACP_DPRINTF((lsc, lacpp, "distributing disabled\n")); + CLR(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING); + + s = pserialize_read_enter(); + act = LACP_PORTMAP_ACTIVE(lsc); + pm = &lsc->lsc_portmaps[act]; + + do_update = false; + for (i = 0; i < pm->pm_count; i++) { + if (pm->pm_ports[i] == lacpp->lp_laggport) { + do_update = true; + break; + } + } + pserialize_read_exit(s); + + if (do_update) + lacp_update_portmap(lsc); +} + +static void +lacp_enable_distributing(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + + KASSERT(LACP_LOCKED(lsc)); + + KASSERT(lacp_isactive(lsc, lacpp)); + + LACP_DPRINTF((lsc, lacpp, "distributing enabled\n")); + SET(lacpp->lp_actor.lpi_state, LACP_STATE_DISTRIBUTING); + lacp_suppress_distributing(lsc); + lacp_update_portmap(lsc); +} + +static void +lacp_select_active_aggregator(struct lacp_softc *lsc) +{ + struct lacp_aggregator *la, *best_la; + char str[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + + KASSERT(LACP_LOCKED(lsc)); + + la = lsc->lsc_aggregator; + if (la != NULL && la->la_attached_port > 0) { + best_la = la; + } else { + best_la = NULL; + } + + TAILQ_FOREACH(la, &lsc->lsc_aggregators, la_q) { + if (la->la_attached_port <= 0) + continue; + + if (best_la == NULL || + LACP_SYS_PRI(la) < LACP_SYS_PRI(best_la)) + best_la = la; + } + + if (best_la != lsc->lsc_aggregator) { + LACP_DPRINTF((lsc, NULL, "active aggregator changed\n")); + + if (lsc->lsc_aggregator != NULL) { + LACP_AGGREGATOR_STR(lsc->lsc_aggregator, + str, sizeof(str)); + } else { + snprintf(str, sizeof(str), "(null)"); + } + LACP_DPRINTF((lsc, NULL, "old aggregator=%s\n", str)); + + if (best_la != NULL) { + LACP_AGGREGATOR_STR(best_la, str, sizeof(str)); + } else { + snprintf(str, sizeof(str), "(null)"); + } + LACP_DPRINTF((lsc, NULL, "new aggregator=%s\n", str)); + + lsc->lsc_aggregator = best_la; + } +} + +static void +lacp_port_attached(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_aggregator *la; + + KASSERT(LACP_LOCKED(lsc)); + + if (ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_SYNC)) + return; + + la = lacpp->lp_aggregator; + KASSERT(la != NULL); + KASSERT(la->la_attached_port >= 0); + + SET(lacpp->lp_actor.lpi_state, LACP_STATE_SYNC); + la->la_attached_port++; + lacp_select_active_aggregator(lsc); +} + +static void +lacp_port_detached(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_aggregator *la; + + KASSERT(LACP_LOCKED(lsc)); + + if (!ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_SYNC)) + return; + + la = lacpp->lp_aggregator; + KASSERT(la != NULL); + KASSERT(la->la_attached_port > 0); + + CLR(lacpp->lp_actor.lpi_state, LACP_STATE_SYNC); + la->la_attached_port--; + lacp_select_active_aggregator(lsc); +} + +static int +lacp_set_mux(struct lacp_softc *lsc, struct lacp_port *lacpp, + enum lacp_mux_state new_state) +{ + struct lagg_softc *sc; + struct ifnet *ifp; + + KASSERT(LACP_LOCKED(lsc)); + + sc = lacpp->lp_laggport->lp_softc; + ifp = &sc->sc_if; + + if (lacpp->lp_mux_state == new_state) { + return -1; + } + + switch (new_state) { + case LACP_MUX_DETACHED: + lacp_port_detached(lsc, lacpp); + lacp_disable_distributing(lsc, lacpp); + lacp_disable_collecting(lacpp); + lacp_sm_assert_ntt(lacpp); + /* cancel timer */ + if (LACP_TIMER_ISARMED(lacpp, LACP_TIMER_WAIT_WHILE)) { + KASSERT(lacpp->lp_pending > 0); + lacpp->lp_pending--; + LACP_TIMER_DISARM(lacpp, LACP_TIMER_WAIT_WHILE); + } + lacp_unselect(lsc, lacpp); + break; + case LACP_MUX_WAITING: + LACP_TIMER_ARM(lacpp, LACP_TIMER_WAIT_WHILE, + LACP_AGGREGATE_WAIT_TIME); + lacpp->lp_pending++; + break; + case LACP_MUX_ATTACHED: + lacp_port_attached(lsc, lacpp); + lacp_disable_collecting(lacpp); + lacp_sm_assert_ntt(lacpp); + break; + case LACP_MUX_COLLECTING: + lacp_enable_collecting(lacpp); + lacp_disable_distributing(lsc, lacpp); + lacp_sm_assert_ntt(lacpp); + break; + case LACP_MUX_DISTRIBUTING: + lacp_enable_distributing(lsc, lacpp); + break; + case LACP_MUX_INIT: + default: + panic("%s: unknown state", ifp->if_xname); + } + + LACP_DPRINTF((lsc, lacpp, "mux_state %d -> %d\n", + lacpp->lp_mux_state, new_state)); + + lacpp->lp_mux_state = new_state; + return 0; +} + +static void +lacp_sm_mux(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_aggregator *la; + enum lacp_mux_state next_state; + enum lacp_selected selected; + bool p_sync, p_collecting; + + p_sync = ISSET(lacpp->lp_partner.lpi_state, LACP_STATE_SYNC); + p_collecting = ISSET(lacpp->lp_partner.lpi_state, + LACP_STATE_COLLECTING); + + do { + next_state = lacpp->lp_mux_state; + la = lacpp->lp_aggregator; + selected = lacpp->lp_selected; + KASSERT(la != NULL || + lacpp->lp_mux_state == LACP_MUX_DETACHED); + + switch (lacpp->lp_mux_state) { + case LACP_MUX_DETACHED: + if (selected != LACP_UNSELECTED) + next_state = LACP_MUX_WAITING; + break; + case LACP_MUX_WAITING: + KASSERTMSG((lacpp->lp_pending > 0 || + !LACP_TIMER_ISARMED(lacpp, LACP_TIMER_WAIT_WHILE)), + "lp_pending=%d, timer=%d", lacpp->lp_pending, + !LACP_TIMER_ISARMED(lacpp, LACP_TIMER_WAIT_WHILE)); + + if (selected == LACP_SELECTED && + lacpp->lp_pending == 0) { + next_state = LACP_MUX_ATTACHED; + } else if (selected == LACP_UNSELECTED) { + next_state = LACP_MUX_DETACHED; + } + break; + case LACP_MUX_ATTACHED: + if (selected != LACP_SELECTED) { + next_state = LACP_MUX_DETACHED; + } else if (lacp_isactive(lsc, lacpp) && p_sync) { + next_state = LACP_MUX_COLLECTING; + } + break; + case LACP_MUX_COLLECTING: + if (selected != LACP_SELECTED || + !lacp_isactive(lsc, lacpp) + || !p_sync) { + next_state = LACP_MUX_ATTACHED; + } else if (p_collecting) { + next_state = LACP_MUX_DISTRIBUTING; + } + break; + case LACP_MUX_DISTRIBUTING: + if (selected != LACP_SELECTED || + !lacp_isactive(lsc, lacpp) + || !p_sync || !p_collecting) { + next_state = LACP_MUX_COLLECTING; + LACP_DPRINTF((lsc, lacpp, + "Interface stopped DISTRIBUTING," + " possible flapping\n")); + } + break; + case LACP_MUX_INIT: + default: + panic("%s: unknown state", + lsc->lsc_softc->sc_if.if_xname); + } + } while (lacp_set_mux(lsc, lacpp, next_state) == 0); +} + +static bool +lacp_aggregator_is_match(struct lacp_aggregator_systemid *a, +struct lacp_aggregator_systemid *b) +{ + + if (a->sid_prio != b->sid_prio) + return false; + + if (a->sid_key != b->sid_key) + return false; + + if (memcmp(a->sid_mac, b->sid_mac, sizeof(a->sid_mac)) != 0) + return false; + + return true; +} + +static void +lacp_selected_update(struct lacp_softc *lsc, struct lacp_aggregator *la) +{ + struct lacp_port *lacpp; + size_t nselected; + uint32_t media; + + KASSERT(LACP_LOCKED(lsc)); + + lacpp = LIST_FIRST(&la->la_ports); + if (lacpp == NULL) + return; + + media = lacpp->lp_media; + nselected = 0; + LIST_FOREACH(lacpp, &la->la_ports, lp_entry_la) { + if (nselected >= lsc->lsc_max_ports || + (!lsc->lsc_multi_linkspeed && media != lacpp->lp_media)) { + if (lacpp->lp_selected == LACP_SELECTED) + lacpp->lp_selected = LACP_STANDBY; + continue; + } + + switch (lacpp->lp_selected) { + case LACP_STANDBY: + lacpp->lp_selected = LACP_SELECTED; + /* fall through */ + case LACP_SELECTED: + nselected++; + break; + default: + /* do nothing */ + break; + } + } +} + +static void +lacp_select(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_aggregator *la; + struct lacp_aggregator_systemid *sid; + struct lacp_port *lacpp0; + char buf[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + bool insert_after; + + if (lacpp->lp_aggregator != NULL) + return; + + /* If we haven't heard from our peer, skip this step. */ + if (ISSET(lacpp->lp_actor.lpi_state, LACP_STATE_DEFAULTED)) + return + + KASSERT(!LACP_TIMER_ISARMED(lacpp, LACP_TIMER_WAIT_WHILE)); + + sid = &lacpp->lp_aggregator_sidbuf; + + TAILQ_FOREACH(la, &lsc->lsc_aggregators, la_q) { + if (lacp_aggregator_is_match(&la->la_sid, sid)) + break; + } + + if (la == NULL) { + la = kmem_zalloc(sizeof(*la), KM_NOSLEEP); + if (la == NULL) { + LACP_DPRINTF((lsc, lacpp, + "couldn't allocate aggregator\n")); + /* will retry the next tick. */ + return; + } + LIST_INIT(&la->la_ports); + + la->la_sid = *sid; + TAILQ_INSERT_TAIL(&lsc->lsc_aggregators, la, la_q); + LACP_DPRINTF((lsc, lacpp, "a new aggregator created\n")); + } else { + LACP_DPRINTF((lsc, lacpp, "aggregator found\n")); + } + + KASSERT(la != NULL); + LACP_AGGREGATOR_STR(la, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "aggregator lagid=%s\n", buf)); + + lacpp->lp_aggregator = la; + lacpp->lp_selected = LACP_STANDBY; + + insert_after = false; + + LIST_FOREACH(lacpp0, &la->la_ports, lp_entry_la) { + if (lacp_port_priority_max(lacpp0, lacpp) == lacpp) + break; + + if (LIST_NEXT(lacpp0, lp_entry_la) == NULL) { + insert_after = true; + break; + } + } + + if (lacpp0 == NULL) { + LIST_INSERT_HEAD(&la->la_ports, lacpp, lp_entry_la); + } else if (insert_after) { + LIST_INSERT_AFTER(lacpp0, lacpp, lp_entry_la); + } else { + LIST_INSERT_BEFORE(lacpp0, lacpp, lp_entry_la); + } + + lacp_selected_update(lsc, la); +} + +static void +lacp_unselect(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct lacp_aggregator *la; + char buf[LACP_SYSTEMIDSTR_LEN] __LACPDEBUGUSED; + bool remove_actaggr; + + KASSERT(LACP_LOCKED(lsc)); + KASSERT(!LACP_TIMER_ISARMED(lacpp, LACP_TIMER_WAIT_WHILE)); + + la = lacpp->lp_aggregator; + lacpp->lp_selected = LACP_UNSELECTED; + + if (la == NULL) + return; + + KASSERT(!LIST_EMPTY(&la->la_ports)); + + LACP_AGGREGATOR_STR(la, buf, sizeof(buf)); + LACP_DPRINTF((lsc, lacpp, "unselect aggregator lagid=%s\n", buf)); + + LIST_REMOVE(lacpp, lp_entry_la); + lacpp->lp_aggregator = NULL; + + if (LIST_EMPTY(&la->la_ports)) { + if (la == lsc->lsc_aggregator) { + LACP_DPRINTF((lsc, NULL, "remove active aggregator\n")); + lsc->lsc_aggregator = NULL; + remove_actaggr = true; + } + TAILQ_REMOVE(&lsc->lsc_aggregators, la, la_q); + kmem_free(la, sizeof(*la)); + if (remove_actaggr) { + lacp_select_active_aggregator(lsc); + } + } else { + lacp_selected_update(lsc, la); + } +} + +static void +lacp_suppress_distributing(struct lacp_softc *lsc) +{ + struct lacp_aggregator *la; + struct lacp_port *lacpp; + + KASSERT(LACP_LOCKED(lsc)); + + la = lsc->lsc_aggregator; + + LIST_FOREACH(lacpp, &la->la_ports, lp_entry_la) { + if (ISSET(lacpp->lp_actor.lpi_state, + LACP_STATE_DISTRIBUTING)) { + lagg_workq_add(lsc->lsc_workq, + &lacpp->lp_work_marker); + } + } + + LACP_PTIMER_ARM(lsc, LACP_PTIMER_DISTRIBUTING, + LACP_TRANSIT_DELAY); +} + +static void +lacp_distributing_timer(struct lacp_softc *lsc) +{ + + KASSERT(LACP_LOCKED(lsc)); + + if (lsc->lsc_suppress_distributing) { + LACP_DPRINTF((lsc, NULL, + "disable suppress distributing\n")); + lsc->lsc_suppress_distributing = false; + } +} + +static struct mbuf * +lacp_markerdu_mbuf(struct lacp_softc *lsc, struct lacp_port *lacpp) +{ + struct ifnet *ifp_port; + struct mbuf *m; + struct markerdu *mdu; + struct markerdu_info *mi; + + KASSERT(LACP_LOCKED(lsc)); + + ifp_port = lacpp->lp_laggport->lp_ifp; + + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + lsc->lsc_mgethdr_failed.ev_count++; + return NULL; + } + + m->m_pkthdr.len = m->m_len = sizeof(*mdu); + + mdu = mtod(m, struct markerdu *); + + memset(mdu, 0, sizeof(*mdu)); + + m->m_flags |= M_MCAST; + memcpy(mdu->mdu_eh.ether_dhost, ethermulticastaddr_slowprotocols, + ETHER_ADDR_LEN); + memcpy(mdu->mdu_eh.ether_shost, CLLADDR(ifp_port->if_sadl), + ETHER_ADDR_LEN); + mdu->mdu_eh.ether_type = ntohs(ETHERTYPE_SLOWPROTOCOLS); + mdu->mdu_sph.sph_subtype = SLOWPROTOCOLS_SUBTYPE_MARKER; + mdu->mdu_sph.sph_version = 1; + + mi = &mdu->mdu_info; + tlv_set(&mdu->mdu_tlv_info, MARKER_TYPE_INFO, + sizeof(*mi)); + mi->mi_rq_port = lacpp->lp_actor.lpi_portno; + mi->mi_rq_xid = htonl(lacpp->lp_marker_xid); + memcpy(mi->mi_rq_system, lsc->lsc_system_mac, LACP_MAC_LEN); + + mdu->mdu_tlv_term.tlv_type = MARKER_TYPE_TERMINATE; + mdu->mdu_tlv_term.tlv_length = 0; + + return m; +} + +static void +lacp_marker_work(struct lagg_work *lw, void *xlsc) +{ + struct lacp_softc *lsc; + struct lacp_port *lacpp; + struct lagg_port *lp; + struct markerdu *mdu; + struct mbuf *m; + struct psref psref; + int bound; + + lsc = xlsc; + lacpp = container_of(lw, struct lacp_port, lp_work_marker); + + LACP_LOCK(lsc); + lacpp->lp_marker_xid++; + m = lacp_markerdu_mbuf(lsc, lacpp); + if (m == NULL) { + LACP_UNLOCK(lsc); + return; + } + SET(lacpp->lp_flags, LACP_PORT_MARK); + lsc->lsc_suppress_distributing = true; + lp = lacpp->lp_laggport; + bound = curlwp_bind(); + lagg_port_getref(lp, &psref); + LACP_UNLOCK(lsc); + + if (LACP_ISDUMPING(lsc)) { + lacp_dprintf(lsc, lacpp, "markerdu transmit\n"); + mdu = mtod(m, struct markerdu *); + lacp_dump_markertlv(&mdu->mdu_info, NULL); + } + + lagg_port_xmit(lp, m); + lagg_port_putref(lp, &psref); + curlwp_bindx(bound); +} + +static void +lacp_dump_lacpdutlv(const struct lacpdu_peerinfo *pi_actor, + const struct lacpdu_peerinfo *pi_partner, + const struct lacpdu_collectorinfo *lci) +{ + char str[LACP_STATESTR_LEN]; + + if (pi_actor != NULL) { + lacp_peerinfo_idstr(pi_actor, str, sizeof(str)); + printf("actor=%s\n", str); + lacp_state_str(pi_actor->lpi_state, + str, sizeof(str)); + printf("actor.state=%s portno=%d portprio=0x%04x\n", + str, + ntohs(pi_actor->lpi_port_no), + ntohs(pi_actor->lpi_port_prio));; + } else { + printf("no actor info\n"); + } + + if (pi_partner != NULL) { + lacp_peerinfo_idstr(pi_partner, str, sizeof(str)); + printf("partner=%s\n", str); + lacp_state_str(pi_partner->lpi_state, + str, sizeof(str)); + printf("partner.state=%s portno=%d portprio=0x%04x\n", + str, + ntohs(pi_partner->lpi_port_no), + ntohs(pi_partner->lpi_port_prio)); + } else { + printf("no partner info\n"); + } + + if (lci != NULL) { + printf("maxdelay=%d\n", ntohs(lci->lci_maxdelay)); + } else { + printf("no collector info\n"); + } +} + +static void +lacp_dump_markertlv(const struct markerdu_info *mi_info, + const struct markerdu_info *mi_res) +{ + + if (mi_info != NULL) { + printf("marker info: port=%d, sys=%s, id=%u\n", + ntohs(mi_info->mi_rq_port), + ether_sprintf(mi_info->mi_rq_system), + ntohl(mi_info->mi_rq_xid)); + } + + if (mi_res != NULL) { + printf("marker resp: port=%d, sys=%s, id=%u\n", + ntohs(mi_res->mi_rq_port), + ether_sprintf(mi_res->mi_rq_system), + ntohl(mi_res->mi_rq_xid)); + + } +} + +static uint32_t +lacp_ifmedia2lacpmedia(u_int ifmedia) +{ + uint32_t rv; + + switch (IFM_SUBTYPE(ifmedia)) { + case IFM_10_T: + case IFM_10_2: + case IFM_10_5: + case IFM_10_STP: + case IFM_10_FL: + rv = LACP_LINKSPEED_10; + break; + case IFM_100_TX: + case IFM_100_FX: + case IFM_100_T4: + case IFM_100_VG: + case IFM_100_T2: + rv = LACP_LINKSPEED_100; + break; + case IFM_1000_SX: + case IFM_1000_LX: + case IFM_1000_CX: + case IFM_1000_T: + case IFM_1000_BX10: + case IFM_1000_KX: + rv = LACP_LINKSPEED_1000; + break; + case IFM_2500_SX: + case IFM_2500_KX: + rv = LACP_LINKSPEED_2500; + break; + case IFM_5000_T: + rv = LACP_LINKSPEED_5000; + 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: + rv = LACP_LINKSPEED_10G; + break; + default: + rv = LACP_LINKSPEED_UNKNOWN; + } + + if (IFM_TYPE(ifmedia) == IFM_ETHER) + SET(rv, LACP_MEDIA_ETHER); + if ((ifmedia & IFM_FDX) != 0) + SET(rv, LACP_MEDIA_FDX); + + return rv; +} diff --git a/sys/net/lagg/if_lagg_lacp.h b/sys/net/lagg/if_lagg_lacp.h new file mode 100644 index 00000000000..149dbfdff86 --- /dev/null +++ b/sys/net/lagg/if_lagg_lacp.h @@ -0,0 +1,142 @@ + +#ifndef _NET_LAGG_IF_LAGG_LACP_H_ +#define _NET_LAGG_IF_LAGG_LACP_H_ + +/* timeout values (in sec) */ +#define LACP_FAST_PERIODIC_TIME 1 +#define LACP_SLOW_PERIODIC_TIME 30 +#define LACP_SHORT_TIMEOUT_TIME (3 * LACP_FAST_PERIODIC_TIME) +#define LACP_LONG_TIMEOUT_TIME (3 * LACP_SLOW_PERIODIC_TIME) +#define LACP_CHURN_DETECTION_TIME 60 +#define LACP_AGGREGATE_WAIT_TIME 2 +#define LACP_TRANSIT_DELAY 3 + +#define LACP_MAX_PORTS 16 +#define LACP_SYSTEM_PRIO 0x8000U +#define LACP_PORT_PRIO LAGG_PORT_PRIO + +#define LACP_PARTNER_ADMIN_OPTIMISTIC (LACP_STATE_SYNC | \ + LACP_STATE_AGGREGATION | \ + LACP_STATE_COLLECTING | \ + LACP_STATE_DISTRIBUTING) +#define LACP_PARTNER_ADMIN_STRICT 0 + +#define SLOWPROTOCOLS_SUBTYPE_LACP 1 +#define SLOWPROTOCOLS_SUBTYPE_MARKER 2 + +struct slowprothdr { + uint8_t sph_subtype; + uint8_t sph_version; +} __packed; + +#define TLV_TYPE_TERMINATE 0 + +#define LACP_TYPE_TERMINATE TLV_TYPE_TERMINATE +#define LACP_TYPE_ACTORINFO 1 +#define LACP_TYPE_PARTNERINFO 2 +#define LACP_TYPE_COLLECTORINFO 3 + +#define MARKER_TYPE_TERMINATE TLV_TYPE_TERMINATE +#define MARKER_TYPE_INFO 1 +#define MARKER_TYPE_RESPONSE 2 + +struct tlvhdr { + uint8_t tlv_type; + uint8_t tlv_length; +} __packed; + +struct tlv { + uint8_t tlv_t; + uint8_t tlv_l; + void *tlv_v; +}; + +static inline void +tlv_set(struct tlvhdr *th, uint8_t t, uint8_t l) +{ + + th->tlv_type = t; + th->tlv_length = sizeof(*th) + l; +} + +struct lacpdu_peerinfo { + uint16_t lpi_system_prio; + uint8_t lpi_system_mac[LACP_MAC_LEN]; + uint16_t lpi_key; + uint16_t lpi_port_prio; + uint16_t lpi_port_no; + uint8_t lpi_state; + uint8_t lpi_resv[3]; +} __packed; + +struct lacpdu_collectorinfo { + uint16_t lci_maxdelay; + uint8_t lci_resv[12]; +} __packed; + +struct lacpdu { + struct ether_header ldu_eh; + struct slowprothdr ldu_sph; + + struct tlvhdr ldu_tlv_actor; + struct lacpdu_peerinfo ldu_actor; + struct tlvhdr ldu_tlv_partner; + struct lacpdu_peerinfo ldu_partner; + + struct tlvhdr ldu_tlv_collector; + struct lacpdu_collectorinfo + ldu_collector; + + struct tlvhdr ldu_tlv_term; + uint8_t ldu_resv[50]; +} __packed; + +struct markerdu_info { + uint16_t mi_rq_port; + uint8_t mi_rq_system[LACP_MAC_LEN]; + uint32_t mi_rq_xid; + uint8_t mi_pad[2]; +} __packed; + +struct markerdu { + struct ether_header mdu_eh; + struct slowprothdr mdu_sph; + + struct tlvhdr mdu_tlv_info; + struct markerdu_info mdu_info; + + struct tlvhdr mdu_tlv_term; + uint8_t mdu_resv[90]; +} __packed; + +/* + * lacp media: + * 1byte + * +-------+-------+-------+-------+ + * | media | speed | + * +-------+-------+-------+-------+ + */ + +enum lacp_linkspeed { + LACP_LINKSPEED_UNKNOWN = 0, + LACP_LINKSPEED_10, + LACP_LINKSPEED_100, + LACP_LINKSPEED_1000, + LACP_LINKSPEED_2500, + LACP_LINKSPEED_5000, + LACP_LINKSPEED_10G, + LACP_LINKSPEED_25G, + LACP_LINKSPEED_40G, + LACP_LINKSPEED_50G, + LACP_LINKSPEED_56G, + LACP_LINKSPEED_100G, + LACP_LINKSPEED_200G, +}; + +#define LACP_MEDIA_OFFSET 24 +#define LACP_MEDIA_MASK 0xff000000U +#define LACP_MEDIA_ETHER (__BIT(0) << LACP_MEDIA_OFFSET) +#define LACP_MEDIA_FDX (__BIT(1) << LACP_MEDIA_OFFSET) +#define LACP_MEDIA_DEFAULT (LACP_LINKSPEED_UNKNOWN | \ + LACP_MEDIA_ETHER | LACP_MEDIA_FDX) +#endif diff --git a/sys/net/lagg/if_laggproto.c b/sys/net/lagg/if_laggproto.c new file mode 100644 index 00000000000..0263c01bb9a --- /dev/null +++ b/sys/net/lagg/if_laggproto.c @@ -0,0 +1,647 @@ + +/* $NetBSD: $ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c)2021 Internet Initiative Japan, Inc. + * 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 +__KERNEL_RCSID(0, "$NetBSD:$"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +struct lagg_proto_softc { + struct lagg_softc *psc_softc; + struct pslist_head psc_ports; + kmutex_t psc_lock; + pserialize_t psc_psz; + size_t psc_ctxsiz; + void *psc_ctx; + size_t psc_nactports; +}; + +/* + * Locking notes: + * - Items of struct lagg_proto_softc is protected by + * psc_lock (an adaptive mutex) + * - psc_ports is protected by pserialize (psc_psz) + * - Updates of psc_ports is serialized by sc_lock in + * struct lagg_softc + * - Other locking notes are described in if_laggproto.h + */ + +struct lagg_failover { + bool fo_rx_all; +}; + +struct lagg_portmap { + struct lagg_port *pm_ports[LAGG_MAX_PORTS]; + size_t pm_nports; +}; + +struct lagg_portmaps { + struct lagg_portmap maps_pmap[2]; + size_t maps_activepmap; +}; + +struct lagg_lb { + struct lagg_portmaps lb_pmaps; +}; + +struct lagg_proto_port { + struct pslist_entry lpp_entry; + struct lagg_port *lpp_laggport; + bool lpp_active; +}; + +#define LAGG_PROTO_LOCK(_psc) mutex_enter(&(_psc)->psc_lock) +#define LAGG_PROTO_UNLOCK(_psc) mutex_exit(&(_psc)->psc_lock) +#define LAGG_PROTO_LOCKED(_psc) mutex_owned(&(_psc)->psc_lock) + +static struct lagg_proto_softc * + lagg_proto_alloc(lagg_proto, struct lagg_softc *); +static void lagg_proto_free(struct lagg_proto_softc *); +static void lagg_proto_insert_port(struct lagg_proto_softc *, + struct lagg_proto_port *); +static void lagg_proto_remove_port(struct lagg_proto_softc *, + struct lagg_proto_port *); +static struct lagg_port * + lagg_link_active(struct lagg_proto_softc *psc, + struct lagg_proto_port *, struct psref *); + +static inline struct lagg_portmap * +lagg_portmap_active(struct lagg_portmaps *maps) +{ + size_t i; + + i = atomic_load_consume(&maps->maps_activepmap); + + return &maps->maps_pmap[i]; +} + +static inline struct lagg_portmap * +lagg_portmap_next(struct lagg_portmaps *maps) +{ + size_t i; + + i = atomic_load_consume(&maps->maps_activepmap); + i &= 0x1; + i ^= 0x1; + + return &maps->maps_pmap[i]; +} + +static inline void +lagg_portmap_switch(struct lagg_portmaps *maps) +{ + size_t i; + + i = atomic_load_consume(&maps->maps_activepmap); + i &= 0x1; + i ^= 0x1; + + atomic_store_release(&maps->maps_activepmap, i); +} + +static struct lagg_proto_softc * +lagg_proto_alloc(lagg_proto pr, struct lagg_softc *sc) +{ + struct lagg_proto_softc *psc; + size_t ctxsiz; + + switch (pr) { + case LAGG_PROTO_FAILOVER: + ctxsiz = sizeof(struct lagg_failover); + break; + case LAGG_PROTO_LOADBALANCE: + ctxsiz = sizeof(struct lagg_lb); + break; + default: + ctxsiz = 0; + } + + psc = kmem_zalloc(sizeof(*psc), KM_NOSLEEP); + if (psc == NULL) + return NULL; + + if (ctxsiz > 0) { + psc->psc_ctx = kmem_zalloc(ctxsiz, KM_NOSLEEP); + if (psc->psc_ctx == NULL) { + kmem_free(psc, sizeof(*psc)); + return NULL; + } + + psc->psc_ctxsiz = ctxsiz; + } + + PSLIST_INIT(&psc->psc_ports); + psc->psc_psz = pserialize_create(); + mutex_init(&psc->psc_lock, MUTEX_DEFAULT, IPL_SOFTNET); + psc->psc_softc = sc; + + return psc; +} + +static void +lagg_proto_free(struct lagg_proto_softc *psc) +{ + + pserialize_destroy(psc->psc_psz); + mutex_destroy(&psc->psc_lock); + + if (psc->psc_ctxsiz > 0) + kmem_free(psc->psc_ctx, psc->psc_ctxsiz); + + kmem_free(psc, sizeof(*psc)); +} + +static struct lagg_port * +lagg_link_active(struct lagg_proto_softc *psc, + struct lagg_proto_port *pport, struct psref *psref) +{ + struct lagg_port *lp; + int s; + + lp = NULL; + s = pserialize_read_enter(); + + for (;pport != NULL; + pport = PSLIST_READER_NEXT(pport, + struct lagg_proto_port, lpp_entry)) { + if (atomic_load_relaxed(&pport->lpp_active)) { + lp = pport->lpp_laggport; + goto done; + } + } + + PSLIST_READER_FOREACH(pport, &psc->psc_ports, + struct lagg_proto_port, lpp_entry) { + if (atomic_load_relaxed(&pport->lpp_active)) { + lp = pport->lpp_laggport; + break; + } + } +done: + if (lp != NULL) + lagg_port_getref(lp, psref); + pserialize_read_exit(s); + + return lp; +} + +int +lagg_common_allocport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_proto_port *pport; + + KASSERT(LAGG_LOCKED(psc->psc_softc)); + + pport = kmem_zalloc(sizeof(*pport), KM_NOSLEEP); + if (pport == NULL) + return ENOMEM; + + PSLIST_ENTRY_INIT(pport, lpp_entry); + pport->lpp_laggport = lp; + lp->lp_proto_ctx = (void *)pport; + return 0; +} + +void +lagg_common_freeport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_proto_port *pport; + + pport = lp->lp_proto_ctx; + lp->lp_proto_ctx = NULL; + + kmem_free(pport, sizeof(*pport)); +} + +static void +lagg_proto_insert_port(struct lagg_proto_softc *psc, + struct lagg_proto_port *pport) +{ + struct lagg_proto_port *pport0; + struct lagg_port *lp, *lp0; + bool insert_after; + + insert_after = false; + lp = pport->lpp_laggport; + + LAGG_PROTO_LOCK(psc); + PSLIST_WRITER_FOREACH(pport0, &psc->psc_ports, + struct lagg_proto_port, lpp_entry) { + lp0 = pport0->lpp_laggport; + if (lp0->lp_prio > lp->lp_prio) + break; + + if (PSLIST_WRITER_NEXT(pport0, + struct lagg_proto_port, lpp_entry) == NULL) { + insert_after = true; + break; + } + } + + if (pport0 == NULL) { + PSLIST_WRITER_INSERT_HEAD(&psc->psc_ports, pport, + lpp_entry); + } else if (insert_after) { + PSLIST_WRITER_INSERT_AFTER(pport0, pport, lpp_entry); + } else { + PSLIST_WRITER_INSERT_BEFORE(pport0, pport, lpp_entry); + } + LAGG_PROTO_UNLOCK(psc); +} + +static void +lagg_proto_remove_port(struct lagg_proto_softc *psc, + struct lagg_proto_port *pport) +{ + + LAGG_PROTO_LOCK(psc); + PSLIST_WRITER_REMOVE(pport, lpp_entry); + pserialize_perform(psc->psc_psz); + LAGG_PROTO_UNLOCK(psc); +} + +void +lagg_common_startport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_proto_port *pport; + + pport = lp->lp_proto_ctx; + lagg_proto_insert_port(psc, pport); + + lagg_common_linkstate(psc, lp); +} + +void +lagg_common_stopport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_proto_port *pport; + struct ifnet *ifp; + + pport = lp->lp_proto_ctx; + lagg_proto_remove_port(psc, pport); + + if (pport->lpp_active) { + if (psc->psc_nactports > 0) + psc->psc_nactports--; + + if (psc->psc_nactports == 0) { + ifp = &psc->psc_softc->sc_if; + if_link_state_change(ifp, LINK_STATE_DOWN); + } + + pport->lpp_active = false; + } +} + +void +lagg_common_linkstate(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_proto_port *pport; + struct ifnet *ifp; + bool is_active; + + pport = lp->lp_proto_ctx; + is_active = lagg_portactive(lp); + + if (pport->lpp_active == is_active) + return; + + ifp = &psc->psc_softc->sc_if; + if (is_active) { + psc->psc_nactports++; + if (psc->psc_nactports == 1) + if_link_state_change(ifp, LINK_STATE_UP); + } else { + if (psc->psc_nactports > 0) + psc->psc_nactports--; + + if (psc->psc_nactports == 0) + if_link_state_change(ifp, LINK_STATE_DOWN); + } + + atomic_store_relaxed(&pport->lpp_active, is_active); +} + +void +lagg_common_detach(struct lagg_proto_softc *psc) +{ + + lagg_proto_free(psc); +} + +int +lagg_none_attach(struct lagg_softc *sc, struct lagg_proto_softc **pscp) +{ + + *pscp = NULL; + return 0; +} + +int +lagg_none_up(struct lagg_proto_softc *psc __unused) +{ + + return EBUSY; +} + +int +lagg_fail_attach(struct lagg_softc *sc, struct lagg_proto_softc **xpsc) +{ + struct lagg_proto_softc *psc; + struct lagg_failover *fovr; + + psc = lagg_proto_alloc(LAGG_PROTO_FAILOVER, sc); + if (psc == NULL) + return ENOMEM; + + fovr = psc->psc_ctx; + fovr->fo_rx_all = true; + + *xpsc = psc; + return 0; +} + +int +lagg_fail_transmit(struct lagg_proto_softc *psc, struct mbuf *m) +{ + struct ifnet *ifp; + struct lagg_port *lp; + struct psref psref; + + lp = lagg_link_active(psc, NULL, &psref); + if (lp == NULL) { + ifp = &psc->psc_softc->sc_if; + if_statinc(ifp, if_oerrors); + m_freem(m); + return ENOENT; + } + + lagg_enqueue(psc->psc_softc, lp, m); + lagg_port_putref(lp, &psref); + return 0; +} + +struct mbuf * +lagg_fail_input(struct lagg_proto_softc *psc, struct lagg_port *lp, + struct mbuf *m) +{ + struct lagg_failover *fovr; + struct lagg_port *lp0; + struct ifnet *ifp; + struct psref psref; + + fovr = psc->psc_ctx; + if (atomic_load_relaxed(&fovr->fo_rx_all)) + return m; + + lp0 = lagg_link_active(psc, NULL, &psref); + if (lp0 == NULL) { + goto drop; + } + + if (lp0 != lp) { + lagg_port_putref(lp0, &psref); + goto drop; + } + + lagg_port_putref(lp0, &psref); + + return m; +drop: + ifp = &psc->psc_softc->sc_if; + if_statinc(ifp, if_ierrors); + m_freem(m); + return NULL; +} + +void +lagg_fail_portstat(struct lagg_proto_softc *psc, struct lagg_port *lp, + struct laggreqport *resp) +{ + struct lagg_failover *fovr; + struct lagg_proto_port *pport; + struct lagg_port *lp0; + struct psref psref; + + fovr = psc->psc_ctx; + pport = lp->lp_proto_ctx; + + if (pport->lpp_active) { + SET(resp->rp_flags, LAGG_PORT_ACTIVE); + if (fovr->fo_rx_all) { + SET(resp->rp_flags, LAGG_PORT_COLLECTING); + } + + lp0 = lagg_link_active(psc, NULL, &psref); + if (lp0 == lp) { + SET(resp->rp_flags, + LAGG_PORT_COLLECTING | LAGG_PORT_DISTRIBUTING); + } + if (lp0 != NULL) + lagg_port_putref(lp0, &psref); + } +} + +int +lagg_fail_ioctl(struct lagg_proto_softc *psc, struct laggreqproto *lreq) +{ + struct lagg_failover *fovr; + struct laggreq_fail *rpfail; + int error; + bool set; + + error = 0; + fovr = psc->psc_ctx; + rpfail = &lreq->rp_fail; + + switch (rpfail->command) { + case LAGGIOC_FAILSETFLAGS: + case LAGGIOC_FAILCLRFLAGS: + set = (rpfail->command == LAGGIOC_FAILSETFLAGS) ? + true : false; + + if (ISSET(rpfail->flags, LAGGREQFAIL_RXALL)) + fovr->fo_rx_all = set; + break; + default: + error = ENOTTY; + break; + } + + return error; +} + +int +lagg_lb_attach(struct lagg_softc *sc, struct lagg_proto_softc **xpsc) +{ + struct lagg_proto_softc *psc; + struct lagg_lb *lb; + + psc = lagg_proto_alloc(LAGG_PROTO_LOADBALANCE, sc); + if (psc == NULL) + return ENOMEM; + + lb = psc->psc_ctx; + lb->lb_pmaps.maps_activepmap = 0; + + *xpsc = psc; + return 0; +} + +void +lagg_lb_startport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_lb *lb; + struct lagg_portmap *pm_act, *pm_next; + size_t n; + + lb = psc->psc_ctx; + lagg_common_startport(psc, lp); + + LAGG_PROTO_LOCK(psc); + pm_act = lagg_portmap_active(&lb->lb_pmaps); + pm_next = lagg_portmap_next(&lb->lb_pmaps); + + *pm_next = *pm_act; + + n = pm_next->pm_nports; + pm_next->pm_ports[n] = lp; + + n++; + pm_next->pm_nports = n; + + lagg_portmap_switch(&lb->lb_pmaps); + pserialize_perform(psc->psc_psz); + LAGG_PROTO_UNLOCK(psc); +} + +void +lagg_lb_stopport(struct lagg_proto_softc *psc, struct lagg_port *lp) +{ + struct lagg_lb *lb; + struct lagg_portmap *pm_act, *pm_next; + size_t i, n; + + lb = psc->psc_ctx; + + LAGG_PROTO_LOCK(psc); + pm_act = lagg_portmap_active(&lb->lb_pmaps); + pm_next = lagg_portmap_next(&lb->lb_pmaps); + n = 0; + + for (i = 0; i < pm_act->pm_nports; i++) { + if (pm_act->pm_ports[i] == lp) + continue; + + pm_next->pm_ports[n] = pm_act->pm_ports[i]; + n++; + } + + lagg_portmap_switch(&lb->lb_pmaps); + pserialize_perform(psc->psc_psz); + LAGG_PROTO_UNLOCK(psc); + + lagg_common_stopport(psc, lp); +} + +int +lagg_lb_transmit(struct lagg_proto_softc *psc, struct mbuf *m) +{ + struct lagg_lb *lb; + struct lagg_portmap *pm; + struct lagg_port *lp, *lp0; + struct ifnet *ifp; + struct psref psref; + uint32_t hash; + int s; + + lb = psc->psc_ctx; + hash = lagg_hashmbuf(psc->psc_softc, m); + + s = pserialize_read_enter(); + + pm = lagg_portmap_active(&lb->lb_pmaps); + hash %= pm->pm_nports; + lp0 = pm->pm_ports[hash]; + lp = lagg_link_active(psc, lp0->lp_proto_ctx, &psref); + + pserialize_read_exit(s); + + if (__predict_false(lp == NULL)) { + ifp = &psc->psc_softc->sc_if; + if_statinc(ifp, if_oerrors); + m_freem(m); + return ENOENT; + } + + lagg_enqueue(psc->psc_softc, lp, m); + lagg_port_putref(lp, &psref); + + return 0; +} + +struct mbuf * +lagg_lb_input(struct lagg_proto_softc *psc __unused, + struct lagg_port *lp __unused, struct mbuf *m) +{ + + return m; +} + +void +lagg_lb_portstat(struct lagg_proto_softc *psc, struct lagg_port *lp, + struct laggreqport *resp) +{ + struct lagg_proto_port *pport; + + pport = lp->lp_proto_ctx; + + if (pport->lpp_active) { + SET(resp->rp_flags, LAGG_PORT_ACTIVE | + LAGG_PORT_COLLECTING | LAGG_PORT_DISTRIBUTING); + } +} diff --git a/sys/net/lagg/if_laggproto.h b/sys/net/lagg/if_laggproto.h new file mode 100644 index 00000000000..df4d223c53a --- /dev/null +++ b/sys/net/lagg/if_laggproto.h @@ -0,0 +1,289 @@ +/* $NetBSD: $ */ + +#ifndef _NET_LAGG_IF_LAGGPROTO_H_ +#define _NET_LAGG_IF_LAGGPROTO_H_ + +struct lagg_softc; +struct lagg_proto_softc; + +#define LAGG_MAX_PORTS 32 +#define LAGG_PORT_PRIO 0x8000U + +enum lagg_work_state { + LAGG_WORK_IDLE, + LAGG_WORK_ENQUEUED, + LAGG_WORK_STOPPING +}; +struct lagg_work { + struct work lw_cookie; + void (*lw_func)(struct lagg_work *, void *); + void *lw_arg; + int lw_state; +}; + +static inline void +lagg_work_set(struct lagg_work *w, + void (*func)(struct lagg_work *, void *), void *arg) +{ + + w->lw_func = func; + w->lw_arg = arg; +} + +struct workqueue * + lagg_workq_create(const char *, pri_t, int, int); +void lagg_workq_destroy(struct workqueue *); +void lagg_workq_add(struct workqueue *, struct lagg_work *); +void lagg_workq_wait(struct workqueue *, struct lagg_work *); + +struct lagg_port { + struct psref_target lp_psref; + struct ifnet *lp_ifp; /* physical interface */ + struct lagg_softc *lp_softc; /* parent lagg */ + void *lp_proto_ctx; + bool lp_detaching; + + uint32_t lp_prio; /* port priority */ + uint32_t lp_flags; /* port flags */ + + u_char lp_iftype; + uint8_t lp_lladdr[ETHER_ADDR_LEN]; + int (*lp_ioctl)(struct ifnet *, u_long, void *); + int (*lp_output)(struct ifnet *, struct mbuf *, + const struct sockaddr *, + const struct rtentry *); + uint64_t lp_ifcapenable; + uint64_t lp_mtu; + + SIMPLEQ_ENTRY(lagg_port) + lp_entry; +}; + +struct lagg_proto { + lagg_proto pr_num; + void (*pr_init)(void); + void (*pr_fini)(void); + int (*pr_attach)(struct lagg_softc *, + struct lagg_proto_softc **); + void (*pr_detach)(struct lagg_proto_softc *); + int (*pr_up)(struct lagg_proto_softc *); + void (*pr_down)(struct lagg_proto_softc *); + int (*pr_transmit)(struct lagg_proto_softc *, + struct mbuf *); + struct mbuf * (*pr_input)(struct lagg_proto_softc *, + struct lagg_port *, struct mbuf *); + int (*pr_allocport)(struct lagg_proto_softc *, + struct lagg_port *); + void (*pr_freeport)(struct lagg_proto_softc *, + struct lagg_port *); + void (*pr_startport)(struct lagg_proto_softc *, + struct lagg_port *); + void (*pr_stopport)(struct lagg_proto_softc *, + struct lagg_port *); + void (*pr_protostat)(struct lagg_proto_softc *, + struct laggreqproto *); + void (*pr_portstat)(struct lagg_proto_softc *, + struct lagg_port *, struct laggreqport *); + void (*pr_linkstate)(struct lagg_proto_softc *, + struct lagg_port *); + int (*pr_ioctl)(struct lagg_proto_softc *, + struct laggreqproto *); +}; + +struct lagg_variant { + lagg_proto lv_proto; + struct lagg_proto_softc + *lv_psc; + + struct psref_target lv_psref; +}; + +struct lagg_mc_entry { + LIST_ENTRY(lagg_mc_entry) + mc_entry; + struct ether_multi *mc_enm; + struct sockaddr_storage mc_addr; +}; + +struct lagg_vlantag { + uint16_t lvt_vtag; + TAILQ_ENTRY(lagg_vlantag) + lvt_entry; +}; + +struct lagg_softc { + kmutex_t sc_lock; + struct ifmedia sc_media; + u_char sc_iftype; + uint8_t sc_lladdr[ETHER_ADDR_LEN]; + LIST_HEAD(, lagg_mc_entry) + sc_mclist; + TAILQ_HEAD(, lagg_vlantag) + sc_vtags; + pserialize_t sc_psz; + struct lagg_variant *sc_var; + SIMPLEQ_HEAD(, lagg_port) + sc_ports; + size_t sc_nports; + char sc_evgroup[16]; + struct evcnt sc_novar; + + struct sysctllog *sc_sysctllog; + const struct sysctlnode *sc_sysctlnode; + bool sc_hash_mac; + bool sc_hash_ipaddr; + bool sc_hash_ip6addr; + bool sc_hash_tcp; + bool sc_hash_udp; + + /* + * storage size of sc_if is a variable-length, + * should be the last + */ + struct ifnet sc_if; +}; + +/* + * Locking notes: + * - sc_lock(LAGG_LOCK()) is an adaptive mutex and protects items + * of struct lagg_softc + * - a lock in struct lagg_proto_softc, for example LACP_LOCK(), is + * an adaptive mutex and protects member contained in the struct + * - sc_var is protected by both pselialize (sc_psz) and psref (lv_psref) + * - Updates of sc_var is serialized by sc_lock + * - Items in sc_ports is protected by both psref (lp_psref) and + * pserialize contained in struct lagg_proto_softc + * - details are discribed in if_laggport.c and if_lagg_lacp.c + * - Updates of items in sc_ports are serialized by sc_lock + * - an instance referenced by lp_proto_ctx in struct lagg_port is + * protected by a lock in struct lagg_proto_softc + * + * Locking order: + * - IFNET_LOCK(sc_if) -> LAGG_LOCK -> ETHER_LOCK(sc_if) -> a lock in + * struct lagg_port_softc + * - IFNET_LOCK(sc_if) -> LAGG_LOCK -> IFNET_LOCK(lp_ifp) + * - Currently, there is no combination of following locks + * - IFNET_LOCK(lp_ifp) and a lock in struct lagg_proto_softc + * - IFNET_LOCK(lp_ifp) and ETHER_LOCK(sc_if) + */ +#define LAGG_LOCK(_sc) mutex_enter(&(_sc)->sc_lock) +#define LAGG_UNLOCK(_sc) mutex_exit(&(_sc)->sc_lock) +#define LAGG_LOCKED(_sc) mutex_owned(&(_sc)->sc_lock) +#define LAGG_CLLADDR(_sc) CLLADDR((_sc)->sc_if.if_sadl) + +#define LAGG_PORTS_FOREACH(_sc, _lp) \ + SIMPLEQ_FOREACH((_lp), &(_sc)->sc_ports, lp_entry) +#define LAGG_PORTS_FOREACH_SAFE(_sc, _lp, _lptmp) \ + SIMPLEQ_FOREACH_SAFE((_lp), &(_sc)->sc_ports, lp_entry, (_lptmp)) +#define LAGG_PORTS_EMPTY(_sc) SIMPLEQ_EMPTY(&(_sc)->sc_ports) +#define LAGG_PORT_IOCTL(_lp, _cmd, _data) \ + (_lp)->lp_ioctl == NULL ? ENOTTY : \ + (_lp)->lp_ioctl((_lp)->lp_ifp, (_cmd), (_data)) + + +static inline const void * +lagg_m_extract(struct mbuf *m, size_t off, size_t reqlen, void *buf) +{ + ssize_t len; + const void *rv; + + KASSERT(ISSET(m->m_flags, M_PKTHDR)); + len = off + reqlen; + + if (m->m_pkthdr.len < len) { + return NULL; + } + + if (m->m_len >= len) { + rv = mtod(m, uint8_t *) + off; + } else { + m_copydata(m, off, reqlen, buf); + rv = buf; + } + + return rv; +} + +static inline int +lagg_port_xmit(struct lagg_port *lp, struct mbuf *m) +{ + + return if_transmit_lock(lp->lp_ifp, m); +} + +static inline bool +lagg_portactive(struct lagg_port *lp) +{ + struct ifnet *ifp; + + ifp = lp->lp_ifp; + + if (ifp->if_link_state != LINK_STATE_DOWN && + ISSET(ifp->if_flags, IFF_UP)) { + return true; + } + + return false; +} + +void lagg_log(struct lagg_softc *, int, const char *, ...); +void lagg_port_getref(struct lagg_port *, struct psref *); +void lagg_port_putref(struct lagg_port *, struct psref *); +void lagg_enqueue(struct lagg_softc *, + struct lagg_port *, struct mbuf *); +uint32_t lagg_hashmbuf(struct lagg_softc *, struct mbuf *); + +void lagg_common_detach(struct lagg_proto_softc *); +int lagg_common_allocport(struct lagg_proto_softc *, + struct lagg_port *); +void lagg_common_freeport(struct lagg_proto_softc *, + struct lagg_port *); +void lagg_common_startport(struct lagg_proto_softc *, + struct lagg_port *); +void lagg_common_stopport(struct lagg_proto_softc *, + struct lagg_port *); +void lagg_common_linkstate(struct lagg_proto_softc *, + struct lagg_port *); + +int lagg_none_attach(struct lagg_softc *, + struct lagg_proto_softc **); +int lagg_none_up(struct lagg_proto_softc *); + +int lagg_fail_attach(struct lagg_softc *, + struct lagg_proto_softc **); +int lagg_fail_transmit(struct lagg_proto_softc *, struct mbuf *); +struct mbuf * lagg_fail_input(struct lagg_proto_softc *, struct lagg_port *, + struct mbuf *); +void lagg_fail_portstat(struct lagg_proto_softc *, + struct lagg_port *, struct laggreqport *); +int lagg_fail_ioctl(struct lagg_proto_softc *, + struct laggreqproto *); + +int lagg_lb_attach(struct lagg_softc *, struct lagg_proto_softc **); +void lagg_lb_startport(struct lagg_proto_softc *, + struct lagg_port *); +void lagg_lb_stopport(struct lagg_proto_softc *, struct lagg_port *); +int lagg_lb_transmit(struct lagg_proto_softc *, struct mbuf *); +struct mbuf * lagg_lb_input(struct lagg_proto_softc *, struct lagg_port *, + struct mbuf *); +void lagg_lb_portstat(struct lagg_proto_softc *, + struct lagg_port *, struct laggreqport *); + +int lacp_attach(struct lagg_softc *, struct lagg_proto_softc **); +void lacp_detach(struct lagg_proto_softc *); +int lacp_up(struct lagg_proto_softc *); +void lacp_down(struct lagg_proto_softc *); +int lacp_transmit(struct lagg_proto_softc *, struct mbuf *); +struct mbuf * lacp_input(struct lagg_proto_softc *, struct lagg_port *, + struct mbuf *); +int lacp_allocport(struct lagg_proto_softc *, struct lagg_port *); +void lacp_freeport(struct lagg_proto_softc *, struct lagg_port *); +void lacp_startport(struct lagg_proto_softc *, struct lagg_port *); +void lacp_stopport(struct lagg_proto_softc *, struct lagg_port *); +void lacp_protostat(struct lagg_proto_softc *, + struct laggreqproto *); +void lacp_portstat(struct lagg_proto_softc *, struct lagg_port *, + struct laggreqport *); +void lacp_linkstate(struct lagg_proto_softc *, struct lagg_port *); +int lacp_ioctl(struct lagg_proto_softc *, struct laggreqproto *); +#endif diff --git a/sys/net/lagg/if_laggvar.h b/sys/net/lagg/if_laggvar.h new file mode 100644 index 00000000000..2c472097fc5 --- /dev/null +++ b/sys/net/lagg/if_laggvar.h @@ -0,0 +1,13 @@ +/* $NetBSD: $ */ + +#ifndef _NET_LAGG_IF_LAGGVAR_H_ +#define _NET_LAGG_IF_LAGGVAR_H_ + +void lagg_ifdetach(struct ifnet *); +void lagg_linkstate_changed(struct ifnet *); + +extern struct mbuf * + (*lagg_input_ethernet_p)(struct ifnet *, + struct mbuf *); + +#endif diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c index 55c3acf9c36..7ff81e000f9 100644 --- a/sys/netinet6/in6_ifattach.c +++ b/sys/netinet6/in6_ifattach.c @@ -563,6 +563,7 @@ in6_ifattach(struct ifnet *ifp, struct ifnet *altifp) switch (ifp->if_type) { case IFT_BRIDGE: case IFT_L2TP: + case IFT_IEEE8023ADLAG: #ifdef IFT_PFLOG case IFT_PFLOG: #endif diff --git a/sys/rump/include/opt/lagg.h b/sys/rump/include/opt/lagg.h new file mode 100644 index 00000000000..cc697a7d9f2 --- /dev/null +++ b/sys/rump/include/opt/lagg.h @@ -0,0 +1,3 @@ +/* $NetBSD: $ */ + +#define NLAGG 1 diff --git a/sys/rump/librump/rumpnet/net_stub.c b/sys/rump/librump/rumpnet/net_stub.c index e3e4481dc5b..19ba88ea138 100644 --- a/sys/rump/librump/rumpnet/net_stub.c +++ b/sys/rump/librump/rumpnet/net_stub.c @@ -108,6 +108,11 @@ __weak_alias(ipsec_pcbdisconn,rumpnet_stub); __weak_alias(key_sa_routechange,rumpnet_stub); __weak_alias(key_sp_unref,rumpnet_stub); +/* lagg */ +__weak_alias(lagg_ifdetach,rumpnet_stub); +__weak_alias(lagg_input_ethernet,rumpnet_stub); +__weak_alias(lagg_linkstate_changed,rumpnet_stub); + struct ifnet_head ifnet_list; struct pslist_head ifnet_pslist; kmutex_t ifnet_mtx; diff --git a/sys/rump/net/Makefile.rumpnetcomp b/sys/rump/net/Makefile.rumpnetcomp index f7265458f0f..1f88c271c32 100644 --- a/sys/rump/net/Makefile.rumpnetcomp +++ b/sys/rump/net/Makefile.rumpnetcomp @@ -4,8 +4,8 @@ .include RUMPNETCOMP= agr bridge net net80211 netbt netcan netinet netinet6 netipsec -RUMPNETCOMP+= gif ipsec netmpls npf l2tp local pppoe shmif tap tun vlan vether -RUMPNETCOMP+= wg +RUMPNETCOMP+= gif ipsec netmpls npf l2tp lagg local pppoe shmif tap tun vlan +RUMPNETCOMP+= vether wg .if ${MKSLJIT} != "no" || make(rumpdescribe) RUMPNETCOMP+= bpfjit diff --git a/sys/rump/net/lib/liblagg/LAGG.ioconf b/sys/rump/net/lib/liblagg/LAGG.ioconf new file mode 100644 index 00000000000..fc7d957d835 --- /dev/null +++ b/sys/rump/net/lib/liblagg/LAGG.ioconf @@ -0,0 +1,7 @@ +# $NETBSD: $ + +ioconf lagg + +include "conf/files" + +pseudo-device lagg diff --git a/sys/rump/net/lib/liblagg/Makefile b/sys/rump/net/lib/liblagg/Makefile new file mode 100644 index 00000000000..40a323881cb --- /dev/null +++ b/sys/rump/net/lib/liblagg/Makefile @@ -0,0 +1,22 @@ +# $NetBSD: $ +# + +.PATH: ${.CURDIR}/../../../../net/lagg + +LIB= rumpnet_lagg +COMMENT=Layer 2 trunking pseudo interface + +IOCONF= LAGG.ioconf +SRCS= if_lagg.c +SRCS+= if_laggproto.c +SRCS+= if_lagg_lacp.c + +SRCS+= lagg_component.c + +.ifdef RUMP_DEBUG +CPPFLAGS.if_lagg.c+= -DLAGG_DEBUG +CPPFLAGS.if_lagg_lacp.c+= -DLACP_DEBUG +.endif + +.include +.include diff --git a/sys/rump/net/lib/liblagg/lagg_component.c b/sys/rump/net/lib/liblagg/lagg_component.c new file mode 100644 index 00000000000..fd574992e4d --- /dev/null +++ b/sys/rump/net/lib/liblagg/lagg_component.c @@ -0,0 +1,43 @@ +/* $NetBSD: $ */ + +/* + * Copyright (c) 2021 Internet Initiative Japan Inc. + * 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 ``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 +__KERNEL_RCSID(0, "$NetBSD: $"); + +#include + +#include + +int laggattach(int); + +RUMP_COMPONENT(RUMP_COMPONENT_NET_IF) +{ + + laggattach(0); +} diff --git a/tests/net/Makefile b/tests/net/Makefile index e666ac8b8b8..e647d663ab5 100644 --- a/tests/net/Makefile +++ b/tests/net/Makefile @@ -7,8 +7,9 @@ TESTSDIR= ${TESTSBASE}/net TESTS_SUBDIRS= fdpass in_cksum net sys .if (${MKRUMP} != "no") && !defined(BSD_MK_COMPAT_FILE) TESTS_SUBDIRS+= arp bpf bpfilter can carp icmp if if_bridge if_gif -TESTS_SUBDIRS+= if_ipsec if_l2tp if_loop if_pppoe if_tap if_tun ipsec -TESTS_SUBDIRS+= mcast mpls ndp npf route if_vether if_vlan if_wg +TESTS_SUBDIRS+= if_ipsec if_l2tp if_lagg if_loop if_pppoe if_tap +TESTS_SUBDIRS+= if_tun if_vether if_vlan if_wg ipsec mcast mpls +TESTS_SUBDIRS+= ndp npf route .if (${MKSLJIT} != "no") TESTS_SUBDIRS+= bpfjit .endif diff --git a/tests/net/if_lagg/Makefile b/tests/net/if_lagg/Makefile new file mode 100644 index 00000000000..a06424755f3 --- /dev/null +++ b/tests/net/if_lagg/Makefile @@ -0,0 +1,12 @@ +# $NetBSD: $ + +.include + +TESTSDIR= ${TESTSBASE}/net/if_lagg + +.for name in lagg +TESTS_SH+= t_${name} +TESTS_SH_SRC_t_${name}= ../net_common.sh t_${name}.sh +.endfor + +.include diff --git a/tests/net/if_lagg/t_lagg.sh b/tests/net/if_lagg/t_lagg.sh new file mode 100644 index 00000000000..6e05bef7b35 --- /dev/null +++ b/tests/net/if_lagg/t_lagg.sh @@ -0,0 +1,1060 @@ +# $NetBSD: $ +# +# Copyright (c) 2021 Internet Initiative Japan Inc. +# 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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. +# + +SOCK_HOST0=unix://commsock0 +SOCK_HOST1=unix://commsock1 +SOCK_HOST2=unix://commsock2 +BUS0=bus0 +BUS1=bus1 +BUS2=bus2 +IP4ADDR0=192.168.0.1 +IP4ADDR1=192.168.0.2 +IP4ADDR2=192.168.1.1 +IP4ADDR3=192.168.1.2 +IP6ADDR0=fc00::1 +IP6ADDR1=fc00::2 +IP6ADDR2=fc00:1::1 +IP6ADDR3=fc00:1::2 +WAITTIME=20 + +DEBUG=${DEBUG:-false} + +wait_state() +{ + local state=$1 + local if_lagg=$2 + local if_port=$3 + + local n=$WAITTIME + local cmd_grep="grep -q ${state}" + + if [ x"$if_port" != x"" ]; then + cmd_grep="grep $if_port | $cmd_grep" + fi + + for i in $(seq $n); do + rump.ifconfig $if_lagg | eval $cmd_grep + if [ $? = 0 ] ; then + $DEBUG && echo "wait for $i seconds." + return 0 + fi + + sleep 1 + done + + $DEBUG && rump.ifconfig -v $if_lagg + atf_fail "Couldn't be ${state} for $n seconds." +} +wait_for_distributing() +{ + + wait_state "DISTRIBUTING" $* +} + +expected_inactive() +{ + local if_lagg=$1 + local if_port=$2 + + sleep 3 # wait a little + atf_check -s exit:0 -o not-match:"${if_port}.*ACTIVE" \ + rump.ifconfig ${if_lagg} +} + +atf_test_case lagg_ifconfig cleanup +lagg_ifconfig_head() +{ + + atf_set "descr" "tests for create, destroy, and ioctl of lagg(4)" + atf_set "require.progs" "rump_server" +} + +lagg_ifconfig_body() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + rump_server_start $SOCK_HOST0 lagg + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 destroy + + $atf_ifconfig lagg0 create + $atf_ifconfig shmif0 create + + $atf_ifconfig lagg0 laggproto none + atf_check -s exit:0 -o match:'laggproto none' \ + rump.ifconfig lagg0 + + # cannot add a port while protocol is none + atf_check -s not-exit:0 -e ignore \ + rump.ifconfig lagg0 laggport shmif0 + + $atf_ifconfig lagg0 laggproto lacp + atf_check -s exit:0 -o match:'laggproto lacp' \ + rump.ifconfig lagg0 + + # add a port and an added port + $atf_ifconfig lagg0 laggport shmif0 + atf_check -s not-exit:0 -e ignore \ + rump.ifconfig lagg0 laggport shmif0 + + # remove an added port and a removed port + $atf_ifconfig lagg0 -laggport shmif0 + atf_check -s not-exit:0 -e ignore \ + rump.ifconfig lagg0 -laggport shmif0 + + # re-add a removed port + $atf_ifconfig lagg0 laggport shmif0 + + # detach protocol even if the I/F has ports + $atf_ifconfig lagg0 laggproto none + + # destroy the interface while grouping ports + $atf_ifconfig lagg0 destroy + + $atf_ifconfig lagg0 create + $atf_ifconfig shmif1 create + + $atf_ifconfig lagg0 laggproto lacp + $atf_ifconfig lagg0 laggport shmif0 + $atf_ifconfig lagg0 laggport shmif1 + + $atf_ifconfig lagg0 -laggport shmif0 + $atf_ifconfig lagg0 laggport shmif0 + $atf_ifconfig lagg0 -laggport shmif1 + $atf_ifconfig lagg0 laggport shmif1 + + # destroy a LAGed port + atf_check -s exit:0 -o match:shmif0 rump.ifconfig lagg0 + atf_check -s exit:0 -o match:shmif1 rump.ifconfig lagg0 + $atf_ifconfig shmif0 destroy + $atf_ifconfig shmif1 destroy + + $atf_ifconfig lagg0 laggproto none + atf_check -s exit:0 -o ignore rump.ifconfig lagg0 +} + +lagg_ifconfig_cleanup() +{ + $DEBG && dump + cleanup +} + +atf_test_case lagg_macaddr cleanup +lagg_macaddr_head() +{ + atf_set "descr" "tests for a MAC address to assign to lagg(4)" + atf_set "require.progs" "rump_server" +} + +lagg_macaddr_body() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + rump_server_start $SOCK_HOST0 lagg + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + + maddr=$(get_macaddr $SOCK_HOST0 lagg0) + maddr0=$(get_macaddr $SOCK_HOST0 shmif0) + maddr1=$(get_macaddr $SOCK_HOST0 shmif1) + + $atf_ifconfig lagg0 laggproto lacp + + $atf_ifconfig lagg0 laggport shmif0 + atf_check -s exit:0 -o match:$maddr0 rump.ifconfig lagg0 + + $atf_ifconfig lagg0 laggport shmif1 + atf_check -s exit:0 -o match:$maddr0 rump.ifconfig lagg0 + atf_check -s exit:0 -o match:$maddr0 rump.ifconfig shmif1 + + $atf_ifconfig lagg0 -laggport shmif0 + atf_check -s exit:0 -o match:$maddr1 rump.ifconfig lagg0 + atf_check -s exit:0 -o match:$maddr0 rump.ifconfig shmif0 + + $atf_ifconfig lagg0 laggport shmif0 + atf_check -s exit:0 -o match:$maddr1 rump.ifconfig lagg0 + atf_check -s exit:0 -o match:$maddr1 rump.ifconfig shmif0 + + $atf_ifconfig lagg0 -laggport shmif0 + atf_check -s exit:0 -o match:$maddr0 rump.ifconfig shmif0 + + $atf_ifconfig lagg0 -laggport shmif1 + atf_check -s exit:0 -o match:$maddr rump.ifconfig lagg0 +} + +lagg_macaddr_cleanup() +{ + $DEBUG && dump + cleanup +} + +atf_test_case lagg_ipv6lla cleanup +lagg_ipv6lla_head() +{ + atf_set "descr" "tests for a IPV6 LLA to assign to lagg(4)" + atf_set "require.progs" "rump_server" +} + +lagg_ipv6lla_body() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + rump_server_start $SOCK_HOST0 netinet6 lagg + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + + $atf_ifconfig lagg0 laggproto lacp + + $atf_ifconfig shmif0 up + atf_check -s exit:0 -o match:'inet6 fe80:' rump.ifconfig shmif0 + + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + atf_check -s exit:0 -o not-match:'inet6 fe80:' rump.ifconfig shmif0 + + $atf_ifconfig lagg0 laggport shmif1 + $atf_ifconfig shmif1 up + atf_check -s exit:0 -o not-match:'inet6 fe80:' rump.ifconfig shmif1 + + $atf_ifconfig lagg0 -laggport shmif0 + atf_check -s exit:0 -o match:'inet6 fe80:' rump.ifconfig shmif0 + + $atf_ifconfig shmif1 down + $atf_ifconfig lagg0 -laggport shmif1 + atf_check -s exit:0 -o not-match:'inet fe80:' rump.ifconfig shmif1 +} + +lagg_ipv6lla_cleanup() +{ + $DEBUG && dump + cleanup +} + +atf_test_case lagg_lacp_basic cleanup +lagg_lacp_basic_head() +{ + + atf_set "descr" "tests for LACP basic functions" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_basic_body() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + rump_server_start $SOCK_HOST0 lagg + rump_server_start $SOCK_HOST1 lagg + rump_server_start $SOCK_HOST2 lagg + + export RUMP_SERVER=$SOCK_HOST0 + + # added running interface + $atf_ifconfig shmif0 create + $atf_ifconfig shmif0 linkstr $BUS0 + + $atf_ifconfig shmif1 create + $atf_ifconfig shmif1 linkstr $BUS1 + + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp + + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig lagg0 up + + $atf_ifconfig lagg0 laggport shmif0 + $atf_ifconfig lagg0 laggport shmif1 + $atf_ifconfig -w 10 + + $atf_ifconfig lagg0 -laggport shmif0 + $atf_ifconfig lagg0 -laggport shmif1 + $atf_ifconfig lagg0 down + + # add the same interfaces again + $atf_ifconfig lagg0 up + $atf_ifconfig lagg0 laggport shmif0 + $atf_ifconfig lagg0 laggport shmif1 + + # detach and re-attach protocol + $atf_ifconfig lagg0 laggproto none + $atf_ifconfig lagg0 laggproto lacp \ + laggport shmif0 laggport shmif1 + + $atf_ifconfig lagg0 -laggport shmif0 -laggport shmif1 + $atf_ifconfig lagg0 destroy + $atf_ifconfig shmif0 destroy + $atf_ifconfig shmif1 destroy + + # tests for a loopback condition + $atf_ifconfig shmif0 create + $atf_ifconfig shmif0 linkstr $BUS0 + $atf_ifconfig shmif1 create + $atf_ifconfig shmif1 linkstr $BUS0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp \ + laggport shmif0 laggport shmif1 + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig lagg0 up + + expected_inactive lagg0 + + $atf_ifconfig shmif0 down + $atf_ifconfig shmif0 destroy + $atf_ifconfig shmif1 down + $atf_ifconfig shmif1 destroy + $atf_ifconfig lagg0 down + $atf_ifconfig lagg0 destroy + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig shmif0 create + $atf_ifconfig shmif0 linkstr $BUS0 + $atf_ifconfig shmif0 up + + $atf_ifconfig shmif1 create + $atf_ifconfig shmif1 linkstr $BUS1 + $atf_ifconfig shmif1 up + + $atf_ifconfig shmif2 create + $atf_ifconfig shmif2 linkstr $BUS2 + $atf_ifconfig shmif2 up + + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 \ + laggport shmif1 laggport shmif2 + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif0 create + $atf_ifconfig shmif0 linkstr $BUS0 + $atf_ifconfig shmif0 up + + $atf_ifconfig shmif1 create + $atf_ifconfig shmif1 linkstr $BUS1 + $atf_ifconfig shmif1 up + + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp + $atf_ifconfig lagg1 create + $atf_ifconfig lagg1 laggproto lacp + + $atf_ifconfig lagg0 laggport shmif0 + $atf_ifconfig lagg0 up + wait_for_distributing lagg0 shmif0 + + $atf_ifconfig lagg1 laggport shmif1 + $atf_ifconfig lagg1 up + + export RUMP_SERVER=$SOCK_HOST2 + $atf_ifconfig shmif0 create + $atf_ifconfig shmif0 linkstr $BUS2 + $atf_ifconfig shmif0 up + + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 shmif0 + expected_inactive lagg0 shmif1 + expected_inactive lagg0 shmif2 +} + +lagg_lacp_basic_cleanup() +{ + + $DEBUG && dump + cleanup +} + +lagg_lacp_ping() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + local af=$1 + local atf_ping="atf_check -s exit:0 -o ignore rump.ping -c 1" + local ping=rump.ping + local rumplib="" + local pfx=24 + local addr_host0=$IP4ADDR0 + local addr_host1=$IP4ADDR1 + + case $af in + "inet") + # do nothing + ;; + "inet6") + atf_ping="atf_check -s exit:0 -o ignore rump.ping6 -c 1" + rumplib="netinet6" + pfx=64 + addr_host0=$IP6ADDR0 + addr_host1=$IP6ADDR1 + ;; + esac + + rump_server_start $SOCK_HOST0 lagg $rumplib + rump_server_start $SOCK_HOST1 lagg $rumplib + + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST0 shmif2 $BUS2 + + rump_server_add_iface $SOCK_HOST1 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST1 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST1 shmif2 $BUS2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + $atf_ifconfig lagg0 $af $addr_host0/$pfx + $atf_ifconfig shmif0 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + $atf_ifconfig lagg0 $af $addr_host1/$pfx + $atf_ifconfig shmif0 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 + $atf_ifconfig -w 10 + + export RUMP_SERVER=$SOCK_HOST1 + wait_for_distributing lagg0 + $atf_ifconfig -w 10 + + $atf_ping $addr_host0 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig shmif1 up + $atf_ifconfig lagg0 laggport shmif1 laggport shmif2 + $atf_ifconfig shmif2 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif1 up + $atf_ifconfig lagg0 laggport shmif1 laggport shmif2 + $atf_ifconfig shmif2 up + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 shmif1 + wait_for_distributing lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST1 + wait_for_distributing lagg0 shmif1 + wait_for_distributing lagg0 shmif2 + + $atf_ping $addr_host0 +} + +atf_test_case lagg_lacp_ipv4 cleanup +lagg_lacp_ipv4_head() +{ + + atf_set "descr" "tests for IPv4 with LACP" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_ipv4_body() +{ + + lagg_lacp_ping "inet" +} + +lagg_lacp_ipv4_cleanup() +{ + + $DEBUG && dump + cleanup +} + +atf_test_case lagg_lacp_ipv6 +lagg_lacp_ipv6_head() +{ + + atf_set "descr" "tests for IPv6 with LACP" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_ipv6_body() +{ + + lagg_lacp_ping "inet6" +} + +lagg_lacp_ipv6_cleanup() +{ + + $DEBUG && dump + cleanup +} + +lagg_lacp_vlan() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + local af=$1 + local atf_ping="atf_check -s exit:0 -o ignore rump.ping -c 1" + local rumplib="vlan" + local pfx=24 + local vlan0_addr_host0=$IP4ADDR0 + local host0addr0=$IP4ADDR0 + local host1addr0=$IP4ADDR1 + local host0addr1=$IP4ADDR2 + local host1addr1=$IP4ADDR3 + + case $af in + "inet") + # do nothing + ;; + "inet6") + atf_ping="atf_check -s exit:0 -o ignore rump.ping6 -c 1" + rumplib="netinet6" + pfx=64 + host0addr0=$IP6ADDR0 + host1addr0=$IP6ADDR1 + host0addr1=$IP6ADDR2 + host1addr1=$IP6ADDR3 + ;; + esac + + rump_server_start $SOCK_HOST0 lagg $rumplib + rump_server_start $SOCK_HOST1 lagg $rumplib + + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST0 shmif2 $BUS2 + + rump_server_add_iface $SOCK_HOST1 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST1 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST1 shmif2 $BUS2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + $atf_ifconfig shmif0 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp laggport shmif0 + $atf_ifconfig shmif0 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 + + $atf_ifconfig vlan0 create + $atf_ifconfig vlan0 vlan 10 vlanif lagg0 + $atf_ifconfig vlan0 $af $host0addr0/$pfx + $atf_ifconfig vlan0 up + + $atf_ifconfig vlan1 create + $atf_ifconfig vlan1 vlan 11 vlanif lagg0 + $atf_ifconfig vlan1 $af $host0addr1/$pfx + $atf_ifconfig vlan1 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig vlan0 create + $atf_ifconfig vlan0 vlan 10 vlanif lagg0 + $atf_ifconfig vlan0 $af $host1addr0/$pfx + $atf_ifconfig vlan0 up + + $atf_ifconfig vlan1 create + $atf_ifconfig vlan1 vlan 11 vlanif lagg0 + $atf_ifconfig vlan1 $af $host1addr1/$pfx + $atf_ifconfig vlan1 up + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig -w 10 + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig -w 10 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ping $host1addr0 + $atf_ping $host1addr1 + + $atf_ifconfig lagg0 laggport shmif1 + $atf_ifconfig shmif1 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 laggport shmif1 + $atf_ifconfig shmif1 up + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 shmif1 + + export RUMP_SERVER=$SOCK_HOST1 + wait_for_distributing lagg0 shmif1 + + $atf_ping $host0addr0 + $atf_ping $host0addr1 +} + +atf_test_case lagg_lacp_vlan_ipv4 cleanup +lagg_lacp_vlan_ipv4_head() +{ + + atf_set "descr" "tests for IPv4 VLAN frames over LACP LAG" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_vlan_ipv4_body() +{ + + lagg_lacp_vlan "inet" +} + +lagg_lacp_vlan_ipv4_cleanup() +{ + $DEBUG && dump + cleanup +} + +atf_test_case lagg_lacp_vlan_ipv6 cleanup +lagg_lacp_vlan_ipv6_head() +{ + + atf_set "descr" "tests for IPv6 VLAN frames over LACP LAG" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_vlan_ipv6_body() +{ + + lagg_lacp_vlan "inet" +} + +lagg_lacp_vlan_ipv6_cleanup() +{ + $DEBUG && dump + cleanup +} + +atf_test_case lagg_lacp_portpri cleanup +lagg_lacp_portpri_head() +{ + + atf_set "descr" "tests for LACP port priority" + atf_set "require.progs" "rump_server" +} + +lagg_lacp_portpri_body() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + rump_server_start $SOCK_HOST0 lagg + rump_server_start $SOCK_HOST1 lagg + + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST0 shmif2 $BUS2 + + rump_server_add_iface $SOCK_HOST1 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST1 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST1 shmif2 $BUS2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp + $atf_ifconfig lagg0 lagglacp maxports 2 + + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + + $atf_ifconfig lagg0 laggport shmif0 pri 1000 + $atf_ifconfig lagg0 laggport shmif1 pri 2000 + $atf_ifconfig lagg0 laggport shmif2 pri 3000 + $atf_ifconfig lagg0 up + + atf_check -s exit:0 -o match:'shmif0 pri=1000' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif1 pri=2000' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif2 pri=3000' rump.ifconfig lagg0 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto lacp + + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + + $atf_ifconfig lagg0 laggport shmif0 pri 300 + $atf_ifconfig lagg0 laggport shmif1 pri 200 + $atf_ifconfig lagg0 laggport shmif2 pri 100 + $atf_ifconfig lagg0 up + + atf_check -s exit:0 -o match:'shmif0 pri=300' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif1 pri=200' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif2 pri=100' rump.ifconfig lagg0 + + export RUMP_SERVER=$SOCK_HOST0 + wait_for_distributing lagg0 shmif0 + wait_for_distributing lagg0 shmif1 + wait_state "STANDBY" lagg0 shmif2 + + $atf_ifconfig shmif0 down + wait_for_distributing lagg0 shmif2 + + $atf_ifconfig shmif0 up + wait_for_distributing lagg0 shmif0 + + $atf_ifconfig lagg0 laggportpri shmif0 5000 + $atf_ifconfig lagg0 laggportpri shmif1 5000 + $atf_ifconfig lagg0 laggportpri shmif2 5000 + + atf_check -s exit:0 -o match:'shmif0 pri=5000' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif1 pri=5000' rump.ifconfig lagg0 + atf_check -s exit:0 -o match:'shmif2 pri=5000' rump.ifconfig lagg0 + + wait_state "STANDBY" lagg0 shmif0 + wait_for_distributing lagg0 shmif1 + wait_for_distributing lagg0 shmif2 +} + +lagg_lacp_portpri_cleanup() +{ + + $DEBUG && dump + cleanup +} + +lagg_failover() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + local af=$1 + local ping="rump.ping -c 1" + local rumplib="" + local pfx=24 + local addr_host0=$IP4ADDR0 + local addr_host1=$IP4ADDR1 + + case $af in + "inet") + # do nothing + ;; + "inet6") + ping="rump.ping6 -c 1" + rumplib="netinet6" + pfx=64 + addr_host0=$IP6ADDR0 + addr_host1=$IP6ADDR1 + ;; + esac + + local atf_ping="atf_check -s exit:0 -o ignore ${ping}" + + rump_server_start $SOCK_HOST0 lagg $rumplib + rump_server_start $SOCK_HOST1 lagg $rumplib + + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST0 shmif2 $BUS2 + + rump_server_add_iface $SOCK_HOST1 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST1 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST1 shmif2 $BUS2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto failover + + $atf_ifconfig lagg0 laggport shmif0 pri 1000 + $atf_ifconfig lagg0 laggport shmif1 pri 2000 + $atf_ifconfig lagg0 laggport shmif2 pri 3000 + $atf_ifconfig lagg0 $af $addr_host0/$pfx + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto failover + + $atf_ifconfig lagg0 laggport shmif0 pri 1000 + $atf_ifconfig lagg0 laggport shmif1 pri 3000 + $atf_ifconfig lagg0 laggport shmif2 pri 2000 + $atf_ifconfig lagg0 $af $addr_host1/$pfx + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig -w 10 + wait_for_distributing lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif1 + wait_state "COLLECTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig -w 10 + wait_for_distributing lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif1 + wait_state "COLLECTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ping $addr_host1 + + $atf_ifconfig shmif0 down + wait_for_distributing lagg0 shmif1 + wait_state "COLLECTING" lagg0 shmif1 + wait_state "COLLECTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif0 down + wait_for_distributing lagg0 shmif2 + wait_state "COLLECTING" lagg0 shmif2 + wait_state "COLLECTING" lagg0 shmif1 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ping $addr_host1 + + $atf_ifconfig lagg0 laggfailover -rx-all + atf_check -s exit:0 -o not-match:'shmif2.+COLLECTING' rump.ifconfig lagg0 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 laggfailover -rx-all + atf_check -s exit:0 -o not-match:'shmif1.+COLLECTING' rump.ifconfig lagg0 + + export RUMP_SERVER=$SOCK_HOST0 + atf_check -s not-exit:0 -o ignore -e ignore $ping -c 1 $addr_host1 +} + +atf_test_case lagg_failover_ipv4 cleanup +lagg_failover_ipv4_head() +{ + + atf_set "descr" "tests for failover using IPv4" + atf_set "require.progs" "rump_server" +} + +lagg_failover_ipv4_body() +{ + + lagg_failover "inet" +} + +lagg_failover_ipv4_cleanup() +{ + + $DEBUG && dump + cleanup +} + +atf_test_case lagg_failover_ipv6 cleanup +lagg_failover_ipv6_head() +{ + + atf_set "descr" "tests for failover using IPv6" + atf_set "require.progs" "rump_server" +} + +lagg_failover_ipv6_body() +{ + + lagg_failover "inet6" +} + +lagg_failover_ipv6_cleanup() +{ + + $DEBUG && dump + cleanup +} + +lagg_loadbalance() +{ + local atf_ifconfig="atf_check -s exit:0 rump.ifconfig" + + local af=$1 + local ping="rump.ping -c 1" + local rumplib="" + local pfx=24 + local addr_host0=$IP4ADDR0 + local addr_host1=$IP4ADDR1 + + case $af in + "inet") + # do nothing + ;; + "inet6") + ping="rump.ping6 -c 1" + rumplib="netinet6" + pfx=64 + addr_host0=$IP6ADDR0 + addr_host1=$IP6ADDR1 + ;; + esac + + local atf_ping="atf_check -s exit:0 -o ignore ${ping}" + + rump_server_start $SOCK_HOST0 lagg $rumplib + rump_server_start $SOCK_HOST1 lagg $rumplib + + rump_server_add_iface $SOCK_HOST0 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST0 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST0 shmif2 $BUS2 + + rump_server_add_iface $SOCK_HOST1 shmif0 $BUS0 + rump_server_add_iface $SOCK_HOST1 shmif1 $BUS1 + rump_server_add_iface $SOCK_HOST1 shmif2 $BUS2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto loadbalance + + $atf_ifconfig lagg0 laggport shmif0 pri 1000 + $atf_ifconfig lagg0 laggport shmif1 pri 2000 + $atf_ifconfig lagg0 laggport shmif2 pri 3000 + $atf_ifconfig lagg0 $af $addr_host0/$pfx + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig lagg0 create + $atf_ifconfig lagg0 laggproto loadbalance + + $atf_ifconfig lagg0 laggport shmif0 pri 1000 + $atf_ifconfig lagg0 laggport shmif1 pri 3000 + $atf_ifconfig lagg0 laggport shmif2 pri 2000 + $atf_ifconfig lagg0 $af $addr_host1/$pfx + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif0 up + $atf_ifconfig shmif1 up + $atf_ifconfig shmif2 up + $atf_ifconfig lagg0 up + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ifconfig -w 10 + wait_for_distributing lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif0 + wait_state "COLLECTING" lagg0 shmif1 + wait_state "COLLECTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig -w 10 + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif0 + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif1 + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ping $addr_host1 + + $atf_ifconfig shmif0 down + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif1 + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST1 + $atf_ifconfig shmif0 down + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif1 + wait_state "COLLECTING,DISTRIBUTING" lagg0 shmif2 + + export RUMP_SERVER=$SOCK_HOST0 + $atf_ping $addr_host1 +} + +atf_test_case lagg_loadbalance_ipv4 cleanup +lagg_loadbalance_ipv4_head() +{ + + atf_set "descr" "tests for loadbalance using IPv4" + atf_set "require.progs" "rump_server" +} + +lagg_loadbalance_ipv4_body() +{ + + lagg_loadbalance "inet" +} + +lagg_loadbalance_ipv4_cleanup() +{ + + $DEBUG && dump + cleanup +} + +atf_test_case lagg_loadbalance_ipv6 cleanup +lagg_loadbalance_ipv6_head() +{ + + atf_set "descr" "tests for loadbalance using IPv6" + atf_set "require.progs" "rump_server" +} + +lagg_loadbalance_ipv6_body() +{ + + lagg_loadbalance "inet6" +} + +lagg_loadbalance_ipv6_cleanup() +{ + + $DEBUG && dump + cleanup +} + +atf_init_test_cases() +{ + + atf_add_test_case lagg_ifconfig + atf_add_test_case lagg_macaddr + atf_add_test_case lagg_ipv6lla + atf_add_test_case lagg_lacp_basic + atf_add_test_case lagg_lacp_ipv4 + atf_add_test_case lagg_lacp_ipv6 + atf_add_test_case lagg_lacp_vlan_ipv4 + atf_add_test_case lagg_lacp_vlan_ipv6 + atf_add_test_case lagg_lacp_portpri + atf_add_test_case lagg_failover_ipv4 + atf_add_test_case lagg_failover_ipv6 + atf_add_test_case lagg_loadbalance_ipv4 + atf_add_test_case lagg_loadbalance_ipv6 +}