mirror of
https://github.com/darlinghq/darling-xnu.git
synced 2024-11-26 22:10:24 +00:00
3607 lines
92 KiB
C
3607 lines
92 KiB
C
/*
|
|
* Copyright (c) 2019-2020 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, 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 (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,
|
|
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, 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)
|
|
* - 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, 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, 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 (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);
|
|
}
|