mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-22 08:51:41 +00:00
net/{netx,memnet},all: add netx.DialFunc, move memnet Network impl
This adds netx.DialFunc, unifying a type we have a bazillion other places, giving it now a nice short name that's clickable in editors, etc. That highlighted that my earlier move (03b47a55c7956) of stuff from nettest into netx moved too much: it also dragged along the memnet impl, meaning all users of netx.DialFunc who just wanted netx for the type definition were instead also pulling in all of memnet. So move the memnet implementation netx.Network into memnet, a package we already had. Then use netx.DialFunc in a bunch of places. I'm sure I missed some. And plenty remain in other repos, to be updated later. Updates tailscale/corp#27636 Change-Id: I7296cd4591218e8624e214f8c70dab05fb884e95 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
b95df54b06
commit
fb96137d79
@ -111,6 +111,7 @@ tailscale.com/cmd/derper dependencies: (generated by github.com/tailscale/depawa
|
|||||||
💣 tailscale.com/net/netmon from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/netmon from tailscale.com/derp/derphttp+
|
||||||
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp
|
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/netutil from tailscale.com/client/local
|
tailscale.com/net/netutil from tailscale.com/client/local
|
||||||
|
tailscale.com/net/netx from tailscale.com/net/dnscache+
|
||||||
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
tailscale.com/net/sockstats from tailscale.com/derp/derphttp
|
||||||
tailscale.com/net/stun from tailscale.com/net/stunserver
|
tailscale.com/net/stun from tailscale.com/net/stunserver
|
||||||
tailscale.com/net/stunserver from tailscale.com/cmd/derper
|
tailscale.com/net/stunserver from tailscale.com/cmd/derper
|
||||||
|
@ -866,6 +866,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
|||||||
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||||
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
||||||
tailscale.com/net/netutil from tailscale.com/client/local+
|
tailscale.com/net/netutil from tailscale.com/client/local+
|
||||||
|
tailscale.com/net/netx from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/packet from tailscale.com/net/connstats+
|
tailscale.com/net/packet from tailscale.com/net/connstats+
|
||||||
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
||||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
"github.com/inetaf/tcpproxy"
|
"github.com/inetaf/tcpproxy"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tcpRoundRobinHandler struct {
|
type tcpRoundRobinHandler struct {
|
||||||
@ -22,7 +23,7 @@ type tcpRoundRobinHandler struct {
|
|||||||
To []string
|
To []string
|
||||||
|
|
||||||
// DialContext is used to make the outgoing TCP connection.
|
// DialContext is used to make the outgoing TCP connection.
|
||||||
DialContext func(ctx context.Context, network, address string) (net.Conn, error)
|
DialContext netx.DialFunc
|
||||||
|
|
||||||
// ReachableIPs enumerates the IP addresses this handler is reachable on.
|
// ReachableIPs enumerates the IP addresses this handler is reachable on.
|
||||||
ReachableIPs []netip.Addr
|
ReachableIPs []netip.Addr
|
||||||
|
@ -112,6 +112,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
|
|||||||
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
💣 tailscale.com/net/netmon from tailscale.com/cmd/tailscale/cli+
|
||||||
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
💣 tailscale.com/net/netns from tailscale.com/derp/derphttp+
|
||||||
tailscale.com/net/netutil from tailscale.com/client/local+
|
tailscale.com/net/netutil from tailscale.com/client/local+
|
||||||
|
tailscale.com/net/netx from tailscale.com/control/controlhttp+
|
||||||
tailscale.com/net/ping from tailscale.com/net/netcheck
|
tailscale.com/net/ping from tailscale.com/net/netcheck
|
||||||
tailscale.com/net/portmapper from tailscale.com/cmd/tailscale/cli+
|
tailscale.com/net/portmapper from tailscale.com/cmd/tailscale/cli+
|
||||||
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
tailscale.com/net/sockstats from tailscale.com/control/controlhttp+
|
||||||
|
@ -316,6 +316,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
|||||||
💣 tailscale.com/net/netns from tailscale.com/cmd/tailscaled+
|
💣 tailscale.com/net/netns from tailscale.com/cmd/tailscaled+
|
||||||
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
W 💣 tailscale.com/net/netstat from tailscale.com/portlist
|
||||||
tailscale.com/net/netutil from tailscale.com/client/local+
|
tailscale.com/net/netutil from tailscale.com/client/local+
|
||||||
|
tailscale.com/net/netx from tailscale.com/control/controlclient+
|
||||||
tailscale.com/net/packet from tailscale.com/net/connstats+
|
tailscale.com/net/packet from tailscale.com/net/connstats+
|
||||||
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
tailscale.com/net/packet/checksum from tailscale.com/net/tstun
|
||||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||||
|
@ -37,6 +37,7 @@ import (
|
|||||||
"tailscale.com/net/dnsfallback"
|
"tailscale.com/net/dnsfallback"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/net/tshttpproxy"
|
"tailscale.com/net/tshttpproxy"
|
||||||
@ -272,7 +273,7 @@ func NewDirect(opts Options) (*Direct, error) {
|
|||||||
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
tr.Proxy = tshttpproxy.ProxyFromEnvironment
|
||||||
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
tshttpproxy.SetTransportGetProxyConnectHeader(tr)
|
||||||
tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), opts.HealthTracker, tr.TLSClientConfig)
|
tr.TLSClientConfig = tlsdial.Config(serverURL.Hostname(), opts.HealthTracker, tr.TLSClientConfig)
|
||||||
var dialFunc dialFunc
|
var dialFunc netx.DialFunc
|
||||||
dialFunc, interceptedDial = makeScreenTimeDetectingDialFunc(opts.Dialer.SystemDial)
|
dialFunc, interceptedDial = makeScreenTimeDetectingDialFunc(opts.Dialer.SystemDial)
|
||||||
tr.DialContext = dnscache.Dialer(dialFunc, dnsCache)
|
tr.DialContext = dnscache.Dialer(dialFunc, dnsCache)
|
||||||
tr.DialTLSContext = dnscache.TLSDialer(dialFunc, dnsCache, tr.TLSClientConfig)
|
tr.DialTLSContext = dnscache.TLSDialer(dialFunc, dnsCache, tr.TLSClientConfig)
|
||||||
@ -1749,14 +1750,12 @@ func addLBHeader(req *http.Request, nodeKey key.NodePublic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type dialFunc = func(ctx context.Context, network, addr string) (net.Conn, error)
|
|
||||||
|
|
||||||
// makeScreenTimeDetectingDialFunc returns dialFunc, optionally wrapped (on
|
// makeScreenTimeDetectingDialFunc returns dialFunc, optionally wrapped (on
|
||||||
// Apple systems) with a func that sets the returned atomic.Bool for whether
|
// Apple systems) with a func that sets the returned atomic.Bool for whether
|
||||||
// Screen Time seemed to intercept the connection.
|
// Screen Time seemed to intercept the connection.
|
||||||
//
|
//
|
||||||
// The returned *atomic.Bool is nil on non-Apple systems.
|
// The returned *atomic.Bool is nil on non-Apple systems.
|
||||||
func makeScreenTimeDetectingDialFunc(dial dialFunc) (dialFunc, *atomic.Bool) {
|
func makeScreenTimeDetectingDialFunc(dial netx.DialFunc) (netx.DialFunc, *atomic.Bool) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "darwin", "ios":
|
case "darwin", "ios":
|
||||||
// Continue below.
|
// Continue below.
|
||||||
|
@ -44,6 +44,7 @@ import (
|
|||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/dnsfallback"
|
"tailscale.com/net/dnsfallback"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/sockstats"
|
"tailscale.com/net/sockstats"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
"tailscale.com/net/tshttpproxy"
|
"tailscale.com/net/tshttpproxy"
|
||||||
@ -494,7 +495,7 @@ func (a *Dialer) tryURLUpgrade(ctx context.Context, u *url.URL, optAddr netip.Ad
|
|||||||
dns = a.resolver()
|
dns = a.resolver()
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialer dnscache.DialContextFunc
|
var dialer netx.DialFunc
|
||||||
if a.Dialer != nil {
|
if a.Dialer != nil {
|
||||||
dialer = a.Dialer
|
dialer = a.Dialer
|
||||||
} else {
|
} else {
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tstime"
|
"tailscale.com/tstime"
|
||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
@ -66,7 +67,7 @@ type Dialer struct {
|
|||||||
// Dialer is the dialer used to make outbound connections.
|
// Dialer is the dialer used to make outbound connections.
|
||||||
//
|
//
|
||||||
// If not specified, this defaults to net.Dialer.DialContext.
|
// If not specified, this defaults to net.Dialer.DialContext.
|
||||||
Dialer dnscache.DialContextFunc
|
Dialer netx.DialFunc
|
||||||
|
|
||||||
// DNSCache is the caching Resolver used by this Dialer.
|
// DNSCache is the caching Resolver used by this Dialer.
|
||||||
//
|
//
|
||||||
|
@ -26,8 +26,8 @@ import (
|
|||||||
"tailscale.com/control/controlhttp/controlhttpcommon"
|
"tailscale.com/control/controlhttp/controlhttpcommon"
|
||||||
"tailscale.com/control/controlhttp/controlhttpserver"
|
"tailscale.com/control/controlhttp/controlhttpserver"
|
||||||
"tailscale.com/health"
|
"tailscale.com/health"
|
||||||
"tailscale.com/net/dnscache"
|
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/socks5"
|
"tailscale.com/net/socks5"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -760,7 +760,7 @@ func TestDialPlan(t *testing.T) {
|
|||||||
|
|
||||||
type closeTrackDialer struct {
|
type closeTrackDialer struct {
|
||||||
t testing.TB
|
t testing.TB
|
||||||
inner dnscache.DialContextFunc
|
inner netx.DialFunc
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
conns map[*closeTrackConn]bool
|
conns map[*closeTrackConn]bool
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/sockstats"
|
"tailscale.com/net/sockstats"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
"tailscale.com/net/tshttpproxy"
|
"tailscale.com/net/tshttpproxy"
|
||||||
@ -587,7 +588,7 @@ func (c *Client) connect(ctx context.Context, caller string) (client *derp.Clien
|
|||||||
//
|
//
|
||||||
// The primary use for this is the derper mesh mode to connect to each
|
// The primary use for this is the derper mesh mode to connect to each
|
||||||
// other over a VPC network.
|
// other over a VPC network.
|
||||||
func (c *Client) SetURLDialer(dialer func(ctx context.Context, network, addr string) (net.Conn, error)) {
|
func (c *Client) SetURLDialer(dialer netx.DialFunc) {
|
||||||
c.dialer = dialer
|
c.dialer = dialer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
"tailscale.com/k8s-operator/sessionrecording/spdy"
|
"tailscale.com/k8s-operator/sessionrecording/spdy"
|
||||||
"tailscale.com/k8s-operator/sessionrecording/tsrecorder"
|
"tailscale.com/k8s-operator/sessionrecording/tsrecorder"
|
||||||
"tailscale.com/k8s-operator/sessionrecording/ws"
|
"tailscale.com/k8s-operator/sessionrecording/ws"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/sessionrecording"
|
"tailscale.com/sessionrecording"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
@ -102,7 +103,7 @@ type Hijacker struct {
|
|||||||
// connection succeeds. In case of success, returns a list with a single
|
// connection succeeds. In case of success, returns a list with a single
|
||||||
// successful recording attempt and an error channel. If the connection errors
|
// successful recording attempt and an error channel. If the connection errors
|
||||||
// after having been established, an error is sent down the channel.
|
// after having been established, an error is sent down the channel.
|
||||||
type RecorderDialFn func(context.Context, []netip.AddrPort, sessionrecording.DialFunc) (io.WriteCloser, []*tailcfg.SSHRecordingAttempt, <-chan error, error)
|
type RecorderDialFn func(context.Context, []netip.AddrPort, netx.DialFunc) (io.WriteCloser, []*tailcfg.SSHRecordingAttempt, <-chan error, error)
|
||||||
|
|
||||||
// Hijack hijacks a 'kubectl exec' session and configures for the session
|
// Hijack hijacks a 'kubectl exec' session and configures for the session
|
||||||
// contents to be sent to a recorder.
|
// contents to be sent to a recorder.
|
||||||
|
@ -19,7 +19,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"tailscale.com/client/tailscale/apitype"
|
"tailscale.com/client/tailscale/apitype"
|
||||||
"tailscale.com/k8s-operator/sessionrecording/fakes"
|
"tailscale.com/k8s-operator/sessionrecording/fakes"
|
||||||
"tailscale.com/sessionrecording"
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/tsnet"
|
"tailscale.com/tsnet"
|
||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
@ -80,7 +80,7 @@ func Test_Hijacker(t *testing.T) {
|
|||||||
h := &Hijacker{
|
h := &Hijacker{
|
||||||
connectToRecorder: func(context.Context,
|
connectToRecorder: func(context.Context,
|
||||||
[]netip.AddrPort,
|
[]netip.AddrPort,
|
||||||
sessionrecording.DialFunc,
|
netx.DialFunc,
|
||||||
) (wc io.WriteCloser, rec []*tailcfg.SSHRecordingAttempt, _ <-chan error, err error) {
|
) (wc io.WriteCloser, rec []*tailcfg.SSHRecordingAttempt, _ <-chan error, err error) {
|
||||||
if tt.failRecorderConnect {
|
if tt.failRecorderConnect {
|
||||||
err = errors.New("test")
|
err = errors.New("test")
|
||||||
|
@ -42,6 +42,7 @@ import (
|
|||||||
"tailscale.com/net/netknob"
|
"tailscale.com/net/netknob"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/tlsdial"
|
"tailscale.com/net/tlsdial"
|
||||||
"tailscale.com/net/tshttpproxy"
|
"tailscale.com/net/tshttpproxy"
|
||||||
"tailscale.com/paths"
|
"tailscale.com/paths"
|
||||||
@ -769,7 +770,7 @@ func (p *Policy) Shutdown(ctx context.Context) error {
|
|||||||
//
|
//
|
||||||
// The netMon parameter is optional. It should be specified in environments where
|
// The netMon parameter is optional. It should be specified in environments where
|
||||||
// Tailscaled is manipulating the routing table.
|
// Tailscaled is manipulating the routing table.
|
||||||
func MakeDialFunc(netMon *netmon.Monitor, logf logger.Logf) func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
func MakeDialFunc(netMon *netmon.Monitor, logf logger.Logf) netx.DialFunc {
|
||||||
if netMon == nil {
|
if netMon == nil {
|
||||||
netMon = netmon.NewStatic()
|
netMon = netmon.NewStatic()
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import (
|
|||||||
"tailscale.com/net/dnscache"
|
"tailscale.com/net/dnscache"
|
||||||
"tailscale.com/net/neterror"
|
"tailscale.com/net/neterror"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/sockstats"
|
"tailscale.com/net/sockstats"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
"tailscale.com/types/dnstype"
|
"tailscale.com/types/dnstype"
|
||||||
@ -739,7 +740,7 @@ func (f *forwarder) sendUDP(ctx context.Context, fq *forwardQuery, rr resolverAn
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *forwarder) getDialerType() dnscache.DialContextFunc {
|
func (f *forwarder) getDialerType() netx.DialFunc {
|
||||||
if f.controlKnobs != nil && f.controlKnobs.UserDialUseRoutes.Load() {
|
if f.controlKnobs != nil && f.controlKnobs.UserDialUseRoutes.Load() {
|
||||||
// It is safe to use UserDial as it dials external servers without going through Tailscale
|
// It is safe to use UserDial as it dials external servers without going through Tailscale
|
||||||
// and closes connections on interface change in the same way as SystemDial does,
|
// and closes connections on interface change in the same way as SystemDial does,
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tailscale.com/envknob"
|
"tailscale.com/envknob"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/cloudenv"
|
"tailscale.com/util/cloudenv"
|
||||||
"tailscale.com/util/singleflight"
|
"tailscale.com/util/singleflight"
|
||||||
@ -355,10 +356,8 @@ func (r *Resolver) addIPCache(host string, ip, ip6 netip.Addr, allIPs []netip.Ad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DialContextFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
|
||||||
|
|
||||||
// Dialer returns a wrapped DialContext func that uses the provided dnsCache.
|
// Dialer returns a wrapped DialContext func that uses the provided dnsCache.
|
||||||
func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
|
func Dialer(fwd netx.DialFunc, dnsCache *Resolver) netx.DialFunc {
|
||||||
d := &dialer{
|
d := &dialer{
|
||||||
fwd: fwd,
|
fwd: fwd,
|
||||||
dnsCache: dnsCache,
|
dnsCache: dnsCache,
|
||||||
@ -369,7 +368,7 @@ func Dialer(fwd DialContextFunc, dnsCache *Resolver) DialContextFunc {
|
|||||||
|
|
||||||
// dialer is the config and accumulated state for a dial func returned by Dialer.
|
// dialer is the config and accumulated state for a dial func returned by Dialer.
|
||||||
type dialer struct {
|
type dialer struct {
|
||||||
fwd DialContextFunc
|
fwd netx.DialFunc
|
||||||
dnsCache *Resolver
|
dnsCache *Resolver
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
@ -653,7 +652,7 @@ func v6addrs(aa []netip.Addr) (ret []netip.Addr) {
|
|||||||
// TLSDialer is like Dialer but returns a func suitable for using with net/http.Transport.DialTLSContext.
|
// TLSDialer is like Dialer but returns a func suitable for using with net/http.Transport.DialTLSContext.
|
||||||
// It returns a *tls.Conn type on success.
|
// It returns a *tls.Conn type on success.
|
||||||
// On TLS cert validation failure, it can invoke a backup DNS resolution strategy.
|
// On TLS cert validation failure, it can invoke a backup DNS resolution strategy.
|
||||||
func TLSDialer(fwd DialContextFunc, dnsCache *Resolver, tlsConfigBase *tls.Config) DialContextFunc {
|
func TLSDialer(fwd netx.DialFunc, dnsCache *Resolver, tlsConfigBase *tls.Config) netx.DialFunc {
|
||||||
tcpDialer := Dialer(fwd, dnsCache)
|
tcpDialer := Dialer(fwd, dnsCache)
|
||||||
return func(ctx context.Context, network, address string) (net.Conn, error) {
|
return func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
host, _, err := net.SplitHostPort(address)
|
host, _, err := net.SplitHostPort(address)
|
||||||
|
@ -6,3 +6,82 @@
|
|||||||
// in tests and other situations where you don't want to use the
|
// in tests and other situations where you don't want to use the
|
||||||
// network.
|
// network.
|
||||||
package memnet
|
package memnet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"tailscale.com/net/netx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ netx.Network = (*Network)(nil)
|
||||||
|
|
||||||
|
// Network implements [Network] using an in-memory network, usually
|
||||||
|
// used for testing.
|
||||||
|
//
|
||||||
|
// As of 2025-04-08, it only supports TCP.
|
||||||
|
//
|
||||||
|
// Its zero value is a valid [netx.Network] implementation.
|
||||||
|
type Network struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
lns map[string]*Listener // address -> listener
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Network) Listen(network, address string) (net.Listener, error) {
|
||||||
|
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||||
|
return nil, fmt.Errorf("memNetwork: Listen called with unsupported network %q", network)
|
||||||
|
}
|
||||||
|
ap, err := netip.ParseAddrPort(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("memNetwork: Listen called with invalid address %q: %w", address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.mu.Lock()
|
||||||
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
|
if m.lns == nil {
|
||||||
|
m.lns = make(map[string]*Listener)
|
||||||
|
}
|
||||||
|
port := ap.Port()
|
||||||
|
for {
|
||||||
|
if port == 0 {
|
||||||
|
port = 33000
|
||||||
|
}
|
||||||
|
key := net.JoinHostPort(ap.Addr().String(), fmt.Sprint(port))
|
||||||
|
_, ok := m.lns[key]
|
||||||
|
if ok {
|
||||||
|
if ap.Port() != 0 {
|
||||||
|
return nil, fmt.Errorf("memNetwork: Listen called with duplicate address %q", address)
|
||||||
|
}
|
||||||
|
port++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ln := Listen(key)
|
||||||
|
m.lns[key] = ln
|
||||||
|
return ln, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Network) NewLocalTCPListener() net.Listener {
|
||||||
|
ln, err := m.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("memNetwork: failed to create local TCP listener: %v", err))
|
||||||
|
}
|
||||||
|
return ln
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Network) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
||||||
|
return nil, fmt.Errorf("memNetwork: Dial called with unsupported network %q", network)
|
||||||
|
}
|
||||||
|
m.mu.Lock()
|
||||||
|
ln, ok := m.lns[address]
|
||||||
|
m.mu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("memNetwork: Dial called on unknown address %q", address)
|
||||||
|
}
|
||||||
|
return ln.Dial(ctx, network, address)
|
||||||
|
}
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
// Copyright (c) Tailscale Inc & AUTHORS
|
// Copyright (c) Tailscale Inc & AUTHORS
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// Package netx contains the Network type to abstract over either a real
|
// Package netx contains types to describe and abstract over how dialing and
|
||||||
// network or a virtual network for testing.
|
// listening are performed.
|
||||||
package netx
|
package netx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"tailscale.com/net/memnet"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DialFunc is a function that dials a network address.
|
||||||
|
//
|
||||||
|
// It's the type implemented by net.Dialer.DialContext or required
|
||||||
|
// by net/http.Transport.DialContext, etc.
|
||||||
|
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
||||||
|
|
||||||
// Network describes a network that can listen and dial. The two common
|
// Network describes a network that can listen and dial. The two common
|
||||||
// implementations are [RealNetwork], using the net package to use the real
|
// implementations are [RealNetwork], using the net package to use the real
|
||||||
// network, or [MemNetwork], using an in-memory network (typically for testing)
|
// network, or [memnet.Network], using an in-memory network (typically for testing)
|
||||||
type Network interface {
|
type Network interface {
|
||||||
NewLocalTCPListener() net.Listener
|
NewLocalTCPListener() net.Listener
|
||||||
Listen(network, address string) (net.Listener, error)
|
Listen(network, address string) (net.Listener, error)
|
||||||
@ -44,77 +46,8 @@ func (realNetwork) NewLocalTCPListener() net.Listener {
|
|||||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ln, err = net.Listen("tcp6", "[::1]:0"); err != nil {
|
if ln, err = net.Listen("tcp6", "[::1]:0"); err != nil {
|
||||||
panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
|
panic(fmt.Sprintf("failed to listen on either IPv4 or IPv6 localhost port: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ln
|
return ln
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemNetwork returns a Network implementation that uses an in-memory
|
|
||||||
// network for testing. It is only suitable for tests that do not
|
|
||||||
// require real network access.
|
|
||||||
//
|
|
||||||
// As of 2025-04-08, it only supports TCP.
|
|
||||||
func MemNetwork() Network { return &memNetwork{} }
|
|
||||||
|
|
||||||
// memNetwork implements [Network] using an in-memory network.
|
|
||||||
type memNetwork struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
lns map[string]*memnet.Listener // address -> listener
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memNetwork) Listen(network, address string) (net.Listener, error) {
|
|
||||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
|
||||||
return nil, fmt.Errorf("memNetwork: Listen called with unsupported network %q", network)
|
|
||||||
}
|
|
||||||
ap, err := netip.ParseAddrPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("memNetwork: Listen called with invalid address %q: %w", address, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
|
|
||||||
if m.lns == nil {
|
|
||||||
m.lns = make(map[string]*memnet.Listener)
|
|
||||||
}
|
|
||||||
port := ap.Port()
|
|
||||||
for {
|
|
||||||
if port == 0 {
|
|
||||||
port = 33000
|
|
||||||
}
|
|
||||||
key := net.JoinHostPort(ap.Addr().String(), fmt.Sprint(port))
|
|
||||||
_, ok := m.lns[key]
|
|
||||||
if ok {
|
|
||||||
if ap.Port() != 0 {
|
|
||||||
return nil, fmt.Errorf("memNetwork: Listen called with duplicate address %q", address)
|
|
||||||
}
|
|
||||||
port++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ln := memnet.Listen(key)
|
|
||||||
m.lns[key] = ln
|
|
||||||
return ln, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memNetwork) NewLocalTCPListener() net.Listener {
|
|
||||||
ln, err := m.Listen("tcp", "127.0.0.1:0")
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("memNetwork: failed to create local TCP listener: %v", err))
|
|
||||||
}
|
|
||||||
return ln
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memNetwork) Dial(ctx context.Context, network, address string) (net.Conn, error) {
|
|
||||||
if network != "tcp" && network != "tcp4" && network != "tcp6" {
|
|
||||||
return nil, fmt.Errorf("memNetwork: Dial called with unsupported network %q", network)
|
|
||||||
}
|
|
||||||
m.mu.Lock()
|
|
||||||
ln, ok := m.lns[address]
|
|
||||||
m.mu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("memNetwork: Dial called on unknown address %q", address)
|
|
||||||
}
|
|
||||||
return ln.Dial(ctx, network, address)
|
|
||||||
}
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"tailscale.com/net/netknob"
|
"tailscale.com/net/netknob"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netns"
|
"tailscale.com/net/netns"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/netmap"
|
"tailscale.com/types/netmap"
|
||||||
@ -71,7 +72,7 @@ type Dialer struct {
|
|||||||
|
|
||||||
netnsDialerOnce sync.Once
|
netnsDialerOnce sync.Once
|
||||||
netnsDialer netns.Dialer
|
netnsDialer netns.Dialer
|
||||||
sysDialForTest func(_ context.Context, network, addr string) (net.Conn, error) // or nil
|
sysDialForTest netx.DialFunc // or nil
|
||||||
|
|
||||||
routes atomic.Pointer[bart.Table[bool]] // or nil if UserDial should not use routes. `true` indicates routes that point into the Tailscale interface
|
routes atomic.Pointer[bart.Table[bool]] // or nil if UserDial should not use routes. `true` indicates routes that point into the Tailscale interface
|
||||||
|
|
||||||
@ -364,7 +365,7 @@ func (d *Dialer) logf(format string, args ...any) {
|
|||||||
|
|
||||||
// SetSystemDialerForTest sets an alternate function to use for SystemDial
|
// SetSystemDialerForTest sets an alternate function to use for SystemDial
|
||||||
// instead of netns.Dialer. This is intended for use with nettest.MemoryNetwork.
|
// instead of netns.Dialer. This is intended for use with nettest.MemoryNetwork.
|
||||||
func (d *Dialer) SetSystemDialerForTest(fn func(ctx context.Context, network, addr string) (net.Conn, error)) {
|
func (d *Dialer) SetSystemDialerForTest(fn netx.DialFunc) {
|
||||||
testenv.AssertInTest()
|
testenv.AssertInTest()
|
||||||
d.sysDialForTest = fn
|
d.sysDialForTest = fn
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
"tailscale.com/util/httpm"
|
"tailscale.com/util/httpm"
|
||||||
"tailscale.com/util/multierr"
|
"tailscale.com/util/multierr"
|
||||||
@ -40,9 +41,6 @@ const (
|
|||||||
// in tests.
|
// in tests.
|
||||||
var uploadAckWindow = 30 * time.Second
|
var uploadAckWindow = 30 * time.Second
|
||||||
|
|
||||||
// DialFunc is a function for dialing the recorder.
|
|
||||||
type DialFunc func(ctx context.Context, network, host string) (net.Conn, error)
|
|
||||||
|
|
||||||
// ConnectToRecorder connects to the recorder at any of the provided addresses.
|
// ConnectToRecorder connects to the recorder at any of the provided addresses.
|
||||||
// It returns the first successful response, or a multierr if all attempts fail.
|
// It returns the first successful response, or a multierr if all attempts fail.
|
||||||
//
|
//
|
||||||
@ -55,7 +53,7 @@ type DialFunc func(ctx context.Context, network, host string) (net.Conn, error)
|
|||||||
// attempts are in order the recorder(s) was attempted. If successful a
|
// attempts are in order the recorder(s) was attempted. If successful a
|
||||||
// successful connection is made, the last attempt in the slice is the
|
// successful connection is made, the last attempt in the slice is the
|
||||||
// attempt for connected recorder.
|
// attempt for connected recorder.
|
||||||
func ConnectToRecorder(ctx context.Context, recs []netip.AddrPort, dial DialFunc) (io.WriteCloser, []*tailcfg.SSHRecordingAttempt, <-chan error, error) {
|
func ConnectToRecorder(ctx context.Context, recs []netip.AddrPort, dial netx.DialFunc) (io.WriteCloser, []*tailcfg.SSHRecordingAttempt, <-chan error, error) {
|
||||||
if len(recs) == 0 {
|
if len(recs) == 0 {
|
||||||
return nil, nil, nil, errors.New("no recorders configured")
|
return nil, nil, nil, errors.New("no recorders configured")
|
||||||
}
|
}
|
||||||
@ -293,7 +291,7 @@ func (u *readCounter) Read(buf []byte) (int, error) {
|
|||||||
|
|
||||||
// clientHTTP1 returns a claassic http.Client with a per-dial context. It uses
|
// clientHTTP1 returns a claassic http.Client with a per-dial context. It uses
|
||||||
// dialCtx and adds a 5s timeout to it.
|
// dialCtx and adds a 5s timeout to it.
|
||||||
func clientHTTP1(dialCtx context.Context, dial DialFunc) *http.Client {
|
func clientHTTP1(dialCtx context.Context, dial netx.DialFunc) *http.Client {
|
||||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
perAttemptCtx, cancel := context.WithTimeout(ctx, perDialAttemptTimeout)
|
perAttemptCtx, cancel := context.WithTimeout(ctx, perDialAttemptTimeout)
|
||||||
@ -313,7 +311,7 @@ func clientHTTP1(dialCtx context.Context, dial DialFunc) *http.Client {
|
|||||||
// clientHTTP2 is like clientHTTP1 but returns an http.Client suitable for h2c
|
// clientHTTP2 is like clientHTTP1 but returns an http.Client suitable for h2c
|
||||||
// requests (HTTP/2 over plaintext). Unfortunately the same client does not
|
// requests (HTTP/2 over plaintext). Unfortunately the same client does not
|
||||||
// work for HTTP/1 so we need to split these up.
|
// work for HTTP/1 so we need to split these up.
|
||||||
func clientHTTP2(dialCtx context.Context, dial DialFunc) *http.Client {
|
func clientHTTP2(dialCtx context.Context, dial netx.DialFunc) *http.Client {
|
||||||
return &http.Client{
|
return &http.Client{
|
||||||
Transport: &http2.Transport{
|
Transport: &http2.Transport{
|
||||||
// Allow "http://" scheme in URLs.
|
// Allow "http://" scheme in URLs.
|
||||||
|
@ -54,6 +54,7 @@ import (
|
|||||||
"tailscale.com/derp"
|
"tailscale.com/derp"
|
||||||
"tailscale.com/derp/derphttp"
|
"tailscale.com/derp/derphttp"
|
||||||
"tailscale.com/net/netutil"
|
"tailscale.com/net/netutil"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/stun"
|
"tailscale.com/net/stun"
|
||||||
"tailscale.com/syncs"
|
"tailscale.com/syncs"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -649,7 +650,7 @@ type Server struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
agentConnWaiter map[*node]chan<- struct{} // signaled after added to set
|
agentConnWaiter map[*node]chan<- struct{} // signaled after added to set
|
||||||
agentConns set.Set[*agentConn] // not keyed by node; should be small/cheap enough to scan all
|
agentConns set.Set[*agentConn] // not keyed by node; should be small/cheap enough to scan all
|
||||||
agentDialer map[*node]DialFunc
|
agentDialer map[*node]netx.DialFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) logf(format string, args ...any) {
|
func (s *Server) logf(format string, args ...any) {
|
||||||
@ -664,8 +665,6 @@ func (s *Server) SetLoggerForTest(logf func(format string, args ...any)) {
|
|||||||
s.optLogf = logf
|
s.optLogf = logf
|
||||||
}
|
}
|
||||||
|
|
||||||
type DialFunc func(ctx context.Context, network, address string) (net.Conn, error)
|
|
||||||
|
|
||||||
var derpMap = &tailcfg.DERPMap{
|
var derpMap = &tailcfg.DERPMap{
|
||||||
Regions: map[int]*tailcfg.DERPRegion{
|
Regions: map[int]*tailcfg.DERPRegion{
|
||||||
1: {
|
1: {
|
||||||
@ -2130,7 +2129,7 @@ type NodeAgentClient struct {
|
|||||||
HTTPClient *http.Client
|
HTTPClient *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) NodeAgentDialer(n *Node) DialFunc {
|
func (s *Server) NodeAgentDialer(n *Node) netx.DialFunc {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"tailscale.com/net/memnet"
|
||||||
"tailscale.com/net/netmon"
|
"tailscale.com/net/netmon"
|
||||||
"tailscale.com/net/netx"
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/util/testenv"
|
"tailscale.com/util/testenv"
|
||||||
@ -42,7 +43,7 @@ func PreferMemNetwork() bool {
|
|||||||
func GetNetwork(tb testing.TB) netx.Network {
|
func GetNetwork(tb testing.TB) netx.Network {
|
||||||
var n netx.Network
|
var n netx.Network
|
||||||
if PreferMemNetwork() {
|
if PreferMemNetwork() {
|
||||||
n = netx.MemNetwork()
|
n = &memnet.Network{}
|
||||||
} else {
|
} else {
|
||||||
n = netx.RealNetwork()
|
n = netx.RealNetwork()
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,7 @@ import (
|
|||||||
"tailscale.com/net/dns"
|
"tailscale.com/net/dns"
|
||||||
"tailscale.com/net/ipset"
|
"tailscale.com/net/ipset"
|
||||||
"tailscale.com/net/netaddr"
|
"tailscale.com/net/netaddr"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
@ -208,7 +209,7 @@ type Impl struct {
|
|||||||
// TCP connection to another host (e.g. in subnet router mode).
|
// TCP connection to another host (e.g. in subnet router mode).
|
||||||
//
|
//
|
||||||
// This is currently only used in tests.
|
// This is currently only used in tests.
|
||||||
forwardDialFunc func(context.Context, string, string) (net.Conn, error)
|
forwardDialFunc netx.DialFunc
|
||||||
|
|
||||||
// forwardInFlightPerClientDropped is a metric that tracks how many
|
// forwardInFlightPerClientDropped is a metric that tracks how many
|
||||||
// in-flight TCP forward requests were dropped due to the per-client
|
// in-flight TCP forward requests were dropped due to the per-client
|
||||||
@ -1457,7 +1458,7 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet.
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Attempt to dial the outbound connection before we accept the inbound one.
|
// Attempt to dial the outbound connection before we accept the inbound one.
|
||||||
var dialFunc func(context.Context, string, string) (net.Conn, error)
|
var dialFunc netx.DialFunc
|
||||||
if ns.forwardDialFunc != nil {
|
if ns.forwardDialFunc != nil {
|
||||||
dialFunc = ns.forwardDialFunc
|
dialFunc = ns.forwardDialFunc
|
||||||
} else {
|
} else {
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"tailscale.com/ipn/ipnlocal"
|
"tailscale.com/ipn/ipnlocal"
|
||||||
"tailscale.com/ipn/store/mem"
|
"tailscale.com/ipn/store/mem"
|
||||||
"tailscale.com/metrics"
|
"tailscale.com/metrics"
|
||||||
|
"tailscale.com/net/netx"
|
||||||
"tailscale.com/net/packet"
|
"tailscale.com/net/packet"
|
||||||
"tailscale.com/net/tsaddr"
|
"tailscale.com/net/tsaddr"
|
||||||
"tailscale.com/net/tsdial"
|
"tailscale.com/net/tsdial"
|
||||||
@ -512,7 +513,7 @@ func tcp4syn(tb testing.TB, src, dst netip.Addr, sport, dport uint16) []byte {
|
|||||||
|
|
||||||
// makeHangDialer returns a dialer that notifies the returned channel when a
|
// makeHangDialer returns a dialer that notifies the returned channel when a
|
||||||
// connection is dialed and then hangs until the test finishes.
|
// connection is dialed and then hangs until the test finishes.
|
||||||
func makeHangDialer(tb testing.TB) (func(context.Context, string, string) (net.Conn, error), chan struct{}) {
|
func makeHangDialer(tb testing.TB) (netx.DialFunc, chan struct{}) {
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
tb.Cleanup(func() {
|
tb.Cleanup(func() {
|
||||||
close(done)
|
close(done)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user