Add support for UDP IPv6

Also ironed out all the bugs found on the IPv6 code path that affects socket
bind, send and receive.

PiperOrigin-RevId: 321202653
This commit is contained in:
Jay Zhuang
2020-07-14 11:49:51 -07:00
committed by gVisor bot
parent 87c33be9af
commit 822fc99ecd
10 changed files with 464 additions and 73 deletions
+8 -2
View File
@@ -53,7 +53,10 @@
response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo));
response_in6->mutable_addr()->assign(
reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16);
response_in6->set_scope_id(ntohl(addr_in6->sin6_scope_id));
// sin6_scope_id is stored in host byte order.
//
// https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
response_in6->set_scope_id(addr_in6->sin6_scope_id);
return ::grpc::Status::OK;
}
}
@@ -89,7 +92,10 @@
addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo());
proto_in6.addr().copy(
reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
addr_in6->sin6_scope_id = htonl(proto_in6.scope_id());
// sin6_scope_id is stored in host byte order.
//
// https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
addr_in6->sin6_scope_id = proto_in6.scope_id();
*addr_len = sizeof(*addr_in6);
break;
}
+9 -1
View File
@@ -1,4 +1,4 @@
load("//tools:defs.bzl", "go_library")
load("//tools:defs.bzl", "go_library", "go_test")
package(
licenses = ["notice"],
@@ -13,3 +13,11 @@ go_library(
"//pkg/tcpip/header",
],
)
go_test(
name = "netdevs_test",
size = "small",
srcs = ["netdevs_test.go"],
library = ":netdevs",
deps = ["@com_github_google_go_cmp//cmp:go_default_library"],
)
+14 -3
View File
@@ -19,6 +19,7 @@ import (
"fmt"
"net"
"regexp"
"strconv"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
@@ -27,6 +28,7 @@ import (
// A DeviceInfo represents a network device.
type DeviceInfo struct {
ID uint32
MAC net.HardwareAddr
IPv4Addr net.IP
IPv4Net *net.IPNet
@@ -35,7 +37,7 @@ type DeviceInfo struct {
}
var (
deviceLine = regexp.MustCompile(`^\s*\d+: (\w+)`)
deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`)
linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`)
@@ -43,6 +45,11 @@ var (
// ParseDevices parses the output from `ip addr show` into a map from device
// name to information about the device.
//
// Note: if multiple IPv6 addresses are assigned to a device, the last address
// displayed by `ip addr show` will be used. This is fine for packetimpact
// because we will always only have at most one IPv6 address assigned to each
// device.
func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
var currentDevice string
var currentInfo DeviceInfo
@@ -52,8 +59,12 @@ func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
if currentDevice != "" {
deviceInfos[currentDevice] = currentInfo
}
currentInfo = DeviceInfo{}
currentDevice = m[1]
id, err := strconv.ParseUint(m[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("parsing device ID %s: %w", m[1], err)
}
currentInfo = DeviceInfo{ID: uint32(id)}
currentDevice = m[2]
} else if m := linkLine.FindStringSubmatch(line); m != nil {
mac, err := net.ParseMAC(m[1])
if err != nil {
+227
View File
@@ -0,0 +1,227 @@
// Copyright 2020 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package netdevs
import (
"fmt"
"net"
"testing"
"github.com/google/go-cmp/cmp"
)
func mustParseMAC(s string) net.HardwareAddr {
mac, err := net.ParseMAC(s)
if err != nil {
panic(fmt.Sprintf("failed to parse test MAC %q: %s", s, err))
}
return mac
}
func TestParseDevices(t *testing.T) {
for _, v := range []struct {
desc string
cmdOutput string
want map[string]DeviceInfo
}{
{
desc: "v4 and v6",
cmdOutput: `
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::42:c0ff:fea8:902/64 scope link tentative
valid_lft forever preferred_lft forever
2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 223.245.225.10/24 brd 223.245.225.255 scope global eth2
valid_lft forever preferred_lft forever
inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
valid_lft forever preferred_lft forever
2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::42:daff:fe33:130a/64 scope link tentative
valid_lft forever preferred_lft forever`,
want: map[string]DeviceInfo{
"lo": DeviceInfo{
ID: 1,
MAC: mustParseMAC("00:00:00:00:00:00"),
IPv4Addr: net.IPv4(127, 0, 0, 1),
IPv4Net: &net.IPNet{
IP: net.IPv4(127, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
},
IPv6Addr: net.ParseIP("::1"),
IPv6Net: &net.IPNet{
IP: net.ParseIP("::1"),
Mask: net.CIDRMask(128, 128),
},
},
"eth0": DeviceInfo{
ID: 2613,
MAC: mustParseMAC("02:42:c0:a8:09:02"),
IPv4Addr: net.IPv4(192, 168, 9, 2),
IPv4Net: &net.IPNet{
IP: net.IPv4(192, 168, 9, 0),
Mask: net.CIDRMask(24, 32),
},
IPv6Addr: net.ParseIP("fe80::42:c0ff:fea8:902"),
IPv6Net: &net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(64, 128),
},
},
"eth1": DeviceInfo{
ID: 2617,
MAC: mustParseMAC("02:42:da:33:13:0a"),
IPv4Addr: net.IPv4(218, 51, 19, 10),
IPv4Net: &net.IPNet{
IP: net.IPv4(218, 51, 19, 0),
Mask: net.CIDRMask(24, 32),
},
IPv6Addr: net.ParseIP("fe80::42:daff:fe33:130a"),
IPv6Net: &net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(64, 128),
},
},
"eth2": DeviceInfo{
ID: 2615,
MAC: mustParseMAC("02:42:df:f5:e1:0a"),
IPv4Addr: net.IPv4(223, 245, 225, 10),
IPv4Net: &net.IPNet{
IP: net.IPv4(223, 245, 225, 0),
Mask: net.CIDRMask(24, 32),
},
IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
IPv6Net: &net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(64, 128),
},
},
},
},
{
desc: "v4 only",
cmdOutput: `
2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
valid_lft forever preferred_lft forever`,
want: map[string]DeviceInfo{
"eth0": DeviceInfo{
ID: 2613,
MAC: mustParseMAC("02:42:c0:a8:09:02"),
IPv4Addr: net.IPv4(192, 168, 9, 2),
IPv4Net: &net.IPNet{
IP: net.IPv4(192, 168, 9, 0),
Mask: net.CIDRMask(24, 32),
},
},
},
},
{
desc: "v6 only",
cmdOutput: `
2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
valid_lft forever preferred_lft forever`,
want: map[string]DeviceInfo{
"eth2": DeviceInfo{
ID: 2615,
MAC: mustParseMAC("02:42:df:f5:e1:0a"),
IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
IPv6Net: &net.IPNet{
IP: net.ParseIP("fe80::"),
Mask: net.CIDRMask(64, 128),
},
},
},
},
} {
t.Run(v.desc, func(t *testing.T) {
got, err := ParseDevices(v.cmdOutput)
if err != nil {
t.Errorf("ParseDevices(\n%s\n) got unexpected error: %s", v.cmdOutput, err)
}
if diff := cmp.Diff(v.want, got); diff != "" {
t.Errorf("ParseDevices(\n%s\n) got output diff (-want, +got):\n%s", v.cmdOutput, diff)
}
})
}
}
func TestParseDevicesErrors(t *testing.T) {
for _, v := range []struct {
desc string
cmdOutput string
}{
{
desc: "invalid MAC addr",
cmdOutput: `
2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:33:13:0a:ffffffff brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::42:daff:fe33:130a/64 scope link tentative
valid_lft forever preferred_lft forever`,
},
{
desc: "invalid v4 addr",
cmdOutput: `
2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 1234.4321.424242.0/24 brd 218.51.19.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::42:daff:fe33:130a/64 scope link tentative
valid_lft forever preferred_lft forever`,
},
{
desc: "invalid v6 addr",
cmdOutput: `
2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80:ffffffff::42:daff:fe33:130a/64 scope link tentative
valid_lft forever preferred_lft forever`,
},
{
desc: "invalid CIDR missing prefixlen",
cmdOutput: `
2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 218.51.19.10 brd 218.51.19.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::42:daff:fe33:130a scope link tentative
valid_lft forever preferred_lft forever`,
},
} {
t.Run(v.desc, func(t *testing.T) {
if _, err := ParseDevices(v.cmdOutput); err == nil {
t.Errorf("ParseDevices(\n%s\n) succeeded unexpectedly, want error", v.cmdOutput)
}
})
}
}
@@ -304,6 +304,7 @@ func TestOne(t *testing.T) {
"--local_ipv4", addressInSubnet(testbenchAddr, *testNet.Subnet).String(),
"--remote_ipv6", remoteIPv6.String(),
"--remote_mac", remoteMAC.String(),
"--remote_interface_id", fmt.Sprintf("%d", dutDeviceInfo.ID),
"--device", testNetDev,
"--dut_type", *dutPlatform,
)
+125 -12
View File
@@ -43,14 +43,16 @@ func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
// pickPort makes a new socket and returns the socket FD and port. The domain should be AF_INET or AF_INET6. The caller must close the FD when done with
// the port if there is no error.
func pickPort(domain, typ int) (int, uint16, error) {
fd, err := unix.Socket(domain, typ, 0)
func pickPort(domain, typ int) (fd int, port uint16, err error) {
fd, err = unix.Socket(domain, typ, 0)
if err != nil {
return -1, 0, err
return -1, 0, fmt.Errorf("creating socket: %w", err)
}
defer func() {
if err != nil {
err = multierr.Append(err, unix.Close(fd))
if cerr := unix.Close(fd); cerr != nil {
err = multierr.Append(err, fmt.Errorf("failed to close socket %d: %w", fd, cerr))
}
}
}()
var sa unix.Sockaddr
@@ -60,22 +62,22 @@ func pickPort(domain, typ int) (int, uint16, error) {
copy(sa4.Addr[:], net.ParseIP(LocalIPv4).To4())
sa = &sa4
case unix.AF_INET6:
var sa6 unix.SockaddrInet6
sa6 := unix.SockaddrInet6{ZoneId: uint32(LocalInterfaceID)}
copy(sa6.Addr[:], net.ParseIP(LocalIPv6).To16())
sa = &sa6
default:
return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
}
if err = unix.Bind(fd, sa); err != nil {
return -1, 0, err
return -1, 0, fmt.Errorf("binding to %+v: %w", sa, err)
}
sa, err = unix.Getsockname(fd)
if err != nil {
return -1, 0, err
return -1, 0, fmt.Errorf("Getsocketname(%d): %w", fd, err)
}
port, err := portFromSockaddr(sa)
port, err = portFromSockaddr(sa)
if err != nil {
return -1, 0, err
return -1, 0, fmt.Errorf("extracting port from socket address %+v: %w", sa, err)
}
return fd, port, nil
}
@@ -378,7 +380,7 @@ var _ layerState = (*udpState)(nil)
func newUDPState(domain int, out, in UDP) (*udpState, error) {
portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
if err != nil {
return nil, err
return nil, fmt.Errorf("picking port: %w", err)
}
s := udpState{
out: UDP{SrcPort: &localPort},
@@ -916,14 +918,14 @@ func (conn *UDPIPv4) SendIP(ip IPv4, udp UDP, additionalLayers ...Layer) {
func (conn *UDPIPv4) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
conn.t.Helper()
layer, err := (*Connection)(conn).Expect(&udp, timeout)
if layer == nil {
if err != nil {
return nil, err
}
gotUDP, ok := layer.(*UDP)
if !ok {
conn.t.Fatalf("expected %s to be UDP", layer)
}
return gotUDP, err
return gotUDP, nil
}
// ExpectData is a convenient method that expects a Layer and the Layer after
@@ -948,3 +950,114 @@ func (conn *UDPIPv4) Close() {
func (conn *UDPIPv4) Drain() {
conn.sniffer.Drain()
}
// UDPIPv6 maintains the state for all the layers in a UDP/IPv6 connection.
type UDPIPv6 Connection
// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults.
func NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
etherState, err := newEtherState(Ether{}, Ether{})
if err != nil {
t.Fatalf("can't make etherState: %s", err)
}
ipv6State, err := newIPv6State(IPv6{}, IPv6{})
if err != nil {
t.Fatalf("can't make IPv6State: %s", err)
}
udpState, err := newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
if err != nil {
t.Fatalf("can't make udpState: %s", err)
}
injector, err := NewInjector(t)
if err != nil {
t.Fatalf("can't make injector: %s", err)
}
sniffer, err := NewSniffer(t)
if err != nil {
t.Fatalf("can't make sniffer: %s", err)
}
return UDPIPv6{
layerStates: []layerState{etherState, ipv6State, udpState},
injector: injector,
sniffer: sniffer,
t: t,
}
}
func (conn *UDPIPv6) udpState() *udpState {
state, ok := conn.layerStates[2].(*udpState)
if !ok {
conn.t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
}
return state
}
func (conn *UDPIPv6) ipv6State() *ipv6State {
state, ok := conn.layerStates[1].(*ipv6State)
if !ok {
conn.t.Fatalf("got network-layer state type=%T, expected ipv6State", conn.layerStates[1])
}
return state
}
// LocalAddr gets the local socket address of this connection.
func (conn *UDPIPv6) LocalAddr() *unix.SockaddrInet6 {
sa := &unix.SockaddrInet6{
Port: int(*conn.udpState().out.SrcPort),
// Local address is in perspective to the remote host, so it's scoped to the
// ID of the remote interface.
ZoneId: uint32(RemoteInterfaceID),
}
copy(sa.Addr[:], *conn.ipv6State().out.SrcAddr)
return sa
}
// Send sends a packet with reasonable defaults, potentially overriding the UDP
// layer and adding additionLayers.
func (conn *UDPIPv6) Send(udp UDP, additionalLayers ...Layer) {
(*Connection)(conn).send(Layers{&udp}, additionalLayers...)
}
// SendIPv6 sends a packet with reasonable defaults, potentially overriding the
// UDP and IPv6 headers and adding additionLayers.
func (conn *UDPIPv6) SendIPv6(ip IPv6, udp UDP, additionalLayers ...Layer) {
(*Connection)(conn).send(Layers{&ip, &udp}, additionalLayers...)
}
// Expect expects a frame with the UDP layer matching the provided UDP within
// the timeout specified. If it doesn't arrive in time, an error is returned.
func (conn *UDPIPv6) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
conn.t.Helper()
layer, err := (*Connection)(conn).Expect(&udp, timeout)
if err != nil {
return nil, err
}
gotUDP, ok := layer.(*UDP)
if !ok {
conn.t.Fatalf("expected %s to be UDP", layer)
}
return gotUDP, nil
}
// ExpectData is a convenient method that expects a Layer and the Layer after
// it. If it doens't arrive in time, it returns nil.
func (conn *UDPIPv6) ExpectData(udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
conn.t.Helper()
expected := make([]Layer, len(conn.layerStates))
expected[len(expected)-1] = &udp
if payload.length() != 0 {
expected = append(expected, &payload)
}
return (*Connection)(conn).ExpectFrame(expected, timeout)
}
// Close frees associated resources held by the UDPIPv6 connection.
func (conn *UDPIPv6) Close() {
(*Connection)(conn).Close()
}
// Drain drains the sniffer's receive buffer by receiving packets until there's
// nothing else to receive.
func (conn *UDPIPv6) Drain() {
conn.sniffer.Drain()
}
+4 -2
View File
@@ -87,7 +87,7 @@ func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
},
}
}
dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
dut.t.Fatalf("can't parse Sockaddr struct: %+v", sa)
return nil
}
@@ -106,8 +106,9 @@ func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
ZoneId: s.In6.GetScopeId(),
}
copy(ret.Addr[:], s.In6.GetAddr())
return &ret
}
dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
dut.t.Fatalf("can't parse Sockaddr proto: %+v", sa)
return nil
}
@@ -126,6 +127,7 @@ func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16)
fd = dut.Socket(unix.AF_INET6, typ, proto)
sa := unix.SockaddrInet6{}
copy(sa.Addr[:], addr.To16())
sa.ZoneId = uint32(RemoteInterfaceID)
dut.Bind(fd, &sa)
} else {
dut.t.Fatalf("unknown ip addr type for remoteIP")
+6 -4
View File
@@ -904,12 +904,14 @@ func payload(l Layer) (buffer.VectorisedView, error) {
func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) {
totalLength := uint16(totalLength(l))
var xsum uint16
switch s := l.Prev().(type) {
switch p := l.Prev().(type) {
case *IPv4:
xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
case *IPv6:
xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
default:
// TODO(b/150301488): Support more protocols, like IPv6.
return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
// TODO(b/161246171): Support more protocols.
return 0, fmt.Errorf("checksum for protocol %d is not supported when previous layer is %T", protoNumber, p)
}
payloadBytes, err := payload(l)
if err != nil {
+9
View File
@@ -44,6 +44,13 @@ var (
// RemoteIPv6 is the DUT's IPv6 address on the test network.
RemoteIPv6 = ""
// LocalInterfaceID is the ID of the local interface on the test network.
LocalInterfaceID uint32
// RemoteInterfaceID is the ID of the remote interface on the test network.
//
// Not using uint32 because package flag does not support uint32.
RemoteInterfaceID uint64
// LocalMAC is the local MAC address on the test network.
LocalMAC = ""
// RemoteMAC is the DUT's MAC address on the test network.
@@ -75,6 +82,7 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
fs.StringVar(&Device, "device", Device, "local device for test packets")
fs.StringVar(&DUTType, "dut_type", DUTType, "type of device under test")
fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets")
}
// genPseudoFlags populates flag-like global config based on real flags.
@@ -97,6 +105,7 @@ func genPseudoFlags() error {
LocalMAC = deviceInfo.MAC.String()
LocalIPv6 = deviceInfo.IPv6Addr.String()
LocalInterfaceID = deviceInfo.ID
if deviceInfo.IPv4Net != nil {
IPv4PrefixLength, _ = deviceInfo.IPv4Net.Mask.Size()
@@ -28,62 +28,74 @@ func init() {
testbench.RegisterFlags(flag.CommandLine)
}
func TestUDPRecv(t *testing.T) {
type udpConn interface {
Send(testbench.UDP, ...testbench.Layer)
ExpectData(testbench.UDP, testbench.Payload, time.Duration) (testbench.Layers, error)
Drain()
Close()
}
func TestUDP(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
defer dut.Close(boundFD)
conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close()
testCases := []struct {
name string
payload []byte
}{
{"emptypayload", nil},
{"small payload", []byte("hello world")},
{"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
// Even though UDP allows larger dgrams we don't test it here as
// they need to be fragmented and written out as individual
// frames.
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conn.Send(testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
if got, want := string(dut.Recv(boundFD, int32(len(tc.payload)), 0)), string(tc.payload); got != want {
t.Fatalf("received payload does not match sent payload got: %s, want: %s", got, want)
for _, isIPv4 := range []bool{true, false} {
ipVersionName := "IPv6"
if isIPv4 {
ipVersionName = "IPv4"
}
t.Run(ipVersionName, func(t *testing.T) {
var addr string
if isIPv4 {
addr = testbench.RemoteIPv4
} else {
addr = testbench.RemoteIPv6
}
})
}
}
boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(addr))
defer dut.Close(boundFD)
func TestUDPSend(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
defer dut.Close(boundFD)
conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close()
var conn udpConn
var localAddr unix.Sockaddr
if isIPv4 {
v4Conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
localAddr = v4Conn.LocalAddr()
conn = &v4Conn
} else {
v6Conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
localAddr = v6Conn.LocalAddr()
conn = &v6Conn
}
defer conn.Close()
testCases := []struct {
name string
payload []byte
}{
{"emptypayload", nil},
{"small payload", []byte("hello world")},
{"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
// Even though UDP allows larger dgrams we don't test it here as
// they need to be fragmented and written out as individual
// frames.
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
conn.Drain()
if got, want := int(dut.SendTo(boundFD, tc.payload, 0, conn.LocalAddr())), len(tc.payload); got != want {
t.Fatalf("short write got: %d, want: %d", got, want)
testCases := []struct {
name string
payload []byte
}{
{"emptypayload", nil},
{"small payload", []byte("hello world")},
{"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
// Even though UDP allows larger dgrams we don't test it here as
// they need to be fragmented and written out as individual
// frames.
}
if _, err := conn.ExpectData(testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, 1*time.Second); err != nil {
t.Fatal(err)
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Run("Send", func(t *testing.T) {
conn.Send(testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
if got, want := string(dut.Recv(boundFD, int32(len(tc.payload)), 0)), string(tc.payload); got != want {
t.Fatalf("received payload does not match sent payload got: %s, want: %s", got, want)
}
})
t.Run("Recv", func(t *testing.T) {
conn.Drain()
if got, want := int(dut.SendTo(boundFD, tc.payload, 0, localAddr)), len(tc.payload); got != want {
t.Fatalf("short write got: %d, want: %d", got, want)
}
if _, err := conn.ExpectData(testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, time.Second); err != nil {
t.Fatal(err)
}
})
})
}
})
}