/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 ""; } 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, T_META_ENABLED(false /* rdar://133955717 */)) { 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, T_META_ENABLED(false /* rdar://133955717 */)) { 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(); }