| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| //go:build go1.21 |
| |
| package quic |
| |
| import ( |
| "bytes" |
| "fmt" |
| "net" |
| "net/netip" |
| "runtime" |
| "testing" |
| ) |
| |
| func TestUDPSourceUnspecified(t *testing.T) { |
| // Send datagram with no source address set. |
| runUDPTest(t, func(t *testing.T, test udpTest) { |
| t.Logf("%v", test.dstAddr) |
| data := []byte("source unspecified") |
| if err := test.src.Write(datagram{ |
| b: data, |
| peerAddr: test.dstAddr, |
| }); err != nil { |
| t.Fatalf("Write: %v", err) |
| } |
| got := <-test.dgramc |
| if !bytes.Equal(got.b, data) { |
| t.Errorf("got datagram {%x}, want {%x}", got.b, data) |
| } |
| }) |
| } |
| |
| func TestUDPSourceSpecified(t *testing.T) { |
| // Send datagram with source address set. |
| runUDPTest(t, func(t *testing.T, test udpTest) { |
| data := []byte("source specified") |
| if err := test.src.Write(datagram{ |
| b: data, |
| peerAddr: test.dstAddr, |
| localAddr: test.src.LocalAddr(), |
| }); err != nil { |
| t.Fatalf("Write: %v", err) |
| } |
| got := <-test.dgramc |
| if !bytes.Equal(got.b, data) { |
| t.Errorf("got datagram {%x}, want {%x}", got.b, data) |
| } |
| }) |
| } |
| |
| func TestUDPSourceInvalid(t *testing.T) { |
| // Send datagram with source address set to an address not associated with the connection. |
| if !udpInvalidLocalAddrIsError { |
| t.Skipf("%v: sending from invalid source succeeds", runtime.GOOS) |
| } |
| runUDPTest(t, func(t *testing.T, test udpTest) { |
| var localAddr netip.AddrPort |
| if test.src.LocalAddr().Addr().Is4() { |
| localAddr = netip.MustParseAddrPort("127.0.0.2:1234") |
| } else { |
| localAddr = netip.MustParseAddrPort("[::2]:1234") |
| } |
| data := []byte("source invalid") |
| if err := test.src.Write(datagram{ |
| b: data, |
| peerAddr: test.dstAddr, |
| localAddr: localAddr, |
| }); err == nil { |
| t.Errorf("Write with invalid localAddr succeeded; want error") |
| } |
| }) |
| } |
| |
| func TestUDPECN(t *testing.T) { |
| if !udpECNSupport { |
| t.Skipf("%v: no ECN support", runtime.GOOS) |
| } |
| // Send datagrams with ECN bits set, verify the ECN bits are received. |
| runUDPTest(t, func(t *testing.T, test udpTest) { |
| for _, ecn := range []ecnBits{ecnNotECT, ecnECT1, ecnECT0, ecnCE} { |
| if err := test.src.Write(datagram{ |
| b: []byte{1, 2, 3, 4}, |
| peerAddr: test.dstAddr, |
| ecn: ecn, |
| }); err != nil { |
| t.Fatalf("Write: %v", err) |
| } |
| got := <-test.dgramc |
| if got.ecn != ecn { |
| t.Errorf("sending ECN bits %x, got %x", ecn, got.ecn) |
| } |
| } |
| }) |
| } |
| |
| type udpTest struct { |
| src *netUDPConn |
| dst *netUDPConn |
| dstAddr netip.AddrPort |
| dgramc chan *datagram |
| } |
| |
| // runUDPTest calls f with a pair of UDPConns in a matrix of network variations: |
| // udp, udp4, and udp6, and variations on binding to an unspecified address (0.0.0.0) |
| // or a specified one. |
| func runUDPTest(t *testing.T, f func(t *testing.T, u udpTest)) { |
| for _, test := range []struct { |
| srcNet, srcAddr, dstNet, dstAddr string |
| }{ |
| {"udp4", "127.0.0.1", "udp", ""}, |
| {"udp4", "127.0.0.1", "udp4", ""}, |
| {"udp4", "127.0.0.1", "udp4", "127.0.0.1"}, |
| {"udp6", "::1", "udp", ""}, |
| {"udp6", "::1", "udp6", ""}, |
| {"udp6", "::1", "udp6", "::1"}, |
| } { |
| spec := "spec" |
| if test.dstAddr == "" { |
| spec = "unspec" |
| } |
| t.Run(fmt.Sprintf("%v/%v/%v", test.srcNet, test.dstNet, spec), func(t *testing.T) { |
| // See: https://go.googlesource.com/go/+/refs/tags/go1.22.0/src/net/ipsock.go#47 |
| // On these platforms, conns with network="udp" cannot accept IPv6. |
| switch runtime.GOOS { |
| case "dragonfly", "openbsd": |
| if test.srcNet == "udp6" && test.dstNet == "udp" { |
| t.Skipf("%v: no support for mapping IPv4 address to IPv6", runtime.GOOS) |
| } |
| } |
| if runtime.GOARCH == "wasm" && test.srcNet == "udp6" { |
| t.Skipf("%v: IPv6 tests fail when using wasm fake net", runtime.GOARCH) |
| } |
| |
| srcAddr := netip.AddrPortFrom(netip.MustParseAddr(test.srcAddr), 0) |
| srcConn, err := net.ListenUDP(test.srcNet, net.UDPAddrFromAddrPort(srcAddr)) |
| if err != nil { |
| // If ListenUDP fails here, we presumably don't have |
| // IPv4/IPv6 configured. |
| t.Skipf("ListenUDP(%q, %v) = %v", test.srcNet, srcAddr, err) |
| } |
| t.Cleanup(func() { srcConn.Close() }) |
| src, err := newNetUDPConn(srcConn) |
| if err != nil { |
| t.Fatalf("newNetUDPConn: %v", err) |
| } |
| |
| var dstAddr netip.AddrPort |
| if test.dstAddr != "" { |
| dstAddr = netip.AddrPortFrom(netip.MustParseAddr(test.dstAddr), 0) |
| } |
| dstConn, err := net.ListenUDP(test.dstNet, net.UDPAddrFromAddrPort(dstAddr)) |
| if err != nil { |
| t.Skipf("ListenUDP(%q, nil) = %v", test.dstNet, err) |
| } |
| dst, err := newNetUDPConn(dstConn) |
| if err != nil { |
| dstConn.Close() |
| t.Fatalf("newNetUDPConn: %v", err) |
| } |
| |
| dgramc := make(chan *datagram) |
| go func() { |
| defer close(dgramc) |
| dst.Read(func(dgram *datagram) { |
| dgramc <- dgram |
| }) |
| }() |
| t.Cleanup(func() { |
| dstConn.Close() |
| for range dgramc { |
| t.Errorf("test read unexpected datagram") |
| } |
| }) |
| |
| f(t, udpTest{ |
| src: src, |
| dst: dst, |
| dstAddr: netip.AddrPortFrom( |
| srcAddr.Addr(), |
| dst.LocalAddr().Port(), |
| ), |
| dgramc: dgramc, |
| }) |
| }) |
| } |
| } |