wgengine/tstun: add tests and benchmarks (#436)
Signed-off-by: Dmytro Shynkevych <dmytro@tailscale.com>main
parent
6f590f5b52
commit
02231e968e
@ -0,0 +1,355 @@ |
||||
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package tstun |
||||
|
||||
import ( |
||||
"bytes" |
||||
"testing" |
||||
|
||||
"github.com/tailscale/wireguard-go/tun/tuntest" |
||||
"tailscale.com/types/logger" |
||||
"tailscale.com/wgengine/filter" |
||||
"tailscale.com/wgengine/packet" |
||||
) |
||||
|
||||
func udp(src, dst packet.IP, sport, dport uint16) []byte { |
||||
header := &packet.UDPHeader{ |
||||
IPHeader: packet.IPHeader{ |
||||
SrcIP: src, |
||||
DstIP: dst, |
||||
IPID: 0, |
||||
}, |
||||
SrcPort: sport, |
||||
DstPort: dport, |
||||
} |
||||
return packet.Generate(header, []byte("udp_payload")) |
||||
} |
||||
|
||||
func nets(ips []packet.IP) []filter.Net { |
||||
out := make([]filter.Net, 0, len(ips)) |
||||
for _, ip := range ips { |
||||
out = append(out, filter.Net{ip, filter.Netmask(32)}) |
||||
} |
||||
return out |
||||
} |
||||
|
||||
func ippr(ip packet.IP, start, end uint16) []filter.NetPortRange { |
||||
return []filter.NetPortRange{ |
||||
filter.NetPortRange{filter.Net{ip, filter.Netmask(32)}, filter.PortRange{start, end}}, |
||||
} |
||||
} |
||||
|
||||
func setfilter(logf logger.Logf, tun *TUN) { |
||||
matches := filter.Matches{ |
||||
{Srcs: nets([]packet.IP{0x05060708}), Dsts: ippr(0x01020304, 89, 90)}, |
||||
{Srcs: nets([]packet.IP{0x01020304}), Dsts: ippr(0x05060708, 98, 98)}, |
||||
} |
||||
localNets := []filter.Net{ |
||||
{packet.IP(0x01020304), filter.Netmask(16)}, |
||||
} |
||||
tun.SetFilter(filter.New(matches, localNets, nil, logf)) |
||||
} |
||||
|
||||
func newChannelTUN(logf logger.Logf, secure bool) (*tuntest.ChannelTUN, *TUN) { |
||||
chtun := tuntest.NewChannelTUN() |
||||
tun := WrapTUN(logf, chtun.TUN()) |
||||
if secure { |
||||
setfilter(logf, tun) |
||||
} else { |
||||
tun.insecure = true |
||||
} |
||||
return chtun, tun |
||||
} |
||||
|
||||
func newFakeTUN(logf logger.Logf, secure bool) (*fakeTUN, *TUN) { |
||||
ftun := NewFakeTUN() |
||||
tun := WrapTUN(logf, ftun) |
||||
if secure { |
||||
setfilter(logf, tun) |
||||
} else { |
||||
tun.insecure = true |
||||
} |
||||
return ftun.(*fakeTUN), tun |
||||
} |
||||
|
||||
func TestReadAndInject(t *testing.T) { |
||||
chtun, tun := newChannelTUN(t.Logf, false) |
||||
defer tun.Close() |
||||
|
||||
const size = 2 // all payloads have this size
|
||||
written := []string{"w0", "w1"} |
||||
injected := []string{"i0", "i1"} |
||||
|
||||
go func() { |
||||
for _, packet := range written { |
||||
payload := []byte(packet) |
||||
chtun.Outbound <- payload |
||||
} |
||||
}() |
||||
|
||||
for _, packet := range injected { |
||||
go func(packet string) { |
||||
payload := []byte(packet) |
||||
err := tun.InjectOutbound(payload) |
||||
if err != nil { |
||||
t.Errorf("%s: error: %v", packet, err) |
||||
} |
||||
}(packet) |
||||
} |
||||
|
||||
var buf [MaxPacketSize]byte |
||||
var seen = make(map[string]bool) |
||||
// We expect the same packets back, in no particular order.
|
||||
for i := 0; i < len(written)+len(injected); i++ { |
||||
n, err := tun.Read(buf[:], 0) |
||||
if err != nil { |
||||
t.Errorf("read %d: error: %v", i, err) |
||||
} |
||||
if n != size { |
||||
t.Errorf("read %d: got size %d; want %d", i, n, size) |
||||
} |
||||
got := string(buf[:n]) |
||||
t.Logf("read %d: got %s", i, got) |
||||
seen[got] = true |
||||
} |
||||
|
||||
for _, packet := range written { |
||||
if !seen[packet] { |
||||
t.Errorf("%s not received", packet) |
||||
} |
||||
} |
||||
for _, packet := range injected { |
||||
if !seen[packet] { |
||||
t.Errorf("%s not received", packet) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestWriteAndInject(t *testing.T) { |
||||
chtun, tun := newChannelTUN(t.Logf, false) |
||||
defer tun.Close() |
||||
|
||||
const size = 2 // all payloads have this size
|
||||
written := []string{"w0", "w1"} |
||||
injected := []string{"i0", "i1"} |
||||
|
||||
go func() { |
||||
for _, packet := range written { |
||||
payload := []byte(packet) |
||||
n, err := tun.Write(payload, 0) |
||||
if err != nil { |
||||
t.Errorf("%s: error: %v", packet, err) |
||||
} |
||||
if n != size { |
||||
t.Errorf("%s: got size %d; want %d", packet, n, size) |
||||
} |
||||
} |
||||
}() |
||||
|
||||
for _, packet := range injected { |
||||
go func(packet string) { |
||||
payload := []byte(packet) |
||||
err := tun.InjectInbound(payload) |
||||
if err != nil { |
||||
t.Errorf("%s: error: %v", packet, err) |
||||
} |
||||
}(packet) |
||||
} |
||||
|
||||
seen := make(map[string]bool) |
||||
// We expect the same packets back, in no particular order.
|
||||
for i := 0; i < len(written)+len(injected); i++ { |
||||
packet := <-chtun.Inbound |
||||
got := string(packet) |
||||
t.Logf("read %d: got %s", i, got) |
||||
seen[got] = true |
||||
} |
||||
|
||||
for _, packet := range written { |
||||
if !seen[packet] { |
||||
t.Errorf("%s not received", packet) |
||||
} |
||||
} |
||||
for _, packet := range injected { |
||||
if !seen[packet] { |
||||
t.Errorf("%s not received", packet) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func TestFilter(t *testing.T) { |
||||
chtun, tun := newChannelTUN(t.Logf, true) |
||||
defer tun.Close() |
||||
|
||||
type direction int |
||||
|
||||
const ( |
||||
in direction = iota |
||||
out |
||||
) |
||||
|
||||
tests := []struct { |
||||
name string |
||||
dir direction |
||||
drop bool |
||||
data []byte |
||||
}{ |
||||
{"junk_in", in, true, []byte("\x45not a valid IPv4 packet")}, |
||||
{"junk_out", out, true, []byte("\x45not a valid IPv4 packet")}, |
||||
{"bad_port_in", in, true, udp(0x05060708, 0x01020304, 22, 22)}, |
||||
{"bad_port_out", out, false, udp(0x01020304, 0x05060708, 22, 22)}, |
||||
{"bad_ip_in", in, true, udp(0x08010101, 0x01020304, 89, 89)}, |
||||
{"bad_ip_out", out, false, udp(0x01020304, 0x08010101, 98, 98)}, |
||||
{"good_packet_in", in, false, udp(0x05060708, 0x01020304, 89, 89)}, |
||||
{"good_packet_out", out, false, udp(0x01020304, 0x05060708, 98, 98)}, |
||||
} |
||||
|
||||
// A reader on the other end of the TUN.
|
||||
go func() { |
||||
var recvbuf []byte |
||||
for { |
||||
select { |
||||
case <-tun.closed: |
||||
return |
||||
case recvbuf = <-chtun.Inbound: |
||||
// continue
|
||||
} |
||||
for _, tt := range tests { |
||||
if tt.drop && bytes.Equal(recvbuf, tt.data) { |
||||
t.Errorf("did not drop %s", tt.name) |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
|
||||
var buf [MaxPacketSize]byte |
||||
for _, tt := range tests { |
||||
t.Run(tt.name, func(t *testing.T) { |
||||
var n int |
||||
var err error |
||||
var filtered bool |
||||
|
||||
if tt.dir == in { |
||||
_, err = tun.Write(tt.data, 0) |
||||
if err == ErrFiltered { |
||||
filtered = true |
||||
err = nil |
||||
} |
||||
} else { |
||||
chtun.Outbound <- tt.data |
||||
n, err = tun.Read(buf[:], 0) |
||||
// In the read direction, errors are fatal, so we return n = 0 instead.
|
||||
filtered = (n == 0) |
||||
} |
||||
|
||||
if err != nil { |
||||
t.Errorf("got err %v; want nil", err) |
||||
} |
||||
|
||||
if filtered { |
||||
if !tt.drop { |
||||
t.Errorf("got drop; want accept") |
||||
} |
||||
} else { |
||||
if tt.drop { |
||||
t.Errorf("got accept; want drop") |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestAllocs(t *testing.T) { |
||||
ftun, tun := newFakeTUN(t.Logf, false) |
||||
defer tun.Close() |
||||
|
||||
go func() { |
||||
var buf []byte |
||||
for { |
||||
select { |
||||
case <-tun.closed: |
||||
return |
||||
case buf = <-ftun.datachan: |
||||
// continue
|
||||
} |
||||
|
||||
select { |
||||
case <-tun.closed: |
||||
return |
||||
case ftun.datachan <- buf: |
||||
// continue
|
||||
} |
||||
} |
||||
}() |
||||
|
||||
buf := []byte{0x00} |
||||
allocs := testing.AllocsPerRun(100, func() { |
||||
_, err := tun.Write(buf, 0) |
||||
if err != nil { |
||||
t.Errorf("write: error: %v", err) |
||||
return |
||||
} |
||||
|
||||
_, err = tun.Read(buf, 0) |
||||
if err != nil { |
||||
t.Errorf("read: error: %v", err) |
||||
return |
||||
} |
||||
|
||||
}) |
||||
|
||||
if allocs > 0 { |
||||
t.Errorf("read allocs = %v; want 0", allocs) |
||||
} |
||||
} |
||||
|
||||
func BenchmarkWrite(b *testing.B) { |
||||
ftun, tun := newFakeTUN(b.Logf, true) |
||||
defer tun.Close() |
||||
|
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-tun.closed: |
||||
return |
||||
case <-ftun.datachan: |
||||
// continue
|
||||
} |
||||
} |
||||
}() |
||||
|
||||
packet := udp(0x05060708, 0x01020304, 89, 89) |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := tun.Write(packet, 0) |
||||
if err != nil { |
||||
b.Errorf("err = %v; want nil", err) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func BenchmarkRead(b *testing.B) { |
||||
ftun, tun := newFakeTUN(b.Logf, true) |
||||
defer tun.Close() |
||||
|
||||
packet := udp(0x05060708, 0x01020304, 89, 89) |
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-tun.closed: |
||||
return |
||||
case ftun.datachan <- packet: |
||||
// continue
|
||||
} |
||||
} |
||||
}() |
||||
|
||||
var buf [128]byte |
||||
for i := 0; i < b.N; i++ { |
||||
_, err := tun.Read(buf[:], 0) |
||||
if err != nil { |
||||
b.Errorf("err = %v; want nil", err) |
||||
} |
||||
} |
||||
} |
||||
Loading…
Reference in new issue