summaryrefslogtreecommitdiffhomepage
path: root/wgengine
diff options
context:
space:
mode:
authorkari-ts <kari@tailscale.com>2025-03-19 11:28:04 -0700
committerkari-ts <kari@tailscale.com>2025-04-04 14:24:56 -0700
commit6d5c7b11913e09b061e863411ad488dc44a13870 (patch)
tree9e1789b5080ae4a92523611e49920dcb1102604b /wgengine
parentca50599c95e0a4cb7b4aab179e866e202f10c0c4 (diff)
parent3a2c92f08eac8cd8f50356ff288e40a28636ee42 (diff)
downloadtailscale-kari/taildropsaf.tar.xz
tailscale-kari/taildropsaf.zip
-check if Context.getExternalFilesDirs works as is for private dir
Diffstat (limited to 'wgengine')
-rw-r--r--wgengine/magicsock/derp.go24
-rw-r--r--wgengine/magicsock/endpoint.go10
-rw-r--r--wgengine/magicsock/magicsock.go12
-rw-r--r--wgengine/netstack/netstack.go43
4 files changed, 85 insertions, 4 deletions
diff --git a/wgengine/magicsock/derp.go b/wgengine/magicsock/derp.go
index 7c8ffc01a..ffdff14a1 100644
--- a/wgengine/magicsock/derp.go
+++ b/wgengine/magicsock/derp.go
@@ -64,10 +64,30 @@ func (c *Conn) removeDerpPeerRoute(peer key.NodePublic, regionID int, dc *derpht
// addDerpPeerRoute adds a DERP route entry, noting that peer was seen
// on DERP node derpID, at least on the connection identified by dc.
// See issue 150 for details.
-func (c *Conn) addDerpPeerRoute(peer key.NodePublic, derpID int, dc *derphttp.Client) {
+func (c *Conn) addDerpPeerRoute(peer key.NodePublic, regionID int, dc *derphttp.Client) {
c.mu.Lock()
defer c.mu.Unlock()
- mak.Set(&c.derpRoute, peer, derpRoute{derpID, dc})
+ mak.Set(&c.derpRoute, peer, derpRoute{regionID, dc})
+}
+
+// fallbackDERPRegionForPeer returns the DERP region ID we might be able to use
+// to contact peer, learned from observing recent DERP traffic from them.
+//
+// This is used as a fallback when a peer receives a packet from a peer
+// over DERP but doesn't known that peer's home DERP or any UDP endpoints.
+// This is particularly useful for large one-way nodes (such as hello.ts.net)
+// that don't actively reach out to other nodes, so don't need to be told
+// the DERP home of peers. They can instead learn the DERP home upon getting the
+// first connection.
+//
+// This can also help nodes from a slow or misbehaving control plane.
+func (c *Conn) fallbackDERPRegionForPeer(peer key.NodePublic) (regionID int) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if dr, ok := c.derpRoute[peer]; ok {
+ return dr.regionID
+ }
+ return 0
}
// activeDerp contains fields for an active DERP connection.
diff --git a/wgengine/magicsock/endpoint.go b/wgengine/magicsock/endpoint.go
index 7780c7db6..0c48acddf 100644
--- a/wgengine/magicsock/endpoint.go
+++ b/wgengine/magicsock/endpoint.go
@@ -948,7 +948,15 @@ func (de *endpoint) send(buffs [][]byte) error {
de.mu.Unlock()
if !udpAddr.IsValid() && !derpAddr.IsValid() {
- return errNoUDPOrDERP
+ // Make a last ditch effort to see if we have a DERP route for them. If
+ // they contacted us over DERP and we don't know their UDP endpoints or
+ // their DERP home, we can at least assume they're reachable over the
+ // DERP they used to contact us.
+ if rid := de.c.fallbackDERPRegionForPeer(de.publicKey); rid != 0 {
+ derpAddr = netip.AddrPortFrom(tailcfg.DerpMagicIPAddr, uint16(rid))
+ } else {
+ return errNoUDPOrDERP
+ }
}
var err error
if udpAddr.IsValid() {
diff --git a/wgengine/magicsock/magicsock.go b/wgengine/magicsock/magicsock.go
index acf7114e1..e8e966582 100644
--- a/wgengine/magicsock/magicsock.go
+++ b/wgengine/magicsock/magicsock.go
@@ -177,6 +177,10 @@ type Conn struct {
// port mappings from NAT devices.
portMapper *portmapper.Client
+ // portMapperLogfUnregister is the function to call to unregister
+ // the portmapper log limiter.
+ portMapperLogfUnregister func()
+
// derpRecvCh is used by receiveDERP to read DERP messages.
// It must have buffer size > 0; see issue 3736.
derpRecvCh chan derpReadResult
@@ -532,10 +536,15 @@ func NewConn(opts Options) (*Conn, error) {
c.idleFunc = opts.IdleFunc
c.testOnlyPacketListener = opts.TestOnlyPacketListener
c.noteRecvActivity = opts.NoteRecvActivity
+
+ // Don't log the same log messages possibly every few seconds in our
+ // portmapper.
+ portmapperLogf := logger.WithPrefix(c.logf, "portmapper: ")
+ portmapperLogf, c.portMapperLogfUnregister = netmon.LinkChangeLogLimiter(portmapperLogf, opts.NetMon)
portMapOpts := &portmapper.DebugKnobs{
DisableAll: func() bool { return opts.DisablePortMapper || c.onlyTCP443.Load() },
}
- c.portMapper = portmapper.NewClient(logger.WithPrefix(c.logf, "portmapper: "), opts.NetMon, portMapOpts, opts.ControlKnobs, c.onPortMapChanged)
+ c.portMapper = portmapper.NewClient(portmapperLogf, opts.NetMon, portMapOpts, opts.ControlKnobs, c.onPortMapChanged)
c.portMapper.SetGatewayLookupFunc(opts.NetMon.GatewayAndSelfIP)
c.netMon = opts.NetMon
c.health = opts.HealthTracker
@@ -2481,6 +2490,7 @@ func (c *Conn) Close() error {
}
c.stopPeriodicReSTUNTimerLocked()
c.portMapper.Close()
+ c.portMapperLogfUnregister()
c.peerMap.forEachEndpoint(func(ep *endpoint) {
ep.stopAndReset()
diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go
index 0bbd20b79..591bedde4 100644
--- a/wgengine/netstack/netstack.go
+++ b/wgengine/netstack/netstack.go
@@ -843,6 +843,27 @@ func (ns *Impl) DialContextTCP(ctx context.Context, ipp netip.AddrPort) (*gonet.
return gonet.DialContextTCP(ctx, ns.ipstack, remoteAddress, ipType)
}
+// DialContextTCPWithBind creates a new gonet.TCPConn connected to the specified
+// remoteAddress with its local address bound to localAddr on an available port.
+func (ns *Impl) DialContextTCPWithBind(ctx context.Context, localAddr netip.Addr, remoteAddr netip.AddrPort) (*gonet.TCPConn, error) {
+ remoteAddress := tcpip.FullAddress{
+ NIC: nicID,
+ Addr: tcpip.AddrFromSlice(remoteAddr.Addr().AsSlice()),
+ Port: remoteAddr.Port(),
+ }
+ localAddress := tcpip.FullAddress{
+ NIC: nicID,
+ Addr: tcpip.AddrFromSlice(localAddr.AsSlice()),
+ }
+ var ipType tcpip.NetworkProtocolNumber
+ if remoteAddr.Addr().Is4() {
+ ipType = ipv4.ProtocolNumber
+ } else {
+ ipType = ipv6.ProtocolNumber
+ }
+ return gonet.DialTCPWithBind(ctx, ns.ipstack, localAddress, remoteAddress, ipType)
+}
+
func (ns *Impl) DialContextUDP(ctx context.Context, ipp netip.AddrPort) (*gonet.UDPConn, error) {
remoteAddress := &tcpip.FullAddress{
NIC: nicID,
@@ -859,6 +880,28 @@ func (ns *Impl) DialContextUDP(ctx context.Context, ipp netip.AddrPort) (*gonet.
return gonet.DialUDP(ns.ipstack, nil, remoteAddress, ipType)
}
+// DialContextUDPWithBind creates a new gonet.UDPConn. Connected to remoteAddr.
+// With its local address bound to localAddr on an available port.
+func (ns *Impl) DialContextUDPWithBind(ctx context.Context, localAddr netip.Addr, remoteAddr netip.AddrPort) (*gonet.UDPConn, error) {
+ remoteAddress := &tcpip.FullAddress{
+ NIC: nicID,
+ Addr: tcpip.AddrFromSlice(remoteAddr.Addr().AsSlice()),
+ Port: remoteAddr.Port(),
+ }
+ localAddress := &tcpip.FullAddress{
+ NIC: nicID,
+ Addr: tcpip.AddrFromSlice(localAddr.AsSlice()),
+ }
+ var ipType tcpip.NetworkProtocolNumber
+ if remoteAddr.Addr().Is4() {
+ ipType = ipv4.ProtocolNumber
+ } else {
+ ipType = ipv6.ProtocolNumber
+ }
+
+ return gonet.DialUDP(ns.ipstack, localAddress, remoteAddress, ipType)
+}
+
// getInjectInboundBuffsSizes returns packet memory and a sizes slice for usage
// when calling tstun.Wrapper.InjectInboundPacketBuffer(). These are sized with
// consideration for MTU and GSO support on ns.linkEP. They should be recycled