Compare commits

...

18 Commits

Author SHA1 Message Date
Denton Gentry
a5b1456410 VERSION.txt: this is v1.14.3
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-16 16:56:31 -07:00
Denton Gentry
27d0e7cb0a net/dnsfallback: add DERP servers
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-13 20:46:41 -07:00
Denton Gentry
f0b70ff186 Revert "net/dnsfallback: add DERP servers"
This reverts commit f5d17dae18.
2021-09-13 20:46:31 -07:00
Denton Gentry
f5d17dae18 net/dnsfallback: add DERP servers 2021-09-13 18:24:53 -07:00
Denton Gentry
ceaecdd4d5 Revert back to pre-1.14.1 build.
This reverts the following commits:
8704fb308d
afb95d7246
277bf8f48c
c995ac72a3
e699226e80

We're going to try again to build 1.14.1

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-13 18:23:54 -07:00
Denton Gentry
8704fb308d VERSION.txt: this is v1.14.2
I had updated VERSION.txt to 1.14.1, tagged and pushed the tag, then
noticed that continuous integration failed because the list of DERP
servers for emergency DNS fallback needed to be updated.

I tried to revert VERSION.txt, delete the tag, update DERP, and tag
again but it won't import in the next step because the checksum is
wrong. I had deleted the tag and moved it, and something out there
wants none of that funny business.

Honestly I'm kindof glad, gives confidence in the integrity of
the tree.

ANYWAY, 1.14.1 is unusable and we're going to 1.14.2.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-09 19:33:30 -07:00
Denton Gentry
afb95d7246 VERSION.txt: this is v1.14.1
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-09 19:27:15 -07:00
Denton Gentry
277bf8f48c net/dnsfallback: add DERP servers 2021-09-09 19:26:18 -07:00
Denton Gentry
c995ac72a3 Revert "VERSION.txt: this is v1.14.1"
Nevermind, that was not 1.14.1 after all
This reverts commit e699226e80.

Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-09 19:25:37 -07:00
Denton Gentry
e699226e80 VERSION.txt: this is v1.14.1
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-09-09 19:20:38 -07:00
Brad Fitzpatrick
d8e37edb40 safesocket: add connect retry loop to wait for tailscaled
Updates #2708

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 21cb0b361f)
2021-09-01 11:54:21 -07:00
Joe Tsai
0744d75238 tstime/rate: deflake TestLongRunningQPS
This test is highly dependent on the accuracy of OS timers.
Reduce the number of failures by decreasing the required
accuracy from 0.999 to 0.995.
Also, switch from repeated time.Sleep to using a time.Ticker
for improved accuracy.

Updates #2727

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
(cherry picked from commit 30458c71c8)
2021-08-30 12:20:31 -07:00
Joe Tsai
15835f03b3 util/deephash: fix TestArrayAllocs
Unfortunately this test fails on certain architectures.
The problem comes down to inconsistencies in the Go escape analysis
where specific variables are marked as escaping on certain architectures.
The variables escaping to the heap are unfortunately in crypto/sha256,
which makes it impossible to fixthis locally in deephash.

For now, fix the test by compensating for the allocations that
occur from calling sha256.digest.Sum.

See golang/go#48055

Fixes #2727

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
(cherry picked from commit 3f1317e3e5)
2021-08-30 12:19:30 -07:00
Brad Fitzpatrick
e78ac523da net/interfaces: fix default route lookup on Windows
It wasn't using the right metric. Apparently you're supposed to sum the route
metric and interface metric. Whoops.

While here, optimize a few little things too, not that this code
should be too hot.

Fixes #2707 (at least; probably dups but I'm failing to find)

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 3606e68721)
2021-08-26 14:51:47 -07:00
David Anderson
15c87017b8 net/portmapper: fix "running a test" condition.
Fixes #2686.

