quic: add the ability to create an endpoint with a fake network

Add a NewEndpoint function to create an *Endpoint using a net.PacketConn.
We rely on a number of features of *net.UDPConn which aren't provided
by PacketConn, so an Endpoint using anything other than a UDPConn will
be limited. The main use case for providing a non-UDPConn connection
is testing, in particular tests which use testing/synctest and cannot
use a concrete network connection.

Change-Id: I9e62cb8d7d545f64d99103beb9a32f149d4119bf
Reviewed-on: https://go-review.googlesource.com/c/net/+/641498
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Jonathan Amsterdam <jba@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
This commit is contained in:
Damien Neil
2024-12-18 09:59:16 -08:00
committed by Gopher Robot
parent 97dd44e201
commit 5566b43feb
2 changed files with 88 additions and 0 deletions

View File

@@ -73,6 +73,25 @@ func Listen(network, address string, listenConfig *Config) (*Endpoint, error) {
return newEndpoint(pc, listenConfig, nil)
}
// NewEndpoint creates an endpoint using a net.PacketConn as the underlying transport.
//
// If the PacketConn is not a *net.UDPConn, the endpoint may be slower and lack
// access to some features of the network.
func NewEndpoint(conn net.PacketConn, config *Config) (*Endpoint, error) {
var pc packetConn
var err error
switch conn := conn.(type) {
case *net.UDPConn:
pc, err = newNetUDPConn(conn)
default:
pc, err = newNetPacketConn(conn)
}
if err != nil {
return nil, err
}
return newEndpoint(pc, config, nil)
}
func newEndpoint(pc packetConn, config *Config, hooks endpointTestHooks) (*Endpoint, error) {
e := &Endpoint{
listenConfig: config,

69
quic/udp_packetconn.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright 2024 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 (
"net"
"net/netip"
)
// netPacketConn is a packetConn implementation wrapping a net.PacketConn.
//
// This is mostly useful for tests, since PacketConn doesn't provide access to
// important features such as identifying the local address packets were received on.
type netPacketConn struct {
c net.PacketConn
localAddr netip.AddrPort
}
func newNetPacketConn(pc net.PacketConn) (*netPacketConn, error) {
addr, err := addrPortFromAddr(pc.LocalAddr())
if err != nil {
return nil, err
}
return &netPacketConn{
c: pc,
localAddr: addr,
}, nil
}
func (c *netPacketConn) Close() error {
return c.c.Close()
}
func (c *netPacketConn) LocalAddr() netip.AddrPort {
return c.localAddr
}
func (c *netPacketConn) Read(f func(*datagram)) {
for {
dgram := newDatagram()
n, peerAddr, err := c.c.ReadFrom(dgram.b)
if err != nil {
return
}
dgram.peerAddr, err = addrPortFromAddr(peerAddr)
if err != nil {
continue
}
dgram.b = dgram.b[:n]
f(dgram)
}
}
func (c *netPacketConn) Write(dgram datagram) error {
_, err := c.c.WriteTo(dgram.b, net.UDPAddrFromAddrPort(dgram.peerAddr))
return err
}
func addrPortFromAddr(addr net.Addr) (netip.AddrPort, error) {
switch a := addr.(type) {
case *net.UDPAddr:
return a.AddrPort(), nil
}
return netip.ParseAddrPort(addr.String())
}