This is xnu-11215.1.10. See this file in:
/*
* Copyright (c) 2019-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@
*/
/*
* net_bridge.c
* - test if_bridge.c functionality
*/
#include <darwintest.h>
#include <stdio.h>
#include <unistd.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/event.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netinet6/in6_var.h>
#include <netinet6/nd6.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <net/if_arp.h>
#include <net/bpf.h>
#include <net/if_bridgevar.h>
#include <net/if_fake_var.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <pthread.h>
#include <stdbool.h>
#include <TargetConditionals.h>
#include <darwintest_utils.h>
#include "net_test_lib.h"
#include "inet_transfer.h"
#include "bpflib.h"
#include "in_cksum.h"
static bool S_cleaning_up;
#define ALL_ADDRS (uint32_t)(-1)
typedef struct {
char ifname[IFNAMSIZ]; /* port we do I/O on */
ether_addr_t mac;
char member_ifname[IFNAMSIZ]; /* member of bridge */
ether_addr_t member_mac;
int fd;
u_int unit;
u_int num_addrs;
void * rx_buf;
int rx_buf_size;
bool mac_nat;
struct in_addr ip;
struct in6_addr ip6;
u_short if_index;
u_int test_count;
u_int test_address_count;
uint64_t test_address_present;
} switch_port, *switch_port_t;
typedef struct {
u_int size;
u_int count;
bool mac_nat;
switch_port list[1];
} switch_port_list, * switch_port_list_t;
static struct in_addr bridge_ip_addr;
static struct in6_addr bridge_ipv6_addr;
static u_short bridge_if_index;
static struct ifbareq *
bridge_rt_table_copy(u_int * ret_count);
static void
bridge_rt_table_log(struct ifbareq *rt_table, u_int count);
static struct ifbrmne *
bridge_mac_nat_entries_copy(u_int * ret_count);
static void
bridge_mac_nat_entries_log(struct ifbrmne * entries, u_int count);
#define SETUP_FLAGS_MAC_NAT 0x01
#define SETUP_FLAGS_CHECKSUM_OFFLOAD 0x02
#define SETUP_FLAGS_ATTACH_STACK 0x04
#define SETUP_FLAGS_TRAILERS 0x08
#define SETUP_FLAGS_SHARE_MEMBER_MAC 0x10
#define s6_addr16 __u6_addr.__u6_addr16
/**
** Packet creation/display
**/
#define BOOTP_SERVER_PORT 67
#define BOOTP_CLIENT_PORT 68
#define TEST_SOURCE_PORT 14
#define TEST_DEST_PORT 15
#define EA_UNIT_INDEX 4
#define EA_ADDR_INDEX 5
static void
set_ethernet_address(ether_addr_t *eaddr, u_int unit, u_int addr_index)
{
u_char *a = eaddr->octet;
a[0] = 0x02;
a[2] = 0x00;
a[3] = 0x00;
a[1] = 0x00;
a[EA_UNIT_INDEX] = (u_char)unit;
a[EA_ADDR_INDEX] = (u_char)addr_index;
}
#define TEN_NET 0x0a000000
#define TEN_1_NET (TEN_NET | 0x010000)
static void
get_ipv4_address(u_int unit, u_int addr_index, struct in_addr *ip)
{
/* up to 255 units, 255 addresses */
ip->s_addr = htonl(TEN_1_NET | (unit << 8) | addr_index);
return;
}
#define IN6ADDR_ULA_INIT \
{{{ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}}
static struct in6_addr ula_address = IN6ADDR_ULA_INIT;
#define ULA_UNIT_INDEX 14
#define ULA_ADDR_INDEX 15
static void
get_ipv6_ula_address(u_int unit, u_int addr_index, struct in6_addr *ip)
{
*ip = ula_address;
/* up to 255 units, 255 addresses */
ip->s6_addr[ULA_UNIT_INDEX] = (uint8_t)unit;
ip->s6_addr[ULA_ADDR_INDEX] = (uint8_t)addr_index;
}
#define ND6_EUI64_GBIT 0x01
#define ND6_EUI64_UBIT 0x02
#define ND6_EUI64_TO_IFID(in6) \
do {(in6)->s6_addr[8] ^= ND6_EUI64_UBIT; } while (0)
static void
get_ipv6_ll_address(const ether_addr_t *mac, struct in6_addr * in6)
{
const u_char * addr = mac->octet;
bzero(in6, sizeof(*in6));
in6->s6_addr16[0] = htons(0xfe80);
in6->s6_addr[8] = addr[0];
in6->s6_addr[9] = addr[1];
in6->s6_addr[10] = addr[2];
in6->s6_addr[11] = 0xff;
in6->s6_addr[12] = 0xfe;
in6->s6_addr[13] = addr[3];
in6->s6_addr[14] = addr[4];
in6->s6_addr[15] = addr[5];
ND6_EUI64_TO_IFID(in6);
return;
}
static void
get_ip_address(uint8_t af, u_int unit, u_int addr_index, union ifbrip *ip)
{
switch (af) {
case AF_INET:
get_ipv4_address(unit, addr_index, &ip->ifbrip_addr);
break;
case AF_INET6:
get_ipv6_ula_address(unit, addr_index, &ip->ifbrip_addr6);
break;
default:
T_FAIL("unrecognized address family %u", af);
break;
}
}
static bool
ip_addresses_are_equal(uint8_t af, union ifbrip * ip1, union ifbrip * ip2)
{
bool equal;
switch (af) {
case AF_INET:
equal = (ip1->ifbrip_addr.s_addr == ip2->ifbrip_addr.s_addr);
break;
case AF_INET6:
equal = IN6_ARE_ADDR_EQUAL(&ip1->ifbrip_addr6,
&ip2->ifbrip_addr6);
break;
default:
T_FAIL("unrecognized address family %u", af);
equal = false;
break;
}
return equal;
}
static ether_addr_t ether_external = {
{ 0x80, 0x00, 0x00, 0x00, 0x00, 0x01 }
};
static inline struct in_addr
get_external_ipv4_address(void)
{
struct in_addr ip;
/* IP 10.1.255.1 */
ip.s_addr = htonl(TEN_1_NET | 0xff01);
return ip;
}
static inline void
get_external_ip_address(uint8_t af, union ifbrip * ip)
{
switch (af) {
case AF_INET:
/* IP 10.1.255.1 */
ip->ifbrip_addr = get_external_ipv4_address();
break;
case AF_INET6:
/* fd80::1 */
ip->ifbrip_addr6 = ula_address;
ip->ifbrip_addr6.s6_addr[1] = 0x80;
ip->ifbrip_addr6.s6_addr[15] = 0x01;
break;
default:
T_FAIL("unrecognized address family %u", af);
break;
}
}
static inline void
get_broadcast_ip_address(uint8_t af, union ifbrip * ip)
{
switch (af) {
case AF_INET:
ip->ifbrip_addr.s_addr = INADDR_BROADCAST;
break;
case AF_INET6:
/* 0xff0e::0 linklocal scope multicast */
ip->ifbrip_addr6 = in6addr_any;
ip->ifbrip_addr6.s6_addr[0] = 0xff;
ip->ifbrip_addr6.s6_addr[1] = __IPV6_ADDR_SCOPE_LINKLOCAL;
break;
default:
T_FAIL("unrecognized address family %u", af);
break;
}
}
#define ETHER_NTOA_BUFSIZE (ETHER_ADDR_LEN * 3)
static const char *
ether_ntoa_buf(const ether_addr_t *n, char * buf, int buf_size)
{
char * str;
str = ether_ntoa(n);
strlcpy(buf, str, buf_size);
return buf;
}
static const char *
inet_ptrtop(int af, const void * ptr, char * buf, socklen_t buf_size)
{
union {
struct in_addr ip;
struct in6_addr ip6;
} u;
switch (af) {
case AF_INET:
bcopy(ptr, &u.ip, sizeof(u.ip));
break;
case AF_INET6:
bcopy(ptr, &u.ip6, sizeof(u.ip6));
break;
default:
return NULL;
}
return inet_ntop(af, &u, buf, buf_size);
}
static __inline__ char *
arpop_name(u_int16_t op)
{
switch (op) {
case ARPOP_REQUEST:
return "ARP REQUEST";
case ARPOP_REPLY:
return "ARP REPLY";
case ARPOP_REVREQUEST:
return "REVARP REQUEST";
case ARPOP_REVREPLY:
return "REVARP REPLY";
default:
break;
}
return "<unknown>";
}
static void
arp_frame_validate(const struct ether_arp * earp, u_int len, bool dump)
{
const struct arphdr * arp_p;
int arphrd;
char buf_sender_ether[ETHER_NTOA_BUFSIZE];
char buf_sender_ip[INET_ADDRSTRLEN];
char buf_target_ether[ETHER_NTOA_BUFSIZE];
char buf_target_ip[INET_ADDRSTRLEN];
T_QUIET;
T_ASSERT_GE(len, (u_int)sizeof(*earp),
"%s ARP packet size %u need %u",
__func__, len, (u_int)sizeof(*earp));
if (!dump) {
return;
}
arp_p = &earp->ea_hdr;
arphrd = ntohs(arp_p->ar_hrd);
T_LOG("%s type=0x%x proto=0x%x", arpop_name(ntohs(arp_p->ar_op)),
arphrd, ntohs(arp_p->ar_pro));
if (arp_p->ar_hln == sizeof(earp->arp_sha)) {
ether_ntoa_buf((const ether_addr_t *)earp->arp_sha,
buf_sender_ether,
sizeof(buf_sender_ether));
ether_ntoa_buf((const ether_addr_t *)earp->arp_tha,
buf_target_ether,
sizeof(buf_target_ether));
T_LOG("Sender H/W\t%s", buf_sender_ether);
T_LOG("Target H/W\t%s", buf_target_ether);
}
inet_ptrtop(AF_INET, earp->arp_spa,
buf_sender_ip, sizeof(buf_sender_ip));
inet_ptrtop(AF_INET, earp->arp_tpa,
buf_target_ip, sizeof(buf_target_ip));
T_LOG("Sender IP\t%s", buf_sender_ip);
T_LOG("Target IP\t%s", buf_target_ip);
return;
}
static void
ip_frame_validate(const void * buf, u_int buf_len, bool dump)
{
char buf_dst[INET_ADDRSTRLEN];
char buf_src[INET_ADDRSTRLEN];
const ip_udp_header_t * ip_udp;
u_int ip_len;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(struct ip), NULL);
ip_udp = (const ip_udp_header_t *)buf;
ip_len = ntohs(ip_udp->ip.ip_len);
inet_ptrtop(AF_INET, &ip_udp->ip.ip_src,
buf_src, sizeof(buf_src));
inet_ptrtop(AF_INET, &ip_udp->ip.ip_dst,
buf_dst, sizeof(buf_dst));
if (dump) {
T_LOG("ip src %s dst %s len %u id %d",
buf_src, buf_dst, ip_len,
ntohs(ip_udp->ip.ip_id));
}
T_QUIET;
T_ASSERT_GE(buf_len, ip_len, NULL);
T_QUIET;
T_ASSERT_EQ((u_int)ip_udp->ip.ip_v, IPVERSION, NULL);
T_QUIET;
T_ASSERT_EQ((u_int)(ip_udp->ip.ip_hl << 2),
(u_int)sizeof(struct ip), NULL);
if (ip_udp->ip.ip_p == IPPROTO_UDP) {
u_int udp_len;
u_int data_len;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(*ip_udp), NULL);
udp_len = ntohs(ip_udp->udp.uh_ulen);
T_QUIET;
T_ASSERT_GE(udp_len, (u_int)sizeof(ip_udp->udp), NULL);
data_len = udp_len - (u_int)sizeof(ip_udp->udp);
if (dump) {
T_LOG("udp src 0x%x dst 0x%x len %u"
" csum 0x%x datalen %u",
ntohs(ip_udp->udp.uh_sport),
ntohs(ip_udp->udp.uh_dport),
udp_len,
ntohs(ip_udp->udp.uh_sum),
data_len);
}
}
}
static void
ip6_frame_validate(const void * buf, u_int buf_len, bool dump)
{
char buf_dst[INET6_ADDRSTRLEN];
char buf_src[INET6_ADDRSTRLEN];
const struct ip6_hdr * ip6;
u_int ip6_len;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(struct ip6_hdr), NULL);
ip6 = (const struct ip6_hdr *)buf;
ip6_len = ntohs(ip6->ip6_plen);
inet_ptrtop(AF_INET6, &ip6->ip6_src, buf_src, sizeof(buf_src));
inet_ptrtop(AF_INET6, &ip6->ip6_dst, buf_dst, sizeof(buf_dst));
if (dump) {
T_LOG("ip6 src %s dst %s len %u", buf_src, buf_dst, ip6_len);
}
T_QUIET;
T_ASSERT_GE(buf_len, ip6_len + (u_int)sizeof(struct ip6_hdr), NULL);
T_QUIET;
T_ASSERT_EQ((ip6->ip6_vfc & IPV6_VERSION_MASK),
IPV6_VERSION, NULL);
T_QUIET;
switch (ip6->ip6_nxt) {
case IPPROTO_UDP: {
u_int data_len;
const ip6_udp_header_t *ip6_udp;
u_int udp_len;
ip6_udp = (const ip6_udp_header_t *)buf;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(*ip6_udp), NULL);
udp_len = ntohs(ip6_udp->udp.uh_ulen);
T_QUIET;
T_ASSERT_GE(udp_len, (u_int)sizeof(ip6_udp->udp), NULL);
data_len = udp_len - (u_int)sizeof(ip6_udp->udp);
if (dump) {
T_LOG("udp src 0x%x dst 0x%x len %u"
" csum 0x%x datalen %u",
ntohs(ip6_udp->udp.uh_sport),
ntohs(ip6_udp->udp.uh_dport),
udp_len,
ntohs(ip6_udp->udp.uh_sum),
data_len);
}
break;
}
case IPPROTO_ICMPV6: {
const struct icmp6_hdr *icmp6;
u_int icmp6_len;
icmp6_len = buf_len - sizeof(*ip6);
T_QUIET;
T_ASSERT_GE(buf_len, icmp6_len, NULL);
icmp6 = (const struct icmp6_hdr *)(ip6 + 1);
switch (icmp6->icmp6_type) {
case ND_NEIGHBOR_SOLICIT:
if (dump) {
T_LOG("neighbor solicit");
}
break;
case ND_NEIGHBOR_ADVERT:
if (dump) {
T_LOG("neighbor advert");
}
break;
case ND_ROUTER_SOLICIT:
if (dump) {
T_LOG("router solicit");
}
break;
default:
if (dump) {
T_LOG("icmp6 code 0x%x", icmp6->icmp6_type);
}
break;
}
break;
}
default:
break;
}
}
static void
ethernet_frame_validate(const void * buf, u_int buf_len, bool dump)
{
char ether_dst[ETHER_NTOA_BUFSIZE];
char ether_src[ETHER_NTOA_BUFSIZE];
uint16_t ether_type;
const ether_header_t * eh_p;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(*eh_p), NULL);
eh_p = (const ether_header_t *)buf;
ether_type = ntohs(eh_p->ether_type);
ether_ntoa_buf((const ether_addr_t *)&eh_p->ether_dhost,
ether_dst, sizeof(ether_dst));
ether_ntoa_buf((const ether_addr_t *)&eh_p->ether_shost,
ether_src, sizeof(ether_src));
if (dump) {
T_LOG("ether dst %s src %s type 0x%x",
ether_dst, ether_src, ether_type);
}
switch (ether_type) {
case ETHERTYPE_IP:
ip_frame_validate(eh_p + 1, (u_int)(buf_len - sizeof(*eh_p)),
dump);
break;
case ETHERTYPE_ARP:
arp_frame_validate((const struct ether_arp *)(eh_p + 1),
(u_int)(buf_len - sizeof(*eh_p)),
dump);
break;
case ETHERTYPE_IPV6:
ip6_frame_validate(eh_p + 1, (u_int)(buf_len - sizeof(*eh_p)),
dump);
break;
default:
T_FAIL("unrecognized ethertype 0x%x", ether_type);
break;
}
}
static u_int
ethernet_udp_frame_populate(void * buf, size_t buf_len,
uint8_t af,
const ether_addr_t * src,
union ifbrip * src_ip,
uint16_t src_port,
const ether_addr_t * dst,
union ifbrip * dst_ip,
uint16_t dst_port,
const void * data, u_int data_len)
{
u_int len;
switch (af) {
case AF_INET:
len = ethernet_udp4_frame_populate(buf, buf_len,
src,
src_ip->ifbrip_addr,
src_port,
dst,
dst_ip->ifbrip_addr,
dst_port,
data, data_len);
break;
case AF_INET6:
len = ethernet_udp6_frame_populate(buf, buf_len,
src,
&src_ip->ifbrip_addr6,
src_port,
dst,
&dst_ip->ifbrip_addr6,
dst_port,
data, data_len);
break;
default:
T_FAIL("unrecognized address family %u", af);
len = 0;
break;
}
return len;
}
static u_int
ethernet_arp_frame_populate(void * buf, u_int buf_len,
uint16_t op,
const ether_addr_t * sender_hw,
struct in_addr sender_ip,
const ether_addr_t * target_hw,
struct in_addr target_ip)
{
ether_header_t * eh_p;
struct ether_arp * earp;
struct arphdr * arp_p;
u_int frame_length;
frame_length = sizeof(*earp) + sizeof(*eh_p);
T_QUIET;
T_ASSERT_GE(buf_len, frame_length,
"%s buffer size %u needed %u",
__func__, buf_len, frame_length);
/* ethernet_header */
eh_p = (ether_header_t *)buf;
bcopy(sender_hw, eh_p->ether_shost, ETHER_ADDR_LEN);
if (target_hw != NULL) {
bcopy(target_hw, eh_p->ether_dhost,
sizeof(eh_p->ether_dhost));
} else {
bcopy(ðer_broadcast, eh_p->ether_dhost,
sizeof(eh_p->ether_dhost));
}
eh_p->ether_type = htons(ETHERTYPE_ARP);
/* ARP payload */
earp = (struct ether_arp *)(void *)(eh_p + 1);
arp_p = &earp->ea_hdr;
arp_p->ar_hrd = htons(ARPHRD_ETHER);
arp_p->ar_pro = htons(ETHERTYPE_IP);
arp_p->ar_hln = sizeof(earp->arp_sha);
arp_p->ar_pln = sizeof(struct in_addr);
arp_p->ar_op = htons(op);
bcopy(sender_hw, earp->arp_sha, sizeof(earp->arp_sha));
bcopy(&sender_ip, earp->arp_spa, sizeof(earp->arp_spa));
if (target_hw != NULL) {
bcopy(target_hw, earp->arp_tha, sizeof(earp->arp_tha));
} else {
bzero(earp->arp_tha, sizeof(earp->arp_tha));
}
bcopy(&target_ip, earp->arp_tpa, sizeof(earp->arp_tpa));
return frame_length;
}
static uint32_t G_generation;
static uint32_t
next_generation(void)
{
return G_generation++;
}
static const void *
ethernet_frame_get_udp4_payload(void * buf, u_int buf_len,
u_int * ret_payload_length)
{
ether_header_t * eh_p;
uint16_t ether_type;
ip_udp_header_t * ip_udp;
u_int ip_len;
u_int left;
const void * payload = NULL;
u_int payload_length = 0;
u_int udp_len;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)(sizeof(*eh_p) + sizeof(*ip_udp)), NULL);
left = buf_len;
eh_p = (ether_header_t *)buf;
ether_type = ntohs(eh_p->ether_type);
T_QUIET;
T_ASSERT_EQ((int)ether_type, ETHERTYPE_IP, NULL);
ip_udp = (ip_udp_header_t *)(void *)(eh_p + 1);
left -= sizeof(*eh_p);
ip_len = ntohs(ip_udp->ip.ip_len);
T_QUIET;
T_ASSERT_GE(left, ip_len, NULL);
T_QUIET;
T_ASSERT_EQ((int)ip_udp->ip.ip_v, IPVERSION, NULL);
T_QUIET;
T_ASSERT_EQ((u_int)ip_udp->ip.ip_hl << 2, (u_int)sizeof(struct ip),
NULL);
T_QUIET;
T_ASSERT_EQ((int)ip_udp->ip.ip_p, IPPROTO_UDP, NULL);
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(*ip_udp), NULL);
udp_len = ntohs(ip_udp->udp.uh_ulen);
T_QUIET;
T_ASSERT_GE(udp_len, (u_int)sizeof(ip_udp->udp), NULL);
payload_length = udp_len - (int)sizeof(ip_udp->udp);
if (payload_length > 0) {
payload = (ip_udp + 1);
}
if (payload == NULL) {
payload_length = 0;
}
*ret_payload_length = payload_length;
return payload;
}
static const void *
ethernet_frame_get_udp6_payload(void * buf, u_int buf_len,
u_int * ret_payload_length)
{
ether_header_t * eh_p;
uint16_t ether_type;
ip6_udp_header_t * ip6_udp;
u_int ip6_len;
u_int left;
const void * payload = NULL;
u_int payload_length = 0;
u_int udp_len;
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)(sizeof(*eh_p) + sizeof(*ip6_udp)), NULL);
left = buf_len;
eh_p = (ether_header_t *)buf;
ether_type = ntohs(eh_p->ether_type);
T_QUIET;
T_ASSERT_EQ((int)ether_type, ETHERTYPE_IPV6, NULL);
ip6_udp = (ip6_udp_header_t *)(void *)(eh_p + 1);
left -= sizeof(*eh_p);
ip6_len = ntohs(ip6_udp->ip6.ip6_plen);
T_QUIET;
T_ASSERT_GE(left, ip6_len + (u_int)sizeof(struct ip6_hdr), NULL);
T_QUIET;
T_ASSERT_EQ((int)(ip6_udp->ip6.ip6_vfc & IPV6_VERSION_MASK),
IPV6_VERSION, NULL);
T_QUIET;
T_ASSERT_EQ((int)ip6_udp->ip6.ip6_nxt, IPPROTO_UDP, NULL);
T_QUIET;
T_ASSERT_GE(buf_len, (u_int)sizeof(*ip6_udp), NULL);
udp_len = ntohs(ip6_udp->udp.uh_ulen);
T_QUIET;
T_ASSERT_GE(udp_len, (u_int)sizeof(ip6_udp->udp), NULL);
payload_length = udp_len - (int)sizeof(ip6_udp->udp);
if (payload_length > 0) {
payload = (ip6_udp + 1);
}
if (payload == NULL) {
payload_length = 0;
}
*ret_payload_length = payload_length;
return payload;
}
static const void *
ethernet_frame_get_udp_payload(uint8_t af, void * buf, u_int buf_len,
u_int * ret_payload_length)
{
const void * payload;
switch (af) {
case AF_INET:
payload = ethernet_frame_get_udp4_payload(buf, buf_len,
ret_payload_length);
break;
case AF_INET6:
payload = ethernet_frame_get_udp6_payload(buf, buf_len,
ret_payload_length);
break;
default:
T_FAIL("unrecognized address family %u", af);
payload = NULL;
break;
}
return payload;
}
#define MIN_ICMP6_LEN ((u_int)(sizeof(ether_header_t) + \
sizeof(struct ip6_hdr) + \
sizeof(struct icmp6_hdr)))
#define ALIGNED_ND_OPT_LEN 8
#define SET_ND_OPT_LEN(a) (u_int)((a) >> 3)
#define GET_ND_OPT_LEN(a) (u_int)((a) << 3)
#define ALIGN_ND_OPT(a) (u_int)roundup(a, ALIGNED_ND_OPT_LEN)
#define LINKADDR_OPT_LEN (ALIGN_ND_OPT(sizeof(struct nd_opt_hdr) + \
sizeof(ether_addr_t)))
#define ETHER_IPV6_LEN (sizeof(*eh_p) + sizeof(*ip6))
static u_int
ethernet_nd6_frame_populate(void * buf, u_int buf_len,
uint8_t type,
const ether_addr_t * sender_hw,
struct in6_addr * sender_ip,
const ether_addr_t * dest_ether,
const ether_addr_t * target_hw,
struct in6_addr * target_ip)
{
u_int data_len = 0;
ether_header_t * eh_p;
u_int frame_length;
struct icmp6_hdr * icmp6;
struct ip6_hdr * ip6;
struct nd_opt_hdr * nd_opt;
switch (type) {
case ND_ROUTER_SOLICIT:
case ND_NEIGHBOR_ADVERT:
case ND_NEIGHBOR_SOLICIT:
break;
default:
T_FAIL("%s: unsupported type %u", __func__, type);
return 0;
}
T_QUIET;
T_ASSERT_GE(buf_len, MIN_ICMP6_LEN, NULL);
eh_p = (ether_header_t *)buf;
ip6 = (struct ip6_hdr *)(void *)(eh_p + 1);
icmp6 = (struct icmp6_hdr *)(void *)(ip6 + 1);
frame_length = sizeof(*eh_p) + sizeof(*ip6);
switch (type) {
case ND_NEIGHBOR_SOLICIT: {
struct nd_neighbor_solicit * nd_ns;
bool sender_is_specified;
sender_is_specified = !IN6_IS_ADDR_UNSPECIFIED(sender_ip);
data_len = sizeof(*nd_ns);
if (sender_is_specified) {
data_len += LINKADDR_OPT_LEN;
}
frame_length += data_len;
T_QUIET;
T_ASSERT_GE(buf_len, frame_length, NULL);
nd_ns = (struct nd_neighbor_solicit *)(void *)icmp6;
if (sender_is_specified) {
/* add the source lladdr option */
nd_opt = (struct nd_opt_hdr *)(nd_ns + 1);
nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd_opt->nd_opt_len = SET_ND_OPT_LEN(LINKADDR_OPT_LEN);
bcopy(sender_hw, (nd_opt + 1), sizeof(*sender_hw));
}
bcopy(target_ip, &nd_ns->nd_ns_target,
sizeof(nd_ns->nd_ns_target));
break;
}
case ND_NEIGHBOR_ADVERT: {
struct nd_neighbor_advert * nd_na;
data_len = sizeof(*nd_na) + LINKADDR_OPT_LEN;
frame_length += data_len;
T_QUIET;
T_ASSERT_GE(buf_len, frame_length, NULL);
nd_na = (struct nd_neighbor_advert *)(void *)icmp6;
bcopy(target_ip, &nd_na->nd_na_target,
sizeof(nd_na->nd_na_target));
/* add the target lladdr option */
nd_opt = (struct nd_opt_hdr *)(nd_na + 1);
nd_opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
nd_opt->nd_opt_len = SET_ND_OPT_LEN(LINKADDR_OPT_LEN);
bcopy(target_hw, (nd_opt + 1), sizeof(*target_hw));
break;
}
case ND_ROUTER_SOLICIT: {
struct nd_router_solicit * nd_rs;
data_len = sizeof(*nd_rs) + LINKADDR_OPT_LEN;
frame_length += data_len;
T_QUIET;
T_ASSERT_GE(buf_len, frame_length, NULL);
nd_rs = (struct nd_router_solicit *)(void *)icmp6;
/* add the source lladdr option */
nd_opt = (struct nd_opt_hdr *)(nd_rs + 1);
nd_opt->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
nd_opt->nd_opt_len = SET_ND_OPT_LEN(LINKADDR_OPT_LEN);
bcopy(sender_hw, (nd_opt + 1), sizeof(*sender_hw));
break;
}
default:
T_FAIL("%s: unsupported type %u", __func__, type);
return 0;
}
/* icmp6 header */
icmp6->icmp6_type = type;
icmp6->icmp6_code = 0;
icmp6->icmp6_cksum = 0;
icmp6->icmp6_data32[0] = 0;
/* ethernet_header */
bcopy(sender_hw, eh_p->ether_shost, ETHER_ADDR_LEN);
if (dest_ether != NULL) {
bcopy(dest_ether, eh_p->ether_dhost,
sizeof(eh_p->ether_dhost));
} else {
/* XXX ether_dhost should be multicast */
bcopy(ðer_broadcast, eh_p->ether_dhost,
sizeof(eh_p->ether_dhost));
}
eh_p->ether_type = htons(ETHERTYPE_IPV6);
/* IPv6 header */
bzero(ip6, sizeof(*ip6));
ip6->ip6_nxt = IPPROTO_ICMPV6;
ip6->ip6_vfc = IPV6_VERSION;
bcopy(sender_ip, &ip6->ip6_src, sizeof(ip6->ip6_src));
/* XXX ip6_dst should be specific multicast */
bcopy(&in6addr_linklocal_allnodes, &ip6->ip6_dst, sizeof(ip6->ip6_dst));
ip6->ip6_plen = htons(data_len);
return frame_length;
}
/**
** Switch port
**/
static void
switch_port_check_tx(switch_port_t port)
{
int error;
struct kevent kev;
int kq;
struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000};
kq = kqueue();
T_QUIET;
T_ASSERT_POSIX_SUCCESS(kq, "kqueue check_tx");
EV_SET(&kev, port->fd, EVFILT_WRITE, EV_ADD | EV_ENABLE, 0, 0, NULL);
error = kevent(kq, &kev, 1, &kev, 1, &ts);
T_QUIET;
T_ASSERT_EQ(error, 1, "kevent");
T_QUIET;
T_ASSERT_EQ((int)kev.filter, EVFILT_WRITE, NULL);
T_QUIET;
T_ASSERT_EQ((int)kev.ident, port->fd, NULL);
T_QUIET;
T_ASSERT_NULL(kev.udata, NULL);
close(kq);
return;
}
static void
switch_port_send_arp(switch_port_t port,
uint16_t op,
const ether_addr_t * sender_hw,
struct in_addr sender_ip,
const ether_addr_t * target_hw,
struct in_addr target_ip)
{
u_int frame_length;
ether_packet pkt;
ssize_t n;
/* make sure we can send */
switch_port_check_tx(port);
frame_length = ethernet_arp_frame_populate(&pkt, sizeof(pkt),
op,
sender_hw,
sender_ip,
target_hw,
target_ip);
T_QUIET;
T_ASSERT_GT(frame_length, 0, "%s: frame_length %u",
__func__, frame_length);
if (G_debug) {
T_LOG("Port %s -> %s transmitting %u bytes",
port->ifname, port->member_ifname, frame_length);
}
ethernet_frame_validate(&pkt, frame_length, G_debug);
n = write(port->fd, &pkt, frame_length);
if (n < 0) {
T_ASSERT_POSIX_SUCCESS(n, "%s write fd %d failed %ld",
port->ifname, port->fd, n);
}
T_QUIET;
T_ASSERT_EQ((u_int)n, frame_length,
"%s fd %d wrote %ld",
port->ifname, port->fd, n);
}
static void
switch_port_send_nd6(switch_port_t port,
uint8_t type,
const ether_addr_t * sender_hw,
struct in6_addr * sender_ip,
const ether_addr_t * dest_ether,
const ether_addr_t * target_hw,
struct in6_addr * target_ip)
{
u_int frame_length;
ether_packet pkt;
ssize_t n;
/* make sure we can send */
switch_port_check_tx(port);
frame_length = ethernet_nd6_frame_populate(&pkt, sizeof(pkt),
type,
sender_hw,
sender_ip,
dest_ether,
target_hw,
target_ip);
T_QUIET;
T_ASSERT_GT(frame_length, 0, "%s: frame_length %u",
__func__, frame_length);
if (G_debug) {
T_LOG("Port %s -> %s transmitting %u bytes",
port->ifname, port->member_ifname, frame_length);
}
ethernet_frame_validate(&pkt, frame_length, G_debug);
n = write(port->fd, &pkt, frame_length);
if (n < 0) {
T_ASSERT_POSIX_SUCCESS(n, "%s write fd %d failed %ld",
port->ifname, port->fd, n);
}
T_QUIET;
T_ASSERT_EQ((u_int)n, frame_length,
"%s fd %d wrote %ld",
port->ifname, port->fd, n);
}
static void
switch_port_send_udp(switch_port_t port,
uint8_t af,
const ether_addr_t * src_eaddr,
union ifbrip * src_ip,
uint16_t src_port,
const ether_addr_t * dst_eaddr,
union ifbrip * dst_ip,
uint16_t dst_port,
const void * payload, u_int payload_length)
{
u_int frame_length;
ether_packet pkt;
ssize_t n;
/* make sure we can send */
switch_port_check_tx(port);
/* generate the packet */
frame_length
= ethernet_udp_frame_populate((void *)&pkt,
(u_int)sizeof(pkt),
af,
src_eaddr,
src_ip,
src_port,
dst_eaddr,
dst_ip,
dst_port,
payload,
payload_length);
T_QUIET;
T_ASSERT_GT(frame_length, 0, NULL);
if (G_debug) {
T_LOG("Port %s transmitting %u bytes",
port->ifname, frame_length);
}
ethernet_frame_validate(&pkt, frame_length, G_debug);
n = write(port->fd, &pkt, frame_length);
if (n < 0) {
T_ASSERT_POSIX_SUCCESS(n, "%s write fd %d failed %ld",
port->ifname, port->fd, n);
}
T_QUIET;
T_ASSERT_EQ((u_int)n, frame_length,
"%s fd %d wrote %ld",
port->ifname, port->fd, n);
}
static void
switch_port_send_udp_addr_index(switch_port_t port,
uint8_t af,
u_int addr_index,
const ether_addr_t * dst_eaddr,
union ifbrip * dst_ip,
const void * payload, u_int payload_length)
{
ether_addr_t eaddr;
union ifbrip ip;
/* generate traffic for the unit and address */
set_ethernet_address(&eaddr, port->unit, addr_index);
get_ip_address(af, port->unit, addr_index, &ip);
switch_port_send_udp(port, af,
&eaddr, &ip, TEST_SOURCE_PORT,
dst_eaddr, dst_ip, TEST_DEST_PORT,
payload, payload_length);
}
typedef void
(packet_validator)(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, void * context);
typedef packet_validator * packet_validator_t;
static void
switch_port_receive(switch_port_t port,
uint8_t af,
const void * payload, u_int payload_length,
packet_validator_t validator,
void * context)
{
ether_header_t * eh_p;
ssize_t n;
char * offset;
n = read(port->fd, port->rx_buf, (unsigned)port->rx_buf_size);
if (n < 0) {
if (errno == EAGAIN) {
return;
}
T_QUIET;
T_ASSERT_POSIX_SUCCESS(n, "read %s port %d fd %d",
port->ifname, port->unit, port->fd);
return;
}
for (offset = port->rx_buf; n > 0;) {
struct bpf_hdr * bpf = (struct bpf_hdr *)(void *)offset;
u_int pkt_len;
char * pkt;
u_int skip;
pkt = offset + bpf->bh_hdrlen;
pkt_len = bpf->bh_caplen;
eh_p = (ether_header_t *)(void *)pkt;
T_QUIET;
T_ASSERT_GE(pkt_len, (u_int)sizeof(*eh_p),
"short packet %ld", n);
/* source shouldn't be broadcast/multicast */
T_QUIET;
T_ASSERT_EQ(eh_p->ether_shost[0] & 0x01, 0,
"broadcast/multicast source");
if (G_debug) {
T_LOG("Port %s [unit %d] [fd %d] Received %u bytes",
port->ifname, port->unit, port->fd, pkt_len);
}
ethernet_frame_validate(pkt, pkt_len, G_debug);
/* call the validation function */
(*validator)(port, eh_p, pkt_len, context);
if (payload != NULL) {
const void * p;
u_int p_len;
p = ethernet_frame_get_udp_payload(af, pkt, pkt_len,
&p_len);
T_QUIET;
T_ASSERT_NOTNULL(p, "ethernet_frame_get_udp_payload");
T_QUIET;
T_ASSERT_EQ(p_len, payload_length,
"payload length %u < expected %u",
p_len, payload_length);
T_QUIET;
T_ASSERT_EQ(bcmp(payload, p, payload_length), 0,
"unexpected payload");
}
skip = BPF_WORDALIGN(pkt_len + bpf->bh_hdrlen);
if (skip == 0) {
break;
}
offset += skip;
n -= skip;
}
return;
}
static void
switch_port_log(switch_port_t port)
{
T_LOG("%s [unit %d] [member %s]%s bpf fd %d bufsize %d\n",
port->ifname, port->unit,
port->member_ifname,
port->mac_nat ? " [mac-nat]" : "",
port->fd, port->rx_buf_size);
}
#define switch_port_list_size(port_count) \
offsetof(switch_port_list, list[port_count])
static switch_port_list_t
switch_port_list_alloc(u_int port_count, bool mac_nat)
{
switch_port_list_t list;
list = (switch_port_list_t)
calloc(1, switch_port_list_size(port_count));;
list->size = port_count;
list->mac_nat = mac_nat;
return list;
}
static void
switch_port_list_dealloc(switch_port_list_t list)
{
u_int i;
switch_port_t port;
for (i = 0, port = list->list; i < list->count; i++, port++) {
close(port->fd);
free(port->rx_buf);
}
free(list);
return;
}
static errno_t
switch_port_list_add_port(switch_port_list_t port_list, u_int unit,
const char * ifname, u_short if_index, const char * member_ifname,
u_int num_addrs, bool mac_nat, struct in_addr * ip)
{
int buf_size;
errno_t err = EINVAL;
int fd = -1;
char ntopbuf_ip[INET6_ADDRSTRLEN];
int opt;
switch_port_t p;
if (port_list->count >= port_list->size) {
T_LOG("Internal error: port_list count %u >= size %u\n",
port_list->count, port_list->size);
goto failed;
}
fd = bpf_new();
if (fd < 0) {
err = errno;
T_LOG("bpf_new");
goto failed;
}
bpf_set_traffic_class(fd, SO_TC_CTL);
opt = 1;
T_QUIET;
T_ASSERT_POSIX_SUCCESS(ioctl(fd, FIONBIO, &opt), NULL);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(bpf_set_immediate(fd, 1), NULL);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(bpf_setif(fd, ifname), "bpf set if %s",
ifname);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(bpf_set_see_sent(fd, 0), NULL);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(bpf_set_header_complete(fd, 1), NULL);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(bpf_get_blen(fd, &buf_size), NULL);
if (G_debug) {
T_LOG("%s [unit %d] [member %s] bpf fd %d bufsize %d\n",
ifname, unit,
member_ifname, fd, buf_size);
}
p = port_list->list + port_list->count++;
p->fd = fd;
p->unit = unit;
strlcpy(p->ifname, ifname, sizeof(p->ifname));
strlcpy(p->member_ifname, member_ifname, sizeof(p->member_ifname));
p->num_addrs = num_addrs;
p->rx_buf_size = buf_size;
p->rx_buf = malloc((unsigned)buf_size);
p->mac_nat = mac_nat;
ifnet_get_lladdr(ifname, &p->mac);
ifnet_get_lladdr(member_ifname, &p->member_mac);
p->ip = *ip;
p->if_index = if_index;
get_ipv6_ll_address(&p->mac, &p->ip6);
inet_ntop(AF_INET6, &p->ip6, ntopbuf_ip, sizeof(ntopbuf_ip));
T_LOG("%s %s", ifname, ntopbuf_ip);
return 0;
failed:
if (fd >= 0) {
close(fd);
}
return err;
}
static switch_port_t
switch_port_list_find_fd(switch_port_list_t ports, int fd)
{
u_int i;
switch_port_t port;
for (i = 0, port = ports->list; i < ports->count; i++, port++) {
if (port->fd == fd) {
return port;
}
}
return NULL;
}
static void
switch_port_list_log(switch_port_list_t port_list)
{
u_int i;
switch_port_t port;
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
switch_port_log(port);
}
return;
}
static switch_port_t
switch_port_list_find_member(switch_port_list_t ports, const char * member_ifname)
{
u_int i;
switch_port_t port;
for (i = 0, port = ports->list; i < ports->count; i++, port++) {
if (strcmp(port->member_ifname, member_ifname) == 0) {
return port;
}
}
return NULL;
}
static void
switch_port_list_check_receive(switch_port_list_t ports, uint8_t af,
const void * payload, u_int payload_length,
packet_validator_t validator,
void * context)
{
int i;
int n_events;
struct kevent kev[ports->count];
int kq;
switch_port_t port;
struct timespec ts = { .tv_sec = 0, .tv_nsec = 10 * 1000 * 1000};
u_int u;
kq = kqueue();
T_QUIET;
T_ASSERT_POSIX_SUCCESS(kq, "kqueue check_receive");
for (u = 0, port = ports->list; u < ports->count; u++, port++) {
port->test_count = 0;
EV_SET(kev + u, port->fd,
EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
}
do {
n_events = kevent(kq, kev, (int)ports->count, kev,
(int)ports->count, &ts);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(n_events, "kevent receive %d", n_events);
for (i = 0; i < n_events; i++) {
T_QUIET;
T_ASSERT_EQ((int)kev[i].filter, EVFILT_READ, NULL);
T_QUIET;
T_ASSERT_NULL(kev[i].udata, NULL);
port = switch_port_list_find_fd(ports,
(int)kev[i].ident);
T_QUIET;
T_ASSERT_NE(port, NULL,
"port %p fd %d", (void *)port,
(int)kev[i].ident);
switch_port_receive(port, af, payload, payload_length,
validator, context);
}
} while (n_events != 0);
close(kq);
}
static bool
switch_port_list_verify_rt_table(switch_port_list_t port_list, bool log)
{
bool all_present = true;
u_int i;
u_int count;
struct ifbareq *ifba;
struct ifbareq *rt_table;
switch_port_t port;
/* clear out current notion of how many addresses are present */
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
port->test_address_count = 0;
port->test_address_present = 0;
}
rt_table = bridge_rt_table_copy(&count);
if (rt_table == NULL) {
return false;
}
if (log) {
bridge_rt_table_log(rt_table, count);
}
for (i = 0, ifba = rt_table; i < count; i++, ifba++) {
uint64_t addr_bit;
u_int addr_index;
u_int unit_index;
u_char * ea;
ether_addr_t * eaddr;
eaddr = (ether_addr_t *)&ifba->ifba_dst;
ea = eaddr->octet;
addr_index = ea[EA_ADDR_INDEX];
unit_index = ea[EA_UNIT_INDEX];
port = switch_port_list_find_member(port_list,
ifba->ifba_ifsname);
T_QUIET;
T_ASSERT_NOTNULL(port, "switch_port_list_find_member %s",
ifba->ifba_ifsname);
if (!S_cleaning_up) {
T_QUIET;
T_ASSERT_EQ(unit_index, port->unit, NULL);
addr_bit = 1 << addr_index;
T_QUIET;
T_ASSERT_BITS_NOTSET(port->test_address_present,
addr_bit, "%s address %u",
ifba->ifba_ifsname, addr_index);
port->test_address_present |= addr_bit;
port->test_address_count++;
}
}
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (G_debug) {
T_LOG("%s unit %d [member %s] %u expect %u",
port->ifname, port->unit, port->member_ifname,
port->test_address_count, port->num_addrs);
}
if (port->test_address_count != port->num_addrs) {
all_present = false;
}
}
free(rt_table);
return all_present;
}
static bool
switch_port_list_verify_mac_nat(switch_port_list_t port_list, bool log)
{
bool all_present = true;
u_int i;
u_int count;
static struct ifbrmne * entries;
switch_port_t port;
struct ifbrmne * scan;
/* clear out current notion of how many addresses are present */
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
port->test_address_count = 0;
port->test_address_present = 0;
}
entries = bridge_mac_nat_entries_copy(&count);
if (entries == NULL) {
return false;
}
if (log) {
bridge_mac_nat_entries_log(entries, count);
}
for (i = 0, scan = entries; i < count; i++, scan++) {
uint8_t af;
uint64_t addr_bit;
u_int addr_index;
char buf_ip1[INET6_ADDRSTRLEN];
char buf_ip2[INET6_ADDRSTRLEN];
u_char * ea;
ether_addr_t * eaddr;
union ifbrip ip;
u_int unit_index;
eaddr = (ether_addr_t *)&scan->ifbmne_mac;
ea = eaddr->octet;
addr_index = ea[EA_ADDR_INDEX];
unit_index = ea[EA_UNIT_INDEX];
port = switch_port_list_find_member(port_list,
scan->ifbmne_ifname);
T_QUIET;
T_ASSERT_NOTNULL(port,
"switch_port_list_find_member %s",
scan->ifbmne_ifname);
T_QUIET;
T_ASSERT_EQ(unit_index, port->unit, NULL);
af = scan->ifbmne_af;
get_ip_address(af, port->unit, addr_index, &ip);
addr_bit = 1 << addr_index;
T_QUIET;
T_ASSERT_TRUE(ip_addresses_are_equal(af, &ip, &scan->ifbmne_ip),
"mac nat entry IP address %s expected %s",
inet_ntop(af, &scan->ifbmne_ip_addr,
buf_ip1, sizeof(buf_ip1)),
inet_ntop(af, &ip,
buf_ip2, sizeof(buf_ip2)));
T_QUIET;
T_ASSERT_BITS_NOTSET(port->test_address_present,
addr_bit, "%s address %u",
scan->ifbmne_ifname, addr_index);
port->test_address_present |= addr_bit;
port->test_address_count++;
}
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->mac_nat) {
/* MAC-NAT interface should have no entries */
T_QUIET;
T_ASSERT_EQ(port->test_address_count, 0,
"mac nat interface %s has %u entries",
port->member_ifname,
port->test_address_count);
} else {
if (G_debug) {
T_LOG("%s unit %d [member %s] %u expect %u",
port->ifname, port->unit,
port->member_ifname,
port->test_address_count, port->num_addrs);
}
if (port->test_address_count != port->num_addrs) {
all_present = false;
}
}
}
free(entries);
return all_present;
}
/**
** Basic Bridge Tests
**/
static void
send_generation(switch_port_t port, uint8_t af, u_int addr_index,
const ether_addr_t * dst_eaddr, union ifbrip * dst_ip,
uint32_t generation)
{
uint32_t payload;
payload = htonl(generation);
switch_port_send_udp_addr_index(port, af, addr_index, dst_eaddr, dst_ip,
&payload, sizeof(payload));
}
static void
check_receive_generation(switch_port_list_t ports, uint8_t af,
uint32_t generation, packet_validator_t validator,
__unused void * context)
{
uint32_t payload;
payload = htonl(generation);
switch_port_list_check_receive(ports, af, &payload, sizeof(payload),
validator, context);
}
static void
validate_source_ether_mismatch(switch_port_t port, const ether_header_t * eh_p)
{
/* source shouldn't be our own MAC addresses */
T_QUIET;
T_ASSERT_NE(eh_p->ether_shost[EA_UNIT_INDEX], port->unit,
"ether source matches unit %d", port->unit);
}
static void
validate_not_present_dhost(switch_port_t port, const ether_header_t * eh_p,
__unused u_int pkt_len,
__unused void * context)
{
validate_source_ether_mismatch(port, eh_p);
T_QUIET;
T_ASSERT_EQ(bcmp(eh_p->ether_dhost, ðer_external,
sizeof(eh_p->ether_dhost)), 0,
"%s", __func__);
port->test_count++;
}
static void
validate_broadcast_dhost(switch_port_t port, const ether_header_t * eh_p,
__unused u_int pkt_len,
__unused void * context)
{
validate_source_ether_mismatch(port, eh_p);
T_QUIET;
T_ASSERT_NE((eh_p->ether_dhost[0] & 0x01), 0,
"%s", __func__);
port->test_count++;
}
static void
validate_port_dhost(switch_port_t port, const ether_header_t * eh_p,
__unused u_int pkt_len,
__unused void * context)
{
validate_source_ether_mismatch(port, eh_p);
T_QUIET;
T_ASSERT_EQ(eh_p->ether_dhost[EA_UNIT_INDEX], port->unit,
"wrong dhost unit %d != %d",
eh_p->ether_dhost[EA_UNIT_INDEX], port->unit);
port->test_count++;
}
static void
check_received_count(switch_port_list_t port_list,
switch_port_t port, uint32_t expected_packets)
{
u_int i;
switch_port_t scan;
for (i = 0, scan = port_list->list; i < port_list->count; i++, scan++) {
if (scan == port) {
T_QUIET;
T_ASSERT_EQ(port->test_count, 0,
"unexpected receive on port %d",
port->unit);
} else if (expected_packets == ALL_ADDRS) {
T_QUIET;
T_ASSERT_EQ(scan->test_count, scan->num_addrs,
"didn't receive on all addrs");
} else {
T_QUIET;
T_ASSERT_EQ(scan->test_count, expected_packets,
"wrong receive count on port %s", scan->member_ifname);
}
}
}
static void
unicast_send_all(switch_port_list_t port_list, uint8_t af, switch_port_t port)
{
u_int i;
switch_port_t scan;
for (i = 0, scan = port_list->list; i < port_list->count; i++, scan++) {
if (G_debug) {
T_LOG("Unicast send on %s", port->ifname);
}
for (u_int j = 0; j < scan->num_addrs; j++) {
ether_addr_t eaddr;
union ifbrip ip;
set_ethernet_address(&eaddr, scan->unit, j);
get_ip_address(af, scan->unit, j, &ip);
switch_port_send_udp_addr_index(port, af, 0, &eaddr, &ip,
NULL, 0);
}
}
}
static void
bridge_learning_test_once(switch_port_list_t port_list,
uint8_t af,
packet_validator_t validator,
void * context,
const ether_addr_t * dst_eaddr,
bool retry)
{
u_int i;
union ifbrip dst_ip;
switch_port_t port;
get_broadcast_ip_address(af, &dst_ip);
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->test_address_count == port->num_addrs) {
/* already populated */
continue;
}
if (G_debug) {
T_LOG("Sending on %s", port->ifname);
}
for (u_int j = 0; j < port->num_addrs; j++) {
uint32_t generation;
if (retry) {
uint64_t addr_bit;
addr_bit = 1 << j;
if ((port->test_address_present & addr_bit)
!= 0) {
/* already present */
continue;
}
T_LOG("Retry port %s unit %u address %u",
port->ifname, port->unit, j);
}
generation = next_generation();
send_generation(port,
af,
j,
dst_eaddr,
&dst_ip,
generation);
/* receive across all ports */
check_receive_generation(port_list,
af,
generation,
validator,
context);
/* ensure that every port saw the packet */
check_received_count(port_list, port, 1);
}
}
return;
}
static void
bridge_learning_test(switch_port_list_t port_list,
uint8_t af,
packet_validator_t validator,
void * context,
const ether_addr_t * dst_eaddr)
{
char ntoabuf[ETHER_NTOA_BUFSIZE];
u_int i;
switch_port_t port;
bool verified = false;
ether_ntoa_buf(dst_eaddr, ntoabuf, sizeof(ntoabuf));
/*
* Send a broadcast frame from every port in the list so that the bridge
* learns our MAC address.
*/
#define BROADCAST_MAX_TRIES 20
for (int try = 1; try < BROADCAST_MAX_TRIES; try++) {
bool retry = (try > 1);
if (!retry) {
T_LOG("%s: %s #ports %u #addrs %u dest %s",
__func__,
af_get_str(af),
port_list->count, port_list->list->num_addrs,
ntoabuf);
} else {
T_LOG("%s: %s #ports %u #addrs %u dest %s (TRY=%d)",
__func__,
af_get_str(af),
port_list->count, port_list->list->num_addrs,
ntoabuf, try);
}
bridge_learning_test_once(port_list, af, validator, context,
dst_eaddr, retry);
/*
* In the event of a memory allocation failure, it's possible
* that the address was not learned. Figure out whether
* all addresses are present, and if not, we'll retry on
* those that are not present.
*/
verified = switch_port_list_verify_rt_table(port_list, false);
if (verified) {
break;
}
/* wait a short time to allow the system to recover */
usleep(100 * 1000);
}
T_QUIET;
T_ASSERT_TRUE(verified, "All addresses present");
/*
* Since we just broadcast on every port in the switch, the bridge knows
* the port's MAC addresses. The bridge should not need to broadcast the
* packet to learn, which means the unicast traffic should only arrive
* on the intended port.
*/
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
/* send unicast packets to every other port's MAC addresses */
unicast_send_all(port_list, af, port);
/* receive all of that generated traffic */
switch_port_list_check_receive(port_list, af, NULL, 0,
validate_port_dhost, NULL);
/* check that we saw all of the unicast packets */
check_received_count(port_list, port, ALL_ADDRS);
}
T_PASS("%s", __func__);
}
/**
** MAC-NAT tests
**/
static void
mac_nat_check_received_count(switch_port_list_t port_list, switch_port_t port)
{
u_int i;
switch_port_t scan;
for (i = 0, scan = port_list->list; i < port_list->count; i++, scan++) {
u_int expected = 0;
if (scan == port) {
expected = scan->num_addrs;
}
T_QUIET;
T_ASSERT_EQ(scan->test_count, expected,
"%s [member %s]%s expected %u actual %u",
scan->ifname, scan->member_ifname,
scan->mac_nat ? " [mac-nat]" : "",
expected, scan->test_count);
}
}
static void
validate_mac_nat(switch_port_t port, const ether_header_t * eh_p,
__unused u_int pkt_len,
__unused void * context)
{
if (port->mac_nat) {
bool equal;
/* source must match MAC-NAT interface */
equal = (bcmp(eh_p->ether_shost, &port->member_mac,
sizeof(port->member_mac)) == 0);
if (!equal) {
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_TRUE(equal, "source address match");
port->test_count++;
} else {
validate_not_present_dhost(port, eh_p, pkt_len, NULL);
}
}
static void
validate_mac_nat_in(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, __unused void * context)
{
if (G_debug) {
T_LOG("%s received %u bytes", port->member_ifname, pkt_len);
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_EQ(eh_p->ether_dhost[EA_UNIT_INDEX], port->unit,
"dhost unit %u expected %u",
eh_p->ether_dhost[EA_UNIT_INDEX], port->unit);
port->test_count++;
}
static void
validate_mac_nat_arp_out(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, void * context)
{
const struct ether_arp * earp;
switch_port_t send_port = (switch_port_t)context;
if (G_debug) {
T_LOG("%s received %u bytes", port->member_ifname, pkt_len);
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_EQ((int)ntohs(eh_p->ether_type), (int)ETHERTYPE_ARP, NULL);
earp = (const struct ether_arp *)(const void *)(eh_p + 1);
T_QUIET;
T_ASSERT_GE(pkt_len, (u_int)(sizeof(*eh_p) + sizeof(*earp)), NULL);
if (port->mac_nat) {
bool equal;
/* source ethernet must match MAC-NAT interface */
equal = (bcmp(eh_p->ether_shost, &port->member_mac,
sizeof(port->member_mac)) == 0);
if (!equal) {
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_TRUE(equal, "%s -> %s source address translated",
send_port->member_ifname,
port->member_ifname);
/* sender hw must match MAC-NAT interface */
equal = (bcmp(earp->arp_sha, &port->member_mac,
sizeof(port->member_mac)) == 0);
if (!equal) {
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_TRUE(equal, "%s -> %s sender hardware translated",
send_port->member_ifname,
port->member_ifname);
} else {
/* source ethernet must match the sender */
T_QUIET;
T_ASSERT_EQ(eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit);
/* source hw must match the sender */
T_QUIET;
T_ASSERT_EQ(earp->arp_sha[EA_UNIT_INDEX], send_port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
earp->arp_sha[EA_UNIT_INDEX], send_port->unit);
}
port->test_count++;
}
static void
validate_mac_nat_arp_in(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, void * context)
{
const struct ether_arp * earp;
switch_port_t send_port = (switch_port_t)context;
if (G_debug) {
T_LOG("%s received %u bytes", port->member_ifname, pkt_len);
ethernet_frame_validate(eh_p, pkt_len, true);
}
earp = (const struct ether_arp *)(const void *)(eh_p + 1);
T_QUIET;
T_ASSERT_EQ((int)ntohs(eh_p->ether_type), (int)ETHERTYPE_ARP, NULL);
T_QUIET;
T_ASSERT_GE(pkt_len, (u_int)(sizeof(*eh_p) + sizeof(*earp)), NULL);
T_QUIET;
T_ASSERT_FALSE(port->mac_nat, NULL);
/* destination ethernet must match the unit */
T_QUIET;
T_ASSERT_EQ(eh_p->ether_dhost[EA_UNIT_INDEX], port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
eh_p->ether_dhost[EA_UNIT_INDEX], port->unit);
/* source hw must match the sender */
T_QUIET;
T_ASSERT_EQ(earp->arp_tha[EA_UNIT_INDEX], port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
earp->arp_tha[EA_UNIT_INDEX], port->unit);
port->test_count++;
}
static void
mac_nat_test_arp_out(switch_port_list_t port_list)
{
u_int i;
struct in_addr ip_dst;
switch_port_t port;
ip_dst = get_external_ipv4_address();
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->mac_nat) {
continue;
}
for (u_int j = 0; j < port->num_addrs; j++) {
ether_addr_t eaddr;
struct in_addr ip_src;
set_ethernet_address(&eaddr, port->unit, j);
get_ipv4_address(port->unit, j, &ip_src);
switch_port_send_arp(port,
ARPOP_REQUEST,
&eaddr,
ip_src,
NULL,
ip_dst);
switch_port_list_check_receive(port_list, AF_INET,
NULL, 0,
validate_mac_nat_arp_out,
port);
check_received_count(port_list, port, 1);
}
}
T_PASS("%s", __func__);
}
static void
mac_nat_send_arp_response(switch_port_t ext_port, switch_port_t port)
{
struct in_addr ip_src;
T_QUIET;
T_ASSERT_TRUE(ext_port->mac_nat, "%s is MAC-NAT interface",
ext_port->member_ifname);
ip_src = get_external_ipv4_address();
for (u_int j = 0; j < port->num_addrs; j++) {
struct in_addr ip_dst;
get_ipv4_address(port->unit, j, &ip_dst);
if (G_debug) {
T_LOG("Generating ARP destined to %s %s",
port->ifname, inet_ntoa(ip_dst));
}
switch_port_send_arp(ext_port,
ARPOP_REPLY,
ðer_external,
ip_src,
&ext_port->member_mac,
ip_dst);
}
}
static void
mac_nat_test_arp_in(switch_port_list_t port_list)
{
u_int i;
struct in_addr ip_src;
switch_port_t port;
ip_src = get_external_ipv4_address();
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->mac_nat) {
continue;
}
mac_nat_send_arp_response(port_list->list, port);
/* receive the generated traffic */
switch_port_list_check_receive(port_list, AF_INET, NULL, 0,
validate_mac_nat_arp_in,
port_list->list);
/* verify that only the single port got the packet */
mac_nat_check_received_count(port_list, port);
}
T_PASS("%s", __func__);
}
static void
validate_mac_nat_dhcp(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, void * context)
{
u_int dp_flags;
const struct bootp_packet * pkt;
switch_port_t send_port = (switch_port_t)context;
T_QUIET;
T_ASSERT_GE(pkt_len, (u_int)sizeof(*pkt), NULL);
T_QUIET;
T_ASSERT_EQ((int)ntohs(eh_p->ether_type), (int)ETHERTYPE_IP, NULL);
pkt = (const struct bootp_packet *)(const void *)(eh_p + 1);
dp_flags = ntohs(pkt->bp_bootp.bp_unused);
if (port->mac_nat) {
bool equal;
/* Broadcast bit must be set */
T_QUIET;
T_ASSERT_BITS_SET(dp_flags, (u_int)DHCP_FLAGS_BROADCAST,
"%s -> %s: flags 0x%x must have 0x%x",
send_port->member_ifname,
port->member_ifname,
dp_flags, DHCP_FLAGS_BROADCAST);
/* source must match MAC-NAT interface */
equal = (bcmp(eh_p->ether_shost, &port->member_mac,
sizeof(port->member_mac)) == 0);
if (!equal) {
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_TRUE(equal, "%s -> %s source address translated",
send_port->member_ifname,
port->member_ifname);
} else {
/* Broadcast bit must not be set */
T_QUIET;
T_ASSERT_BITS_NOTSET(dp_flags, DHCP_FLAGS_BROADCAST,
"%s -> %s flags 0x%x must not have 0x%x",
send_port->member_ifname,
port->member_ifname,
dp_flags, DHCP_FLAGS_BROADCAST);
T_QUIET;
T_ASSERT_EQ(eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit);
}
port->test_count++;
}
static void
mac_nat_test_dhcp(switch_port_list_t port_list, bool link_layer_unicast)
{
u_int i;
struct in_addr ip_dst = { INADDR_BROADCAST };
struct in_addr ip_src = { INADDR_ANY };
switch_port_t port;
ether_addr_t * ether_dst;
if (link_layer_unicast) {
/* use link-layer address of MAC-NAT interface */
ether_dst = &port_list->list[0].member_mac;
} else {
/* use link-layer broadcast address */
ether_dst = ðer_broadcast;
}
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
ether_addr_t eaddr;
dhcp_min_payload payload;
u_int payload_len;
if (!link_layer_unicast && port->mac_nat) {
/* only send through non-MAC-NAT ports */
continue;
}
set_ethernet_address(&eaddr, port->unit, 0);
payload_len = make_dhcp_payload(&payload, &eaddr);
if (G_debug) {
T_LOG("%s: transmit DHCP packet (member %s)",
port->ifname, port->member_ifname);
}
switch_port_send_udp(port,
AF_INET,
&eaddr,
(union ifbrip *)&ip_src,
BOOTP_CLIENT_PORT,
ether_dst,
(union ifbrip *)&ip_dst,
BOOTP_SERVER_PORT,
&payload,
payload_len);
switch_port_list_check_receive(port_list, AF_INET, NULL, 0,
validate_mac_nat_dhcp,
port);
check_received_count(port_list, port, 1);
if (link_layer_unicast) {
/* send a single unicast to MAC-NAT interface */
break;
}
}
T_PASS("%s %s", __func__,
link_layer_unicast ? "unicast" : "broadcast");
}
static void
validate_mac_nat_nd6(switch_port_t port,
const struct icmp6_hdr * icmp6,
u_int icmp6_len,
uint8_t opt_type,
u_int nd_hdr_size,
switch_port_t send_port)
{
const uint8_t * linkaddr;
const uint8_t * ptr;
const struct nd_opt_hdr * nd_opt;
u_int nd_size;
ptr = (const uint8_t *)icmp6;
nd_size = nd_hdr_size + LINKADDR_OPT_LEN;
if (icmp6_len < nd_size) {
/* no LINKADDR option */
return;
}
nd_opt = (const struct nd_opt_hdr *)(const void *)(ptr + nd_hdr_size);
T_QUIET;
T_ASSERT_EQ(nd_opt->nd_opt_type, opt_type,
"nd_opt->nd_opt_type 0x%x, opt_type 0x%x",
nd_opt->nd_opt_type, opt_type);
T_QUIET;
T_ASSERT_EQ(GET_ND_OPT_LEN(nd_opt->nd_opt_len), LINKADDR_OPT_LEN, NULL);
linkaddr = (const uint8_t *)(nd_opt + 1);
if (port->mac_nat) {
bool equal;
equal = (bcmp(linkaddr, &port->member_mac,
sizeof(port->member_mac)) == 0);
T_QUIET;
T_ASSERT_TRUE(equal, "%s -> %s sender hardware translated",
send_port->member_ifname,
port->member_ifname);
} else {
/* source hw must match the sender */
T_QUIET;
T_ASSERT_EQ(linkaddr[EA_UNIT_INDEX], send_port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
linkaddr[EA_UNIT_INDEX], send_port->unit);
}
}
static void
validate_mac_nat_icmp6_out(switch_port_t port, const struct icmp6_hdr * icmp6,
u_int icmp6_len, switch_port_t send_port)
{
switch (icmp6->icmp6_type) {
case ND_NEIGHBOR_ADVERT:
validate_mac_nat_nd6(port, icmp6, icmp6_len,
ND_OPT_TARGET_LINKADDR,
sizeof(struct nd_neighbor_advert),
send_port);
break;
case ND_NEIGHBOR_SOLICIT:
validate_mac_nat_nd6(port, icmp6, icmp6_len,
ND_OPT_SOURCE_LINKADDR,
sizeof(struct nd_neighbor_solicit),
send_port);
break;
case ND_ROUTER_SOLICIT:
validate_mac_nat_nd6(port, icmp6, icmp6_len,
ND_OPT_SOURCE_LINKADDR,
sizeof(struct nd_router_solicit),
send_port);
break;
default:
T_FAIL("Unsupported icmp6 type %d", icmp6->icmp6_type);
break;
}
}
static void
validate_mac_nat_nd6_out(switch_port_t port, const ether_header_t * eh_p,
u_int pkt_len, void * context)
{
const struct icmp6_hdr * icmp6;
const struct ip6_hdr * ip6;
unsigned int payload_length;
switch_port_t send_port = (switch_port_t)context;
if (G_debug) {
T_LOG("%s received %u bytes", port->member_ifname, pkt_len);
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_EQ(ntohs(eh_p->ether_type), (u_short)ETHERTYPE_IPV6, NULL);
ip6 = (const struct ip6_hdr *)(const void *)(eh_p + 1);
icmp6 = (const struct icmp6_hdr *)(const void *)(ip6 + 1);
T_QUIET;
T_ASSERT_GE(pkt_len, (u_int)MIN_ICMP6_LEN, NULL);
T_QUIET;
T_ASSERT_EQ(ip6->ip6_nxt, IPPROTO_ICMPV6, NULL);
/* validate the ethernet header */
if (port->mac_nat) {
bool equal;
/* source ethernet must match MAC-NAT interface */
equal = (bcmp(eh_p->ether_shost, &port->member_mac,
sizeof(port->member_mac)) == 0);
if (!equal) {
ethernet_frame_validate(eh_p, pkt_len, true);
}
T_QUIET;
T_ASSERT_TRUE(equal, "%s -> %s source address translated",
send_port->member_ifname,
port->member_ifname);
} else {
/* source ethernet must match the sender */
T_QUIET;
T_ASSERT_EQ(eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit,
"%s -> %s unit %u expected %u",
send_port->member_ifname,
port->member_ifname,
eh_p->ether_shost[EA_UNIT_INDEX], send_port->unit);
}
/* validate the icmp6 payload */
payload_length = ntohs(ip6->ip6_plen);
validate_mac_nat_icmp6_out(port, icmp6, payload_length, send_port);
port->test_count++;
}
static void
mac_nat_test_nd6_out(switch_port_list_t port_list)
{
ether_addr_t * ext_mac;
switch_port_t ext_port;
u_int i;
union ifbrip ip_dst;
switch_port_t port;
get_external_ip_address(AF_INET6, &ip_dst);
ext_port = port_list->list;
T_QUIET;
T_ASSERT_TRUE(ext_port->mac_nat, NULL);
ext_mac = &ext_port->member_mac;
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->mac_nat) {
continue;
}
/* neighbor solicit */
for (u_int j = 0; j < port->num_addrs; j++) {
ether_addr_t eaddr;
union ifbrip ip_src;
set_ethernet_address(&eaddr, port->unit, j);
get_ip_address(AF_INET6, port->unit, j, &ip_src);
switch_port_send_nd6(port,
ND_NEIGHBOR_SOLICIT,
&eaddr,
&ip_src.ifbrip_addr6,
NULL,
NULL,
&ip_dst.ifbrip_addr6);
switch_port_list_check_receive(port_list, AF_INET,
NULL, 0,
validate_mac_nat_nd6_out,
port);
check_received_count(port_list, port, 1);
}
/* neighbor advert */
for (u_int j = 0; j < port->num_addrs; j++) {
ether_addr_t eaddr;
union ifbrip ip_src;
set_ethernet_address(&eaddr, port->unit, j);
get_ip_address(AF_INET6, port->unit, j, &ip_src);
switch_port_send_nd6(port,
ND_NEIGHBOR_ADVERT,
&eaddr,
&ip_src.ifbrip_addr6,
NULL,
&eaddr,
&ip_src.ifbrip_addr6);
switch_port_list_check_receive(port_list, AF_INET,
NULL, 0,
validate_mac_nat_nd6_out,
port);
check_received_count(port_list, port, 1);
}
/* router solicit */
for (u_int j = 0; j < port->num_addrs; j++) {
ether_addr_t eaddr;
union ifbrip ip_src;
set_ethernet_address(&eaddr, port->unit, j);
get_ip_address(AF_INET6, port->unit, j, &ip_src);
//get_ipv6ll_address(port->unit, j, &ip_src.ifbrip_addr6);
switch_port_send_nd6(port,
ND_ROUTER_SOLICIT,
&eaddr,
&ip_src.ifbrip_addr6,
NULL,
NULL,
NULL);
switch_port_list_check_receive(port_list, AF_INET,
NULL, 0,
validate_mac_nat_nd6_out,
port);
check_received_count(port_list, port, 1);
}
}
T_PASS("%s", __func__);
}
static void
mac_nat_send_response(switch_port_t ext_port, uint8_t af, switch_port_t port)
{
union ifbrip src_ip;
T_QUIET;
T_ASSERT_TRUE(ext_port->mac_nat, "%s is MAC-NAT interface",
ext_port->member_ifname);
if (G_debug) {
T_LOG("Generating UDP traffic destined to %s", port->ifname);
}
get_external_ip_address(af, &src_ip);
for (u_int j = 0; j < port->num_addrs; j++) {
union ifbrip ip;
get_ip_address(af, port->unit, j, &ip);
switch_port_send_udp(ext_port,
af,
ðer_external,
&src_ip,
TEST_DEST_PORT,
&ext_port->member_mac,
&ip,
TEST_SOURCE_PORT,
NULL, 0);
}
}
static void
mac_nat_test_ip_once(switch_port_list_t port_list, uint8_t af, bool retry)
{
union ifbrip dst_ip;
u_int i;
switch_port_t port;
get_external_ip_address(af, &dst_ip);
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->test_address_count == port->num_addrs) {
/* already populated */
continue;
}
if (G_debug) {
T_LOG("Sending on %s", port->ifname);
}
for (u_int j = 0; j < port->num_addrs; j++) {
uint32_t generation;
if (retry) {
uint64_t addr_bit;
addr_bit = 1 << j;
if ((port->test_address_present & addr_bit)
!= 0) {
/* already present */
continue;
}
T_LOG("Retry port %s unit %u address %u",
port->ifname, port->unit, j);
}
generation = next_generation();
send_generation(port,
af,
j,
ðer_external,
&dst_ip,
generation);
/* receive across all ports */
check_receive_generation(port_list,
af,
generation,
validate_mac_nat,
NULL);
/* ensure that every port saw the packet */
check_received_count(port_list, port, 1);
}
}
return;
}
static void
mac_nat_test_ip(switch_port_list_t port_list, uint8_t af)
{
u_int i;
switch_port_t port;
bool verified = false;
/*
* Send a packet from every port in the list so that the bridge
* learns the MAC addresses and IP addresses.
*/
#define MAC_NAT_MAX_TRIES 20
for (int try = 1; try < BROADCAST_MAX_TRIES; try++) {
bool retry = (try > 1);
if (!retry) {
T_LOG("%s: #ports %u #addrs %u",
__func__,
port_list->count, port_list->list->num_addrs);
} else {
T_LOG("%s: #ports %u #addrs %u destination (TRY=%d)",
__func__,
port_list->count, port_list->list->num_addrs,
try);
}
mac_nat_test_ip_once(port_list, af, retry);
/*
* In the event of a memory allocation failure, it's possible
* that the address was not learned. Figure out whether
* all addresses are present, and if not, we'll retry on
* those that are not present.
*/
verified = switch_port_list_verify_mac_nat(port_list, false);
if (verified) {
break;
}
/* wait a short time to allow the system to recover */
usleep(100 * 1000);
}
T_QUIET;
T_ASSERT_TRUE(verified, "All addresses present");
/*
* The bridge now has an IP address <-> MAC address binding for every
* address on each internal interface.
*
* Generate an inbound packet on the MAC-NAT interface targeting
* each interface address. Verify that the packet appears on
* the appropriate internal address with appropriate translation.
*/
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (port->mac_nat) {
continue;
}
mac_nat_send_response(port_list->list, af, port);
/* receive the generated traffic */
switch_port_list_check_receive(port_list, AF_INET, NULL, 0,
validate_mac_nat_in,
NULL);
/* verify that only the single port got the packet */
mac_nat_check_received_count(port_list, port);
}
T_PASS("%s", __func__);
}
/**
** interface management
**/
static int
bridge_delete_member(const char * bridge, const char * member)
{
struct ifbreq req;
int ret;
memset(&req, 0, sizeof(req));
strlcpy(req.ifbr_ifsname, member, sizeof(req.ifbr_ifsname));
ret = siocdrvspec(bridge, BRDGDEL, &req, sizeof(req), true);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(ret, "%s %s %s", __func__, bridge, member);
return ret;
}
static int
bridge_member_modify_ifflags(const char * bridge, const char * member,
uint32_t flags_to_modify, bool set)
{
uint32_t flags;
bool need_set = false;
struct ifbreq req;
int ret;
memset(&req, 0, sizeof(req));
strlcpy(req.ifbr_ifsname, member, sizeof(req.ifbr_ifsname));
ret = siocdrvspec(bridge, BRDGGIFFLGS, &req, sizeof(req), false);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(ret, "BRDGGIFFLGS %s %s", bridge, member);
flags = req.ifbr_ifsflags;
if (set) {
if ((flags & flags_to_modify) != flags_to_modify) {
need_set = true;
req.ifbr_ifsflags |= flags_to_modify;
}
/* need to set it */
} else if ((flags & flags_to_modify) != 0) {
/* need to clear it */
need_set = true;
req.ifbr_ifsflags &= ~flags_to_modify;
}
if (need_set) {
ret = siocdrvspec(bridge, BRDGSIFFLGS,
&req, sizeof(req), true);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(ret, "BRDGSIFFLGS %s %s 0x%x => 0x%x",
bridge, member,
flags, req.ifbr_ifsflags);
}
return ret;
}
static int
bridge_member_modify_mac_nat(const char * bridge,
const char * member, bool enable)
{
return bridge_member_modify_ifflags(bridge, member,
IFBIF_MAC_NAT,
enable);
}
static int
bridge_member_modify_checksum_offload(const char * bridge,
const char * member, bool enable)
{
#ifndef IFBIF_CHECKSUM_OFFLOAD
#define IFBIF_CHECKSUM_OFFLOAD 0x10000 /* checksum inbound packets,
* drop outbound packets with
* bad checksum
*/
#endif
return bridge_member_modify_ifflags(bridge, member,
IFBIF_CHECKSUM_OFFLOAD,
enable);
}
static struct ifbareq *
bridge_rt_table_copy_common(const char * bridge, u_int * ret_count)
{
struct ifbaconf ifbac;
u_int len = 8 * 1024;
char * inbuf = NULL;
char * ninbuf;
int ret;
struct ifbareq * rt_table = NULL;
/*
* BRDGRTS should work like other ioctl's where passing in NULL
* for the buffer says "tell me how many there are". Unfortunately,
* it doesn't so we have to pass in a buffer, then check that it
* was too big.
*/
for (;;) {
ninbuf = realloc(inbuf, len);
T_QUIET;
T_ASSERT_NOTNULL((void *)ninbuf, "realloc %u", len);
ifbac.ifbac_len = len;
ifbac.ifbac_buf = inbuf = ninbuf;
ret = siocdrvspec(bridge, BRDGRTS,
&ifbac, sizeof(ifbac), false);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(ret, "%s %s", __func__, bridge);
if ((ifbac.ifbac_len + sizeof(*rt_table)) < len) {
/* we passed a buffer larger than what was required */
break;
}
len *= 2;
}
if (ifbac.ifbac_len == 0) {
free(ninbuf);
T_LOG("No bridge routing entries");
goto done;
}
*ret_count = ifbac.ifbac_len / sizeof(*rt_table);
rt_table = (struct ifbareq *)(void *)ninbuf;
done:
if (rt_table == NULL) {
*ret_count = 0;
}
return rt_table;
}
static struct ifbareq *
bridge_rt_table_copy(u_int * ret_count)
{
return bridge_rt_table_copy_common(BRIDGE200, ret_count);
}
static void
bridge_rt_table_log(struct ifbareq *rt_table, u_int count)
{
u_int i;
char ntoabuf[ETHER_NTOA_BUFSIZE];
struct ifbareq * ifba;
for (i = 0, ifba = rt_table; i < count; i++, ifba++) {
ether_ntoa_buf((const ether_addr_t *)&ifba->ifba_dst,
ntoabuf, sizeof(ntoabuf));
T_LOG("%s %s %lu", ifba->ifba_ifsname, ntoabuf,
ifba->ifba_expire);
}
return;
}
static struct ifbrmne *
bridge_mac_nat_entries_copy_common(const char * bridge, u_int * ret_count)
{
char * buf = NULL;
u_int count = 0;
int err;
u_int i;
struct ifbrmnelist mnl;
struct ifbrmne * ret_list = NULL;
char * scan;
/* find out how many there are */
bzero(&mnl, sizeof(mnl));
err = siocdrvspec(bridge, BRDGGMACNATLIST, &mnl, sizeof(mnl), false);
if (err != 0 && S_cleaning_up) {
T_LOG("BRDGGMACNATLIST %s failed %d", bridge, errno);
goto done;
}
T_QUIET;
T_ASSERT_POSIX_SUCCESS(err, "BRDGGMACNATLIST %s", bridge);
T_QUIET;
T_ASSERT_GE(mnl.ifbml_elsize, (uint16_t)sizeof(struct ifbrmne),
"mac nat entry size %u minsize %u",
mnl.ifbml_elsize, (u_int)sizeof(struct ifbrmne));
if (mnl.ifbml_len == 0) {
goto done;
}
/* call again with a buffer large enough to hold them */
buf = malloc(mnl.ifbml_len);
T_QUIET;
T_ASSERT_NOTNULL(buf, "mac nat entries buffer");
mnl.ifbml_buf = buf;
err = siocdrvspec(bridge, BRDGGMACNATLIST, &mnl, sizeof(mnl), false);
T_QUIET;
T_ASSERT_POSIX_SUCCESS(err, "BRDGGMACNATLIST %s", bridge);
count = mnl.ifbml_len / mnl.ifbml_elsize;
if (count == 0) {
goto done;
}
if (mnl.ifbml_elsize == sizeof(struct ifbrmne)) {
/* element size is expected size, no need to "right-size" it */
ret_list = (struct ifbrmne *)(void *)buf;
buf = NULL;
goto done;
}
/* element size is larger than we expect, create a "right-sized" array */
ret_list = malloc(count * sizeof(*ret_list));
T_QUIET;
T_ASSERT_NOTNULL(ret_list, "mac nat entries list");
for (i = 0, scan = buf; i < count; i++, scan += mnl.ifbml_elsize) {
struct ifbrmne * ifbmne;
ifbmne = (struct ifbrmne *)(void *)scan;
ret_list[i] = *ifbmne;
}
done:
if (buf != NULL) {
free(buf);
}
*ret_count = count;
return ret_list;
}
static struct ifbrmne *
bridge_mac_nat_entries_copy(u_int * ret_count)
{
return bridge_mac_nat_entries_copy_common(BRIDGE200, ret_count);
}
static void
bridge_mac_nat_entries_log(struct ifbrmne * entries, u_int count)
{
u_int i;
char ntoabuf[ETHER_NTOA_BUFSIZE];
char ntopbuf[INET6_ADDRSTRLEN];
struct ifbrmne * scan;
for (i = 0, scan = entries; i < count; i++, scan++) {
ether_ntoa_buf((const ether_addr_t *)&scan->ifbmne_mac,
ntoabuf, sizeof(ntoabuf));
inet_ntop(scan->ifbmne_af, &scan->ifbmne_ip,
ntopbuf, sizeof(ntopbuf));
printf("%s %s %s %lu\n",
scan->ifbmne_ifname, ntopbuf, ntoabuf,
(unsigned long)scan->ifbmne_expire);
}
return;
}
/**
** Test Main
**/
static u_int S_n_ports;
static switch_port_list_t S_port_list;
static void
bridge_cleanup(const char * bridge, u_int n_ports, bool fail_on_error);
static int fake_bsd_mode;
static int fake_fcs;
static int fake_trailer_length;
static void
fake_set_trailers_fcs(bool enable)
{
int error;
int fcs;
size_t len;
int trailer_length;
if (enable) {
fcs = 1;
trailer_length = 28;
} else {
fcs = 0;
trailer_length = 0;
}
/* set fcs */
len = sizeof(fake_fcs);
error = sysctlbyname("net.link.fake.fcs",
&fake_fcs, &len,
&fcs, sizeof(fcs));
T_ASSERT_EQ(error, 0, "sysctl net.link.fake.fcs %d", fcs);
/* set trailer_length */
len = sizeof(fake_trailer_length);
error = sysctlbyname("net.link.fake.trailer_length",
&fake_trailer_length, &len,
&trailer_length, sizeof(trailer_length));
T_ASSERT_EQ(error, 0, "sysctl net.link.fake.trailer_length %d",
trailer_length);
}
static void
fake_restore_trailers_fcs(void)
{
int error;
error = sysctlbyname("net.link.fake.fcs",
NULL, 0, &fake_fcs, sizeof(fake_fcs));
T_LOG("sysctl net.link.fake.fcs=%d returned %d", fake_fcs, error);
error = sysctlbyname("net.link.fake.trailer_length",
NULL, 0, &fake_trailer_length, sizeof(fake_trailer_length));
T_LOG("sysctl net.link.fake.trailer_length=%d returned %d",
fake_trailer_length, error);
}
static void
fake_set_bsd_mode(bool enable)
{
int error;
int bsd_mode;
size_t len;
bsd_mode = (enable) ? 1 : 0;
len = sizeof(fake_bsd_mode);
error = sysctlbyname("net.link.fake.bsd_mode",
&fake_bsd_mode, &len,
&bsd_mode, sizeof(bsd_mode));
T_ASSERT_EQ(error, 0, "sysctl net.link.fake.bsd_mode %d", bsd_mode);
}
static void
fake_restore_bsd_mode(void)
{
int error;
error = sysctlbyname("net.link.fake.bsd_mode",
NULL, 0, &fake_bsd_mode, sizeof(fake_bsd_mode));
T_LOG("sysctl net.link.fake.bsd_mode=%d returned %d",
fake_bsd_mode, error);
}
static void
fake_set_lro(bool enable)
{
int error;
int lro;
size_t len;
lro = (enable) ? 1 : 0;
len = sizeof(fake_bsd_mode);
error = sysctlbyname("net.link.fake.lro", NULL, 0,
&lro, sizeof(lro));
T_ASSERT_EQ(error, 0, "sysctl net.link.fake.lro %d", lro);
}
static void
cleanup_common(bool dump_table)
{
if (S_n_ports == 0) {
return;
}
S_cleaning_up = true;
if (S_port_list != NULL &&
(S_port_list->mac_nat || dump_table)) {
switch_port_list_log(S_port_list);
if (S_port_list->mac_nat) {
switch_port_list_verify_mac_nat(S_port_list, true);
}
(void)switch_port_list_verify_rt_table(S_port_list, true);
}
if (G_debug) {
T_LOG("sleeping for 5 seconds\n");
sleep(5);
}
bridge_cleanup(BRIDGE200, S_n_ports, false);
return;
}
static void
cleanup(void)
{
cleanup_common(true);
return;
}
static void
sigint_handler(__unused int sig)
{
cleanup_common(false);
signal(SIGINT, SIG_DFL);
}
static switch_port_list_t
bridge_setup(char * bridge, u_int n_ports, u_int num_addrs,
uint8_t setup_flags)
{
u_int addr_index = 1;
bool attach_stack;
ether_addr_t bridge_mac;
bool checksum_offload;
errno_t err;
struct in_addr ip;
switch_port_list_t list = NULL;
bool mac_nat;
bool share_member_mac = false;
uint8_t trailers;
attach_stack = (setup_flags & SETUP_FLAGS_ATTACH_STACK) != 0;
checksum_offload = (setup_flags & SETUP_FLAGS_CHECKSUM_OFFLOAD) != 0;
mac_nat = (setup_flags & SETUP_FLAGS_MAC_NAT) != 0;
trailers = (setup_flags & SETUP_FLAGS_TRAILERS) != 0;
share_member_mac = (setup_flags & SETUP_FLAGS_SHARE_MEMBER_MAC) != 0;
S_n_ports = n_ports;
T_ATEND(cleanup);
T_SETUPBEGIN;
err = ifnet_create(bridge);
if (err != 0) {
goto done;
}
ifnet_get_lladdr(bridge, &bridge_mac);
bridge_if_index = (u_short)if_nametoindex(bridge);
if (attach_stack) {
char ntopbuf_ip[INET6_ADDRSTRLEN];
/* bridge gets .1 */
get_ipv4_address(0, addr_index, &bridge_ip_addr);
addr_index++;
ifnet_add_ip_address(bridge, bridge_ip_addr,
inet_class_c_subnet_mask);
ifnet_start_ipv6(bridge);
get_ipv6_ll_address(&bridge_mac, &bridge_ipv6_addr);
inet_ntop(AF_INET6, &bridge_ipv6_addr, ntopbuf_ip,
sizeof(ntopbuf_ip));
T_LOG("%s %s", bridge, ntopbuf_ip);
}
list = switch_port_list_alloc(n_ports, mac_nat);
fake_set_bsd_mode(true);
fake_set_trailers_fcs(trailers);
for (u_int i = 0; i < n_ports; i++) {
bool do_mac_nat;
char ifname[IFNAMSIZ];
u_short if_index = 0;
char member_ifname[IFNAMSIZ];
snprintf(ifname, sizeof(ifname), "%s%d",
FETH_NAME, i);
snprintf(member_ifname, sizeof(member_ifname), "%s%d",
FETH_NAME, i + n_ports);
err = ifnet_create(ifname);
if (err != 0) {
goto done;
}
ifnet_attach_ip(ifname);
err = ifnet_create(member_ifname);
if (err != 0) {
goto done;
}
if (i == 0 && share_member_mac) {
err = ifnet_set_lladdr(member_ifname, &bridge_mac);
if (err != 0) {
goto done;
}
}
fake_set_peer(ifname, member_ifname);
if (attach_stack) {
/* members get .2, .3, etc. */
if_index = (u_short)if_nametoindex(ifname);
get_ipv4_address(0, addr_index, &ip);
ifnet_add_ip_address(ifname, ip,
inet_class_c_subnet_mask);
route_add_inet_scoped_subnet(ifname, if_index,
ip, inet_class_c_subnet_mask);
addr_index++;
ifnet_start_ipv6(ifname);
}
/* add the interface's peer to the bridge */
err = bridge_add_member(bridge, member_ifname);
if (err != 0) {
goto done;
}
do_mac_nat = (i == 0 && mac_nat);
if (do_mac_nat) {
/* enable MAC NAT on unit 0 */
err = bridge_member_modify_mac_nat(bridge,
member_ifname,
true);
if (err != 0) {
goto done;
}
} else if (checksum_offload) {
err = bridge_member_modify_checksum_offload(bridge,
member_ifname,
true);
if (err != 0) {
goto done;
}
}
/* we'll send/receive on the interface */
err = switch_port_list_add_port(list, i, ifname, if_index,
member_ifname, num_addrs, do_mac_nat, &ip);
if (err != 0) {
goto done;
}
}
done:
if (err != 0 && list != NULL) {
switch_port_list_dealloc(list);
list = NULL;
}
T_SETUPEND;
return list;
}
static void
bridge_cleanup(const char * bridge, u_int n_ports, bool fail_on_error)
{
ifnet_destroy(bridge, fail_on_error);
for (u_int i = 0; i < n_ports; i++) {
char ifname[IFNAMSIZ];
char member_ifname[IFNAMSIZ];
snprintf(ifname, sizeof(ifname), "%s%d",
FETH_NAME, i);
snprintf(member_ifname, sizeof(member_ifname), "%s%d",
FETH_NAME, i + n_ports);
ifnet_destroy(ifname, fail_on_error);
ifnet_destroy(member_ifname, fail_on_error);
}
S_n_ports = 0;
fake_restore_trailers_fcs();
fake_restore_bsd_mode();
return;
}
/*
* Basic Bridge Tests
*
* Broadcast
* - two cases: actual broadcast, unknown ethernet
* - send broadcast packets
* - verify all received
* - check bridge rt list contains all expected MAC addresses
* - send unicast ARP packets
* - verify packets received only on expected port
*
* MAC-NAT
* - verify ARP translation
* - verify IPv4 translation
* - verify DHCP broadcast bit conversion
* - verify IPv6 translation
* - verify ND6 translation (Neighbor, Router)
* - verify IPv4 subnet-local broadcast to MAC-NAT interface link-layer
* address arrives on all member links
*/
static void
bridge_test(packet_validator_t validator,
void * context,
const ether_addr_t * dst_eaddr,
uint8_t af, u_int n_ports, u_int num_addrs)
{
#if TARGET_OS_BRIDGE
T_SKIP("Test uses too much memory");
#else /* TARGET_OS_BRIDGE */
switch_port_list_t port_list;
signal(SIGINT, sigint_handler);
port_list = bridge_setup(BRIDGE200, n_ports, num_addrs, 0);
if (port_list == NULL) {
T_FAIL("bridge_setup");
return;
}
S_port_list = port_list;
bridge_learning_test(port_list, af, validator, context, dst_eaddr);
//T_LOG("Sleeping for 5 seconds");
//sleep(5);
bridge_cleanup(BRIDGE200, n_ports, true);
switch_port_list_dealloc(port_list);
return;
#endif /* TARGET_OS_BRIDGE */
}
static void
bridge_test_mac_nat_ipv4(u_int n_ports, u_int num_addrs)
{
#if TARGET_OS_BRIDGE
T_SKIP("Test uses too much memory");
#else /* TARGET_OS_BRIDGE */
switch_port_list_t port_list;
signal(SIGINT, sigint_handler);
port_list = bridge_setup(BRIDGE200, n_ports, num_addrs,
SETUP_FLAGS_MAC_NAT);
if (port_list == NULL) {
T_FAIL("bridge_setup");
return;
}
S_port_list = port_list;
/* verify that IPv4 packets get translated when necessary */
mac_nat_test_ip(port_list, AF_INET);
/* verify the DHCP broadcast bit gets set appropriately */
mac_nat_test_dhcp(port_list, false);
/* verify that ARP packet gets translated when necessary */
mac_nat_test_arp_out(port_list);
mac_nat_test_arp_in(port_list);
/* verify IP broadcast to MAC-NAT interface link layer address */
mac_nat_test_dhcp(port_list, true);
if (G_debug) {
T_LOG("Sleeping for 5 seconds");
sleep(5);
}
bridge_cleanup(BRIDGE200, n_ports, true);
switch_port_list_dealloc(port_list);
return;
#endif /* TARGET_OS_BRIDGE */
}
static void
bridge_test_mac_nat_ipv6(u_int n_ports, u_int num_addrs, uint8_t flags)
{
#if TARGET_OS_BRIDGE
T_SKIP("Test uses too much memory");
#else /* TARGET_OS_BRIDGE */
switch_port_list_t port_list;
signal(SIGINT, sigint_handler);
flags |= SETUP_FLAGS_MAC_NAT;
port_list = bridge_setup(BRIDGE200, n_ports, num_addrs, flags);
if (port_list == NULL) {
T_FAIL("bridge_setup");
return;
}
S_port_list = port_list;
/* verify that IPv6 packets get translated when necessary */
mac_nat_test_ip(port_list, AF_INET6);
/* verify that ND6 packet gets translated when necessary */
mac_nat_test_nd6_out(port_list);
if (G_debug) {
T_LOG("Sleeping for 5 seconds");
sleep(5);
}
bridge_cleanup(BRIDGE200, n_ports, true);
switch_port_list_dealloc(port_list);
return;
#endif /* TARGET_OS_BRIDGE */
}
/*
* Filter test utilities
*/
static void
system_cmd(const char *cmd, bool fail_on_error)
{
pid_t pid = -1;
int exit_status = 0;
const char *argv[] = {
"/usr/local/bin/bash",
"-c",
cmd,
NULL
};
int rc = dt_launch_tool(&pid, (char **)(void *)argv, false, NULL, NULL);
T_QUIET;
T_ASSERT_EQ(rc, 0, "dt_launch_tool(%s) failed", cmd);
if (dt_waitpid(pid, &exit_status, NULL, 30)) {
T_QUIET;
T_ASSERT_MACH_SUCCESS(exit_status, "command(%s)", cmd);
} else {
if (fail_on_error) {
T_FAIL("dt_waitpid(%s) failed", cmd);
}
}
}
static bool
executable_is_present(const char * path)
{
struct stat statb = { 0 };
return stat(path, &statb) == 0 && (statb.st_mode & S_IXUSR) != 0;
}
static void
cleanup_pf(void)
{
struct ifbrparam param;
system_cmd("pfctl -d", false);
system_cmd("pfctl -F all", false);
param.ifbrp_filter = 0;
siocdrvspec(BRIDGE200, BRDGSFILT,
¶m, sizeof(param), true);
return;
}
static void
block_all_traffic(bool input, const char* infname1, const char* infname2)
{
int ret;
struct ifbrparam param;
char command[512];
char *dir = input ? "in" : "out";
snprintf(command, sizeof(command), "echo \"block %s on %s all\nblock %s on %s all\n\" | pfctl -vvv -f -",
dir, infname1, dir, infname2);
/* enable block all filter */
param.ifbrp_filter = IFBF_FILT_MEMBER | IFBF_FILT_ONLYIP;
ret = siocdrvspec(BRIDGE200, BRDGSFILT,
¶m, sizeof(param), true);
T_ASSERT_POSIX_SUCCESS(ret,
"SIOCDRVSPEC(BRDGSFILT %s, 0x%x)",
BRIDGE200, param.ifbrp_filter);
// ignore errors such that not having pf.os doesn't raise any issues
system_cmd(command, false);
system_cmd("pfctl -e", true);
system_cmd("pfctl -s all", true);
}
/*
* Basic bridge filter test
*
* For both broadcast and unicast transfers ensure that data can
* be blocked using pf on the bridge
*/
static void
filter_test(uint8_t af)
{
#if TARGET_OS_BRIDGE
T_SKIP("pfctl isn't valid on this platform");
#else /* TARGET_OS_BRIDGE */
switch_port_list_t port_list;
switch_port_t port;
const u_int n_ports = 2;
u_int num_addrs = 1;
u_int i;
char ntoabuf[ETHER_NTOA_BUFSIZE];
union ifbrip dst_ip;
bool blocked = true;
bool input = true;
const char* ifnames[2];
#define PFCTL_PATH "/sbin/pfctl"
if (!executable_is_present(PFCTL_PATH)) {
T_SKIP("%s not present", PFCTL_PATH);
return;
}
signal(SIGINT, sigint_handler);
T_ATEND(cleanup);
T_ATEND(cleanup_pf);
port_list = bridge_setup(BRIDGE200, n_ports, num_addrs, 0);
if (port_list == NULL) {
T_FAIL("bridge_setup");
return;
}
ether_ntoa_buf(ðer_broadcast, ntoabuf, sizeof(ntoabuf));
S_port_list = port_list;
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
ifnames[i] = port->member_ifname;
}
get_broadcast_ip_address(af, &dst_ip);
do {
do {
if (blocked) {
block_all_traffic(input, ifnames[0], ifnames[1]);
}
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
if (G_debug) {
T_LOG("Sending on %s", port->ifname);
}
for (u_int j = 0; j < port->num_addrs; j++) {
uint32_t generation;
generation = next_generation();
send_generation(port,
af,
j,
ðer_broadcast,
&dst_ip,
generation);
/* receive across all ports */
check_receive_generation(port_list,
af,
generation,
validate_broadcast_dhost,
NULL);
/* ensure that every port saw the right amount of packets*/
if (blocked) {
check_received_count(port_list, port, 0);
} else {
check_received_count(port_list, port, 1);
}
}
}
T_PASS("%s broadcast %s %s", __func__, blocked ? "blocked" : "not blocked", input ? "input" : "output");
input = !input;
cleanup_pf();
} while (input == false && blocked);
blocked = !blocked;
} while (blocked == false);
do {
do {
if (blocked) {
block_all_traffic(input, ifnames[0], ifnames[1]);
}
for (i = 0, port = port_list->list; i < port_list->count; i++, port++) {
/* send unicast packets to every other port's MAC addresses */
unicast_send_all(port_list, af, port);
/* receive all of that generated traffic */
switch_port_list_check_receive(port_list, af, NULL, 0,
validate_port_dhost, NULL);
/* ensure that every port saw the right amount of packets*/
if (blocked) {
check_received_count(port_list, port, 0);
} else {
check_received_count(port_list, port, 1);
}
}
T_PASS("%s unicast %s %s", __func__, blocked ? "blocked" : "not blocked", input ? "input" : "output");
input = !input;
cleanup_pf();
} while (input == false && blocked);
blocked = !blocked;
} while (blocked == false);
bridge_cleanup(BRIDGE200, n_ports, true);
switch_port_list_dealloc(port_list);
return;
#endif /* TARGET_OS_BRIDGE */
}
/*
* Bridge checksum offload tests
*/
static void
test_traffic_for_af(switch_port_list_t ports, uint8_t af)
{
u_int i;
inet_address server;
int server_if_index;
const char * server_name;
switch_port_t server_port;
switch_port_t port;
/* bridge as server, each peer as client */
server_if_index = bridge_if_index;
server_name = BRIDGE200;
if (af == AF_INET) {
server.v4 = bridge_ip_addr;
} else {
server.v6 = bridge_ipv6_addr;
}
for (i = 0, port = ports->list; i < ports->count; i++, port++) {
inet_test_traffic(af, &server, server_name,
server_if_index, port->ifname, port->if_index);
}
/* peer 0 as server, other peers as client */
assert(ports->count > 0);
server_port = ports->list;
server_name = server_port->ifname;
server_if_index = server_port->if_index;
if (af == AF_INET) {
server.v4 = server_port->ip;
} else {
server.v6 = server_port->ip6;
}
for (i = 1, port = ports->list + 1; i < ports->count; i++, port++) {
inet_test_traffic(af, &server, server_name,
server_if_index, port->ifname, port->if_index);
}
}
static void
bridge_test_transfer(u_int n_ports, uint8_t setup_flags)
{
#if TARGET_OS_BRIDGE
T_SKIP("Test uses too much memory");
#else /* TARGET_OS_BRIDGE */
switch_port_list_t port_list;
signal(SIGINT, sigint_handler);
port_list = bridge_setup(BRIDGE200, n_ports, 0,
SETUP_FLAGS_ATTACH_STACK | setup_flags);
if (port_list == NULL) {
T_FAIL("bridge_setup");
return;
}
test_traffic_for_af(port_list, AF_INET);
test_traffic_for_af(port_list, AF_INET6);
if (G_debug) {
T_LOG("Sleeping for 5 seconds");
sleep(5);
}
bridge_cleanup(BRIDGE200, n_ports, true);
switch_port_list_dealloc(port_list);
return;
#endif /* TARGET_OS_BRIDGE */
}
static void
lro_test_cleanup(void)
{
ifnet_destroy(BRIDGE200, false);
ifnet_destroy(FETH0, false);
fake_set_lro(false);
}
static void
sigint_lro_cleanup(__unused int sig)
{
signal(SIGINT, SIG_DFL);
lro_test_cleanup();
}
static void
verify_lro_capability(const char * if_name, bool expected)
{
struct ifreq ifr;
int result;
bool lro_enabled;
int s = inet_dgram_socket_get();
memset(&ifr, 0, sizeof(ifr));
strlcpy(ifr.ifr_name, if_name, sizeof(ifr.ifr_name));
result = ioctl(s, SIOCGIFCAP, &ifr);
T_ASSERT_POSIX_SUCCESS(result, "SIOCGIFCAP(%s)", if_name);
lro_enabled = (ifr.ifr_curcap & IFCAP_LRO) != 0;
T_ASSERT_EQ(expected, lro_enabled, "%s %s expected %s",
__func__, if_name, expected ? "enabled" : "disabled");
}
static void
bridge_test_lro_disable(void)
{
int err;
signal(SIGINT, sigint_lro_cleanup);
T_ATEND(lro_test_cleanup);
err = ifnet_create(BRIDGE200);
T_ASSERT_EQ(err, 0, "ifnet_create(%s)", BRIDGE200);
fake_set_lro(true);
err = ifnet_create(FETH0);
T_ASSERT_EQ(err, 0, "ifnet_create(%s)", FETH0);
fake_set_lro(false);
verify_lro_capability(FETH0, true);
bridge_add_member(BRIDGE200, FETH0);
verify_lro_capability(FETH0, false);
bridge_delete_member(BRIDGE200, FETH0);
verify_lro_capability(FETH0, true);
}
T_DECL(net_if_bridge_bcast,
"bridge broadcast IPv4",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
AF_INET, 5, 1);
}
T_DECL(net_if_bridge_bcast_many,
"bridge broadcast many IPv4",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
AF_INET, 5, 20);
}
T_DECL(net_if_bridge_unknown,
"bridge unknown host IPv4",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_not_present_dhost, NULL, ðer_external,
AF_INET, 5, 1);
}
T_DECL(net_if_bridge_bcast_v6,
"bridge broadcast IPv6",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
AF_INET6, 5, 1);
}
T_DECL(net_if_bridge_bcast_many_v6,
"bridge broadcast many IPv6",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
AF_INET6, 5, 20);
}
T_DECL(net_if_bridge_unknown_v6,
"bridge unknown host IPv6",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test(validate_not_present_dhost, NULL, ðer_external,
AF_INET6, 5, 1);
}
T_DECL(net_if_bridge_mac_nat_ipv4,
"bridge mac nat ipv4",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_mac_nat_ipv4(5, 10);
}
T_DECL(net_if_bridge_mac_nat_ipv6,
"bridge mac nat ipv6",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_mac_nat_ipv6(5, 10, 0);
}
T_DECL(net_if_bridge_mac_nat_ipv6_trailers,
"bridge mac nat ipv6 trailers",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_mac_nat_ipv6(5, 10, SETUP_FLAGS_TRAILERS);
}
T_DECL(net_if_bridge_filter_ipv4,
"bridge filter ipv4",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
filter_test(AF_INET);
}
T_DECL(net_if_bridge_filter_ipv6,
"bridge filter ipv6",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
filter_test(AF_INET6);
}
T_DECL(net_if_bridge_checksum_offload,
"bridge checksum offload",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_transfer(2, SETUP_FLAGS_CHECKSUM_OFFLOAD);
}
T_DECL(net_if_bridge_checksum_offload_trailers,
"bridge checksum offload with trailers+fcs",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_transfer(2, SETUP_FLAGS_CHECKSUM_OFFLOAD |
SETUP_FLAGS_TRAILERS);
}
T_DECL(net_if_bridge_transfer,
"bridge transfer",
T_META_ASROOT(true))
{
bridge_test_transfer(2, 0);
}
T_DECL(net_if_bridge_transfer_share_mac,
"bridge transfer share member's MAC",
T_META_ASROOT(true))
{
bridge_test_transfer(2, SETUP_FLAGS_SHARE_MEMBER_MAC);
}
T_DECL(net_if_bridge_lro_disable,
"bridge LRO disable",
T_META_ASROOT(true), T_META_TAG_VM_PREFERRED)
{
bridge_test_lro_disable();
}