Signed-off-by: David Anderson <danderson@tailscale.com>
(cherry picked from commit b49d9bc74d)
2021-08-25 22:06:49 -07:00
Brad Fitzpatrick
bd911fdb12 wgengine/netstack: fix crash in userspace netstack TCP forwarding
Fixes #2658

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 1925fb584e)
2021-08-25 15:48:44 -07:00
Brad Fitzpatrick
0111d33eb8 tailcfg,ipn/ipnlocal: support DNSConfig.Routes with empty values [mapver 23]
Fixes #2706
Updates #1235

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
(cherry picked from commit 88bd796622)
2021-08-25 11:42:10 -07:00
Denton Gentry
62a458f7f4 VERSION.txt: this is v1.14.0
Signed-off-by: Denton Gentry <dgentry@tailscale.com>
2021-08-23 13:36:49 -07:00
13 changed files with 281 additions and 36 deletions

View File

@@ -1 +1 @@
1.13.0
1.14.3

View File

@@ -4,7 +4,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W github.com/alexbrainman/sspi/internal/common from github.com/alexbrainman/sspi/negotiate
W 💣 github.com/alexbrainman/sspi/negotiate from tailscale.com/net/tshttpproxy
github.com/kballard/go-shellquote from tailscale.com/cmd/tailscale/cli
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli
💣 github.com/mitchellh/go-ps from tailscale.com/cmd/tailscale/cli+
github.com/peterbourgon/ff/v2 from github.com/peterbourgon/ff/v2/ffcli
github.com/peterbourgon/ff/v2/ffcli from tailscale.com/cmd/tailscale/cli
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+

View File

@@ -25,6 +25,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
L 💣 github.com/mdlayher/netlink/nlenc from github.com/mdlayher/netlink+
L github.com/mdlayher/sdnotify from tailscale.com/util/systemd
L 💣 github.com/mdlayher/socket from github.com/mdlayher/netlink
💣 github.com/mitchellh/go-ps from tailscale.com/safesocket
W github.com/pkg/errors from github.com/tailscale/certstore
W 💣 github.com/tailscale/certstore from tailscale.com/control/controlclient
github.com/tailscale/goupnp from github.com/tailscale/goupnp/dcps/internetgateway2+

View File

@@ -1837,6 +1837,17 @@ func (b *LocalBackend) authReconfig() {
if err != nil {
b.logf("[unexpected] non-FQDN route suffix %q", suffix)
}
// Create map entry even if len(resolvers) == 0; Issue 2706.
// This lets the control plane send ExtraRecords for which we
// can authoritatively answer "name not exists" for when the
// control plane also sends this explicit but empty route
// making it as something we handle.
//
// While we're already populating it, might as well size the
// slice appropriately.
dcfg.Routes[fqdn] = make([]netaddr.IPPort, 0, len(resolvers))
for _, resolver := range resolvers {
res, err := parseResolver(resolver)
if err != nil {

View File

@@ -49,6 +49,34 @@
}
]
},
"12": {
"RegionID": 12,
"RegionCode": "r12",
"RegionName": "r12",
"Nodes": [
{
"Name": "12a",
"RegionID": 12,
"HostName": "derp12.tailscale.com",
"IPv4": "216.128.144.130",
"IPv6": "2001:19f0:5c01:289:5400:3ff:fe8d:cb5e"
},
{
"Name": "12b",
"RegionID": 12,
"HostName": "derp12b.tailscale.com",
"IPv4": "45.63.71.144",
"IPv6": "2001:19f0:5c01:48a:5400:3ff:fe8d:cb5f"
},
{
"Name": "12c",
"RegionID": 12,
"HostName": "derp12c.tailscale.com",
"IPv4": "149.28.119.105",
"IPv6": "2001:19f0:5c01:2cb:5400:3ff:fe8d:cb60"
}
]
},
"2": {
"RegionID": 2,
"RegionCode": "r2",
@@ -193,6 +221,20 @@
"HostName": "derp9.tailscale.com",
"IPv4": "207.148.3.137",
"IPv6": "2001:19f0:6401:1d9c:5400:2ff:feef:bb82"
},
{
"Name": "9b",
"RegionID": 9,
"HostName": "derp9b.tailscale.com",
"IPv4": "144.202.67.195",
"IPv6": "2001:19f0:6401:eb5:5400:3ff:fe8d:6d9b"
},
{
"Name": "9c",
"RegionID": 9,
"HostName": "derp9c.tailscale.com",
"IPv4": "155.138.243.219",
"IPv6": "2001:19f0:6401:fe7:5400:3ff:fe8d:6d9c"
}
]
}

