package ike import ( "context" "encoding/binary" "errors" "net" "testing" "time" ) type deadlineError struct{} func (deadlineError) Error() string { return "deadline" } func (deadlineError) Timeout() bool { return true } func (deadlineError) Temporary() bool { return true } func TestRoundTripDatagramWaitsBeyondFirst500Milliseconds(t *testing.T) { available := make(chan struct{}) go func() { time.Sleep(700 * time.Millisecond) close(available) }() writes := 0 started := time.Now() response, err := roundTripDatagram( context.Background(), 2*time.Second, func([]byte) error { writes++ return nil }, func(buffer []byte, deadline time.Time) (int, error) { select { case <-available: copy(buffer, []byte("response")) return len("response"), nil case <-time.After(time.Until(deadline)): return 0, deadlineError{} } }, []byte("request"), ) if err != nil { t.Fatalf("roundTripDatagram() error = %v", err) } if string(response) != "response" || writes < 2 { t.Fatalf("response=%q writes=%d", response, writes) } if elapsed := time.Since(started); elapsed < 650*time.Millisecond { t.Fatalf("round trip returned too early after %v", elapsed) } } func TestRoundTripDatagramHonorsTotalTimeout(t *testing.T) { started := time.Now() _, err := roundTripDatagram( context.Background(), 120*time.Millisecond, func([]byte) error { return nil }, func(_ []byte, deadline time.Time) (int, error) { time.Sleep(time.Until(deadline)) return 0, deadlineError{} }, []byte("request"), ) if err == nil { t.Fatal("roundTripDatagram() accepted a missing response") } elapsed := time.Since(started) if elapsed < 100*time.Millisecond || elapsed > 400*time.Millisecond { t.Fatalf("total timeout elapsed = %v, want approximately 120ms", elapsed) } } func TestSOCKS5UDPDatagramRoundTrip(t *testing.T) { remote := &net.UDPAddr{IP: net.IPv4(203, 0, 113, 7), Port: 4500} encoded, err := marshalSOCKS5Datagram(remote, []byte{1, 2, 3, 4}) if err != nil { t.Fatal(err) } payload, decoded, err := parseSOCKS5Datagram(encoded) if err != nil { t.Fatal(err) } if !decoded.IP.Equal(remote.IP) || decoded.Port != remote.Port || string(payload) != string([]byte{1, 2, 3, 4}) { t.Fatalf("decoded SOCKS datagram = %v %v %x", decoded, remote, payload) } fragmented := append([]byte(nil), encoded...) fragmented[2] = 1 if _, _, err := parseSOCKS5Datagram(fragmented); err == nil { t.Fatal("fragmented SOCKS5 UDP datagram was accepted") } } func TestSOCKS5UDPAssociateDomainReplyIsResolved(t *testing.T) { client, server := net.Pipe() defer client.Close() defer server.Close() go func() { reply := []byte{5, 0, 0, 3, byte(len("localhost"))} reply = append(reply, "localhost"...) var port [2]byte binary.BigEndian.PutUint16(port[:], 7897) reply = append(reply, port[:]...) _, _ = server.Write(reply) }() address, err := readSOCKS5Reply(context.Background(), client, net.DefaultResolver) if err != nil { t.Fatalf("readSOCKS5Reply() error = %v", err) } if address.IP == nil || address.Port != 7897 { t.Fatalf("resolved relay = %v", address) } } func TestSOCKS5InitialExchangeFallsBackAcrossResolvedEPDGAddresses(t *testing.T) { relay, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) if err != nil { t.Fatal(err) } defer relay.Close() connection, err := net.DialUDP("udp", nil, relay.LocalAddr().(*net.UDPAddr)) if err != nil { t.Fatal(err) } first := &net.UDPAddr{IP: net.IPv4(192, 0, 2, 10), Port: 500} second := &net.UDPAddr{IP: net.IPv4(192, 0, 2, 20), Port: 500} transport := &socks5UDP{ config: transportConfig{Timeout: 80 * time.Millisecond}, udp: connection, remote: cloneUDPAddr(first), remotes: cloneUDPAddrs([]*net.UDPAddr{first, second}), } defer transport.Close() requestHeader := ikeHeader{ InitiatorSPI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, Exchange: exchangeIKEInit, Flags: flagInitiator, } request := requestHeader.marshal([]byte("request")) response := ikeHeader{ InitiatorSPI: requestHeader.InitiatorSPI, ResponderSPI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, Exchange: exchangeIKEInit, Flags: flagResponse, }.marshal([]byte("response")) serverDone := make(chan error, 1) go func() { buffer := make([]byte, 2048) for { n, peer, readErr := relay.ReadFromUDP(buffer) if readErr != nil { serverDone <- readErr return } _, destination, parseErr := parseSOCKS5Datagram(buffer[:n]) if parseErr != nil { serverDone <- parseErr return } if !destination.IP.Equal(second.IP) { continue } wire, marshalErr := marshalSOCKS5Datagram(second, response) if marshalErr == nil { _, marshalErr = relay.WriteToUDP(wire, peer) } serverDone <- marshalErr return } }() got, err := transport.RoundTrip(context.Background(), request) if err != nil { t.Fatalf("RoundTrip() error = %v", err) } if string(got) != string(response) { t.Fatalf("RoundTrip() response = %x", got) } if !transport.RemoteAddr().IP.Equal(second.IP) { t.Fatalf("selected ePDG = %v, want %v", transport.RemoteAddr(), second) } if err := <-serverDone; err != nil { t.Fatalf("relay: %v", err) } } func TestSOCKS5RoundTripSkipsStaleAndESPDatagrams(t *testing.T) { relay, err := net.ListenUDP( "udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}, ) if err != nil { t.Fatal(err) } defer relay.Close() connection, err := net.DialUDP( "udp", nil, relay.LocalAddr().(*net.UDPAddr), ) if err != nil { t.Fatal(err) } remote := &net.UDPAddr{IP: net.IPv4(203, 0, 113, 7), Port: 4500} transport := &socks5UDP{ config: transportConfig{Timeout: time.Second}, udp: connection, remote: cloneUDPAddr(remote), floated: true, } defer transport.Close() requestHeader := ikeHeader{ InitiatorSPI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ResponderSPI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, Exchange: exchangeIKEAuth, Flags: flagInitiator, MessageID: 3, } request := requestHeader.marshal([]byte("request")) validResponse := ikeHeader{ InitiatorSPI: requestHeader.InitiatorSPI, ResponderSPI: requestHeader.ResponderSPI, Exchange: requestHeader.Exchange, Flags: flagResponse, MessageID: requestHeader.MessageID, }.marshal([]byte("response")) serverDone := make(chan error, 1) go func() { buffer := make([]byte, 2048) _, peer, err := relay.ReadFromUDP(buffer) if err != nil { serverDone <- err return } stale, err := marshalSOCKS5Datagram( &net.UDPAddr{IP: remote.IP, Port: 500}, append([]byte{0, 0, 0, 0}, []byte("stale")...), ) if err != nil { serverDone <- err return } if _, err := relay.WriteToUDP(stale, peer); err != nil { serverDone <- err return } esp, err := marshalSOCKS5Datagram( remote, []byte{1, 2, 3, 4, 5, 6, 7, 8}, ) if err != nil { serverDone <- err return } if _, err := relay.WriteToUDP(esp, peer); err != nil { serverDone <- err return } staleIKE := ikeHeader{ InitiatorSPI: requestHeader.InitiatorSPI, ResponderSPI: requestHeader.ResponderSPI, Exchange: requestHeader.Exchange, Flags: flagResponse, MessageID: requestHeader.MessageID - 1, }.marshal([]byte("stale IKE")) staleIKE, err = marshalSOCKS5Datagram( remote, append([]byte{0, 0, 0, 0}, staleIKE...), ) if err != nil { serverDone <- err return } if _, err := relay.WriteToUDP(staleIKE, peer); err != nil { serverDone <- err return } valid, err := marshalSOCKS5Datagram( remote, append([]byte{0, 0, 0, 0}, validResponse...), ) if err == nil { _, err = relay.WriteToUDP(valid, peer) } serverDone <- err }() response, err := transport.RoundTrip( context.Background(), request, ) if err != nil { t.Fatalf("RoundTrip() error = %v", err) } if string(response) != string(validResponse) { t.Fatalf("RoundTrip() response = %x", response) } if err := <-serverDone; err != nil { t.Fatalf("relay: %v", err) } } func TestSessionReadDoesNotBlockIndependentWrite(t *testing.T) { server, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}) if err != nil { t.Fatal(err) } defer server.Close() connection, err := net.DialUDP("udp", nil, server.LocalAddr().(*net.UDPAddr)) if err != nil { t.Fatal(err) } transport := &directUDP{ config: transportConfig{Timeout: time.Second}, conn: connection, remote: cloneUDPAddr(server.LocalAddr().(*net.UDPAddr)), floated: true, } defer transport.Close() readDone := make(chan error, 1) go func() { buffer := make([]byte, 64) _, _, err := transport.ReceiveSessionPacket(context.Background(), buffer) readDone <- err }() time.Sleep(30 * time.Millisecond) started := time.Now() if err := transport.SendSessionPacket(context.Background(), []byte{1, 2, 3, 4, 5, 6, 7, 8}, false); err != nil { t.Fatalf("SendSessionPacket() error = %v", err) } if elapsed := time.Since(started); elapsed > 200*time.Millisecond { t.Fatalf("session write blocked behind reader for %v", elapsed) } buffer := make([]byte, 64) _ = server.SetReadDeadline(time.Now().Add(time.Second)) n, _, err := server.ReadFromUDP(buffer) if err != nil { t.Fatal(err) } if n != 8 { t.Fatalf("server received %d bytes, want 8", n) } _ = transport.Close() select { case err := <-readDone: if err == nil || (!errors.Is(err, net.ErrClosed) && !isNetworkClose(err)) { t.Fatalf("reader close error = %v", err) } case <-time.After(2 * time.Second): t.Fatal("session reader did not wake after Close") } } func isNetworkClose(err error) bool { var networkError net.Error return errors.As(err, &networkError) }