+/*
+ * Copyright (c) 2019 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 <netinet/in.h>
+#include <arpa/inet.h>
+#include <sys/event.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <netinet/bootp.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 <errno.h>
+#include <pthread.h>
+#include <stdbool.h>
+#include <TargetConditionals.h>
+#include <darwintest_utils.h>
+#include "bpflib.h"
+#include "in_cksum.h"
+
+static bool S_debug;
+static bool S_cleaning_up;
+
+#define ALL_ADDRS (uint32_t)(-1)
+
+#define DHCP_PAYLOAD_MIN sizeof(struct bootp)
+#define DHCP_FLAGS_BROADCAST ((u_short)0x8000)
+
+typedef union {
+ char bytes[DHCP_PAYLOAD_MIN];
+ /* force 4-byte alignment */
+ uint32_t words[DHCP_PAYLOAD_MIN / sizeof(uint32_t)];
+} dhcp_min_payload, *dhcp_min_payload_t;
+
+#define ETHER_PKT_LEN (ETHER_HDR_LEN + ETHERMTU)
+typedef union {
+ char bytes[ETHER_PKT_LEN];
+ /* force 4-byte aligment */
+ uint32_t words[ETHER_PKT_LEN / sizeof(uint32_t)];
+} ether_packet, *ether_packet_t;
+
+typedef struct {
+ struct ip ip;
+ struct udphdr udp;
+} ip_udp_header_t;
+
+typedef struct {
+ struct in_addr src_ip;
+ struct in_addr dst_ip;
+ char zero;
+ char proto;
+ unsigned short length;
+} udp_pseudo_hdr_t;
+
+typedef struct {
+ struct ip ip;
+ struct tcphdr tcp;
+} ip_tcp_header_t;
+
+typedef union {
+ ip_udp_header_t udp;
+ ip_tcp_header_t tcp;
+} ip_udp_tcp_header_u;
+
+typedef struct {
+ struct in_addr src_ip;
+ struct in_addr dst_ip;
+ char zero;
+ char proto;
+ unsigned short length;
+} tcp_pseudo_hdr_t;
+
+typedef struct {
+ struct ip6_hdr ip6;
+ struct udphdr udp;
+} ip6_udp_header_t;
+
+typedef struct {
+ struct in6_addr src_ip;
+ struct in6_addr dst_ip;
+ char zero;
+ char proto;
+ unsigned short length;
+} udp6_pseudo_hdr_t;
+
+typedef struct {
+ char ifname[IFNAMSIZ];
+ 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;
+
+ 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 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);
+
+static void
+system_cmd(const char *cmd, bool fail_on_error);
+
+static int
+inet_dgram_socket(void)
+{
+ int s;
+
+ s = socket(AF_INET, SOCK_DGRAM, 0);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(s, "socket(AF_INET, SOCK_DGRAM, 0)");
+ return s;
+}
+
+
+/**
+** 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_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;
+}
+
+
+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_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_broadcast = {
+ { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }
+};
+
+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(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_udp4_frame_populate(void * buf, size_t buf_len,
+ const ether_addr_t * src,
+ struct in_addr src_ip,
+ uint16_t src_port,
+ const ether_addr_t * dst,
+ struct in_addr dst_ip,
+ uint16_t dst_port,
+ const void * data, u_int data_len)
+{
+ ether_header_t * eh_p;
+ u_int frame_length;
+ static int ip_id;
+ ip_udp_header_t * ip_udp;
+ char * payload;
+ udp_pseudo_hdr_t * udp_pseudo;
+
+ frame_length = (u_int)(sizeof(*eh_p) + sizeof(*ip_udp)) + data_len;
+ if (buf_len < frame_length) {
+ return 0;
+ }
+
+ /* determine frame offsets */
+ eh_p = (ether_header_t *)buf;
+ ip_udp = (ip_udp_header_t *)(void *)(eh_p + 1);
+ udp_pseudo = (udp_pseudo_hdr_t *)(void *)
+ (((char *)&ip_udp->udp) - sizeof(*udp_pseudo));
+ payload = (char *)(eh_p + 1) + sizeof(*ip_udp);
+
+ /* ethernet_header */
+ bcopy(src, eh_p->ether_shost, ETHER_ADDR_LEN);
+ bcopy(dst, eh_p->ether_dhost, ETHER_ADDR_LEN);
+ eh_p->ether_type = htons(ETHERTYPE_IP);
+
+ /* copy the data */
+ bcopy(data, payload, data_len);
+
+ /* fill in UDP pseudo header (gets overwritten by IP header below) */
+ bcopy(&src_ip, &udp_pseudo->src_ip, sizeof(src_ip));
+ bcopy(&dst_ip, &udp_pseudo->dst_ip, sizeof(dst_ip));
+ udp_pseudo->zero = 0;
+ udp_pseudo->proto = IPPROTO_UDP;
+ udp_pseudo->length = htons(sizeof(ip_udp->udp) + data_len);
+
+ /* fill in UDP header */
+ ip_udp->udp.uh_sport = htons(src_port);
+ ip_udp->udp.uh_dport = htons(dst_port);
+ ip_udp->udp.uh_ulen = htons(sizeof(ip_udp->udp) + data_len);
+ ip_udp->udp.uh_sum = 0;
+ ip_udp->udp.uh_sum = in_cksum(udp_pseudo, (int)(sizeof(*udp_pseudo)
+ + sizeof(ip_udp->udp) + data_len));
+
+ /* fill in IP header */
+ bzero(ip_udp, sizeof(ip_udp->ip));
+ ip_udp->ip.ip_v = IPVERSION;
+ ip_udp->ip.ip_hl = sizeof(struct ip) >> 2;
+ ip_udp->ip.ip_ttl = MAXTTL;
+ ip_udp->ip.ip_p = IPPROTO_UDP;
+ bcopy(&src_ip, &ip_udp->ip.ip_src, sizeof(src_ip));
+ bcopy(&dst_ip, &ip_udp->ip.ip_dst, sizeof(dst_ip));
+ ip_udp->ip.ip_len = htons(sizeof(*ip_udp) + data_len);
+ ip_udp->ip.ip_id = htons(ip_id++);
+
+ /* compute the IP checksum */
+ ip_udp->ip.ip_sum = 0; /* needs to be zero for checksum */
+ ip_udp->ip.ip_sum = in_cksum(&ip_udp->ip, sizeof(ip_udp->ip));
+
+ return frame_length;
+}
+
+static u_int
+ethernet_udp6_frame_populate(void * buf, size_t buf_len,
+ const ether_addr_t * src,
+ struct in6_addr *src_ip,
+ uint16_t src_port,
+ const ether_addr_t * dst,
+ struct in6_addr * dst_ip,
+ uint16_t dst_port,
+ const void * data, u_int data_len)
+{
+ ether_header_t * eh_p;
+ u_int frame_length;
+ ip6_udp_header_t * ip6_udp;
+ char * payload;
+ udp6_pseudo_hdr_t * udp6_pseudo;
+
+ frame_length = (u_int)(sizeof(*eh_p) + sizeof(*ip6_udp)) + data_len;
+ if (buf_len < frame_length) {
+ return 0;
+ }
+
+ /* determine frame offsets */
+ eh_p = (ether_header_t *)buf;
+ ip6_udp = (ip6_udp_header_t *)(void *)(eh_p + 1);
+ udp6_pseudo = (udp6_pseudo_hdr_t *)(void *)
+ (((char *)&ip6_udp->udp) - sizeof(*udp6_pseudo));
+ payload = (char *)(eh_p + 1) + sizeof(*ip6_udp);
+
+ /* ethernet_header */
+ bcopy(src, eh_p->ether_shost, ETHER_ADDR_LEN);
+ bcopy(dst, eh_p->ether_dhost, ETHER_ADDR_LEN);
+ eh_p->ether_type = htons(ETHERTYPE_IPV6);
+
+ /* copy the data */
+ bcopy(data, payload, data_len);
+
+ /* fill in UDP pseudo header (gets overwritten by IP header below) */
+ bcopy(src_ip, &udp6_pseudo->src_ip, sizeof(*src_ip));
+ bcopy(dst_ip, &udp6_pseudo->dst_ip, sizeof(*dst_ip));
+ udp6_pseudo->zero = 0;
+ udp6_pseudo->proto = IPPROTO_UDP;
+ udp6_pseudo->length = htons(sizeof(ip6_udp->udp) + data_len);
+
+ /* fill in UDP header */
+ ip6_udp->udp.uh_sport = htons(src_port);
+ ip6_udp->udp.uh_dport = htons(dst_port);
+ ip6_udp->udp.uh_ulen = htons(sizeof(ip6_udp->udp) + data_len);
+ ip6_udp->udp.uh_sum = 0;
+ ip6_udp->udp.uh_sum = in_cksum(udp6_pseudo, (int)(sizeof(*udp6_pseudo)
+ + sizeof(ip6_udp->udp) + data_len));
+
+ /* fill in IP header */
+ bzero(&ip6_udp->ip6, sizeof(ip6_udp->ip6));
+ ip6_udp->ip6.ip6_vfc = IPV6_VERSION;
+ ip6_udp->ip6.ip6_nxt = IPPROTO_UDP;
+ bcopy(src_ip, &ip6_udp->ip6.ip6_src, sizeof(*src_ip));
+ bcopy(dst_ip, &ip6_udp->ip6.ip6_dst, sizeof(*dst_ip));
+ ip6_udp->ip6.ip6_plen = htons(sizeof(struct udphdr) + data_len);
+ /* ip6_udp->ip6.ip6_flow = ? */
+ return frame_length;
+}
+
+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 (S_debug) {
+ T_LOG("Port %s -> %s transmitting %u bytes",
+ port->ifname, port->member_ifname, frame_length);
+ }
+ ethernet_frame_validate(&pkt, frame_length, S_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 (S_debug) {
+ T_LOG("Port %s -> %s transmitting %u bytes",
+ port->ifname, port->member_ifname, frame_length);
+ }
+ ethernet_frame_validate(&pkt, frame_length, S_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 (S_debug) {
+ T_LOG("Port %s transmitting %u bytes",
+ port->ifname, frame_length);
+ }
+ ethernet_frame_validate(&pkt, frame_length, S_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 (S_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, S_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, const char * member_ifname,
+ ether_addr_t * member_mac,
+ u_int num_addrs, bool mac_nat)
+{
+ int buf_size;
+ errno_t err = EINVAL;
+ int fd = -1;
+ 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;
+ }
+ 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 (S_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;
+ p->member_mac = *member_mac;
+ 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 (S_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 (S_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 (S_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 (S_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 inline const char *
+af_get_str(uint8_t af)
+{
+ return (af == AF_INET) ? "IPv4" : "IPv6";
+}
+
+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 (S_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 (S_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 (S_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 (S_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 u_int
+make_dhcp_payload(dhcp_min_payload_t payload, ether_addr_t *eaddr)
+{
+ struct bootp * dhcp;
+ u_int payload_length;
+
+ /* create a minimal BOOTP packet */
+ payload_length = sizeof(*payload);
+ dhcp = (struct bootp *)payload;
+ bzero(dhcp, payload_length);
+ dhcp->bp_op = BOOTREQUEST;
+ dhcp->bp_htype = ARPHRD_ETHER;
+ dhcp->bp_hlen = sizeof(*eaddr);
+ bcopy(eaddr->octet, dhcp->bp_chaddr, sizeof(eaddr->octet));
+ return payload_length;
+}
+
+static void
+mac_nat_test_dhcp(switch_port_list_t port_list)
+{
+ u_int i;
+ struct in_addr ip_dst = { INADDR_BROADCAST };
+ struct in_addr ip_src = { INADDR_ANY };
+ switch_port_t port;
+
+ 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 (port->mac_nat) {
+ continue;
+ }
+ set_ethernet_address(&eaddr, port->unit, 0);
+ payload_len = make_dhcp_payload(&payload, &eaddr);
+ if (S_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,
+ ðer_broadcast,
+ (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);
+ }
+ T_PASS("%s", __func__);
+}
+
+
+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, NULL);
+ 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;
+ switch_port_t send_port = (switch_port_t)context;
+
+ if (S_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 */
+ validate_mac_nat_icmp6_out(port, icmp6,
+ pkt_len - ETHER_IPV6_LEN,
+ 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 (S_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 (S_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
+ifnet_get_lladdr(int s, const char * ifname, ether_addr_t * eaddr)
+{
+ int err;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ifr.ifr_addr.sa_family = AF_LINK;
+ ifr.ifr_addr.sa_len = ETHER_ADDR_LEN;
+ err = ioctl(s, SIOCGIFLLADDR, &ifr);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(err, "SIOCGIFLLADDR %s", ifname);
+ bcopy(ifr.ifr_addr.sa_data, eaddr->octet, ETHER_ADDR_LEN);
+ return err;
+}
+
+
+static int
+ifnet_attach_ip(int s, char * name)
+{
+ int err;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ err = ioctl(s, SIOCPROTOATTACH, &ifr);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(err, "SIOCPROTOATTACH %s", ifr.ifr_name);
+ return err;
+}
+
+#if 0
+static int
+ifnet_detach_ip(int s, char * name)
+{
+ int err;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ err = ioctl(s, SIOCPROTODETACH, &ifr);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(err, "SIOCPROTODETACH %s", ifr.ifr_name);
+ return err;
+}
+#endif
+
+static int
+ifnet_destroy(int s, const char * ifname, bool fail_on_error)
+{
+ int err;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ err = ioctl(s, SIOCIFDESTROY, &ifr);
+ if (fail_on_error) {
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(err, "SIOCSIFDESTROY %s", ifr.ifr_name);
+ }
+ if (err < 0) {
+ T_LOG("SIOCSIFDESTROY %s", ifr.ifr_name);
+ }
+ return err;
+}
+
+static int
+ifnet_set_flags(int s, const char * ifname,
+ uint16_t flags_set, uint16_t flags_clear)
+{
+ uint16_t flags_after;
+ uint16_t flags_before;
+ struct ifreq ifr;
+ int ret;
+
+ bzero(&ifr, sizeof(ifr));
+ strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+ ret = ioctl(s, SIOCGIFFLAGS, (caddr_t)&ifr);
+ if (ret != 0) {
+ T_LOG("SIOCGIFFLAGS %s", ifr.ifr_name);
+ return ret;
+ }
+ flags_before = (uint16_t)ifr.ifr_flags;
+ ifr.ifr_flags |= flags_set;
+ ifr.ifr_flags &= ~(flags_clear);
+ flags_after = (uint16_t)ifr.ifr_flags;
+ if (flags_before == flags_after) {
+ /* nothing to do */
+ ret = 0;
+ } else {
+ /* issue the ioctl */
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(ioctl(s, SIOCSIFFLAGS, &ifr),
+ "SIOCSIFFLAGS %s 0x%x",
+ ifr.ifr_name, (uint16_t)ifr.ifr_flags);
+ if (S_debug) {
+ T_LOG("setflags(%s set 0x%x clear 0x%x) 0x%x => 0x%x",
+ ifr.ifr_name, flags_set, flags_clear,
+ flags_before, flags_after);
+ }
+ }
+ return ret;
+}
+
+#define BRIDGE_NAME "bridge"
+#define BRIDGE200 BRIDGE_NAME "200"
+
+#define FETH_NAME "feth"
+
+/* On some platforms with DEBUG kernel, we need to wait a while */
+#define SIFCREATE_RETRY 600
+
+static int
+ifnet_create(int s, const char * ifname)
+{
+ int error = 0;
+ struct ifreq ifr;
+
+ bzero(&ifr, sizeof(ifr));
+ strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
+
+ for (int i = 0; i < SIFCREATE_RETRY; i++) {
+ if (ioctl(s, SIOCIFCREATE, &ifr) < 0) {
+ error = errno;
+ T_LOG("SIOCSIFCREATE %s: %s", ifname,
+ strerror(error));
+ if (error == EBUSY) {
+ /* interface is tearing down, try again */
+ usleep(10000);
+ } else if (error == EEXIST) {
+ /* interface exists, try destroying it */
+ (void)ifnet_destroy(s, ifname, false);
+ } else {
+ /* unexpected failure */
+ break;
+ }
+ } else {
+ error = 0;
+ break;
+ }
+ }
+ if (error == 0) {
+ error = ifnet_set_flags(s, ifname, IFF_UP, 0);
+ }
+ return error;
+}
+
+static int
+siocdrvspec(int s, const char * ifname,
+ u_long op, void *arg, size_t argsize, bool set)
+{
+ struct ifdrv ifd;
+
+ memset(&ifd, 0, sizeof(ifd));
+ strlcpy(ifd.ifd_name, ifname, sizeof(ifd.ifd_name));
+ ifd.ifd_cmd = op;
+ ifd.ifd_len = argsize;
+ ifd.ifd_data = arg;
+ return ioctl(s, set ? SIOCSDRVSPEC : SIOCGDRVSPEC, &ifd);
+}
+
+
+static int
+fake_set_peer(int s, const char * feth, const char * feth_peer)
+{
+ struct if_fake_request iffr;
+ int ret;
+
+ bzero((char *)&iffr, sizeof(iffr));
+ if (feth_peer != NULL) {
+ strlcpy(iffr.iffr_peer_name, feth_peer,
+ sizeof(iffr.iffr_peer_name));
+ }
+ ret = siocdrvspec(s, feth, IF_FAKE_S_CMD_SET_PEER,
+ &iffr, sizeof(iffr), true);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(ret,
+ "SIOCDRVSPEC(%s, IF_FAKE_S_CMD_SET_PEER, %s)",
+ feth, (feth_peer != NULL) ? feth_peer : "<none>");
+ return ret;
+}
+
+static int
+bridge_add_member(int s, 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(s, bridge, BRDGADD, &req, sizeof(req), true);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(ret, "%s %s %s", __func__, bridge, member);
+ return ret;
+}
+
+
+static int
+bridge_set_mac_nat(int s, const char * bridge, const char * member, bool enable)
+{
+ 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(s, bridge, BRDGGIFFLGS, &req, sizeof(req), false);
+ T_QUIET;
+ T_ASSERT_POSIX_SUCCESS(ret, "BRDGGIFFLGS %s %s", bridge, member);
+ flags = req.ifbr_ifsflags;
+ if (enable) {
+ if ((flags & IFBIF_MAC_NAT) == 0) {
+ need_set = true;
+ req.ifbr_ifsflags |= IFBIF_MAC_NAT;
+ }
+ /* need to set it */
+ } else if ((flags & IFBIF_MAC_NAT) != 0) {
+ /* need to clear it */
+ need_set = true;
+ req.ifbr_ifsflags &= ~(uint32_t)IFBIF_MAC_NAT;
+ }
+ if (need_set) {
+ ret = siocdrvspec(s, 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 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;
+ int s;
+
+ s = inet_dgram_socket();
+
+ /*
+ * 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(s, 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;
+ }
+ if (s >= 0) {
+ close(s);
+ }
+ 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;
+ int s;
+ char * scan;
+
+
+ s = inet_dgram_socket();
+
+ /* find out how many there are */
+ bzero(&mnl, sizeof(mnl));
+ err = siocdrvspec(s, 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(s, 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 (s >= 0) {
+ close(s);
+ }
+ 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 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 && S_port_list != NULL)) {
+ 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 (S_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, bool mac_nat)
+{
+ errno_t err;
+ switch_port_list_t list = NULL;
+ int s;
+
+ S_n_ports = n_ports;
+ T_ATEND(cleanup);
+ T_SETUPBEGIN;
+ s = inet_dgram_socket();
+ err = ifnet_create(s, bridge);
+ if (err != 0) {
+ goto done;
+ }
+ list = switch_port_list_alloc(n_ports, mac_nat);
+ for (u_int i = 0; i < n_ports; i++) {
+ bool do_mac_nat;
+ char ifname[IFNAMSIZ];
+ char member_ifname[IFNAMSIZ];
+ ether_addr_t member_mac;
+
+ 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(s, ifname);
+ if (err != 0) {
+ goto done;
+ }
+ ifnet_attach_ip(s, ifname);
+ err = ifnet_create(s, member_ifname);
+ if (err != 0) {
+ goto done;
+ }
+ err = ifnet_get_lladdr(s, member_ifname, &member_mac);
+ if (err != 0) {
+ goto done;
+ }
+ err = fake_set_peer(s, ifname, member_ifname);
+ if (err != 0) {
+ goto done;
+ }
+ /* add the interface's peer to the bridge */
+ err = bridge_add_member(s, 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_set_mac_nat(s, 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, member_ifname,
+ &member_mac, num_addrs,
+ do_mac_nat);
+ if (err != 0) {
+ goto done;
+ }
+ }
+done:
+ if (s >= 0) {
+ close(s);
+ }
+ 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)
+{
+ int s;
+
+ s = inet_dgram_socket();
+ ifnet_destroy(s, 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(s, ifname, fail_on_error);
+ ifnet_destroy(s, member_ifname, fail_on_error);
+ }
+ if (s >= 0) {
+ close(s);
+ }
+ S_n_ports = 0;
+ 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)
+ */
+
+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, false);
+ 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, true);
+ 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);
+
+ /* verify that ARP packet gets translated when necessary */
+ mac_nat_test_arp_out(port_list);
+ mac_nat_test_arp_in(port_list);
+
+ if (S_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)
+{
+#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, true);
+ 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 (S_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
+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 void
+cleanup_pf(void)
+{
+ struct ifbrparam param;
+ int s = inet_dgram_socket();
+
+ system_cmd("pfctl -d", false);
+ system_cmd("pfctl -F all", false);
+
+ param.ifbrp_filter = 0;
+ siocdrvspec(s, BRIDGE200, BRDGSFILT,
+ ¶m, sizeof(param), true);
+ return;
+}
+
+static void
+block_all_traffic(bool input, const char* infname1, const char* infname2)
+{
+ int s = inet_dgram_socket();
+ 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(s, 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];
+
+ signal(SIGINT, sigint_handler);
+
+ T_ATEND(cleanup);
+ T_ATEND(cleanup_pf);
+
+ port_list = bridge_setup(BRIDGE200, n_ports, num_addrs, false);
+ 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 (S_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 */
+}
+
+T_DECL(if_bridge_bcast,
+ "bridge broadcast IPv4",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
+ AF_INET, 5, 1);
+}
+
+T_DECL(if_bridge_bcast_many,
+ "bridge broadcast many IPv4",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
+ AF_INET, 5, 20);
+}
+
+T_DECL(if_bridge_unknown,
+ "bridge unknown host IPv4",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_not_present_dhost, NULL, ðer_external,
+ AF_INET, 5, 1);
+}
+
+T_DECL(if_bridge_bcast_v6,
+ "bridge broadcast IPv6",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
+ AF_INET6, 5, 1);
+}
+
+T_DECL(if_bridge_bcast_many_v6,
+ "bridge broadcast many IPv6",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_broadcast_dhost, NULL, ðer_broadcast,
+ AF_INET6, 5, 20);
+}
+
+T_DECL(if_bridge_unknown_v6,
+ "bridge unknown host IPv6",
+ T_META_ASROOT(true))
+{
+ bridge_test(validate_not_present_dhost, NULL, ðer_external,
+ AF_INET6, 5, 1);
+}
+
+T_DECL(if_bridge_mac_nat_ipv4,
+ "bridge mac nat ipv4",
+ T_META_ASROOT(true))
+{
+ bridge_test_mac_nat_ipv4(5, 10);
+}
+
+T_DECL(if_bridge_mac_nat_ipv6,
+ "bridge mac nat ipv6",
+ T_META_ASROOT(true))
+{
+ bridge_test_mac_nat_ipv6(5, 10);
+}
+
+T_DECL(if_bridge_filter_ipv4,
+ "bridge filter ipv4",
+ T_META_ASROOT(true))
+{
+ filter_test(AF_INET);
+}
+
+T_DECL(if_bridge_filter_ipv6,
+ "bridge filter ipv6",
+ T_META_ASROOT(true))
+{
+ filter_test(AF_INET6);
+}