Skip to content

Commit dca6313

Browse files
committed
libtailscale: do not close old TUNs during reconfig, except on ChromeOS
Back in 2020, to work around a ChromeOS bug, we started closing previous tunnels, breaking TCP sessions on reconfig, in 8dbac87. This behavior has been preserved during the android app rewrite in 98a72c2, but we never actually needed it on Android. In c290ccf, the author of the original commit said: "note that seamless VPN tunnel handover works on Android But not on ChromeOS, and I haven't found a robust way to detect ChromeOS.". The isChromeOS method was added in a7dfea2 for an unrelated reason, but we never stopped closing the tunnels on Android. In this PR, we update (*backend).updateTUN to avoid closing the old tunnels unless we're running on ChromeOS or have an empty config. Updates tailscale/tailscale#19591 Signed-off-by: Nick Khyl <nickk@tailscale.com>
1 parent 34265e1 commit dca6313

1 file changed

Lines changed: 31 additions & 26 deletions

File tree

libtailscale/net.go

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -80,36 +80,41 @@ func (b *backend) updateTUN(rcfg *router.Config, dcfg *dns.OSConfig) (err error)
8080
b.logger.Logf("updateTUN: changed")
8181
defer b.logger.Logf("updateTUN: finished")
8282

83-
// Close previous tunnel(s).
84-
// This is necessary for ChromeOS, native Android devices
85-
// seem to handle seamless handover between tunnels correctly.
86-
//
87-
// TODO(eliasnaur): If seamless handover becomes a desirable feature, skip
88-
// the closing on ChromeOS.
89-
b.logger.Logf("updateTUN: closing old TUNs")
90-
b.CloseTUNs()
91-
b.logger.Logf("updateTUN: closed old TUNs")
92-
93-
// Since the previous tunnel(s) are closed, the [multiTUN] device is
94-
// not operational until a new underlying tunnel is created and added,
95-
// which may never happen in case of an error or an empty [router.Config].
96-
//
97-
// Therefore, to prevent deadlocks where a [multiTUN.Write] would
98-
// block waiting for a new tunnel to be added, we bring the multiTUN
99-
// device down on exit unless a new [tun.Device] is created and added
100-
// successfully. See tailscale/tailscale#18679.
101-
//
102-
// TODO(nickkhyl): revisit and simplify the [multiTUN] implementation?
10383
hasTunnel := false
104-
defer func() {
105-
if !hasTunnel && b.devices.Down() {
106-
b.logger.Logf("updateTUN: tunnel brought down: %v", err)
107-
}
108-
}()
84+
disableTUN := len(rcfg.LocalAddrs) == 0
85+
isChromeOS, _ := b.appCtx.IsChromeOS()
86+
if disableTUN || isChromeOS {
87+
// Close previous tunnel(s).
88+
// This is necessary for ChromeOS, native Android devices
89+
// seem to handle seamless handover between tunnels correctly.
90+
//
91+
// TODO(eliasnaur): If seamless handover becomes a desirable feature, skip
92+
// the closing on ChromeOS.
93+
b.logger.Logf("updateTUN: closing old TUNs")
94+
b.CloseTUNs()
95+
b.logger.Logf("updateTUN: closed old TUNs")
96+
97+
// Since the previous tunnel(s) are closed, the [multiTUN] device is
98+
// not operational until a new underlying tunnel is created and added,
99+
// which may never happen in case of an error or an empty [router.Config].
100+
//
101+
// Therefore, to prevent deadlocks where a [multiTUN.Write] would
102+
// block waiting for a new tunnel to be added, we bring the multiTUN
103+
// device down on exit unless a new [tun.Device] is created and added
104+
// successfully. See tailscale/tailscale#18679.
105+
//
106+
// TODO(nickkhyl): revisit and simplify the [multiTUN] implementation?
107+
defer func() {
108+
if !hasTunnel && b.devices.Down() {
109+
b.logger.Logf("updateTUN: tunnel brought down: %v", err)
110+
}
111+
}()
112+
}
109113

110-
if len(rcfg.LocalAddrs) == 0 {
114+
if disableTUN {
111115
return nil
112116
}
117+
113118
builder := vpnService.service.NewBuilder()
114119
b.logger.Logf("updateTUN: got new builder")
115120

0 commit comments

Comments
 (0)