View File

@@ -112,22 +112,36 @@ func NonTailscaleMTUs() (map[winipcfg.LUID]uint32, error) {
return mtus, err
}
func notTailscaleInterface(iface *winipcfg.IPAdapterAddresses) bool {
// TODO(bradfitz): do this without the Description method's
// utf16-to-string allocation. But at least we only do it for
// the virtual interfaces, for which there won't be many.
return !(iface.IfType == winipcfg.IfTypePropVirtual &&
iface.Description() == tsconst.WintunInterfaceDesc)
}
// NonTailscaleInterfaces returns a map of interface LUID to interface
// for all interfaces except Tailscale tunnels.
func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
ifs, err := winipcfg.GetAdaptersAddresses(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces)
return getInterfaces(windows.AF_UNSPEC, winipcfg.GAAFlagIncludeAllInterfaces, notTailscaleInterface)
}
// getInterfaces returns a map of interfaces keyed by their LUID for
// all interfaces matching the provided match predicate.
//
// The family (AF_UNSPEC, AF_INET, or AF_INET6) and flags are passed
// to winipcfg.GetAdaptersAddresses.
func getInterfaces(family winipcfg.AddressFamily, flags winipcfg.GAAFlags, match func(*winipcfg.IPAdapterAddresses) bool) (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, error) {
ifs, err := winipcfg.GetAdaptersAddresses(family, flags)
if err != nil {
return nil, err
}
ret := map[winipcfg.LUID]*winipcfg.IPAdapterAddresses{}
for _, iface := range ifs {
if iface.Description() == tsconst.WintunInterfaceDesc {
continue
if match(iface) {
ret[iface.LUID] = iface
}
ret[iface.LUID] = iface
}
return ret, nil
}
@@ -135,8 +149,26 @@ func NonTailscaleInterfaces() (map[winipcfg.LUID]*winipcfg.IPAdapterAddresses, e
// default route for the given address family.
//
// It returns (nil, nil) if no interface is found.
//
// The family must be one of AF_INET or AF_INET6.
func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddresses, error) {
ifs, err := NonTailscaleInterfaces()
ifs, err := getInterfaces(family, winipcfg.GAAFlagIncludeAllInterfaces, func(iface *winipcfg.IPAdapterAddresses) bool {
switch iface.IfType {
case winipcfg.IfTypeSoftwareLoopback:
return false
}
switch family {
case windows.AF_INET:
if iface.Flags&winipcfg.IPAAFlagIpv4Enabled == 0 {
return false
}
case windows.AF_INET6:
if iface.Flags&winipcfg.IPAAFlagIpv6Enabled == 0 {
return false
}
}
return iface.OperStatus == winipcfg.IfOperStatusUp && notTailscaleInterface(iface)
})
if err != nil {
return nil, err
}
@@ -149,12 +181,31 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
bestMetric := ^uint32(0)
var bestIface *winipcfg.IPAdapterAddresses
for _, route := range routes {
iface := ifs[route.InterfaceLUID]
if route.DestinationPrefix.PrefixLength != 0 || iface == nil {
if route.DestinationPrefix.PrefixLength != 0 {
// Not a default route.
continue
}
if iface.OperStatus == winipcfg.IfOperStatusUp && route.Metric < bestMetric {
bestMetric = route.Metric
iface := ifs[route.InterfaceLUID]
if iface == nil {
continue
}
// Microsoft docs say:
//
// "The actual route metric used to compute the route
// preferences for IPv4 is the summation of the route
// metric offset specified in the Metric member of the
// MIB_IPFORWARD_ROW2 structure and the interface
// metric specified in this member for IPv4"
metric := route.Metric
switch family {
case windows.AF_INET:
metric += iface.Ipv4Metric
case windows.AF_INET6:
metric += iface.Ipv6Metric
}
if metric < bestMetric {
bestMetric = metric
bestIface = iface
}
}
@@ -163,6 +214,9 @@ func GetWindowsDefault(family winipcfg.AddressFamily) (*winipcfg.IPAdapterAddres
}
func DefaultRouteInterface() (string, error) {
// We always return the IPv4 default route.
// TODO(bradfitz): adjust API if/when anything cares. They could in theory differ, though,
// in which case we might send traffic to the wrong interface.
iface, err := GetWindowsDefault(windows.AF_INET)
if err != nil {
return "", err

View File

@@ -236,7 +236,19 @@ func (c *Client) upnpPort() uint16 {
}
func (c *Client) listenPacket(ctx context.Context, network, addr string) (net.PacketConn, error) {
if (c.testPxPPort != 0 || c.testUPnPPort != 0) && os.Getenv("GITHUB_ACTIONS") == "true" {
// When running under testing conditions, we bind the IGD server
// to localhost, and may be running in an environment where our
// netns code would decide that binding the portmapper client
// socket to the default route interface is the correct way to
// ensure connectivity. This can result in us trying to send
// packets for 127.0.0.1 out the machine's LAN interface, which
// obviously gets dropped on the floor.
//
// So, under those testing conditions, do _not_ use netns to
// create listening sockets. Such sockets are vulnerable to
// routing loops, but it's tests that don't set up routing loops,
// so we don't care.
if c.testPxPPort != 0 || c.testUPnPPort != 0 || os.Getenv("GITHUB_ACTIONS") == "true" {
var lc net.ListenConfig
return lc.ListenPacket(ctx, network, addr)
}

View File

@@ -10,6 +10,7 @@ import (
"errors"
"net"
"runtime"
"time"
)
type closeable interface {
@@ -29,9 +30,39 @@ func ConnCloseWrite(c net.Conn) error {
return c.(closeable).CloseWrite()
}
var processStartTime = time.Now()
var tailscaledProcExists = func() bool { return false } // set by safesocket_ps.go
// tailscaledStillStarting reports whether tailscaled is probably
// still starting up. That is, it reports whether the caller should
// keep retrying to connect.
func tailscaledStillStarting() bool {
d := time.Since(processStartTime)
if d < 2*time.Second {
// Without even checking the process table, assume
// that for the first two seconds that tailscaled is
// probably still starting. That is, assume they're
// running "tailscaled & tailscale up ...." and make
// the tailscale client block for a bit for tailscaled
// to start accepting on the socket.
return true
}
if d > 5*time.Second {
return false
}
return tailscaledProcExists()
}
// Connect connects to either path (on Unix) or the provided localhost port (on Windows).
func Connect(path string, port uint16) (net.Conn, error) {
return connect(path, port)
for {
c, err := connect(path, port)
if err != nil && tailscaledStillStarting() {
time.Sleep(250 * time.Millisecond)
continue
}
return c, err
}
}
// Listen returns a listener either on Unix socket path (on Unix), or

View File

@@ -0,0 +1,37 @@
// Copyright (c) 2021 Tailscale Inc & 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 linux || windows || darwin || freebsd
// +build linux windows darwin freebsd
package safesocket
import (
"strings"
ps "github.com/mitchellh/go-ps"
)
func init() {
tailscaledProcExists = func() bool {
procs, err := ps.Processes()
if err != nil {
return false
}
for _, proc := range procs {
name := proc.Executable()
const tailscaled = "tailscaled"
if len(name) < len(tailscaled) {
continue
}
// Do case insensitive comparison for Windows,
// notably, and ignore any ".exe" suffix.
if strings.EqualFold(name[:len(tailscaled)], tailscaled) {
return true
}
}
return false
}
}

View File

@@ -46,7 +46,8 @@ import (
// 20: 2021-06-11: MapResponse.LastSeen used even less (https://github.com/tailscale/tailscale/issues/2107)
// 21: 2021-06-15: added MapResponse.DNSConfig.CertDomains
// 22: 2021-06-16: added MapResponse.DNSConfig.ExtraRecords
const CurrentMapRequestVersion = 22
// 23: 2021-08-25: DNSConfig.Routes values may be empty (for ExtraRecords support in 1.14.1+)
const CurrentMapRequestVersion = 23
type StableID string
@@ -837,12 +838,19 @@ var FilterAllowAll = []FilterRule{
type DNSConfig struct {
// Resolvers are the DNS resolvers to use, in order of preference.
Resolvers []dnstype.Resolver `json:",omitempty"`
// Routes maps DNS name suffixes to a set of DNS resolvers to
// use. It is used to implement "split DNS" and other advanced DNS
// routing overlays.
// Map keys must be fully-qualified DNS name suffixes, with a
// trailing dot but no leading dot.
//
// Map keys are fully-qualified DNS name suffixes; they may
// optionally contain a trailing dot but no leading dot.
//
// If the value is an empty slice, that means the suffix should still
// be handled by Tailscale's built-in resolver (100.100.100.100), such
// as for the purpose of handling ExtraRecords.
Routes map[string][]dnstype.Resolver `json:",omitempty"`
// FallbackResolvers is like Resolvers, but is only used if a
// split DNS configuration is requested in a configuration that
// doesn't work yet without explicit default resolvers.

View File

@@ -182,15 +182,18 @@ func TestLongRunningQPS(t *testing.T) {
wg.Done()
}
// This will still offer ~500 requests per second,
// but won't consume outrageous amount of CPU.
start := time.Now()
end := start.Add(5 * time.Second)
for time.Now().Before(end) {
ticker := time.NewTicker(2 * time.Millisecond)
defer ticker.Stop()
for now := range ticker.C {
if now.After(end) {
break
}
wg.Add(1)
go f()
// This will still offer ~500 requests per second, but won't consume
// outrageous amount of CPU.
time.Sleep(2 * time.Millisecond)
}
wg.Wait()
elapsed := time.Since(start)
@@ -201,7 +204,7 @@ func TestLongRunningQPS(t *testing.T) {
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
}
// We should get very close to the number of requests allowed.
if want := int32(0.999 * ideal); numOK < want {
if want := int32(0.995 * ideal); numOK < want {
t.Errorf("numOK = %d, want %d (ideal %f)", numOK, want, ideal)
}
}

View File

@@ -8,9 +8,11 @@ import (
"archive/tar"
"bufio"
"bytes"
"crypto/sha256"
"fmt"
"math"
"reflect"
"runtime"
"testing"
"inet.af/netaddr"
@@ -332,15 +334,32 @@ func TestArrayAllocs(t *testing.T) {
if version.IsRace() {
t.Skip("skipping test under race detector")
}
// In theory, there should be no allocations. However, escape analysis on
// certain architectures fails to detect that certain cases do not escape.
// This discrepency currently affects sha256.digest.Sum.
// Measure the number of allocations in sha256 to ensure that Hash does
// not allocate on top of its usage of sha256.
// See https://golang.org/issue/48055.
var b []byte
h := sha256.New()
want := int(testing.AllocsPerRun(1000, func() {
b = h.Sum(b[:0])
}))
switch runtime.GOARCH {
case "amd64", "arm64":
want = 0 // ensure no allocations on popular architectures
}
type T struct {
X [32]byte
}
x := &T{X: [32]byte{1: 1, 2: 2, 3: 3, 4: 4}}
n := int(testing.AllocsPerRun(1000, func() {
got := int(testing.AllocsPerRun(1000, func() {
sink = Hash(x)
}))
if n > 0 {
t.Errorf("allocs = %v; want 0", n)
if got > want {
t.Errorf("allocs = %v; want %v", got, want)
}
}

View File

@@ -468,19 +468,37 @@ func (ns *Impl) injectInbound(p *packet.Parsed, t *tstun.Wrapper) filter.Respons
return filter.DropSilently
}
func netaddrIPFromNetstackIP(s tcpip.Address) netaddr.IP {
switch len(s) {
case 4:
return netaddr.IPv4(s[0], s[1], s[2], s[3])
case 16:
var a [16]byte
copy(a[:], s)
return netaddr.IPFrom16(a)
}
return netaddr.IP{}
}
func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
reqDetails := r.ID()
if debugNetstack {
ns.logf("[v2] TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
}
dialAddr := reqDetails.LocalAddress
dialNetAddr, _ := netaddr.FromStdIP(net.IP(dialAddr))
isTailscaleIP := tsaddr.IsTailscaleIP(dialNetAddr)
clientRemoteIP := netaddrIPFromNetstackIP(reqDetails.RemoteAddress)
if !clientRemoteIP.IsValid() {
ns.logf("invalid RemoteAddress in TCP ForwarderRequest: %s", stringifyTEI(reqDetails))
r.Complete(true)
return
}
dialIP := netaddrIPFromNetstackIP(reqDetails.LocalAddress)
isTailscaleIP := tsaddr.IsTailscaleIP(dialIP)
defer func() {
if !isTailscaleIP {
// if this is a subnet IP, we added this in before the TCP handshake
// so netstack is happy TCP-handshaking as a subnet IP
ns.removeSubnetAddress(dialNetAddr)
ns.removeSubnetAddress(dialIP)
}
}()
var wq waiter.Queue
@@ -490,21 +508,31 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
return
}
r.Complete(false)
// Asynchronously start the TCP handshake. Note that the
// gonet.TCPConn methods c.RemoteAddr() and c.LocalAddr() will
// return nil until the handshake actually completes. But we
// have the remote address in reqDetails instead, so we don't
// use RemoteAddr. The byte copies in both directions in
// forwardTCP will block until the TCP handshake is complete.
c := gonet.NewTCPConn(&wq, ep)
if ns.ForwardTCPIn != nil {
ns.ForwardTCPIn(c, reqDetails.LocalPort)
return
}
if isTailscaleIP {
dialAddr = tcpip.Address(net.ParseIP("127.0.0.1")).To4()
dialIP = netaddr.IPv4(127, 0, 0, 1)
}
ns.forwardTCP(c, &wq, dialAddr, reqDetails.LocalPort)
dialAddr := netaddr.IPPortFrom(dialIP, uint16(reqDetails.LocalPort))
ns.forwardTCP(c, clientRemoteIP, &wq, dialAddr)
}
func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcpip.Address, dialPort uint16) {
func (ns *Impl) forwardTCP(client *gonet.TCPConn, clientRemoteIP netaddr.IP, wq *waiter.Queue, dialAddr netaddr.IPPort) {
defer client.Close()
dialAddrStr := net.JoinHostPort(dialAddr.String(), strconv.Itoa(int(dialPort)))
dialAddrStr := dialAddr.String()
ns.logf("[v2] netstack: forwarding incoming connection to %s", dialAddrStr)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
waitEntry, notifyCh := waiter.NewChannelEntry(nil)
@@ -530,7 +558,6 @@ func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcp
defer server.Close()
backendLocalAddr := server.LocalAddr().(*net.TCPAddr)
backendLocalIPPort, _ := netaddr.FromStdAddr(backendLocalAddr.IP, backendLocalAddr.Port, backendLocalAddr.Zone)
clientRemoteIP, _ := netaddr.FromStdIP(client.RemoteAddr().(*net.TCPAddr).IP)
ns.e.RegisterIPPortIdentity(backendLocalIPPort, clientRemoteIP)
defer ns.e.UnregisterIPPortIdentity(backendLocalIPPort)
connClosed := make(chan error, 2)