migrate platform-dependent UDP socket test code away from Ginkgo (#4797)

This commit is contained in:
Marten Seemann 2024-12-26 15:02:18 +08:00 committed by GitHub
parent 29ad06c8fa
commit baee2b4aa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 389 additions and 484 deletions

View file

@ -1,80 +0,0 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: sys_conn_oob.go
//
// Generated by this command:
//
// mockgen -typed -package quic -self_package github.com/quic-go/quic-go -source sys_conn_oob.go -destination mock_batch_conn_test.go -mock_names batchConn=MockBatchConn
//
// Package quic is a generated GoMock package.
package quic
import (
reflect "reflect"
gomock "go.uber.org/mock/gomock"
ipv4 "golang.org/x/net/ipv4"
)
// MockBatchConn is a mock of batchConn interface.
type MockBatchConn struct {
ctrl *gomock.Controller
recorder *MockBatchConnMockRecorder
isgomock struct{}
}
// MockBatchConnMockRecorder is the mock recorder for MockBatchConn.
type MockBatchConnMockRecorder struct {
mock *MockBatchConn
}
// NewMockBatchConn creates a new mock instance.
func NewMockBatchConn(ctrl *gomock.Controller) *MockBatchConn {
mock := &MockBatchConn{ctrl: ctrl}
mock.recorder = &MockBatchConnMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockBatchConn) EXPECT() *MockBatchConnMockRecorder {
return m.recorder
}
// ReadBatch mocks base method.
func (m *MockBatchConn) ReadBatch(ms []ipv4.Message, flags int) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ReadBatch", ms, flags)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ReadBatch indicates an expected call of ReadBatch.
func (mr *MockBatchConnMockRecorder) ReadBatch(ms, flags any) *MockBatchConnReadBatchCall {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadBatch", reflect.TypeOf((*MockBatchConn)(nil).ReadBatch), ms, flags)
return &MockBatchConnReadBatchCall{Call: call}
}
// MockBatchConnReadBatchCall wrap *gomock.Call
type MockBatchConnReadBatchCall struct {
*gomock.Call
}
// Return rewrite *gomock.Call.Return
func (c *MockBatchConnReadBatchCall) Return(arg0 int, arg1 error) *MockBatchConnReadBatchCall {
c.Call = c.Call.Return(arg0, arg1)
return c
}
// Do rewrite *gomock.Call.Do
func (c *MockBatchConnReadBatchCall) Do(f func([]ipv4.Message, int) (int, error)) *MockBatchConnReadBatchCall {
c.Call = c.Call.Do(f)
return c
}
// DoAndReturn rewrite *gomock.Call.DoAndReturn
func (c *MockBatchConnReadBatchCall) DoAndReturn(f func([]ipv4.Message, int) (int, error)) *MockBatchConnReadBatchCall {
c.Call = c.Call.DoAndReturn(f)
return c
}

View file

@ -61,9 +61,4 @@ type PacketHandler = packetHandler
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -build_flags=\"-tags=gomock\" -package quic -self_package github.com/quic-go/quic-go -destination mock_packet_handler_manager_test.go github.com/quic-go/quic-go PacketHandlerManager"
type PacketHandlerManager = packetHandlerManager
// Need to use source mode for the batchConn, since reflect mode follows type aliases.
// See https://github.com/golang/mock/issues/244 for details.
//
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -source sys_conn_oob.go -destination mock_batch_conn_test.go -mock_names batchConn=MockBatchConn"
//go:generate sh -c "go run go.uber.org/mock/mockgen -typed -package quic -self_package github.com/quic-go/quic-go -self_package github.com/quic-go/quic-go -destination mock_packetconn_test.go net PacketConn"

View file

@ -6,11 +6,11 @@ import (
"errors"
"net"
"os"
"testing"
"golang.org/x/sys/unix"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/require"
)
var (
@ -18,64 +18,62 @@ var (
errNotPermitted = &os.SyscallError{Syscall: "sendmsg", Err: unix.EPERM}
)
var _ = Describe("forcing a change of send and receive buffer sizes", func() {
It("forces a change of the receive buffer size", func() {
func TestForcingReceiveBufferSize(t *testing.T) {
if os.Getuid() != 0 {
Skip("Must be root to force change the receive buffer size")
t.Skip("Must be root to force change the receive buffer size")
}
c, err := net.ListenPacket("udp", "127.0.0.1:0")
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
defer c.Close()
syscallConn, err := c.(*net.UDPConn).SyscallConn()
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
const small = 256 << 10 // 256 KB
Expect(forceSetReceiveBuffer(syscallConn, small)).To(Succeed())
require.NoError(t, forceSetReceiveBuffer(syscallConn, small))
size, err := inspectReadBuffer(syscallConn)
Expect(err).ToNot(HaveOccurred())
// The kernel doubles this value (to allow space for bookkeeping overhead)
Expect(size).To(Equal(2 * small))
require.NoError(t, err)
// the kernel doubles this value (to allow space for bookkeeping overhead)
require.Equal(t, 2*small, size)
const large = 32 << 20 // 32 MB
Expect(forceSetReceiveBuffer(syscallConn, large)).To(Succeed())
require.NoError(t, forceSetReceiveBuffer(syscallConn, large))
size, err = inspectReadBuffer(syscallConn)
Expect(err).ToNot(HaveOccurred())
// The kernel doubles this value (to allow space for bookkeeping overhead)
Expect(size).To(Equal(2 * large))
})
require.NoError(t, err)
// the kernel doubles this value (to allow space for bookkeeping overhead)
require.Equal(t, 2*large, size)
}
It("forces a change of the send buffer size", func() {
func TestForcingSendBufferSize(t *testing.T) {
if os.Getuid() != 0 {
Skip("Must be root to force change the send buffer size")
t.Skip("Must be root to force change the send buffer size")
}
c, err := net.ListenPacket("udp", "127.0.0.1:0")
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
defer c.Close()
syscallConn, err := c.(*net.UDPConn).SyscallConn()
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
const small = 256 << 10 // 256 KB
Expect(forceSetSendBuffer(syscallConn, small)).To(Succeed())
require.NoError(t, forceSetSendBuffer(syscallConn, small))
size, err := inspectWriteBuffer(syscallConn)
Expect(err).ToNot(HaveOccurred())
// The kernel doubles this value (to allow space for bookkeeping overhead)
Expect(size).To(Equal(2 * small))
require.NoError(t, err)
// the kernel doubles this value (to allow space for bookkeeping overhead)
require.Equal(t, 2*small, size)
const large = 32 << 20 // 32 MB
Expect(forceSetSendBuffer(syscallConn, large)).To(Succeed())
require.NoError(t, forceSetSendBuffer(syscallConn, large))
size, err = inspectWriteBuffer(syscallConn)
Expect(err).ToNot(HaveOccurred())
// The kernel doubles this value (to allow space for bookkeeping overhead)
Expect(size).To(Equal(2 * large))
})
require.NoError(t, err)
// the kernel doubles this value (to allow space for bookkeeping overhead)
require.Equal(t, 2*large, size)
}
It("detects GSO errors", func() {
Expect(isGSOError(errGSO)).To(BeTrue())
Expect(isGSOError(nil)).To(BeFalse())
Expect(isGSOError(errors.New("test"))).To(BeFalse())
})
})
func TestGSOError(t *testing.T) {
require.True(t, isGSOError(errGSO))
require.False(t, isGSOError(nil))
require.False(t, isGSOError(errors.New("test")))
}

View file

@ -83,7 +83,7 @@ func newConn(c OOBCapablePacketConn, supportsDF bool) (*oobConn, error) {
if err != nil {
return nil, err
}
needsPacketInfo := false
var needsPacketInfo bool
if udpAddr, ok := c.LocalAddr().(*net.UDPAddr); ok && udpAddr.IP.IsUnspecified() {
needsPacketInfo = true
}

View file

@ -5,6 +5,7 @@ package quic
import (
"fmt"
"net"
"testing"
"time"
"golang.org/x/net/ipv4"
@ -12,11 +13,255 @@ import (
"github.com/quic-go/quic-go/internal/protocol"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
"github.com/stretchr/testify/require"
)
func isIPv4(ip net.IP) bool { return ip.To4() != nil }
func runSysConnServer(t *testing.T, network string, addr *net.UDPAddr) (*net.UDPAddr, <-chan receivedPacket) {
t.Helper()
udpConn, err := net.ListenUDP(network, addr)
require.NoError(t, err)
t.Cleanup(func() { udpConn.Close() })
oobConn, err := newConn(udpConn, true)
require.NoError(t, err)
require.True(t, oobConn.capabilities().DF)
packetChan := make(chan receivedPacket, 1)
go func() {
for {
p, err := oobConn.ReadPacket()
if err != nil {
return
}
packetChan <- p
}
}()
return udpConn.LocalAddr().(*net.UDPAddr), packetChan
}
// sendUDPPacketWithECN opens a new UDP socket and sends one packet with the ECN set.
// It returns the local address of the socket.
func sendUDPPacketWithECN(t *testing.T, network string, addr *net.UDPAddr, setECN func(uintptr)) net.Addr {
conn, err := net.DialUDP(network, nil, addr)
require.NoError(t, err)
t.Cleanup(func() { conn.Close() })
rawConn, err := conn.SyscallConn()
require.NoError(t, err)
require.NoError(t, rawConn.Control(func(fd uintptr) { setECN(fd) }))
_, err = conn.Write([]byte("foobar"))
require.NoError(t, err)
return conn.LocalAddr()
}
func TestReadECNFlagsIPv4(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
sentFrom := sendUDPPacketWithECN(t,
"udp4",
addr,
func(fd uintptr) {
require.NoError(t, unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 2))
},
)
select {
case p := <-packetChan:
require.WithinDuration(t, time.Now(), p.rcvTime, scaleDuration(20*time.Millisecond))
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, sentFrom, p.remoteAddr)
require.Equal(t, protocol.ECT0, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
func TestReadECNFlagsIPv6(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: 0})
sentFrom := sendUDPPacketWithECN(t,
"udp6",
addr,
func(fd uintptr) {
require.NoError(t, unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 3))
},
)
select {
case p := <-packetChan:
require.WithinDuration(t, time.Now(), p.rcvTime, scaleDuration(20*time.Millisecond))
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, sentFrom, p.remoteAddr)
require.Equal(t, protocol.ECNCE, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
func TestReadECNFlagsDualStack(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp", &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0})
// IPv4
sentFrom := sendUDPPacketWithECN(t,
"udp4",
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: addr.Port},
func(fd uintptr) {
require.NoError(t, unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 3))
},
)
select {
case p := <-packetChan:
require.True(t, isIPv4(p.remoteAddr.(*net.UDPAddr).IP))
require.Equal(t, sentFrom.String(), p.remoteAddr.String())
require.Equal(t, protocol.ECNCE, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
// IPv6
sentFrom = sendUDPPacketWithECN(t,
"udp6",
&net.UDPAddr{IP: net.IPv6loopback, Port: addr.Port},
func(fd uintptr) {
require.NoError(t, unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 1))
},
)
select {
case p := <-packetChan:
require.Equal(t, sentFrom, p.remoteAddr)
require.False(t, isIPv4(p.remoteAddr.(*net.UDPAddr).IP))
require.Equal(t, protocol.ECT1, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
func TestSendPacketsWithECNOnIPv4(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
c, err := net.ListenUDP("udp4", nil)
require.NoError(t, err)
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv4ECNMsg([]byte{}, val), addr)
require.NoError(t, err)
select {
case p := <-packetChan:
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, val, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
}
func TestSendPacketsWithECNOnIPv6(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: 0})
c, err := net.ListenUDP("udp6", nil)
require.NoError(t, err)
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv6ECNMsg([]byte{}, val), addr)
require.NoError(t, err)
select {
case p := <-packetChan:
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, val, p.ecn)
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
}
func TestSysConnPacketInfoIPv4(t *testing.T) {
// need to listen on 0.0.0.0, otherwise we won't get the packet info
addr, packetChan := runSysConnServer(t, "udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0})
conn, err := net.DialUDP("udp4", nil, addr)
require.NoError(t, err)
defer conn.Close()
_, err = conn.Write([]byte("foobar"))
require.NoError(t, err)
select {
case p := <-packetChan:
require.WithinDuration(t, time.Now(), p.rcvTime, scaleDuration(50*time.Millisecond))
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, conn.LocalAddr(), p.remoteAddr)
require.True(t, p.info.addr.IsValid())
require.True(t, isIPv4(p.info.addr.AsSlice()))
require.Equal(t, net.IPv4(127, 0, 0, 1).String(), p.info.addr.String())
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
func TestSysConnPacketInfoIPv6(t *testing.T) {
// need to listen on ::, otherwise we won't get the packet info
addr, packetChan := runSysConnServer(t, "udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0})
conn, err := net.DialUDP("udp6", nil, addr)
require.NoError(t, err)
defer conn.Close()
_, err = conn.Write([]byte("foobar"))
require.NoError(t, err)
select {
case p := <-packetChan:
require.WithinDuration(t, time.Now(), p.rcvTime, scaleDuration(20*time.Millisecond))
require.Equal(t, []byte("foobar"), p.data)
require.Equal(t, conn.LocalAddr(), p.remoteAddr)
require.NotNil(t, p.info)
require.Equal(t, net.IPv6loopback, net.IP(p.info.addr.AsSlice()))
case <-time.After(time.Second):
t.Fatal("timeout waiting for packet")
}
}
func TestSysConnPacketInfoDualStack(t *testing.T) {
addr, packetChan := runSysConnServer(t, "udp", &net.UDPAddr{})
// IPv4
conn4, err := net.DialUDP("udp4", nil, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: addr.Port})
require.NoError(t, err)
defer conn4.Close()
_, err = conn4.Write([]byte("foobar"))
require.NoError(t, err)
select {
case p := <-packetChan:
require.True(t, isIPv4(p.remoteAddr.(*net.UDPAddr).IP))
require.NotNil(t, p.info)
require.True(t, p.info.addr.Is4())
require.Equal(t, net.IPv4(127, 0, 0, 1).String(), p.info.addr.String())
case <-time.After(time.Second):
t.Fatal("timeout waiting for IPv4 packet")
}
// IPv6
conn6, err := net.DialUDP("udp6", nil, addr)
require.NoError(t, err)
defer conn6.Close()
_, err = conn6.Write([]byte("foobar"))
require.NoError(t, err)
select {
case p := <-packetChan:
require.False(t, isIPv4(p.remoteAddr.(*net.UDPAddr).IP))
require.NotNil(t, p.info)
require.Equal(t, net.IPv6loopback.String(), p.info.addr.String())
case <-time.After(time.Second):
t.Fatal("timeout waiting for IPv6 packet")
}
}
type oobRecordingConn struct {
*net.UDPConn
oobs [][]byte
@ -27,312 +272,63 @@ func (c *oobRecordingConn) WriteMsgUDP(b, oob []byte, addr *net.UDPAddr) (n, oob
return c.UDPConn.WriteMsgUDP(b, oob, addr)
}
func isIPv4(ip net.IP) bool { return ip.To4() != nil }
type mockBatchConn struct {
t *testing.T
numMsgRead int
var _ = Describe("OOB Conn Test", func() {
runServer := func(network, address string) (*net.UDPConn, <-chan receivedPacket) {
addr, err := net.ResolveUDPAddr(network, address)
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP(network, addr)
Expect(err).ToNot(HaveOccurred())
oobConn, err := newConn(udpConn, true)
Expect(err).ToNot(HaveOccurred())
Expect(oobConn.capabilities().DF).To(BeTrue())
packetChan := make(chan receivedPacket)
go func() {
defer GinkgoRecover()
for {
p, err := oobConn.ReadPacket()
if err != nil {
return
}
packetChan <- p
}
}()
return udpConn, packetChan
callCounter int
}
Context("reading ECN-marked packets", func() {
sendPacketWithECN := func(network string, addr *net.UDPAddr, setECN func(uintptr)) net.Addr {
conn, err := net.DialUDP(network, nil, addr)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
rawConn, err := conn.SyscallConn()
ExpectWithOffset(1, err).ToNot(HaveOccurred())
ExpectWithOffset(1, rawConn.Control(func(fd uintptr) {
setECN(fd)
})).To(Succeed())
_, err = conn.Write([]byte("foobar"))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return conn.LocalAddr()
}
var _ batchConn = &mockBatchConn{}
It("reads ECN flags on IPv4", func() {
conn, packetChan := runServer("udp4", "localhost:0")
defer conn.Close()
sentFrom := sendPacketWithECN(
"udp4",
conn.LocalAddr().(*net.UDPAddr),
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 2)).To(Succeed())
},
)
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.ecn).To(Equal(protocol.ECT0))
})
It("reads ECN flags on IPv6", func() {
conn, packetChan := runServer("udp6", "[::]:0")
defer conn.Close()
sentFrom := sendPacketWithECN(
"udp6",
conn.LocalAddr().(*net.UDPAddr),
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 3)).To(Succeed())
},
)
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.ecn).To(Equal(protocol.ECNCE))
})
It("reads ECN flags on a connection that supports both IPv4 and IPv6", func() {
conn, packetChan := runServer("udp", "0.0.0.0:0")
defer conn.Close()
port := conn.LocalAddr().(*net.UDPAddr).Port
// IPv4
sendPacketWithECN(
"udp4",
&net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port},
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_TOS, 3)).To(Succeed())
},
)
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(isIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeTrue())
Expect(p.ecn).To(Equal(protocol.ECNCE))
// IPv6
sendPacketWithECN(
"udp6",
&net.UDPAddr{IP: net.IPv6loopback, Port: port},
func(fd uintptr) {
Expect(unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_TCLASS, 1)).To(Succeed())
},
)
Eventually(packetChan).Should(Receive(&p))
Expect(isIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeFalse())
Expect(p.ecn).To(Equal(protocol.ECT1))
})
It("sends packets with ECN on IPv4", func() {
conn, packetChan := runServer("udp4", "localhost:0")
defer conn.Close()
c, err := net.ListenUDP("udp4", nil)
Expect(err).ToNot(HaveOccurred())
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv4ECNMsg([]byte{}, val), conn.LocalAddr().(*net.UDPAddr))
Expect(err).ToNot(HaveOccurred())
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.ecn).To(Equal(val))
}
})
It("sends packets with ECN on IPv6", func() {
conn, packetChan := runServer("udp6", "[::1]:0")
defer conn.Close()
c, err := net.ListenUDP("udp6", nil)
Expect(err).ToNot(HaveOccurred())
defer c.Close()
for _, val := range []protocol.ECN{protocol.ECNNon, protocol.ECT1, protocol.ECT0, protocol.ECNCE} {
_, _, err = c.WriteMsgUDP([]byte("foobar"), appendIPv6ECNMsg([]byte{}, val), conn.LocalAddr().(*net.UDPAddr))
Expect(err).ToNot(HaveOccurred())
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.ecn).To(Equal(val))
}
})
})
Context("Packet Info conn", func() {
sendPacket := func(network string, addr *net.UDPAddr) net.Addr {
conn, err := net.DialUDP(network, nil, addr)
ExpectWithOffset(1, err).ToNot(HaveOccurred())
_, err = conn.Write([]byte("foobar"))
ExpectWithOffset(1, err).ToNot(HaveOccurred())
return conn.LocalAddr()
}
It("reads packet info on IPv4", func() {
conn, packetChan := runServer("udp4", ":0")
defer conn.Close()
addr := conn.LocalAddr().(*net.UDPAddr)
ip := net.ParseIP("127.0.0.1").To4()
addr.IP = ip
sentFrom := sendPacket("udp4", addr)
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.info.addr.IsValid()).To(BeTrue())
Expect(net.IP(p.info.addr.AsSlice())).To(Equal(ip))
})
It("reads packet info on IPv6", func() {
conn, packetChan := runServer("udp6", ":0")
defer conn.Close()
addr := conn.LocalAddr().(*net.UDPAddr)
ip := net.ParseIP("::1")
addr.IP = ip
sentFrom := sendPacket("udp6", addr)
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(20*time.Millisecond)))
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.remoteAddr).To(Equal(sentFrom))
Expect(p.info).To(Not(BeNil()))
Expect(net.IP(p.info.addr.AsSlice())).To(Equal(ip))
})
It("reads packet info on a connection that supports both IPv4 and IPv6", func() {
conn, packetChan := runServer("udp", ":0")
defer conn.Close()
port := conn.LocalAddr().(*net.UDPAddr).Port
// IPv4
ip4 := net.ParseIP("127.0.0.1")
sendPacket("udp4", &net.UDPAddr{IP: ip4, Port: port})
var p receivedPacket
Eventually(packetChan).Should(Receive(&p))
Expect(isIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeTrue())
Expect(p.info).To(Not(BeNil()))
Expect(p.info.addr.Is4()).To(BeTrue())
ip := p.info.addr.As4()
Expect(net.IP(ip[:])).To(Equal(ip4.To4()))
// IPv6
ip6 := net.ParseIP("::1")
sendPacket("udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: port})
Eventually(packetChan).Should(Receive(&p))
Expect(isIPv4(p.remoteAddr.(*net.UDPAddr).IP)).To(BeFalse())
Expect(p.info).To(Not(BeNil()))
Expect(net.IP(p.info.addr.AsSlice())).To(Equal(ip6))
})
})
Context("Batch Reading", func() {
var batchConn *MockBatchConn
BeforeEach(func() {
batchConn = NewMockBatchConn(mockCtrl)
})
It("reads multiple messages in one batch", func() {
const numMsgRead = batchSize/2 + 1
var counter int
batchConn.EXPECT().ReadBatch(gomock.Any(), gomock.Any()).DoAndReturn(func(ms []ipv4.Message, flags int) (int, error) {
Expect(ms).To(HaveLen(batchSize))
for i := 0; i < numMsgRead; i++ {
Expect(ms[i].Buffers).To(HaveLen(1))
Expect(ms[i].Buffers[0]).To(HaveLen(protocol.MaxPacketBufferSize))
data := []byte(fmt.Sprintf("message %d", counter))
counter++
func (c *mockBatchConn) ReadBatch(ms []ipv4.Message, _ int) (int, error) {
require.Len(c.t, ms, batchSize)
for i := 0; i < c.numMsgRead; i++ {
require.Len(c.t, ms[i].Buffers, 1)
require.Len(c.t, ms[i].Buffers[0], protocol.MaxPacketBufferSize)
data := []byte(fmt.Sprintf("message %d", c.callCounter*c.numMsgRead+i))
ms[i].Buffers[0] = data
ms[i].N = len(data)
}
return numMsgRead, nil
}).Times(2)
c.callCounter++
return c.numMsgRead, nil
}
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", addr)
Expect(err).ToNot(HaveOccurred())
func TestReadsMultipleMessagesInOneBatch(t *testing.T) {
bc := &mockBatchConn{t: t, numMsgRead: batchSize/2 + 1}
udpConn := newUPDConnLocalhost(t)
oobConn, err := newConn(udpConn, true)
Expect(err).ToNot(HaveOccurred())
oobConn.batchConn = batchConn
require.NoError(t, err)
oobConn.batchConn = bc
for i := 0; i < batchSize+1; i++ {
p, err := oobConn.ReadPacket()
Expect(err).ToNot(HaveOccurred())
Expect(string(p.data)).To(Equal(fmt.Sprintf("message %d", i)))
require.NoError(t, err)
require.Equal(t, fmt.Sprintf("message %d", i), string(p.data))
}
require.Equal(t, 2, bc.callCounter)
}
})
})
Context("sending ECN-marked packets", func() {
It("sets the ECN control message", func() {
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", addr)
Expect(err).ToNot(HaveOccurred())
func TestSysConnSendGSO(t *testing.T) {
if !platformSupportsGSO {
t.Skip("GSO not supported on this platform")
}
udpConn, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
c := &oobRecordingConn{UDPConn: udpConn}
oobConn, err := newConn(c, true)
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
require.True(t, oobConn.capabilities().GSO)
oob := make([]byte, 0, 123)
oobConn.WritePacket([]byte("foobar"), addr, oob, 0, protocol.ECNCE)
Expect(c.oobs).To(HaveLen(1))
oobConn.WritePacket([]byte("foobar"), udpConn.LocalAddr(), oob, 3, protocol.ECNCE)
require.Len(t, c.oobs, 1)
oobMsg := c.oobs[0]
Expect(oobMsg).ToNot(BeEmpty())
Expect(oobMsg).To(HaveCap(cap(oob))) // check that it appended to oob
expected := appendIPv4ECNMsg([]byte{}, protocol.ECNCE)
Expect(oobMsg).To(Equal(expected))
})
})
if platformSupportsGSO {
Context("GSO", func() {
It("appends the GSO control message", func() {
addr, err := net.ResolveUDPAddr("udp", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp", addr)
Expect(err).ToNot(HaveOccurred())
c := &oobRecordingConn{UDPConn: udpConn}
oobConn, err := newConn(c, true)
Expect(err).ToNot(HaveOccurred())
Expect(oobConn.capabilities().GSO).To(BeTrue())
oob := make([]byte, 0, 123)
oobConn.WritePacket([]byte("foobar"), addr, oob, 3, protocol.ECNCE)
Expect(c.oobs).To(HaveLen(1))
oobMsg := c.oobs[0]
Expect(oobMsg).ToNot(BeEmpty())
Expect(oobMsg).To(HaveCap(cap(oob))) // check that it appended to oob
require.NotEmpty(t, oobMsg)
require.Equal(t, cap(oob), cap(oobMsg)) // check that it appended to oob
expected := appendUDPSegmentSizeMsg([]byte{}, 3)
// Check that the first control message is the OOB control message.
Expect(oobMsg[:len(expected)]).To(Equal(expected))
})
})
require.Equal(t, expected, oobMsg[:len(expected)])
}
})

View file

@ -2,31 +2,31 @@ package quic
import (
"net"
"testing"
"time"
"github.com/quic-go/quic-go/internal/protocol"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)
var _ = Describe("Basic Conn Test", func() {
It("reads a packet", func() {
func TestBasicConn(t *testing.T) {
mockCtrl := gomock.NewController(t)
c := NewMockPacketConn(mockCtrl)
addr := &net.UDPAddr{IP: net.IPv4(1, 2, 3, 4), Port: 1234}
c.EXPECT().ReadFrom(gomock.Any()).DoAndReturn(func(b []byte) (int, net.Addr, error) {
data := []byte("foobar")
Expect(b).To(HaveLen(protocol.MaxPacketBufferSize))
require.Equal(t, protocol.MaxPacketBufferSize, len(b))
return copy(b, data), addr, nil
})
conn, err := wrapConn(c)
Expect(err).ToNot(HaveOccurred())
require.NoError(t, err)
p, err := conn.ReadPacket()
Expect(err).ToNot(HaveOccurred())
Expect(p.data).To(Equal([]byte("foobar")))
Expect(p.rcvTime).To(BeTemporally("~", time.Now(), scaleDuration(100*time.Millisecond)))
Expect(p.remoteAddr).To(Equal(addr))
})
})
require.NoError(t, err)
require.Equal(t, []byte("foobar"), p.data)
require.WithinDuration(t, time.Now(), p.rcvTime, scaleDuration(100*time.Millisecond))
require.Equal(t, addr, p.remoteAddr)
}

View file

@ -4,31 +4,27 @@ package quic
import (
"net"
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/require"
)
var _ = Describe("Windows Conn Test", func() {
It("works on IPv4", func() {
addr, err := net.ResolveUDPAddr("udp4", "localhost:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp4", addr)
Expect(err).ToNot(HaveOccurred())
func TestWindowsConn(t *testing.T) {
t.Run("IPv4", func(t *testing.T) {
udpConn, err := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0})
require.NoError(t, err)
conn, err := newConn(udpConn, true)
Expect(err).ToNot(HaveOccurred())
Expect(conn.Close()).To(Succeed())
Expect(conn.capabilities().DF).To(BeTrue())
require.NoError(t, err)
require.NoError(t, conn.Close())
require.True(t, conn.capabilities().DF)
})
It("works on IPv6", func() {
addr, err := net.ResolveUDPAddr("udp6", "[::1]:0")
Expect(err).ToNot(HaveOccurred())
udpConn, err := net.ListenUDP("udp6", addr)
Expect(err).ToNot(HaveOccurred())
t.Run("IPv6", func(t *testing.T) {
udpConn, err := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6loopback, Port: 0})
require.NoError(t, err)
conn, err := newConn(udpConn, false)
Expect(err).ToNot(HaveOccurred())
Expect(conn.Close()).To(Succeed())
Expect(conn.capabilities().DF).To(BeFalse())
})
require.NoError(t, err)
require.NoError(t, conn.Close())
require.False(t, conn.capabilities().DF)
})
}