This is xnu-12377.1.9. See this file in:
/*
* Copyright (c) 2024 Apple Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
#include <skywalk/nexus/nexus_traffic_rule_eth.h>
#include <skywalk/nexus/netif/nx_netif.h>
#include <net/ethernet.h>
/*
* Eth-specific traffic rule.
*/
struct nxctl_traffic_rule_eth {
struct nxctl_traffic_rule ntre_common;
SLIST_ENTRY(nxctl_traffic_rule_eth) ntre_storage_link;
struct ifnet_traffic_descriptor_eth ntre_td;
struct ifnet_traffic_rule_action_steer ntre_ra;
};
/*
* Currently supported tuple types.
*/
#define ETRM(type, raddr) \
ITDBIT(type, IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_ETHER_TYPE) | \
ITDBIT(raddr, IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_RADDR)
static uint8_t nxctl_eth_traffic_rule_masks[] = {
ETRM(1, 0),
ETRM(0, 1),
};
#define NETHRULEMASKS \
(sizeof(nxctl_eth_traffic_rule_masks)/sizeof(uint8_t))
/* Per-interface lists of eth traffic rules */
SLIST_HEAD(nxctl_traffic_rule_eth_head, nxctl_traffic_rule_eth);
struct nxctl_traffic_rule_eth_if {
char rei_ifname[IFNAMSIZ];
struct nxctl_traffic_rule_eth_head rei_lists[NETHRULEMASKS];
uint32_t rei_count;
SLIST_ENTRY(nxctl_traffic_rule_eth_if) rei_link;
};
/* List of per-interface lists */
SLIST_HEAD(nxctl_traffic_rule_eth_if_head, nxctl_traffic_rule_eth_if);
struct nxctl_traffic_rule_eth_storage {
struct nxctl_traffic_rule_eth_if_head res_if_list;
uint32_t res_count;
};
static struct nxctl_traffic_rule_eth_storage *rs = NULL;
static kern_allocation_name_t nxctl_traffic_rule_tag = NULL;
/*
* If an interface attaches after rule(s) are added, this function is used
* retrieve the current rule count for that interface.
*/
int
nxctl_eth_traffic_rule_get_count(const char *ifname, uint32_t *count)
{
int err;
NXTR_RLOCK();
err = eth_traffic_rule_get_count(ifname, count);
NXTR_RUNLOCK();
return err;
}
/*
* Used for finding the qset id associated with a ether type and ether remote addr.
*/
int
nxctl_eth_traffic_rule_find_qset_id(const char *ifname,
uint16_t eth_type, ether_addr_t *eth_raddr, uint64_t *qset_id)
{
struct nxctl_traffic_rule_eth *__single ntre = NULL;
struct nxctl_traffic_rule *__single ntr = NULL;
struct ifnet_traffic_descriptor_eth td = {0};
int err;
td.eth_common.itd_type = IFNET_TRAFFIC_DESCRIPTOR_TYPE_ETH;
td.eth_common.itd_len = sizeof(td);
td.eth_common.itd_flags = IFNET_TRAFFIC_DESCRIPTOR_FLAG_INBOUND |
IFNET_TRAFFIC_DESCRIPTOR_FLAG_OUTBOUND;
td.eth_type = eth_type;
td.eth_mask = IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_ETHER_TYPE;
if (eth_raddr) {
bcopy(eth_raddr, &td.eth_raddr, ETHER_ADDR_LEN);
td.eth_mask |= IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_RADDR;
}
NXTR_RLOCK();
err = eth_traffic_rule_find(ifname, &td.eth_common, 0, &ntr);
if (err != 0) {
goto fail;
}
ntre = __container_of(ntr, struct nxctl_traffic_rule_eth, ntre_common);
*qset_id = ntre->ntre_ra.ras_qset_id;
NXTR_RUNLOCK();
return 0;
fail:
NXTR_RUNLOCK();
return err;
}
static int
parse_eth_hdr(struct __kern_packet *pkt, uint16_t *eth_type, ether_addr_t *eth_raddr)
{
volatile ether_header_t *_l2 = NULL;
uint8_t *pkt_buf, *l2_hdr;
uint32_t bdlen, bdlim, bdoff, cls_len;
int err;
ASSERT(pkt->pkt_l2_len <= pkt->pkt_length);
MD_BUFLET_ADDR_ABS_DLEN(pkt, pkt_buf, bdlen, bdlim, bdoff);
cls_len = bdlim - bdoff;
cls_len = (uint32_t)MIN(cls_len, pkt->pkt_length);
VERIFY(pkt->pkt_length >= cls_len);
if (cls_len == 0) {
SK_ERR("cls_len == 0");
err = EINVAL;
goto fail;
}
l2_hdr = pkt_buf + pkt->pkt_headroom;
_l2 = (volatile ether_header_t *)(void *)l2_hdr;
*eth_type = ntohs(_l2->ether_type);
bcopy(__DECONST(void *, &_l2->ether_dhost), eth_raddr, ETHER_ADDR_LEN);
return 0;
fail:
DTRACE_SKYWALK4(classify__failed, ether_header_t *, _l2, size_t, pkt->pkt_length,
uint8_t, pkt->pkt_l2_len, int, err);
return err;
}
int
nxctl_eth_traffic_rule_find_qset_id_with_pkt(const char *ifname,
struct __kern_packet *pkt, uint64_t *qset_id)
{
ether_addr_t eth_raddr;
uint16_t eth_type;
int err;
err = parse_eth_hdr(pkt, ð_type, ð_raddr);
if (err != 0) {
return err;
}
return nxctl_eth_traffic_rule_find_qset_id(ifname, eth_type, ð_raddr, qset_id);
}
void
eth_traffic_rule_init(kern_allocation_name_t rule_tag)
{
ASSERT(nxctl_traffic_rule_tag == NULL);
nxctl_traffic_rule_tag = rule_tag;
}
int
eth_traffic_rule_validate(
const char *ifname,
struct ifnet_traffic_descriptor_common *td,
struct ifnet_traffic_rule_action *ra)
{
char buf[IFNAMSIZ];
int unit, i;
struct ifnet_traffic_descriptor_eth *tdi;
if (ifunit_extract(ifname, buf, sizeof(buf), &unit) < 0) {
SK_ERR("invalid ifname: %s", ifname);
return EINVAL;
}
if (td->itd_len != sizeof(*tdi)) {
SK_ERR("invalid td len: expected %lu, actual %d",
sizeof(*tdi), td->itd_len);
return EINVAL;
}
if (td->itd_flags == 0 ||
(td->itd_flags &
~(IFNET_TRAFFIC_DESCRIPTOR_FLAG_INBOUND |
IFNET_TRAFFIC_DESCRIPTOR_FLAG_OUTBOUND)) != 0) {
SK_ERR("invalid td flags: 0x%x", td->itd_flags);
return EINVAL;
}
tdi = (struct ifnet_traffic_descriptor_eth *)td;
for (i = 0; i < NETHRULEMASKS; i++) {
if (tdi->eth_mask == nxctl_eth_traffic_rule_masks[i]) {
break;
}
}
if (i == NETHRULEMASKS) {
SK_ERR("invalid eth mask: 0x%x", tdi->eth_mask);
return EINVAL;
}
if ((tdi->eth_mask & IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_ETHER_TYPE) != 0) {
if (tdi->eth_type != ETHERTYPE_PAE &&
tdi->eth_type != ETHERTYPE_WAI) {
SK_ERR("invalid eth type 0x%x", tdi->eth_type);
return EINVAL;
}
}
if ((tdi->eth_mask & IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_RADDR) != 0) {
ether_addr_t mac_zeros_addr = {0};
if (!_ether_cmp(&tdi->eth_raddr, &mac_zeros_addr)) {
SK_ERR("eth raddr cannot be unspecified");
return EINVAL;
}
}
if (ra->ra_len != sizeof(struct ifnet_traffic_rule_action_steer)) {
SK_ERR("invalid ra len: expected %lu, actual %d",
sizeof(struct ifnet_traffic_rule_action_steer), ra->ra_len);
return EINVAL;
}
return 0;
}
SK_NO_INLINE_ATTRIBUTE
static void
eth_traffic_rule_storage_create(void)
{
rs = sk_alloc_type(struct nxctl_traffic_rule_eth_storage,
Z_WAITOK | Z_NOFAIL, nxctl_traffic_rule_tag);
SLIST_INIT(&rs->res_if_list);
rs->res_count = 0;
return;
}
SK_NO_INLINE_ATTRIBUTE
static void
eth_traffic_rule_storage_destroy(void)
{
ASSERT(rs->res_count == 0);
ASSERT(SLIST_EMPTY(&rs->res_if_list));
sk_free_type(struct nxctl_traffic_rule_eth_storage, rs);
}
SK_NO_INLINE_ATTRIBUTE
static struct nxctl_traffic_rule_eth_if *
eth_traffic_rule_if_create(const char *ifname)
{
struct nxctl_traffic_rule_eth_if *rif;
int i;
rif = sk_alloc_type(struct nxctl_traffic_rule_eth_if,
Z_WAITOK | Z_NOFAIL, nxctl_traffic_rule_tag);
for (i = 0; i < NETHRULEMASKS; i++) {
SLIST_INIT(&rif->rei_lists[i]);
}
strlcpy(rif->rei_ifname, ifname, sizeof(rif->rei_ifname));
rif->rei_count = 0;
return rif;
}
SK_NO_INLINE_ATTRIBUTE
static void
eth_traffic_rule_if_destroy(struct nxctl_traffic_rule_eth_if *rif)
{
int i;
for (i = 0; i < NETHRULEMASKS; i++) {
ASSERT(SLIST_EMPTY(&rif->rei_lists[i]));
}
ASSERT(rif->rei_count == 0);
sk_free_type(struct nxctl_traffic_rule_eth_if, rif);
}
SK_NO_INLINE_ATTRIBUTE
static boolean_t
eth_traffic_rule_match(struct nxctl_traffic_rule_eth *ntre, const char *ifname,
uint32_t flags, struct ifnet_traffic_descriptor_eth *tdi)
{
struct nxctl_traffic_rule *ntr = (struct nxctl_traffic_rule *)ntre;
struct ifnet_traffic_descriptor_eth *tdi0;
uint8_t mask;
boolean_t exact;
VERIFY(strlcmp(ntr->ntr_ifname, ifname, sizeof(ntr->ntr_ifname)) == 0);
tdi0 = &ntre->ntre_td;
exact = ((flags & NTR_FIND_FLAG_EXACT) != 0);
mask = tdi0->eth_mask & tdi->eth_mask;
if (exact) {
ASSERT(tdi0->eth_mask == tdi->eth_mask);
}
if ((mask & IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_ETHER_TYPE) != 0 &&
tdi0->eth_type != tdi->eth_type) {
DTRACE_SKYWALK2(eth_type__mismatch,
uint8_t, tdi0->eth_type, uint8_t, tdi->eth_type);
return FALSE;
}
if ((mask & IFNET_TRAFFIC_DESCRIPTOR_ETH_MASK_RADDR) != 0 &&
_ether_cmp(&tdi0->eth_raddr, &tdi->eth_raddr)) {
DTRACE_SKYWALK2(eth_raddr__mismatch,
ether_addr_t *, &tdi0->eth_raddr,
ether_addr_t *, &tdi->eth_raddr);
return FALSE;
}
return TRUE;
}
int
eth_traffic_rule_find(const char *ifname,
struct ifnet_traffic_descriptor_common *td, uint32_t flags,
struct nxctl_traffic_rule **ntrp)
{
struct nxctl_traffic_rule_eth *ntre = NULL;
struct nxctl_traffic_rule_eth_if *rif;
struct ifnet_traffic_descriptor_eth *tdi =
(struct ifnet_traffic_descriptor_eth *)td;
int i;
if (rs == NULL) {
return ENOENT;
}
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
if (strlcmp(rif->rei_ifname, ifname, sizeof(rif->rei_ifname)) != 0) {
continue;
}
for (i = 0; i < NETHRULEMASKS; i++) {
if ((flags & NTR_FIND_FLAG_EXACT) != 0 &&
tdi->eth_mask != nxctl_eth_traffic_rule_masks[i]) {
continue;
}
SLIST_FOREACH(ntre, &rif->rei_lists[i], ntre_storage_link) {
if (eth_traffic_rule_match(ntre, ifname, flags, tdi)) {
*ntrp = (struct nxctl_traffic_rule *)ntre;
return 0;
}
}
}
}
return ENOENT;
}
int
eth_traffic_rule_find_by_uuid(
uuid_t uuid, struct nxctl_traffic_rule **ntrp)
{
struct nxctl_traffic_rule_eth *ntre;
struct nxctl_traffic_rule *ntr;
struct nxctl_traffic_rule_eth_if *rif;
int i;
if (rs == NULL) {
return ENOENT;
}
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
for (i = 0; i < NETHRULEMASKS; i++) {
SLIST_FOREACH(ntre, &rif->rei_lists[i], ntre_storage_link) {
ntr = &ntre->ntre_common;
if (uuid_compare(ntr->ntr_uuid, uuid) == 0) {
*ntrp = ntr;
return 0;
}
}
}
}
return ENOENT;
}
static void
eth_update_ifnet_traffic_rule_count(const char *ifname, uint32_t count)
{
struct ifnet *ifp;
ifp = ifunit_ref(ifname);
if (ifp == NULL) {
DTRACE_SKYWALK1(ifname__not__found, char *, ifname);
return;
}
ifnet_update_eth_traffic_rule_count(ifp, count);
ifnet_decr_iorefcnt(ifp);
}
void
eth_traffic_rule_link(struct nxctl_traffic_rule *ntr)
{
struct nxctl_traffic_rule_eth_if *rif;
struct nxctl_traffic_rule_eth *ntre =
(struct nxctl_traffic_rule_eth *)ntr;
struct nxctl_traffic_rule_eth_head *list = NULL;
int i;
char *__null_terminated ntr_ifname = NULL;
char *__null_terminated rei_ifname = NULL;
if (rs == NULL) {
eth_traffic_rule_storage_create();
}
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
if (strbufcmp(rif->rei_ifname, ntr->ntr_ifname) == 0) {
break;
}
}
if (rif == NULL) {
ntr_ifname = __unsafe_null_terminated_from_indexable(ntr->ntr_ifname);
rif = eth_traffic_rule_if_create(ntr_ifname);
SLIST_INSERT_HEAD(&rs->res_if_list, rif, rei_link);
}
for (i = 0; i < NETHRULEMASKS; i++) {
if (ntre->ntre_td.eth_mask ==
nxctl_eth_traffic_rule_masks[i]) {
list = &rif->rei_lists[i];
break;
}
}
retain_traffic_rule(ntr);
ASSERT(list != NULL);
SLIST_INSERT_HEAD(list, ntre, ntre_storage_link);
/* per-interface count */
rif->rei_count++;
rei_ifname = __unsafe_null_terminated_from_indexable(rif->rei_ifname);
eth_update_ifnet_traffic_rule_count(rei_ifname, rif->rei_count);
/* global count */
rs->res_count++;
}
void
eth_traffic_rule_unlink(struct nxctl_traffic_rule *ntr)
{
struct nxctl_traffic_rule_eth_if *rif;
struct nxctl_traffic_rule_eth *ntre =
(struct nxctl_traffic_rule_eth *)ntr;
struct nxctl_traffic_rule_eth_head *list = NULL;
int i;
char *__null_terminated rei_ifname = NULL;
ASSERT(rs != NULL);
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
if (strbufcmp(rif->rei_ifname, ntr->ntr_ifname) == 0) {
break;
}
}
ASSERT(rif != NULL);
for (i = 0; i < NETHRULEMASKS; i++) {
if (ntre->ntre_td.eth_mask ==
nxctl_eth_traffic_rule_masks[i]) {
list = &rif->rei_lists[i];
break;
}
}
ASSERT(list != NULL);
SLIST_REMOVE(list, ntre, nxctl_traffic_rule_eth, ntre_storage_link);
rif->rei_count--;
rei_ifname = __unsafe_null_terminated_from_indexable(rif->rei_ifname);
eth_update_ifnet_traffic_rule_count(rei_ifname, rif->rei_count);
rs->res_count--;
release_traffic_rule(ntr);
if (rif->rei_count == 0) {
SLIST_REMOVE(&rs->res_if_list, rif, nxctl_traffic_rule_eth_if, rei_link);
eth_traffic_rule_if_destroy(rif);
}
if (rs->res_count == 0) {
eth_traffic_rule_storage_destroy();
rs = NULL;
}
}
/*
* XXX
* This may need additional changes to ensure safety against detach/attach.
*/
int
eth_traffic_rule_notify(struct nxctl_traffic_rule *ntr, uint32_t flags)
{
struct ifnet *ifp;
struct nx_netif *nif;
struct netif_qset *__single qset = NULL;
struct nxctl_traffic_rule_eth *ntre;
int err = 0;
char *__null_terminated ntr_ifname = NULL;
ntr_ifname = __unsafe_null_terminated_from_indexable(ntr->ntr_ifname);
ifp = ifunit_ref(ntr_ifname);
if (ifp == NULL) {
DTRACE_SKYWALK1(ifname__not__found, char *, ntr->ntr_ifname);
err = ENXIO;
goto done;
}
nif = NA(ifp)->nifna_netif;
if (!NX_LLINK_PROV(nif->nif_nx)) {
DTRACE_SKYWALK1(llink__not__enabled, struct ifnet *, ifp);
err = ENOTSUP;
goto done;
}
ntre = (struct nxctl_traffic_rule_eth *)ntr;
qset = nx_netif_find_qset(nif, ntre->ntre_ra.ras_qset_id);
err = nx_netif_notify_steering_info(nif, qset,
(struct ifnet_traffic_descriptor_common *)&ntre->ntre_td,
((flags & NTR_NOTIFY_FLAG_ADD) != 0));
done:
if (qset != NULL) {
nx_netif_qset_release(&qset);
}
if (ifp != NULL) {
ifnet_decr_iorefcnt(ifp);
}
return err;
}
int
eth_traffic_rule_get_count(const char *ifname, uint32_t *count)
{
struct nxctl_traffic_rule_eth_if *rif;
int err;
if (rs == NULL) {
err = ENOENT;
goto fail;
}
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
if (strlcmp(rif->rei_ifname, ifname, sizeof(rif->rei_ifname)) == 0) {
break;
}
}
if (rif == NULL) {
err = ENOENT;
goto fail;
}
*count = rif->rei_count;
return 0;
fail:
return err;
}
int
eth_traffic_rule_create(
const char *ifname, struct ifnet_traffic_descriptor_common *td,
struct ifnet_traffic_rule_action *ra, uint32_t flags,
struct nxctl_traffic_rule **ntrp)
{
struct nxctl_traffic_rule_eth *ntre;
struct nxctl_traffic_rule *ntr;
struct ifnet_traffic_descriptor_eth *tdi;
struct ifnet_traffic_rule_action_steer *ras;
ntre = sk_alloc_type(struct nxctl_traffic_rule_eth,
Z_WAITOK | Z_NOFAIL, nxctl_traffic_rule_tag);
ntr = &ntre->ntre_common;
ntr->ntrt_type = IFNET_TRAFFIC_DESCRIPTOR_TYPE_ETH;
ntr->ntr_flags = flags;
uuid_generate(ntr->ntr_uuid);
os_ref_init(&ntr->ntr_refcnt, NULL);
strlcpy(ntr->ntr_ifname, ifname, sizeof(ntr->ntr_ifname));
proc_selfname(ntr->ntr_procname, sizeof(ntr->ntr_procname));
tdi = __container_of(td, struct ifnet_traffic_descriptor_eth, eth_common);
ras = __container_of(ra, struct ifnet_traffic_rule_action_steer, ras_common);
bcopy(tdi, &ntre->ntre_td, sizeof(ntre->ntre_td));
bcopy(ras, &ntre->ntre_ra, sizeof(ntre->ntre_ra));
*ntrp = ntr;
return 0;
}
void
eth_traffic_rule_destroy(struct nxctl_traffic_rule *ntr)
{
struct nxctl_traffic_rule_eth *ntre;
ASSERT(os_ref_get_count(&ntr->ntr_refcnt) == 0);
ntre = (struct nxctl_traffic_rule_eth *)ntr;
sk_free_type(struct nxctl_traffic_rule_eth, ntre);
}
static void
convert_ntre_to_iocinfo(struct nxctl_traffic_rule_eth *ntre,
struct nxctl_traffic_rule_eth_iocinfo *info)
{
struct nxctl_traffic_rule *ntr;
struct nxctl_traffic_rule_generic_iocinfo *ginfo;
bzero(info, sizeof(*info));
ntr = &ntre->ntre_common;
ginfo = &info->tre_common;
static_assert(sizeof(ntr->ntr_procname) == sizeof(ginfo->trg_procname));
static_assert(sizeof(ntr->ntr_ifname) == sizeof(ginfo->trg_ifname));
uuid_copy(ginfo->trg_uuid, ntr->ntr_uuid);
strbufcpy(ginfo->trg_procname, ntr->ntr_procname);
strbufcpy(ginfo->trg_ifname, ntr->ntr_ifname);
bcopy(&ntre->ntre_td, &info->tre_td, sizeof(info->tre_td));
bcopy(&ntre->ntre_ra, &info->tre_ra, sizeof(info->tre_ra));
}
int
eth_traffic_rule_get_all(uint32_t size,
uint32_t *count, user_addr_t uaddr)
{
struct nxctl_traffic_rule_eth *ntre = NULL;
struct nxctl_traffic_rule_eth_if *rif;
struct nxctl_traffic_rule_eth_iocinfo info;
int i, err;
if (size != sizeof(info)) {
SK_ERR("size: actual %d, expected %lu", size, sizeof(info));
return EINVAL;
}
if (rs == NULL) {
*count = 0;
return 0;
}
if (*count < rs->res_count) {
SK_ERR("count: given %d, require: %d", *count, rs->res_count);
return ENOBUFS;
}
SLIST_FOREACH(rif, &rs->res_if_list, rei_link) {
for (i = 0; i < NETHRULEMASKS; i++) {
SLIST_FOREACH(ntre, &rif->rei_lists[i], ntre_storage_link) {
convert_ntre_to_iocinfo(ntre, &info);
err = copyout(&info, uaddr, sizeof(info));
if (err != 0) {
SK_ERR("copyout failed: %d", err);
return err;
}
uaddr += sizeof(info);
}
}
}
*count = rs->res_count;
return 0;
}