mirror of
https://github.com/soypat/lneto.git
synced 2026-07-26 10:38:47 +00:00
a2970b923d
* add ipv6 to xnet.StackAsync * dns improvements * improve DNS workings of StackAsync * add tentative ICMPv6 * work on prefixes and fix some small bugs, plan UDP/TCP6 * fix bugs in StackAsync and ipv4.Prefix.Contains * update arpsubtable * completely remove legacy internet.StackIP for StackIPv4/v6 * ipv4/ipv6 tcp/udp * add TCP6/UDP6 dialing APIs * add xnet.Stack6 interface * more ipv6 integration into StackAsync; various tweaks to lneto and documentation+TODOs * add stack6 tests * replace netip.Prefix with ipv4.Prefix where it makes sense
619 lines
17 KiB
Go
619 lines
17 KiB
Go
package xnet
|
||
|
||
import (
|
||
"bytes"
|
||
"testing"
|
||
|
||
"github.com/soypat/lneto/tcp"
|
||
"github.com/soypat/lneto/udp"
|
||
)
|
||
|
||
const (
|
||
ipv6HeaderSize = 40
|
||
mtu6Test = 1500
|
||
maxFrame6 = ipv6HeaderSize + mtu6Test
|
||
)
|
||
|
||
func stack6PairConfigs(seed int64, maxports, icmpQueue uint16) (cfg1, cfg2 StackConfig) {
|
||
var (
|
||
testAddr6A = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} // 2001:db8::1
|
||
testAddr6B = [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2} // 2001:db8::2
|
||
testMAC6A = [6]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x01}
|
||
testMAC6B = [6]byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0x02}
|
||
)
|
||
cfg1 = StackConfig{
|
||
Hostname: "test-s6-1",
|
||
RandSeed: seed,
|
||
StaticAddress6: testAddr6A,
|
||
HardwareAddress: testMAC6A,
|
||
MTU: mtu6Test,
|
||
MaxActiveUDPPorts: maxports,
|
||
MaxActiveTCPPorts: maxports,
|
||
ICMPQueueLimit: int(icmpQueue),
|
||
}
|
||
cfg2 = StackConfig{
|
||
Hostname: "test-s6-2",
|
||
RandSeed: ^seed,
|
||
StaticAddress6: testAddr6B,
|
||
HardwareAddress: testMAC6B,
|
||
MTU: mtu6Test,
|
||
MaxActiveUDPPorts: maxports,
|
||
MaxActiveTCPPorts: maxports,
|
||
ICMPQueueLimit: int(icmpQueue),
|
||
}
|
||
return cfg1, cfg2
|
||
}
|
||
|
||
// newStack6Pair creates two stack6 instances with distinct IPv6 addresses and MACs.
|
||
// ICMPQueueLimit is zero so NDP is disabled; DialUDP6/DialTCP6 skip MAC filtering.
|
||
func newStack6Pair(t testing.TB, seed int64, maxports, icmpQueue uint16) (s1, s2 Stack6) {
|
||
t.Helper()
|
||
cfg1, cfg2 := stack6PairConfigs(seed, maxports, icmpQueue)
|
||
s1 = DefaultStack6()
|
||
s2 = DefaultStack6()
|
||
if err := s1.Reset6(&cfg1); err != nil {
|
||
t.Fatal("s1 Reset6:", err)
|
||
}
|
||
if err := s2.Reset6(&cfg2); err != nil {
|
||
t.Fatal("s2 Reset6:", err)
|
||
}
|
||
return s1, s2
|
||
}
|
||
|
||
// newUDPConn6 allocates and configures a udp.Conn for use with stack6.
|
||
func newUDPConn6(t testing.TB) *udp.Conn {
|
||
t.Helper()
|
||
const bufSize = 2048
|
||
conn := new(udp.Conn)
|
||
if err := conn.Configure(udp.ConnConfig{
|
||
RxBuf: make([]byte, bufSize),
|
||
TxBuf: make([]byte, bufSize),
|
||
RxQueueSize: 4,
|
||
TxQueueSize: 4,
|
||
}); err != nil {
|
||
t.Fatal("UDP Configure:", err)
|
||
}
|
||
return conn
|
||
}
|
||
|
||
// newTCPConn6 allocates and configures a tcp.Conn for use with stack6.
|
||
func newTCPConn6(t testing.TB) *tcp.Conn {
|
||
t.Helper()
|
||
const bufSize = 2048
|
||
conn := new(tcp.Conn)
|
||
if err := conn.Configure(tcp.ConnConfig{
|
||
RxBuf: make([]byte, bufSize),
|
||
TxBuf: make([]byte, bufSize),
|
||
TxPacketQueueSize: 4,
|
||
}); err != nil {
|
||
t.Fatal("TCP Configure:", err)
|
||
}
|
||
return conn
|
||
}
|
||
|
||
// exchangeIPv6Once encapsulates one IPv6 frame from src and delivers it to dst.
|
||
// Returns the number of bytes written (0 if src had nothing to send).
|
||
func exchangeIPv6Once(t testing.TB, src, dst Stack6, buf []byte) int {
|
||
t.Helper()
|
||
n, err := src.EgressIPv6(buf)
|
||
if err != nil {
|
||
t.Error("EgressIPv6:", err)
|
||
return 0
|
||
}
|
||
if n == 0 {
|
||
return 0
|
||
}
|
||
if err := dst.IngressIPv6(buf[:n]); err != nil {
|
||
t.Error("IngressIPv6:", err)
|
||
}
|
||
return n
|
||
}
|
||
|
||
// listenTCP6 opens a passive TCP connection and registers it with a stack6 directly,
|
||
// mirroring what StackAsync.ListenTCP4 does for IPv4.
|
||
func listenTCP6(t testing.TB, s Stack6, conn *tcp.Conn, localPort uint16, iss tcp.Value) {
|
||
t.Helper()
|
||
if err := conn.OpenListen(localPort, iss); err != nil {
|
||
t.Fatal("OpenListen:", err)
|
||
}
|
||
if err := s.(*stack6).tcps6.RegisterMACFiltered(conn, nil); err != nil {
|
||
conn.Abort()
|
||
t.Fatal("RegisterMACFiltered:", err)
|
||
}
|
||
}
|
||
|
||
// ===== Tests =====
|
||
|
||
func TestStack6Reset(t *testing.T) {
|
||
s := DefaultStack6()
|
||
testMAC6A := [6]byte{1, 2, 3}
|
||
testAddr6A := [16]byte{0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}
|
||
err := s.Reset6(&StackConfig{
|
||
Hostname: "reset-test-1",
|
||
RandSeed: 42,
|
||
StaticAddress6: testAddr6A,
|
||
HardwareAddress: testMAC6A,
|
||
MTU: mtu6Test,
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if got := s.Addr6(); got != testAddr6A {
|
||
t.Errorf("Addr6 = %v, want %v", got, testAddr6A)
|
||
}
|
||
testAddr2 := testAddr6A
|
||
testAddr2[15] = 2
|
||
// SetAddr6 must update the returned address.
|
||
s.SetAddr6(testAddr2)
|
||
if got := s.Addr6(); got != testAddr2 {
|
||
t.Errorf("after SetAddr6: got %v, want %v", got, testAddr2)
|
||
}
|
||
}
|
||
|
||
func TestStack6Reset_ICMPConfigured(t *testing.T) {
|
||
s := DefaultStack6()
|
||
err := s.Reset6(&StackConfig{
|
||
Hostname: "icmp-cfg-1",
|
||
RandSeed: 1337,
|
||
MTU: mtu6Test,
|
||
ICMPQueueLimit: 4,
|
||
})
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// EnableICMP6 should succeed since the client was configured.
|
||
if err := s.EnableICMP6(true); err != nil {
|
||
t.Fatal("EnableICMP6:", err)
|
||
}
|
||
// Disable should always succeed.
|
||
if err := s.EnableICMP6(false); err != nil {
|
||
t.Fatal("EnableICMP6(false):", err)
|
||
}
|
||
}
|
||
|
||
// TestStack6UDP_DataExchange sends a datagram from stack A to stack B and reads it back.
|
||
// Both stacks are configured without ICMP so NDP/MAC-filtering is bypassed.
|
||
func TestStack6UDP_DataExchange(t *testing.T) {
|
||
const (
|
||
rngseed = 100
|
||
portA = 5001
|
||
portB = 5002
|
||
nports = 2
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngseed, nports, 0)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
connA := newUDPConn6(t)
|
||
connB := newUDPConn6(t)
|
||
|
||
// Open A -> B direction.
|
||
if err := s1.DialUDP6(connA, portA, s2.Addr6(), portB); err != nil {
|
||
t.Fatal("DialUDP6 A:", err)
|
||
}
|
||
// Open B -> A direction so B's stack accepts datagrams from A.
|
||
if err := s2.DialUDP6(connB, portB, s1.Addr6(), portA); err != nil {
|
||
t.Fatal("DialUDP6 B:", err)
|
||
}
|
||
|
||
want := []byte("hello ipv6 udp")
|
||
if _, err := connA.Write(want); err != nil {
|
||
t.Fatal("Write:", err)
|
||
}
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected packet from A to B")
|
||
}
|
||
|
||
var rbuf [256]byte
|
||
n, err := connB.Read(rbuf[:])
|
||
if err != nil {
|
||
t.Fatal("Read:", err)
|
||
}
|
||
if !bytes.Equal(rbuf[:n], want) {
|
||
t.Errorf("got %q, want %q", rbuf[:n], want)
|
||
}
|
||
}
|
||
|
||
// TestStack6UDP_BidirectionalExchange verifies that both sides can send and receive.
|
||
func TestStack6UDP_BidirectionalExchange(t *testing.T) {
|
||
const (
|
||
rngseed = 100
|
||
nports = 1
|
||
portA = 6001
|
||
portB = 6002
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngseed, nports, 0)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
connA := newUDPConn6(t)
|
||
connB := newUDPConn6(t)
|
||
|
||
if err := s1.DialUDP6(connA, portA, s2.Addr6(), portB); err != nil {
|
||
t.Fatal("DialUDP6 A:", err)
|
||
}
|
||
if err := s2.DialUDP6(connB, portB, s1.Addr6(), portA); err != nil {
|
||
t.Fatal("DialUDP6 B:", err)
|
||
}
|
||
|
||
// A -> B
|
||
msgAtoB := []byte("A->B")
|
||
if _, err := connA.Write(msgAtoB); err != nil {
|
||
t.Fatal("Write A->B:", err)
|
||
}
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected packet A->B")
|
||
}
|
||
var rbuf [256]byte
|
||
n, err := connB.Read(rbuf[:])
|
||
if err != nil {
|
||
t.Fatal("Read B:", err)
|
||
}
|
||
if !bytes.Equal(rbuf[:n], msgAtoB) {
|
||
t.Errorf("B received %q, want %q", rbuf[:n], msgAtoB)
|
||
}
|
||
|
||
// B -> A
|
||
msgBtoA := []byte("B->A reply")
|
||
if _, err := connB.Write(msgBtoA); err != nil {
|
||
t.Fatal("Write B->A:", err)
|
||
}
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatal("expected packet B->A")
|
||
}
|
||
n, err = connA.Read(rbuf[:])
|
||
if err != nil {
|
||
t.Fatal("Read A:", err)
|
||
}
|
||
if !bytes.Equal(rbuf[:n], msgBtoA) {
|
||
t.Errorf("A received %q, want %q", rbuf[:n], msgBtoA)
|
||
}
|
||
}
|
||
|
||
// TestStack6ICMPv6_PingEcho verifies a full ICMPv6 echo request/reply exchange.
|
||
func TestStack6ICMPv6_PingEcho(t *testing.T) {
|
||
const (
|
||
rngSeed = 42
|
||
nports = 1
|
||
icmpQueue = 2
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngSeed, nports, icmpQueue)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
if err := s1.EnableICMP6(true); err != nil {
|
||
t.Fatal("s1 EnableICMP6:", err)
|
||
}
|
||
if err := s2.EnableICMP6(true); err != nil {
|
||
t.Fatal("s2 EnableICMP6:", err)
|
||
}
|
||
|
||
// Verify that no packets are pending before the ping.
|
||
if n, _ := s1.EgressIPv6(buf); n != 0 {
|
||
t.Fatal("unexpected egress from s1 before ping")
|
||
}
|
||
if n, _ := s2.EgressIPv6(buf); n != 0 {
|
||
t.Fatal("unexpected egress from s2 before ping")
|
||
}
|
||
|
||
// Start the ping from s1 to s2.
|
||
pattern := []byte("ping6test")
|
||
key, err := s1.(*stack6).icmp6.PingStart(s2.Addr6(), pattern, 32)
|
||
if err != nil {
|
||
t.Fatal("PingStart:", err)
|
||
}
|
||
|
||
// s1 sends echo request to s2.
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected ICMPv6 echo request from s1")
|
||
}
|
||
|
||
// s2 sends echo reply back to s1.
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatal("expected ICMPv6 echo reply from s2")
|
||
}
|
||
|
||
// No further packets should be needed.
|
||
if n, _ := s1.EgressIPv6(buf); n != 0 {
|
||
t.Error("unexpected extra egress from s1 after ping")
|
||
}
|
||
if n, _ := s2.EgressIPv6(buf); n != 0 {
|
||
t.Error("unexpected extra egress from s2 after ping")
|
||
}
|
||
|
||
completed, ok := s1.(*stack6).icmp6.PingPop(key)
|
||
if !ok {
|
||
t.Fatal("ping key not found after exchange")
|
||
}
|
||
if !completed {
|
||
t.Fatal("expected ping to be completed")
|
||
}
|
||
}
|
||
|
||
// TestStack6ICMPv6_MultiPing verifies multiple sequential pings work.
|
||
func TestStack6ICMPv6_MultiPing(t *testing.T) {
|
||
const (
|
||
rngseed = 193213
|
||
icmpqueue = 2
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngseed, 0, icmpqueue)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
if err := s1.EnableICMP6(true); err != nil {
|
||
t.Fatal("s1 EnableICMP6:", err)
|
||
}
|
||
if err := s2.EnableICMP6(true); err != nil {
|
||
t.Fatal("s2 EnableICMP6:", err)
|
||
}
|
||
|
||
for i, pattern := range [][]byte{
|
||
[]byte("first"),
|
||
[]byte("second"),
|
||
[]byte("third"),
|
||
} {
|
||
key, err := s1.(*stack6).icmp6.PingStart(s2.Addr6(), pattern, uint16(len(pattern)+4))
|
||
if err != nil {
|
||
t.Fatalf("PingStart [%d]: %v", i, err)
|
||
}
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatalf("[%d] expected echo request from s1", i)
|
||
}
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatalf("[%d] expected echo reply from s2", i)
|
||
}
|
||
completed, ok := s1.(*stack6).icmp6.PingPop(key)
|
||
if !ok {
|
||
t.Fatalf("[%d] ping key not found", i)
|
||
}
|
||
if !completed {
|
||
t.Fatalf("[%d] ping not completed", i)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestStack6TCP_Handshake verifies that a TCP connection can be established over stack6.
|
||
func TestStack6TCP_Handshake(t *testing.T) {
|
||
const (
|
||
rngseed = 213213
|
||
svPort = 8080
|
||
clPort = 12345
|
||
nports = 1
|
||
icmpqueue = 2
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngseed, nports, icmpqueue)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
svConn := newTCPConn6(t)
|
||
clConn := newTCPConn6(t)
|
||
|
||
// Server listens on s2.
|
||
listenTCP6(t, s2, svConn, svPort, 200)
|
||
|
||
// Client dials from s1 to s2.
|
||
if err := s1.DialTCP6(clConn, clPort, s2.Addr6(), svPort, 100); err != nil {
|
||
t.Fatal("DialTCP6:", err)
|
||
}
|
||
|
||
// SYN: client -> server.
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected SYN from client")
|
||
}
|
||
if clConn.State() != tcp.StateSynSent {
|
||
t.Errorf("client state = %s, want SYN_SENT", clConn.State())
|
||
}
|
||
if svConn.State() != tcp.StateSynRcvd {
|
||
t.Errorf("server state = %s, want SYN_RCVD", svConn.State())
|
||
}
|
||
|
||
// SYNACK: server -> client.
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatal("expected SYNACK from server")
|
||
}
|
||
if clConn.State() != tcp.StateEstablished {
|
||
t.Errorf("client state = %s, want ESTABLISHED", clConn.State())
|
||
}
|
||
|
||
// ACK: client -> server.
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected ACK from client")
|
||
}
|
||
if svConn.State() != tcp.StateEstablished {
|
||
t.Errorf("server state = %s, want ESTABLISHED", svConn.State())
|
||
}
|
||
}
|
||
|
||
// TestStack6TCP_DataExchange establishes a TCP connection and transfers data.
|
||
func TestStack6TCP_DataExchange(t *testing.T) {
|
||
const (
|
||
rngseed = 400
|
||
svPort = 9090
|
||
clPort = 11111
|
||
nports = 1
|
||
icmpqueue = 1
|
||
)
|
||
s1, s2 := newStack6Pair(t, rngseed, nports, icmpqueue)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
svConn := newTCPConn6(t)
|
||
clConn := newTCPConn6(t)
|
||
|
||
listenTCP6(t, s2, svConn, svPort, 300)
|
||
if err := s1.DialTCP6(clConn, clPort, s2.Addr6(), svPort, 200); err != nil {
|
||
t.Fatal("DialTCP6:", err)
|
||
}
|
||
|
||
// Three-way handshake.
|
||
tcp6Handshake(t, s1, s2, buf)
|
||
|
||
// Send data from client to server.
|
||
payload := []byte("hello over tcp6")
|
||
if _, err := clConn.Write(payload); err != nil {
|
||
t.Fatal("Write:", err)
|
||
}
|
||
|
||
// PSH+ACK: client -> server.
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected data packet from client")
|
||
}
|
||
// ACK: server -> client.
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatal("expected ACK from server")
|
||
}
|
||
// Drain any extra ACKs.
|
||
exchangeIPv6Once(t, s1, s2, buf)
|
||
exchangeIPv6Once(t, s2, s1, buf)
|
||
|
||
var rbuf [256]byte
|
||
n, err := svConn.Read(rbuf[:])
|
||
if err != nil {
|
||
t.Fatal("svConn.Read:", err)
|
||
}
|
||
if !bytes.Equal(rbuf[:n], payload) {
|
||
t.Errorf("server received %q, want %q", rbuf[:n], payload)
|
||
}
|
||
}
|
||
|
||
// TestStack6_NDP_DialUDP verifies that DialUDP6 with ICMP enabled triggers NDP
|
||
// resolution and that seeding the NDP cache allows the connection to proceed.
|
||
func TestStack6_NDP_DialUDP(t *testing.T) {
|
||
const (
|
||
portA = 7001
|
||
portB = 7002
|
||
rngseed = 132131
|
||
nports = 1
|
||
icmpqueue = 4
|
||
)
|
||
cfg1, cfg2 := stack6PairConfigs(rngseed, nports, icmpqueue)
|
||
s1, s2 := newStack6Pair(t, rngseed, nports, icmpqueue)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
if err := s1.EnableICMP6(true); err != nil {
|
||
t.Fatal("s1 EnableICMP6:", err)
|
||
}
|
||
if err := s2.EnableICMP6(true); err != nil {
|
||
t.Fatal("s2 EnableICMP6:", err)
|
||
}
|
||
|
||
// Seed s2's NDP cache so it knows s1's MAC (needed for NA reply).
|
||
if err := s2.(*stack6).icmp6.NDPCacheSeed(cfg1.StaticAddress6, cfg1.HardwareAddress); err != nil {
|
||
t.Fatal("NDPCacheSeed s2:", err)
|
||
}
|
||
|
||
connA := newUDPConn6(t)
|
||
connB := newUDPConn6(t)
|
||
|
||
// DialUDP6 on s1 queues an NS since s2's MAC is not yet in s1's NDP cache.
|
||
if err := s1.DialUDP6(connA, portA, cfg2.StaticAddress6, portB); err != nil {
|
||
t.Fatal("DialUDP6 A:", err)
|
||
}
|
||
// s2 already has s1's address seeded so DialUDP6 resolves immediately.
|
||
if err := s2.DialUDP6(connB, portB, cfg1.StaticAddress6, portA); err != nil {
|
||
t.Fatal("DialUDP6 B:", err)
|
||
}
|
||
|
||
// NDP exchange: s1 sends Neighbor Solicitation, s2 replies with Neighbor Advertisement.
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected NDP Neighbor Solicitation from s1")
|
||
}
|
||
if n := exchangeIPv6Once(t, s2, s1, buf); n == 0 {
|
||
t.Fatal("expected NDP Neighbor Advertisement from s2")
|
||
}
|
||
|
||
// After NDP resolved, connA's macBuf is patched; verify the cache now has s2's MAC.
|
||
mac, err := s1.(*stack6).icmp6.NDPCacheLookup(cfg2.StaticAddress6)
|
||
if err != nil {
|
||
t.Fatal("NDPCacheLookup after NDP exchange:", err)
|
||
}
|
||
if mac != cfg2.HardwareAddress {
|
||
t.Errorf("NDP resolved MAC = %v, want %v", mac, cfg2.HardwareAddress)
|
||
}
|
||
|
||
// Now that NDP is resolved, data should flow.
|
||
want := []byte("ndp resolved udp")
|
||
if _, err := connA.Write(want); err != nil {
|
||
t.Fatal("Write:", err)
|
||
}
|
||
if n := exchangeIPv6Once(t, s1, s2, buf); n == 0 {
|
||
t.Fatal("expected UDP packet after NDP resolution")
|
||
}
|
||
var rbuf [256]byte
|
||
n, err := connB.Read(rbuf[:])
|
||
if err != nil {
|
||
t.Fatal("Read:", err)
|
||
}
|
||
if !bytes.Equal(rbuf[:n], want) {
|
||
t.Errorf("got %q, want %q", rbuf[:n], want)
|
||
}
|
||
}
|
||
|
||
// tcp6Handshake performs the SYN/SYNACK/ACK exchange between two stacks.
|
||
func tcp6Handshake(t testing.TB, client, server Stack6, buf []byte) {
|
||
t.Helper()
|
||
if n := exchangeIPv6Once(t, client, server, buf); n == 0 {
|
||
t.Fatal("handshake: expected SYN")
|
||
}
|
||
if n := exchangeIPv6Once(t, server, client, buf); n == 0 {
|
||
t.Fatal("handshake: expected SYNACK")
|
||
}
|
||
if n := exchangeIPv6Once(t, client, server, buf); n == 0 {
|
||
t.Fatal("handshake: expected ACK")
|
||
}
|
||
}
|
||
|
||
// TestStack6_EgressNoData checks that EgressIPv6 returns 0 when there is nothing to send.
|
||
func TestStack6_EgressNoData(t *testing.T) {
|
||
s, _ := newStack6Pair(t, 13213, 2, 2)
|
||
buf := make([]byte, maxFrame6)
|
||
n, err := s.EgressIPv6(buf)
|
||
if err != nil {
|
||
t.Errorf("EgressIPv6 with no traffic: %v", err)
|
||
}
|
||
if n != 0 {
|
||
t.Errorf("expected 0 bytes, got %d", n)
|
||
}
|
||
}
|
||
|
||
// TestStack6_IngressDropsWrongDst checks that packets destined for a different address are dropped.
|
||
func TestStack6_IngressDropsWrongDst(t *testing.T) {
|
||
s1, s2 := newStack6Pair(t, 2133213, 1, 1)
|
||
buf := make([]byte, maxFrame6)
|
||
|
||
connA := newUDPConn6(t)
|
||
connB := newUDPConn6(t)
|
||
const (
|
||
portA = 4001
|
||
portB = 4002
|
||
)
|
||
if err := s1.DialUDP6(connA, portA, s2.Addr6(), portB); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := s2.DialUDP6(connB, portB, s1.Addr6(), portA); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// Write and encapsulate from s1 (destination = testAddr6B).
|
||
if _, err := connA.Write([]byte("drop me")); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
n, err := s1.EgressIPv6(buf)
|
||
if err != nil || n == 0 {
|
||
t.Fatalf("expected encapsulated packet: n=%d err=%v", n, err)
|
||
}
|
||
|
||
// Feed to s1 itself (wrong destination) – should be dropped (ErrPacketDrop or similar).
|
||
wrongDst := s1
|
||
if err := wrongDst.IngressIPv6(buf[:n]); err == nil {
|
||
t.Error("expected error when delivering packet to wrong destination stack, got nil")
|
||
}
|
||
|
||
// Deliver correctly to s2 – should succeed.
|
||
if err := s2.IngressIPv6(buf[:n]); err != nil {
|
||
t.Errorf("correct delivery to s2 failed: %v", err)
|
||
}
|
||
|
||
// s2 should have received the datagram.
|
||
var rbuf [256]byte
|
||
rn, err := connB.Read(rbuf[:])
|
||
if err != nil || rn == 0 {
|
||
t.Errorf("expected s2 to have received data: n=%d err=%v", rn, err)
|
||
}
|
||
|
||
}
|