github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/tcpip/stack/conntrack.go (about)

     1  // Copyright 2020 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package stack
    16  
    17  import (
    18  	"encoding/binary"
    19  	"fmt"
    20  	"math"
    21  	"math/rand"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/ttpreport/gvisor-ligolo/pkg/atomicbitops"
    26  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip"
    27  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/hash/jenkins"
    28  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/header"
    29  	"github.com/ttpreport/gvisor-ligolo/pkg/tcpip/transport/tcpconntrack"
    30  )
    31  
    32  // Connection tracking is used to track and manipulate packets for NAT rules.
    33  // The connection is created for a packet if it does not exist. Every
    34  // connection contains two tuples (original and reply). The tuples are
    35  // manipulated if there is a matching NAT rule. The packet is modified by
    36  // looking at the tuples in each hook.
    37  //
    38  // Currently, only TCP tracking is supported.
    39  
    40  // Our hash table has 16K buckets.
    41  const numBuckets = 1 << 14
    42  
    43  const (
    44  	establishedTimeout   time.Duration = 5 * 24 * time.Hour
    45  	unestablishedTimeout time.Duration = 120 * time.Second
    46  )
    47  
    48  // tuple holds a connection's identifying and manipulating data in one
    49  // direction. It is immutable.
    50  //
    51  // +stateify savable
    52  type tuple struct {
    53  	// tupleEntry is used to build an intrusive list of tuples.
    54  	tupleEntry
    55  
    56  	// conn is the connection tracking entry this tuple belongs to.
    57  	conn *conn
    58  
    59  	// reply is true iff the tuple's direction is opposite that of the first
    60  	// packet seen on the connection.
    61  	reply bool
    62  
    63  	// tupleID is set at initialization and is immutable.
    64  	tupleID tupleID
    65  }
    66  
    67  // tupleID uniquely identifies a trackable connection in one direction.
    68  //
    69  // +stateify savable
    70  type tupleID struct {
    71  	srcAddr tcpip.Address
    72  	// The source port of a packet in the original direction is overloaded with
    73  	// the ident of an Echo Request packet.
    74  	//
    75  	// This also matches the behaviour of sending packets on Linux where the
    76  	// socket's source port value is used for the source port of outgoing packets
    77  	// for TCP/UDP and the ident field for outgoing Echo Requests on Ping sockets:
    78  	//
    79  	//   IPv4: https://github.com/torvalds/linux/blob/c5c17547b778975b3d83a73c8d84e8fb5ecf3ba5/net/ipv4/ping.c#L810
    80  	//   IPv6: https://github.com/torvalds/linux/blob/c5c17547b778975b3d83a73c8d84e8fb5ecf3ba5/net/ipv6/ping.c#L133
    81  	srcPortOrEchoRequestIdent uint16
    82  	dstAddr                   tcpip.Address
    83  	// The opposite of srcPortOrEchoRequestIdent; the destination port of a packet
    84  	// in the reply direction is overloaded with the ident of an Echo Reply.
    85  	dstPortOrEchoReplyIdent uint16
    86  	transProto              tcpip.TransportProtocolNumber
    87  	netProto                tcpip.NetworkProtocolNumber
    88  }
    89  
    90  // reply creates the reply tupleID.
    91  func (ti tupleID) reply() tupleID {
    92  	return tupleID{
    93  		srcAddr:                   ti.dstAddr,
    94  		srcPortOrEchoRequestIdent: ti.dstPortOrEchoReplyIdent,
    95  		dstAddr:                   ti.srcAddr,
    96  		dstPortOrEchoReplyIdent:   ti.srcPortOrEchoRequestIdent,
    97  		transProto:                ti.transProto,
    98  		netProto:                  ti.netProto,
    99  	}
   100  }
   101  
   102  type manipType int
   103  
   104  const (
   105  	// manipNotPerformed indicates that NAT has not been performed.
   106  	manipNotPerformed manipType = iota
   107  
   108  	// manipPerformed indicates that NAT was performed.
   109  	manipPerformed
   110  
   111  	// manipPerformedNoop indicates that NAT was performed but it was a no-op.
   112  	manipPerformedNoop
   113  )
   114  
   115  type finalizeResult uint32
   116  
   117  const (
   118  	// A finalizeResult must be explicitly set so we don't make use of the zero
   119  	// value.
   120  	_ finalizeResult = iota
   121  
   122  	finalizeResultSuccess
   123  	finalizeResultConflict
   124  )
   125  
   126  // conn is a tracked connection.
   127  //
   128  // +stateify savable
   129  type conn struct {
   130  	ct *ConnTrack
   131  
   132  	// original is the tuple in original direction. It is immutable.
   133  	original tuple
   134  
   135  	// reply is the tuple in reply direction.
   136  	reply tuple
   137  
   138  	finalizeOnce sync.Once
   139  	// Holds a finalizeResult.
   140  	finalizeResult atomicbitops.Uint32
   141  
   142  	mu connRWMutex `state:"nosave"`
   143  	// sourceManip indicates the source manipulation type.
   144  	//
   145  	// +checklocks:mu
   146  	sourceManip manipType
   147  	// destinationManip indicates the destination's manipulation type.
   148  	//
   149  	// +checklocks:mu
   150  	destinationManip manipType
   151  
   152  	stateMu stateConnRWMutex `state:"nosave"`
   153  	// tcb is TCB control block. It is used to keep track of states
   154  	// of tcp connection.
   155  	//
   156  	// +checklocks:stateMu
   157  	tcb tcpconntrack.TCB
   158  	// lastUsed is the last time the connection saw a relevant packet, and
   159  	// is updated by each packet on the connection.
   160  	//
   161  	// +checklocks:stateMu
   162  	lastUsed tcpip.MonotonicTime
   163  }
   164  
   165  // timedOut returns whether the connection timed out based on its state.
   166  func (cn *conn) timedOut(now tcpip.MonotonicTime) bool {
   167  	cn.stateMu.RLock()
   168  	defer cn.stateMu.RUnlock()
   169  	if cn.tcb.State() == tcpconntrack.ResultAlive {
   170  		// Use the same default as Linux, which doesn't delete
   171  		// established connections for 5(!) days.
   172  		return now.Sub(cn.lastUsed) > establishedTimeout
   173  	}
   174  	// Use the same default as Linux, which lets connections in most states
   175  	// other than established remain for <= 120 seconds.
   176  	return now.Sub(cn.lastUsed) > unestablishedTimeout
   177  }
   178  
   179  // update the connection tracking state.
   180  func (cn *conn) update(pkt PacketBufferPtr, reply bool) {
   181  	cn.stateMu.Lock()
   182  	defer cn.stateMu.Unlock()
   183  
   184  	// Mark the connection as having been used recently so it isn't reaped.
   185  	cn.lastUsed = cn.ct.clock.NowMonotonic()
   186  
   187  	if pkt.TransportProtocolNumber != header.TCPProtocolNumber {
   188  		return
   189  	}
   190  
   191  	tcpHeader := header.TCP(pkt.TransportHeader().Slice())
   192  
   193  	// Update the state of tcb. tcb assumes it's always initialized on the
   194  	// client. However, we only need to know whether the connection is
   195  	// established or not, so the client/server distinction isn't important.
   196  	if cn.tcb.IsEmpty() {
   197  		cn.tcb.Init(tcpHeader, pkt.Data().Size())
   198  		return
   199  	}
   200  
   201  	if reply {
   202  		cn.tcb.UpdateStateReply(tcpHeader, pkt.Data().Size())
   203  	} else {
   204  		cn.tcb.UpdateStateOriginal(tcpHeader, pkt.Data().Size())
   205  	}
   206  }
   207  
   208  // ConnTrack tracks all connections created for NAT rules. Most users are
   209  // expected to only call handlePacket, insertRedirectConn, and maybeInsertNoop.
   210  //
   211  // ConnTrack keeps all connections in a slice of buckets, each of which holds a
   212  // linked list of tuples. This gives us some desirable properties:
   213  //   - Each bucket has its own lock, lessening lock contention.
   214  //   - The slice is large enough that lists stay short (<10 elements on average).
   215  //     Thus traversal is fast.
   216  //   - During linked list traversal we reap expired connections. This amortizes
   217  //     the cost of reaping them and makes reapUnused faster.
   218  //
   219  // Locks are ordered by their location in the buckets slice. That is, a
   220  // goroutine that locks buckets[i] can only lock buckets[j] s.t. i < j.
   221  //
   222  // +stateify savable
   223  type ConnTrack struct {
   224  	// seed is a one-time random value initialized at stack startup
   225  	// and is used in the calculation of hash keys for the list of buckets.
   226  	// It is immutable.
   227  	seed uint32
   228  
   229  	// clock provides timing used to determine conntrack reapings.
   230  	clock tcpip.Clock
   231  	rand  *rand.Rand
   232  
   233  	mu connTrackRWMutex `state:"nosave"`
   234  	// mu protects the buckets slice, but not buckets' contents. Only take
   235  	// the write lock if you are modifying the slice or saving for S/R.
   236  	//
   237  	// +checklocks:mu
   238  	buckets []bucket
   239  }
   240  
   241  // +stateify savable
   242  type bucket struct {
   243  	mu bucketRWMutex `state:"nosave"`
   244  	// +checklocks:mu
   245  	tuples tupleList
   246  }
   247  
   248  // A netAndTransHeadersFunc returns the network and transport headers found
   249  // in an ICMP payload. The transport layer's payload will not be returned.
   250  //
   251  // May panic if the packet does not hold the transport header.
   252  type netAndTransHeadersFunc func(icmpPayload []byte, minTransHdrLen int) (netHdr header.Network, transHdrBytes []byte)
   253  
   254  func v4NetAndTransHdr(icmpPayload []byte, minTransHdrLen int) (header.Network, []byte) {
   255  	netHdr := header.IPv4(icmpPayload)
   256  	// Do not use netHdr.Payload() as we might not hold the full packet
   257  	// in the ICMP error; Payload() panics if the buffer is smaller than
   258  	// the total length specified in the IPv4 header.
   259  	transHdr := icmpPayload[netHdr.HeaderLength():]
   260  	return netHdr, transHdr[:minTransHdrLen]
   261  }
   262  
   263  func v6NetAndTransHdr(icmpPayload []byte, minTransHdrLen int) (header.Network, []byte) {
   264  	netHdr := header.IPv6(icmpPayload)
   265  	// Do not use netHdr.Payload() as we might not hold the full packet
   266  	// in the ICMP error; Payload() panics if the IP payload is smaller than
   267  	// the payload length specified in the IPv6 header.
   268  	transHdr := icmpPayload[header.IPv6MinimumSize:]
   269  	return netHdr, transHdr[:minTransHdrLen]
   270  }
   271  
   272  func getEmbeddedNetAndTransHeaders(pkt PacketBufferPtr, netHdrLength int, getNetAndTransHdr netAndTransHeadersFunc, transProto tcpip.TransportProtocolNumber) (header.Network, header.ChecksummableTransport, bool) {
   273  	switch transProto {
   274  	case header.TCPProtocolNumber:
   275  		if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.TCPMinimumSize); ok {
   276  			netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.TCPMinimumSize)
   277  			return netHeader, header.TCP(transHeaderBytes), true
   278  		}
   279  	case header.UDPProtocolNumber:
   280  		if netAndTransHeader, ok := pkt.Data().PullUp(netHdrLength + header.UDPMinimumSize); ok {
   281  			netHeader, transHeaderBytes := getNetAndTransHdr(netAndTransHeader, header.UDPMinimumSize)
   282  			return netHeader, header.UDP(transHeaderBytes), true
   283  		}
   284  	}
   285  	return nil, nil, false
   286  }
   287  
   288  func getHeaders(pkt PacketBufferPtr) (netHdr header.Network, transHdr header.Transport, isICMPError bool, ok bool) {
   289  	switch pkt.TransportProtocolNumber {
   290  	case header.TCPProtocolNumber:
   291  		if tcpHeader := header.TCP(pkt.TransportHeader().Slice()); len(tcpHeader) >= header.TCPMinimumSize {
   292  			return pkt.Network(), tcpHeader, false, true
   293  		}
   294  		return nil, nil, false, false
   295  	case header.UDPProtocolNumber:
   296  		if udpHeader := header.UDP(pkt.TransportHeader().Slice()); len(udpHeader) >= header.UDPMinimumSize {
   297  			return pkt.Network(), udpHeader, false, true
   298  		}
   299  		return nil, nil, false, false
   300  	case header.ICMPv4ProtocolNumber:
   301  		icmpHeader := header.ICMPv4(pkt.TransportHeader().Slice())
   302  		if len(icmpHeader) < header.ICMPv4MinimumSize {
   303  			return nil, nil, false, false
   304  		}
   305  
   306  		switch icmpType := icmpHeader.Type(); icmpType {
   307  		case header.ICMPv4Echo, header.ICMPv4EchoReply:
   308  			return pkt.Network(), icmpHeader, false, true
   309  		case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
   310  		default:
   311  			panic(fmt.Sprintf("unexpected ICMPv4 type = %d", icmpType))
   312  		}
   313  
   314  		h, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
   315  		if !ok {
   316  			panic(fmt.Sprintf("should have a valid IPv4 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv4MinimumSize))
   317  		}
   318  
   319  		if header.IPv4(h).HeaderLength() > header.IPv4MinimumSize {
   320  			// TODO(https://gvisor.dev/issue/6765): Handle IPv4 options.
   321  			panic("should have dropped packets with IPv4 options")
   322  		}
   323  
   324  		if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv4MinimumSize, v4NetAndTransHdr, pkt.tuple.tupleID.transProto); ok {
   325  			return netHdr, transHdr, true, true
   326  		}
   327  		return nil, nil, false, false
   328  	case header.ICMPv6ProtocolNumber:
   329  		icmpHeader := header.ICMPv6(pkt.TransportHeader().Slice())
   330  		if len(icmpHeader) < header.ICMPv6MinimumSize {
   331  			return nil, nil, false, false
   332  		}
   333  
   334  		switch icmpType := icmpHeader.Type(); icmpType {
   335  		case header.ICMPv6EchoRequest, header.ICMPv6EchoReply:
   336  			return pkt.Network(), icmpHeader, false, true
   337  		case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
   338  		default:
   339  			panic(fmt.Sprintf("unexpected ICMPv6 type = %d", icmpType))
   340  		}
   341  
   342  		h, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
   343  		if !ok {
   344  			panic(fmt.Sprintf("should have a valid IPv6 packet; only have %d bytes, want at least %d bytes", pkt.Data().Size(), header.IPv6MinimumSize))
   345  		}
   346  
   347  		// We do not support extension headers in ICMP errors so the next header
   348  		// in the IPv6 packet should be a tracked protocol if we reach this point.
   349  		//
   350  		// TODO(https://gvisor.dev/issue/6789): Support extension headers.
   351  		transProto := pkt.tuple.tupleID.transProto
   352  		if got := header.IPv6(h).TransportProtocol(); got != transProto {
   353  			panic(fmt.Sprintf("got TransportProtocol() = %d, want = %d", got, transProto))
   354  		}
   355  
   356  		if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, header.IPv6MinimumSize, v6NetAndTransHdr, transProto); ok {
   357  			return netHdr, transHdr, true, true
   358  		}
   359  		return nil, nil, false, false
   360  	default:
   361  		panic(fmt.Sprintf("unexpected transport protocol = %d", pkt.TransportProtocolNumber))
   362  	}
   363  }
   364  
   365  func getTupleIDForRegularPacket(netHdr header.Network, netProto tcpip.NetworkProtocolNumber, transHdr header.Transport, transProto tcpip.TransportProtocolNumber) tupleID {
   366  	return tupleID{
   367  		srcAddr:                   netHdr.SourceAddress(),
   368  		srcPortOrEchoRequestIdent: transHdr.SourcePort(),
   369  		dstAddr:                   netHdr.DestinationAddress(),
   370  		dstPortOrEchoReplyIdent:   transHdr.DestinationPort(),
   371  		transProto:                transProto,
   372  		netProto:                  netProto,
   373  	}
   374  }
   375  
   376  func getTupleIDForPacketInICMPError(pkt PacketBufferPtr, getNetAndTransHdr netAndTransHeadersFunc, netProto tcpip.NetworkProtocolNumber, netLen int, transProto tcpip.TransportProtocolNumber) (tupleID, bool) {
   377  	if netHdr, transHdr, ok := getEmbeddedNetAndTransHeaders(pkt, netLen, getNetAndTransHdr, transProto); ok {
   378  		return tupleID{
   379  			srcAddr:                   netHdr.DestinationAddress(),
   380  			srcPortOrEchoRequestIdent: transHdr.DestinationPort(),
   381  			dstAddr:                   netHdr.SourceAddress(),
   382  			dstPortOrEchoReplyIdent:   transHdr.SourcePort(),
   383  			transProto:                transProto,
   384  			netProto:                  netProto,
   385  		}, true
   386  	}
   387  
   388  	return tupleID{}, false
   389  }
   390  
   391  type getTupleIDDisposition int
   392  
   393  const (
   394  	getTupleIDNotOK getTupleIDDisposition = iota
   395  	getTupleIDOKAndAllowNewConn
   396  	getTupleIDOKAndDontAllowNewConn
   397  )
   398  
   399  func getTupleIDForEchoPacket(pkt PacketBufferPtr, ident uint16, request bool) tupleID {
   400  	netHdr := pkt.Network()
   401  	tid := tupleID{
   402  		srcAddr:    netHdr.SourceAddress(),
   403  		dstAddr:    netHdr.DestinationAddress(),
   404  		transProto: pkt.TransportProtocolNumber,
   405  		netProto:   pkt.NetworkProtocolNumber,
   406  	}
   407  
   408  	if request {
   409  		tid.srcPortOrEchoRequestIdent = ident
   410  	} else {
   411  		tid.dstPortOrEchoReplyIdent = ident
   412  	}
   413  
   414  	return tid
   415  }
   416  
   417  func getTupleID(pkt PacketBufferPtr) (tupleID, getTupleIDDisposition) {
   418  	switch pkt.TransportProtocolNumber {
   419  	case header.TCPProtocolNumber:
   420  		if transHeader := header.TCP(pkt.TransportHeader().Slice()); len(transHeader) >= header.TCPMinimumSize {
   421  			return getTupleIDForRegularPacket(pkt.Network(), pkt.NetworkProtocolNumber, transHeader, pkt.TransportProtocolNumber), getTupleIDOKAndAllowNewConn
   422  		}
   423  	case header.UDPProtocolNumber:
   424  		if transHeader := header.UDP(pkt.TransportHeader().Slice()); len(transHeader) >= header.UDPMinimumSize {
   425  			return getTupleIDForRegularPacket(pkt.Network(), pkt.NetworkProtocolNumber, transHeader, pkt.TransportProtocolNumber), getTupleIDOKAndAllowNewConn
   426  		}
   427  	case header.ICMPv4ProtocolNumber:
   428  		icmp := header.ICMPv4(pkt.TransportHeader().Slice())
   429  		if len(icmp) < header.ICMPv4MinimumSize {
   430  			return tupleID{}, getTupleIDNotOK
   431  		}
   432  
   433  		switch icmp.Type() {
   434  		case header.ICMPv4Echo:
   435  			return getTupleIDForEchoPacket(pkt, icmp.Ident(), true /* request */), getTupleIDOKAndAllowNewConn
   436  		case header.ICMPv4EchoReply:
   437  			// Do not create a new connection in response to a reply packet as only
   438  			// the first packet of a connection should create a conntrack entry but
   439  			// a reply is never the first packet sent for a connection.
   440  			return getTupleIDForEchoPacket(pkt, icmp.Ident(), false /* request */), getTupleIDOKAndDontAllowNewConn
   441  		case header.ICMPv4DstUnreachable, header.ICMPv4TimeExceeded, header.ICMPv4ParamProblem:
   442  		default:
   443  			// Unsupported ICMP type for NAT-ing.
   444  			return tupleID{}, getTupleIDNotOK
   445  		}
   446  
   447  		h, ok := pkt.Data().PullUp(header.IPv4MinimumSize)
   448  		if !ok {
   449  			return tupleID{}, getTupleIDNotOK
   450  		}
   451  
   452  		ipv4 := header.IPv4(h)
   453  		if ipv4.HeaderLength() > header.IPv4MinimumSize {
   454  			// TODO(https://gvisor.dev/issue/6765): Handle IPv4 options.
   455  			return tupleID{}, getTupleIDNotOK
   456  		}
   457  
   458  		if tid, ok := getTupleIDForPacketInICMPError(pkt, v4NetAndTransHdr, header.IPv4ProtocolNumber, header.IPv4MinimumSize, ipv4.TransportProtocol()); ok {
   459  			// Do not create a new connection in response to an ICMP error.
   460  			return tid, getTupleIDOKAndDontAllowNewConn
   461  		}
   462  	case header.ICMPv6ProtocolNumber:
   463  		icmp := header.ICMPv6(pkt.TransportHeader().Slice())
   464  		if len(icmp) < header.ICMPv6MinimumSize {
   465  			return tupleID{}, getTupleIDNotOK
   466  		}
   467  
   468  		switch icmp.Type() {
   469  		case header.ICMPv6EchoRequest:
   470  			return getTupleIDForEchoPacket(pkt, icmp.Ident(), true /* request */), getTupleIDOKAndAllowNewConn
   471  		case header.ICMPv6EchoReply:
   472  			// Do not create a new connection in response to a reply packet as only
   473  			// the first packet of a connection should create a conntrack entry but
   474  			// a reply is never the first packet sent for a connection.
   475  			return getTupleIDForEchoPacket(pkt, icmp.Ident(), false /* request */), getTupleIDOKAndDontAllowNewConn
   476  		case header.ICMPv6DstUnreachable, header.ICMPv6PacketTooBig, header.ICMPv6TimeExceeded, header.ICMPv6ParamProblem:
   477  		default:
   478  			return tupleID{}, getTupleIDNotOK
   479  		}
   480  
   481  		h, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
   482  		if !ok {
   483  			return tupleID{}, getTupleIDNotOK
   484  		}
   485  
   486  		// TODO(https://gvisor.dev/issue/6789): Handle extension headers.
   487  		if tid, ok := getTupleIDForPacketInICMPError(pkt, v6NetAndTransHdr, header.IPv6ProtocolNumber, header.IPv6MinimumSize, header.IPv6(h).TransportProtocol()); ok {
   488  			// Do not create a new connection in response to an ICMP error.
   489  			return tid, getTupleIDOKAndDontAllowNewConn
   490  		}
   491  	}
   492  
   493  	return tupleID{}, getTupleIDNotOK
   494  }
   495  
   496  func (ct *ConnTrack) init() {
   497  	ct.mu.Lock()
   498  	defer ct.mu.Unlock()
   499  	ct.buckets = make([]bucket, numBuckets)
   500  }
   501  
   502  // getConnAndUpdate attempts to get a connection or creates one if no
   503  // connection exists for the packet and packet's protocol is trackable.
   504  //
   505  // If the packet's protocol is trackable, the connection's state is updated to
   506  // match the contents of the packet.
   507  func (ct *ConnTrack) getConnAndUpdate(pkt PacketBufferPtr, skipChecksumValidation bool) *tuple {
   508  	// Get or (maybe) create a connection.
   509  	t := func() *tuple {
   510  		var allowNewConn bool
   511  		tid, res := getTupleID(pkt)
   512  		switch res {
   513  		case getTupleIDNotOK:
   514  			return nil
   515  		case getTupleIDOKAndAllowNewConn:
   516  			allowNewConn = true
   517  		case getTupleIDOKAndDontAllowNewConn:
   518  			allowNewConn = false
   519  		default:
   520  			panic(fmt.Sprintf("unhandled %[1]T = %[1]d", res))
   521  		}
   522  
   523  		// Just skip bad packets. They'll be rejected later by the appropriate
   524  		// protocol package.
   525  		switch pkt.TransportProtocolNumber {
   526  		case header.TCPProtocolNumber:
   527  			_, csumValid, ok := header.TCPValid(
   528  				header.TCP(pkt.TransportHeader().Slice()),
   529  				func() uint16 { return pkt.Data().Checksum() },
   530  				uint16(pkt.Data().Size()),
   531  				tid.srcAddr,
   532  				tid.dstAddr,
   533  				pkt.RXChecksumValidated || skipChecksumValidation)
   534  			if !csumValid || !ok {
   535  				return nil
   536  			}
   537  		case header.UDPProtocolNumber:
   538  			lengthValid, csumValid := header.UDPValid(
   539  				header.UDP(pkt.TransportHeader().Slice()),
   540  				func() uint16 { return pkt.Data().Checksum() },
   541  				uint16(pkt.Data().Size()),
   542  				pkt.NetworkProtocolNumber,
   543  				tid.srcAddr,
   544  				tid.dstAddr,
   545  				pkt.RXChecksumValidated || skipChecksumValidation)
   546  			if !lengthValid || !csumValid {
   547  				return nil
   548  			}
   549  		}
   550  
   551  		ct.mu.RLock()
   552  		bkt := &ct.buckets[ct.bucket(tid)]
   553  		ct.mu.RUnlock()
   554  
   555  		now := ct.clock.NowMonotonic()
   556  		if t := bkt.connForTID(tid, now); t != nil {
   557  			return t
   558  		}
   559  
   560  		if !allowNewConn {
   561  			return nil
   562  		}
   563  
   564  		bkt.mu.Lock()
   565  		defer bkt.mu.Unlock()
   566  
   567  		// Make sure a connection wasn't added between when we last checked the
   568  		// bucket and acquired the bucket's write lock.
   569  		if t := bkt.connForTIDRLocked(tid, now); t != nil {
   570  			return t
   571  		}
   572  
   573  		// This is the first packet we're seeing for the connection. Create an entry
   574  		// for this new connection.
   575  		conn := &conn{
   576  			ct:       ct,
   577  			original: tuple{tupleID: tid},
   578  			reply:    tuple{tupleID: tid.reply(), reply: true},
   579  			lastUsed: now,
   580  		}
   581  		conn.original.conn = conn
   582  		conn.reply.conn = conn
   583  
   584  		// For now, we only map an entry for the packet's original tuple as NAT may be
   585  		// performed on this connection. Until the packet goes through all the hooks
   586  		// and its final address/port is known, we cannot know what the response
   587  		// packet's addresses/ports will look like.
   588  		//
   589  		// This is okay because the destination cannot send its response until it
   590  		// receives the packet; the packet will only be received once all the hooks
   591  		// have been performed.
   592  		//
   593  		// See (*conn).finalize.
   594  		bkt.tuples.PushFront(&conn.original)
   595  		return &conn.original
   596  	}()
   597  	if t != nil {
   598  		t.conn.update(pkt, t.reply)
   599  	}
   600  	return t
   601  }
   602  
   603  func (ct *ConnTrack) connForTID(tid tupleID) *tuple {
   604  	ct.mu.RLock()
   605  	bkt := &ct.buckets[ct.bucket(tid)]
   606  	ct.mu.RUnlock()
   607  
   608  	return bkt.connForTID(tid, ct.clock.NowMonotonic())
   609  }
   610  
   611  func (bkt *bucket) connForTID(tid tupleID, now tcpip.MonotonicTime) *tuple {
   612  	bkt.mu.RLock()
   613  	defer bkt.mu.RUnlock()
   614  	return bkt.connForTIDRLocked(tid, now)
   615  }
   616  
   617  // +checklocksread:bkt.mu
   618  func (bkt *bucket) connForTIDRLocked(tid tupleID, now tcpip.MonotonicTime) *tuple {
   619  	for other := bkt.tuples.Front(); other != nil; other = other.Next() {
   620  		if tid == other.tupleID && !other.conn.timedOut(now) {
   621  			return other
   622  		}
   623  	}
   624  	return nil
   625  }
   626  
   627  func (ct *ConnTrack) finalize(cn *conn) finalizeResult {
   628  	ct.mu.RLock()
   629  	buckets := ct.buckets
   630  	ct.mu.RUnlock()
   631  
   632  	{
   633  		tid := cn.reply.tupleID
   634  		id := ct.bucketWithTableLength(tid, len(buckets))
   635  
   636  		bkt := &buckets[id]
   637  		bkt.mu.Lock()
   638  		t := bkt.connForTIDRLocked(tid, ct.clock.NowMonotonic())
   639  		if t == nil {
   640  			bkt.tuples.PushFront(&cn.reply)
   641  			bkt.mu.Unlock()
   642  			return finalizeResultSuccess
   643  		}
   644  		bkt.mu.Unlock()
   645  
   646  		if t.conn == cn {
   647  			// We already have an entry for the reply tuple.
   648  			//
   649  			// This can occur when the source address/port is the same as the
   650  			// destination address/port. In this scenario, tid == tid.reply().
   651  			return finalizeResultSuccess
   652  		}
   653  	}
   654  
   655  	// Another connection for the reply already exists. Remove the original and
   656  	// let the caller know we failed.
   657  	//
   658  	// TODO(https://gvisor.dev/issue/6850): Investigate handling this clash
   659  	// better.
   660  
   661  	tid := cn.original.tupleID
   662  	id := ct.bucketWithTableLength(tid, len(buckets))
   663  	bkt := &buckets[id]
   664  	bkt.mu.Lock()
   665  	defer bkt.mu.Unlock()
   666  	bkt.tuples.Remove(&cn.original)
   667  	return finalizeResultConflict
   668  }
   669  
   670  func (cn *conn) getFinalizeResult() finalizeResult {
   671  	return finalizeResult(cn.finalizeResult.Load())
   672  }
   673  
   674  // finalize attempts to finalize the connection and returns true iff the
   675  // connection was successfully finalized.
   676  //
   677  // If the connection failed to finalize, the caller should drop the packet
   678  // associated with the connection.
   679  //
   680  // If multiple goroutines attempt to finalize at the same time, only one
   681  // goroutine will perform the work to finalize the connection, but all
   682  // goroutines will block until the finalizing goroutine finishes finalizing.
   683  func (cn *conn) finalize() bool {
   684  	cn.finalizeOnce.Do(func() {
   685  		cn.finalizeResult.Store(uint32(cn.ct.finalize(cn)))
   686  	})
   687  
   688  	switch res := cn.getFinalizeResult(); res {
   689  	case finalizeResultSuccess:
   690  		return true
   691  	case finalizeResultConflict:
   692  		return false
   693  	default:
   694  		panic(fmt.Sprintf("unhandled result = %d", res))
   695  	}
   696  }
   697  
   698  func (cn *conn) maybePerformNoopNAT(dnat bool) {
   699  	cn.mu.Lock()
   700  	defer cn.mu.Unlock()
   701  
   702  	var manip *manipType
   703  	if dnat {
   704  		manip = &cn.destinationManip
   705  	} else {
   706  		manip = &cn.sourceManip
   707  	}
   708  
   709  	if *manip == manipNotPerformed {
   710  		*manip = manipPerformedNoop
   711  	}
   712  }
   713  
   714  type portOrIdentRange struct {
   715  	start uint16
   716  	size  uint32
   717  }
   718  
   719  // performNAT setups up the connection for the specified NAT and rewrites the
   720  // packet.
   721  //
   722  // If NAT has already been performed on the connection, then the packet will
   723  // be rewritten with the NAT performed on the connection, ignoring the passed
   724  // address and port range.
   725  //
   726  // Generally, only the first packet of a connection reaches this method; other
   727  // packets will be manipulated without needing to modify the connection.
   728  func (cn *conn) performNAT(pkt PacketBufferPtr, hook Hook, r *Route, portsOrIdents portOrIdentRange, natAddress tcpip.Address, dnat bool) {
   729  	lastPortOrIdent := func() uint16 {
   730  		lastPortOrIdent := uint32(portsOrIdents.start) + portsOrIdents.size - 1
   731  		if lastPortOrIdent > math.MaxUint16 {
   732  			panic(fmt.Sprintf("got lastPortOrIdent = %d, want <= MaxUint16(=%d); portsOrIdents=%#v", lastPortOrIdent, math.MaxUint16, portsOrIdents))
   733  		}
   734  		return uint16(lastPortOrIdent)
   735  	}()
   736  
   737  	// Make sure the packet is re-written after performing NAT.
   738  	defer func() {
   739  		// handlePacket returns true if the packet may skip the NAT table as the
   740  		// connection is already NATed, but if we reach this point we must be in the
   741  		// NAT table, so the return value is useless for us.
   742  		_ = cn.handlePacket(pkt, hook, r)
   743  	}()
   744  
   745  	cn.mu.Lock()
   746  	defer cn.mu.Unlock()
   747  
   748  	var manip *manipType
   749  	var address *tcpip.Address
   750  	var portOrIdent *uint16
   751  	if dnat {
   752  		manip = &cn.destinationManip
   753  		address = &cn.reply.tupleID.srcAddr
   754  		portOrIdent = &cn.reply.tupleID.srcPortOrEchoRequestIdent
   755  	} else {
   756  		manip = &cn.sourceManip
   757  		address = &cn.reply.tupleID.dstAddr
   758  		portOrIdent = &cn.reply.tupleID.dstPortOrEchoReplyIdent
   759  	}
   760  
   761  	if *manip != manipNotPerformed {
   762  		return
   763  	}
   764  	*manip = manipPerformed
   765  	*address = natAddress
   766  
   767  	// Does the current port/ident fit in the range?
   768  	if portsOrIdents.start <= *portOrIdent && *portOrIdent <= lastPortOrIdent {
   769  		// Yes, is the current reply tuple unique?
   770  		if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
   771  			// Yes! No need to change the port.
   772  			return
   773  		}
   774  	}
   775  
   776  	// Try our best to find a port/ident that results in a unique reply tuple.
   777  	//
   778  	// We limit the number of attempts to find a unique tuple to not waste a lot
   779  	// of time looking for a unique tuple.
   780  	//
   781  	// Matches linux behaviour introduced in
   782  	// https://github.com/torvalds/linux/commit/a504b703bb1da526a01593da0e4be2af9d9f5fa8.
   783  	const maxAttemptsForInitialRound uint32 = 128
   784  	const minAttemptsToContinue = 16
   785  
   786  	allowedInitialAttempts := maxAttemptsForInitialRound
   787  	if allowedInitialAttempts > portsOrIdents.size {
   788  		allowedInitialAttempts = portsOrIdents.size
   789  	}
   790  
   791  	for maxAttempts := allowedInitialAttempts; ; maxAttempts /= 2 {
   792  		// Start reach round with a random initial port/ident offset.
   793  		randOffset := cn.ct.rand.Uint32()
   794  
   795  		for i := uint32(0); i < maxAttempts; i++ {
   796  			newPortOrIdentU32 := uint32(portsOrIdents.start) + (randOffset+i)%portsOrIdents.size
   797  			if newPortOrIdentU32 > math.MaxUint16 {
   798  				panic(fmt.Sprintf("got newPortOrIdentU32 = %d, want <= MaxUint16(=%d); portsOrIdents=%#v, randOffset=%d", newPortOrIdentU32, math.MaxUint16, portsOrIdents, randOffset))
   799  			}
   800  
   801  			*portOrIdent = uint16(newPortOrIdentU32)
   802  
   803  			if other := cn.ct.connForTID(cn.reply.tupleID); other == nil {
   804  				// We found a unique tuple!
   805  				return
   806  			}
   807  		}
   808  
   809  		if maxAttempts == portsOrIdents.size {
   810  			// We already tried all the ports/idents in the range so no need to keep
   811  			// trying.
   812  			return
   813  		}
   814  
   815  		if maxAttempts < minAttemptsToContinue {
   816  			return
   817  		}
   818  	}
   819  
   820  	// We did not find a unique tuple, use the last used port anyways.
   821  	// TODO(https://gvisor.dev/issue/6850): Handle not finding a unique tuple
   822  	// better (e.g. remove the connection and drop the packet).
   823  }
   824  
   825  // handlePacket attempts to handle a packet and perform NAT if the connection
   826  // has had NAT performed on it.
   827  //
   828  // Returns true if the packet can skip the NAT table.
   829  func (cn *conn) handlePacket(pkt PacketBufferPtr, hook Hook, rt *Route) bool {
   830  	netHdr, transHdr, isICMPError, ok := getHeaders(pkt)
   831  	if !ok {
   832  		return false
   833  	}
   834  
   835  	fullChecksum := false
   836  	updatePseudoHeader := false
   837  	natDone := &pkt.snatDone
   838  	dnat := false
   839  	switch hook {
   840  	case Prerouting:
   841  		// Packet came from outside the stack so it must have a checksum set
   842  		// already.
   843  		fullChecksum = true
   844  		updatePseudoHeader = true
   845  
   846  		natDone = &pkt.dnatDone
   847  		dnat = true
   848  	case Input:
   849  	case Forward:
   850  		panic("should not handle packet in the forwarding hook")
   851  	case Output:
   852  		natDone = &pkt.dnatDone
   853  		dnat = true
   854  		fallthrough
   855  	case Postrouting:
   856  		if pkt.TransportProtocolNumber == header.TCPProtocolNumber && pkt.GSOOptions.Type != GSONone && pkt.GSOOptions.NeedsCsum {
   857  			updatePseudoHeader = true
   858  		} else if rt.RequiresTXTransportChecksum() {
   859  			fullChecksum = true
   860  			updatePseudoHeader = true
   861  		}
   862  	default:
   863  		panic(fmt.Sprintf("unrecognized hook = %d", hook))
   864  	}
   865  
   866  	if *natDone {
   867  		panic(fmt.Sprintf("packet already had NAT(dnat=%t) performed at hook=%s; pkt=%#v", dnat, hook, pkt))
   868  	}
   869  
   870  	// TODO(gvisor.dev/issue/5748): TCP checksums on inbound packets should be
   871  	// validated if checksum offloading is off. It may require IP defrag if the
   872  	// packets are fragmented.
   873  
   874  	reply := pkt.tuple.reply
   875  
   876  	tid, manip := func() (tupleID, manipType) {
   877  		cn.mu.RLock()
   878  		defer cn.mu.RUnlock()
   879  
   880  		if reply {
   881  			tid := cn.original.tupleID
   882  
   883  			if dnat {
   884  				return tid, cn.sourceManip
   885  			}
   886  			return tid, cn.destinationManip
   887  		}
   888  
   889  		tid := cn.reply.tupleID
   890  		if dnat {
   891  			return tid, cn.destinationManip
   892  		}
   893  		return tid, cn.sourceManip
   894  	}()
   895  	switch manip {
   896  	case manipNotPerformed:
   897  		return false
   898  	case manipPerformedNoop:
   899  		*natDone = true
   900  		return true
   901  	case manipPerformed:
   902  	default:
   903  		panic(fmt.Sprintf("unhandled manip = %d", manip))
   904  	}
   905  
   906  	newPort := tid.dstPortOrEchoReplyIdent
   907  	newAddr := tid.dstAddr
   908  	if dnat {
   909  		newPort = tid.srcPortOrEchoRequestIdent
   910  		newAddr = tid.srcAddr
   911  	}
   912  
   913  	rewritePacket(
   914  		netHdr,
   915  		transHdr,
   916  		!dnat != isICMPError,
   917  		fullChecksum,
   918  		updatePseudoHeader,
   919  		newPort,
   920  		newAddr,
   921  	)
   922  
   923  	*natDone = true
   924  
   925  	if !isICMPError {
   926  		return true
   927  	}
   928  
   929  	// We performed NAT on (erroneous) packet that triggered an ICMP response, but
   930  	// not the ICMP packet itself.
   931  	switch pkt.TransportProtocolNumber {
   932  	case header.ICMPv4ProtocolNumber:
   933  		icmp := header.ICMPv4(pkt.TransportHeader().Slice())
   934  		// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
   935  		icmp.SetChecksum(0)
   936  		icmp.SetChecksum(header.ICMPv4Checksum(icmp, pkt.Data().Checksum()))
   937  
   938  		network := header.IPv4(pkt.NetworkHeader().Slice())
   939  		if dnat {
   940  			network.SetDestinationAddressWithChecksumUpdate(tid.srcAddr)
   941  		} else {
   942  			network.SetSourceAddressWithChecksumUpdate(tid.dstAddr)
   943  		}
   944  	case header.ICMPv6ProtocolNumber:
   945  		network := header.IPv6(pkt.NetworkHeader().Slice())
   946  		srcAddr := network.SourceAddress()
   947  		dstAddr := network.DestinationAddress()
   948  		if dnat {
   949  			dstAddr = tid.srcAddr
   950  		} else {
   951  			srcAddr = tid.dstAddr
   952  		}
   953  
   954  		icmp := header.ICMPv6(pkt.TransportHeader().Slice())
   955  		// TODO(https://gvisor.dev/issue/6788): Incrementally update ICMP checksum.
   956  		icmp.SetChecksum(0)
   957  		payload := pkt.Data()
   958  		icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
   959  			Header:      icmp,
   960  			Src:         srcAddr,
   961  			Dst:         dstAddr,
   962  			PayloadCsum: payload.Checksum(),
   963  			PayloadLen:  payload.Size(),
   964  		}))
   965  
   966  		if dnat {
   967  			network.SetDestinationAddress(dstAddr)
   968  		} else {
   969  			network.SetSourceAddress(srcAddr)
   970  		}
   971  	}
   972  
   973  	return true
   974  }
   975  
   976  // bucket gets the conntrack bucket for a tupleID.
   977  // +checklocksread:ct.mu
   978  func (ct *ConnTrack) bucket(id tupleID) int {
   979  	return ct.bucketWithTableLength(id, len(ct.buckets))
   980  }
   981  
   982  func (ct *ConnTrack) bucketWithTableLength(id tupleID, tableLength int) int {
   983  	h := jenkins.Sum32(ct.seed)
   984  	h.Write(id.srcAddr.AsSlice())
   985  	h.Write(id.dstAddr.AsSlice())
   986  	shortBuf := make([]byte, 2)
   987  	binary.LittleEndian.PutUint16(shortBuf, id.srcPortOrEchoRequestIdent)
   988  	h.Write([]byte(shortBuf))
   989  	binary.LittleEndian.PutUint16(shortBuf, id.dstPortOrEchoReplyIdent)
   990  	h.Write([]byte(shortBuf))
   991  	binary.LittleEndian.PutUint16(shortBuf, uint16(id.transProto))
   992  	h.Write([]byte(shortBuf))
   993  	binary.LittleEndian.PutUint16(shortBuf, uint16(id.netProto))
   994  	h.Write([]byte(shortBuf))
   995  	return int(h.Sum32()) % tableLength
   996  }
   997  
   998  // reapUnused deletes timed out entries from the conntrack map. The rules for
   999  // reaping are:
  1000  //   - Each call to reapUnused traverses a fraction of the conntrack table.
  1001  //     Specifically, it traverses len(ct.buckets)/fractionPerReaping.
  1002  //   - After reaping, reapUnused decides when it should next run based on the
  1003  //     ratio of expired connections to examined connections. If the ratio is
  1004  //     greater than maxExpiredPct, it schedules the next run quickly. Otherwise it
  1005  //     slightly increases the interval between runs.
  1006  //   - maxFullTraversal caps the time it takes to traverse the entire table.
  1007  //
  1008  // reapUnused returns the next bucket that should be checked and the time after
  1009  // which it should be called again.
  1010  func (ct *ConnTrack) reapUnused(start int, prevInterval time.Duration) (int, time.Duration) {
  1011  	const fractionPerReaping = 128
  1012  	const maxExpiredPct = 50
  1013  	const maxFullTraversal = 60 * time.Second
  1014  	const minInterval = 10 * time.Millisecond
  1015  	const maxInterval = maxFullTraversal / fractionPerReaping
  1016  
  1017  	now := ct.clock.NowMonotonic()
  1018  	checked := 0
  1019  	expired := 0
  1020  	var idx int
  1021  	ct.mu.RLock()
  1022  	defer ct.mu.RUnlock()
  1023  	for i := 0; i < len(ct.buckets)/fractionPerReaping; i++ {
  1024  		idx = (i + start) % len(ct.buckets)
  1025  		bkt := &ct.buckets[idx]
  1026  		bkt.mu.Lock()
  1027  		for tuple := bkt.tuples.Front(); tuple != nil; {
  1028  			// reapTupleLocked updates tuple's next pointer so we grab it here.
  1029  			nextTuple := tuple.Next()
  1030  
  1031  			checked++
  1032  			if ct.reapTupleLocked(tuple, idx, bkt, now) {
  1033  				expired++
  1034  			}
  1035  
  1036  			tuple = nextTuple
  1037  		}
  1038  		bkt.mu.Unlock()
  1039  	}
  1040  	// We already checked buckets[idx].
  1041  	idx++
  1042  
  1043  	// If half or more of the connections are expired, the table has gotten
  1044  	// stale. Reschedule quickly.
  1045  	expiredPct := 0
  1046  	if checked != 0 {
  1047  		expiredPct = expired * 100 / checked
  1048  	}
  1049  	if expiredPct > maxExpiredPct {
  1050  		return idx, minInterval
  1051  	}
  1052  	if interval := prevInterval + minInterval; interval <= maxInterval {
  1053  		// Increment the interval between runs.
  1054  		return idx, interval
  1055  	}
  1056  	// We've hit the maximum interval.
  1057  	return idx, maxInterval
  1058  }
  1059  
  1060  // reapTupleLocked tries to remove tuple and its reply from the table. It
  1061  // returns whether the tuple's connection has timed out.
  1062  //
  1063  // Precondition: ct.mu is read locked and bkt.mu is write locked.
  1064  // +checklocksread:ct.mu
  1065  // +checklocks:bkt.mu
  1066  func (ct *ConnTrack) reapTupleLocked(reapingTuple *tuple, bktID int, bkt *bucket, now tcpip.MonotonicTime) bool {
  1067  	if !reapingTuple.conn.timedOut(now) {
  1068  		return false
  1069  	}
  1070  
  1071  	var otherTuple *tuple
  1072  	if reapingTuple.reply {
  1073  		otherTuple = &reapingTuple.conn.original
  1074  	} else {
  1075  		otherTuple = &reapingTuple.conn.reply
  1076  	}
  1077  
  1078  	otherTupleBktID := ct.bucket(otherTuple.tupleID)
  1079  	replyTupleInserted := reapingTuple.conn.getFinalizeResult() == finalizeResultSuccess
  1080  
  1081  	// To maintain lock order, we can only reap both tuples if the tuple for the
  1082  	// other direction appears later in the table.
  1083  	if bktID > otherTupleBktID && replyTupleInserted {
  1084  		return true
  1085  	}
  1086  
  1087  	bkt.tuples.Remove(reapingTuple)
  1088  
  1089  	if !replyTupleInserted {
  1090  		// The other tuple is the reply which has not yet been inserted.
  1091  		return true
  1092  	}
  1093  
  1094  	// Reap the other connection.
  1095  	if bktID == otherTupleBktID {
  1096  		// Don't re-lock if both tuples are in the same bucket.
  1097  		bkt.tuples.Remove(otherTuple)
  1098  	} else {
  1099  		otherTupleBkt := &ct.buckets[otherTupleBktID]
  1100  		otherTupleBkt.mu.NestedLock(bucketLockOthertuple)
  1101  		otherTupleBkt.tuples.Remove(otherTuple)
  1102  		otherTupleBkt.mu.NestedUnlock(bucketLockOthertuple)
  1103  	}
  1104  
  1105  	return true
  1106  }
  1107  
  1108  func (ct *ConnTrack) originalDst(epID TransportEndpointID, netProto tcpip.NetworkProtocolNumber, transProto tcpip.TransportProtocolNumber) (tcpip.Address, uint16, tcpip.Error) {
  1109  	// Lookup the connection. The reply's original destination
  1110  	// describes the original address.
  1111  	tid := tupleID{
  1112  		srcAddr:                   epID.LocalAddress,
  1113  		srcPortOrEchoRequestIdent: epID.LocalPort,
  1114  		dstAddr:                   epID.RemoteAddress,
  1115  		dstPortOrEchoReplyIdent:   epID.RemotePort,
  1116  		transProto:                transProto,
  1117  		netProto:                  netProto,
  1118  	}
  1119  	t := ct.connForTID(tid)
  1120  	if t == nil {
  1121  		// Not a tracked connection.
  1122  		return tcpip.Address{}, 0, &tcpip.ErrNotConnected{}
  1123  	}
  1124  
  1125  	t.conn.mu.RLock()
  1126  	defer t.conn.mu.RUnlock()
  1127  	if t.conn.destinationManip == manipNotPerformed {
  1128  		// Unmanipulated destination.
  1129  		return tcpip.Address{}, 0, &tcpip.ErrInvalidOptionValue{}
  1130  	}
  1131  
  1132  	id := t.conn.original.tupleID
  1133  	return id.dstAddr, id.dstPortOrEchoReplyIdent, nil
  1134  }