@ -136,6 +136,24 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
return ns , nil
}
// wrapProtoHandler returns protocol handler h wrapped in a version
// that dynamically reconfigures ns's subnet addresses as needed for
// outbound traffic.
func ( ns * Impl ) wrapProtoHandler ( h func ( stack . TransportEndpointID , * stack . PacketBuffer ) bool ) func ( stack . TransportEndpointID , * stack . PacketBuffer ) bool {
return func ( tei stack . TransportEndpointID , pb * stack . PacketBuffer ) bool {
addr := tei . LocalAddress
ip , ok := netaddr . FromStdIP ( net . IP ( addr ) )
if ! ok {
ns . logf ( "netstack: could not parse local address for incoming connection" )
return false
}
if ! ns . isLocalIP ( ip ) {
ns . addSubnetAddress ( ip )
}
return h ( tei , pb )
}
}
// Start sets up all the handlers so netstack can start working. Implements
// wgengine.FakeImpl.
func ( ns * Impl ) Start ( ) error {
@ -145,25 +163,8 @@ func (ns *Impl) Start() error {
const maxInFlightConnectionAttempts = 16
tcpFwd := tcp . NewForwarder ( ns . ipstack , tcpReceiveBufferSize , maxInFlightConnectionAttempts , ns . acceptTCP )
udpFwd := udp . NewForwarder ( ns . ipstack , ns . acceptUDP )
ns . ipstack . SetTransportProtocolHandler ( tcp . ProtocolNumber , func ( tei stack . TransportEndpointID , pb * stack . PacketBuffer ) bool {
addr := tei . LocalAddress
var pn tcpip . NetworkProtocolNumber
if addr . To4 ( ) != "" {
pn = ipv4 . ProtocolNumber
} else {
pn = ipv6 . ProtocolNumber
}
ip , ok := netaddr . FromStdIP ( net . IP ( addr ) )
if ! ok {
ns . logf ( "netstack: could not parse local address %s for incoming TCP connection" , ip )
return false
}
if ! ns . isLocalIP ( ip ) {
ns . addSubnetAddress ( pn , ip )
}
return tcpFwd . HandlePacket ( tei , pb )
} )
ns . ipstack . SetTransportProtocolHandler ( udp . ProtocolNumber , udpFwd . HandlePacket )
ns . ipstack . SetTransportProtocolHandler ( tcp . ProtocolNumber , ns . wrapProtoHandler ( tcpFwd . HandlePacket ) )
ns . ipstack . SetTransportProtocolHandler ( udp . ProtocolNumber , ns . wrapProtoHandler ( udpFwd . HandlePacket ) )
go ns . injectOutbound ( )
ns . tundev . PostFilterIn = ns . injectInbound
return nil
@ -214,13 +215,19 @@ func (ns *Impl) updateDNS(nm *netmap.NetworkMap) {
ns . dns = DNSMapFromNetworkMap ( nm )
}
func ( ns * Impl ) addSubnetAddress ( pn tcpip . NetworkProtocolNumber , ip netaddr . IP ) {
func ( ns * Impl ) addSubnetAddress ( ip netaddr . IP ) {
ns . mu . Lock ( )
ns . connsOpenBySubnetIP [ ip ] ++
needAdd := ns . connsOpenBySubnetIP [ ip ] == 1
ns . mu . Unlock ( )
// Only register address into netstack for first concurrent connection.
if needAdd {
var pn tcpip . NetworkProtocolNumber
if ip . Is4 ( ) {
pn = ipv4 . ProtocolNumber
} else if ip . Is6 ( ) {
pn = ipv6 . ProtocolNumber
}
ns . ipstack . AddAddress ( nicID , pn , tcpip . Address ( ip . IPAddr ( ) . IP ) )
}
}
@ -543,9 +550,9 @@ func (ns *Impl) forwardTCP(client *gonet.TCPConn, wq *waiter.Queue, dialAddr tcp
}
func ( ns * Impl ) acceptUDP ( r * udp . ForwarderRequest ) {
reqDetail s := r . ID ( )
ses s := r . ID ( )
if debugNetstack {
ns . logf ( "[v2] UDP ForwarderRequest: %v" , stringifyTEI ( reqDetail s) )
ns . logf ( "[v2] UDP ForwarderRequest: %v" , stringifyTEI ( ses s) )
}
var wq waiter . Queue
ep , err := r . CreateEndpoint ( & wq )
@ -553,30 +560,50 @@ func (ns *Impl) acceptUDP(r *udp.ForwarderRequest) {
ns . logf ( "acceptUDP: could not create endpoint: %v" , err )
return
}
localAddr, err := ep . GetLocalAddress ( )
if err != nil {
dstAddr, ok := ipPortOfNetstackAddr ( sess . LocalAddress , sess . LocalPort )
if ! ok {
return
}
remoteAddr, err := ep . GetRemoteAddress ( )
if err != nil {
srcAddr, ok := ipPortOfNetstackAddr ( sess . RemoteAddress , sess . RemotePort )
if ! ok {
return
}
c := gonet . NewUDPConn ( ns . ipstack , & wq , ep )
go ns . forwardUDP ( c , & wq , localAddr, remote Addr)
go ns . forwardUDP ( c , & wq , srcAddr, dst Addr)
}
func ( ns * Impl ) forwardUDP ( client * gonet . UDPConn , wq * waiter . Queue , clientLocalAddr , clientRemoteAddr tcpip . FullAddress ) {
port := clientLocalAddr . Port
// forwardUDP proxies between client (with addr clientAddr) and dstAddr.
//
// dstAddr may be either a local Tailscale IP, in which we case we proxy to
// 127.0.0.1, or any other IP (from an advertised subnet), in which case we
// proxy to it directly.
func ( ns * Impl ) forwardUDP ( client * gonet . UDPConn , wq * waiter . Queue , clientAddr , dstAddr netaddr . IPPort ) {
port , srcPort := dstAddr . Port ( ) , clientAddr . Port ( )
ns . logf ( "[v2] netstack: forwarding incoming UDP connection on port %v" , port )
backendListenAddr := & net . UDPAddr { IP : net . ParseIP ( "127.0.0.1" ) , Port : int ( clientRemoteAddr . Port ) }
backendRemoteAddr := & net . UDPAddr { IP : net . ParseIP ( "127.0.0.1" ) , Port : int ( port ) }
backendConn , err := net . ListenUDP ( "udp4" , backendListenAddr )
var backendListenAddr * net . UDPAddr
var backendRemoteAddr * net . UDPAddr
isLocal := ns . isLocalIP ( dstAddr . IP ( ) )
if isLocal {
backendRemoteAddr = & net . UDPAddr { IP : net . ParseIP ( "127.0.0.1" ) , Port : int ( port ) }
backendListenAddr = & net . UDPAddr { IP : net . ParseIP ( "127.0.0.1" ) , Port : int ( srcPort ) }
} else {
backendRemoteAddr = dstAddr . UDPAddr ( )
if dstAddr . IP ( ) . Is4 ( ) {
backendListenAddr = & net . UDPAddr { IP : net . ParseIP ( "0.0.0.0" ) , Port : int ( srcPort ) }
} else {
backendListenAddr = & net . UDPAddr { IP : net . ParseIP ( "::" ) , Port : int ( srcPort ) }
}
}
backendConn , err := net . ListenUDP ( "udp" , backendListenAddr )
if err != nil {
ns . logf ( "netstack: could not bind local port %v: %v, trying again with random port" , clientRemoteAddr . Port , err )
ns . logf ( "netstack: could not bind local port %v: %v, trying again with random port" , backendListen Addr. Port , err )
backendListenAddr . Port = 0
backendConn , err = net . ListenUDP ( "udp4" , backendListenAddr )
backendConn , err = net . ListenUDP ( "udp ", backendListenAddr )
if err != nil {
ns . logf ( "netstack: could not connect to local UDP server on port %v: %v" , port , err )
ns . logf ( "netstack: could not c reate UDP socket, preventing forwarding to %v: %v", dstAddr , err )
return
}
}
@ -585,28 +612,47 @@ func (ns *Impl) forwardUDP(client *gonet.UDPConn, wq *waiter.Queue, clientLocalA
if ! ok {
ns . logf ( "could not get backend local IP:port from %v:%v" , backendLocalAddr . IP , backendLocalAddr . Port )
}
clientRemoteIP , _ := netaddr . FromStdIP ( net . ParseIP ( clientRemoteAddr . Addr . String ( ) ) )
ns . e . RegisterIPPortIdentity ( backendLocalIPPort , clientRemoteIP )
if isLocal {
ns . e . RegisterIPPortIdentity ( backendLocalIPPort , dstAddr . IP ( ) )
}
ctx , cancel := context . WithCancel ( context . Background ( ) )
timer := time . AfterFunc ( 2 * time . Minute , func ( ) {
ns . e . UnregisterIPPortIdentity ( backendLocalIPPort )
ns . logf ( "netstack: UDP session between %s and %s timed out" , clientRemoteAddr , backendRemoteAddr )
idleTimeout := 2 * time . Minute
if port == 53 {
// Make DNS packet copies time out much sooner.
//
// TODO(bradfitz): make DNS queries over UDP forwarding even
// cheaper by adding an additional idleTimeout post-DNS-reply.
// For instance, after the DNS response goes back out, then only
// wait a few seconds (or zero, really)
idleTimeout = 30 * time . Second
}
timer := time . AfterFunc ( idleTimeout , func ( ) {
if isLocal {
ns . e . UnregisterIPPortIdentity ( backendLocalIPPort )
}
ns . logf ( "netstack: UDP session between %s and %s timed out" , backendListenAddr , backendRemoteAddr )
cancel ( )
client . Close ( )
backendConn . Close ( )
} )
extend := func ( ) {
timer . Reset ( 2 * time . Minute )
timer . Reset ( idleTimeout )
}
startPacketCopy ( ctx , cancel , client , & net . UDPAddr {
IP : net . ParseIP ( clientRemoteAddr . Addr . String ( ) ) ,
Port : int ( clientRemoteAddr . Port ) ,
} , backendConn , ns . logf , extend )
startPacketCopy ( ctx , cancel , client , clientAddr . UDPAddr ( ) , backendConn , ns . logf , extend )
startPacketCopy ( ctx , cancel , backendConn , backendRemoteAddr , client , ns . logf , extend )
if isLocal {
// Wait for the copies to be done before decrementing the
// subnet address count to potentially remove the route.
<- ctx . Done ( )
ns . removeSubnetAddress ( dstAddr . IP ( ) )
}
}
func startPacketCopy ( ctx context . Context , cancel context . CancelFunc , dst net . PacketConn , dstAddr net . Addr , src net . PacketConn , logf logger . Logf , extend func ( ) ) {
if debugNetstack {
logf ( "[v2] netstack: startPacketCopy to %v (%T) from %T" , dstAddr , dst , src )
}
go func ( ) {
defer cancel ( ) // tear down the other direction's copy
pkt := make ( [ ] byte , mtu )
@ -643,3 +689,7 @@ func stringifyTEI(tei stack.TransportEndpointID) string {
remoteHostPort := net . JoinHostPort ( tei . RemoteAddress . String ( ) , strconv . Itoa ( int ( tei . RemotePort ) ) )
return fmt . Sprintf ( "%s -> %s" , remoteHostPort , localHostPort )
}
func ipPortOfNetstackAddr ( a tcpip . Address , port uint16 ) ( ipp netaddr . IPPort , ok bool ) {
return netaddr . FromStdAddr ( net . IP ( a ) , int ( port ) , "" ) // TODO(bradfitz): can do without allocs
}