Skip to content

Commit

Permalink
manager: make multiple tunnels mode automatic
Browse files Browse the repository at this point in the history
Rather than having to set a registry knob to enable multiple tunnels, it
is now automatic. If an additional activated tunnel has the same route
subnets or interface IP addresses as a previous tunnel, that previous
one is stopped. But if there's no overlap, then they coexist.

Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
  • Loading branch information
zx2c4 committed Aug 12, 2021
1 parent 8b713ec commit 9f92a33
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 46 deletions.
37 changes: 37 additions & 0 deletions conf/config.go
Expand Up @@ -94,6 +94,43 @@ func (r *IPCidr) MaskSelf() {
}
}

func (conf *Config) IntersectsWith(other *Config) bool {
type hashableIPCidr struct {
ip string
cidr byte
}
allRoutes := make(map[hashableIPCidr]bool, len(conf.Interface.Addresses)*2+len(conf.Peers)*3)
for _, a := range conf.Interface.Addresses {
allRoutes[hashableIPCidr{string(a.IP), byte(len(a.IP) * 8)}] = true
a.MaskSelf()
allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] = true
}
for i := range conf.Peers {
for _, a := range conf.Peers[i].AllowedIPs {
a.MaskSelf()
allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] = true
}
}
for _, a := range other.Interface.Addresses {
if allRoutes[hashableIPCidr{string(a.IP), byte(len(a.IP) * 8)}] {
return true
}
a.MaskSelf()
if allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] {
return true
}
}
for i := range other.Peers {
for _, a := range other.Peers[i].AllowedIPs {
a.MaskSelf()
if allRoutes[hashableIPCidr{string(a.IP), a.Cidr}] {
return true
}
}
}
return false
}

func (e *Endpoint) String() string {
if strings.IndexByte(e.Host, ':') > 0 {
return fmt.Sprintf("[%s]:%d", e.Host, e.Port)
Expand Down
14 changes: 0 additions & 14 deletions docs/adminregistry.md
Expand Up @@ -38,20 +38,6 @@ executing these scripts.
> reg add HKLM\Software\WireGuard /v DangerousScriptExecution /t REG_DWORD /d 1 /f
```

#### `HKLM\Software\WireGuard\MultipleSimultaneousTunnels`

When this key is set to `DWORD(1)`, the UI may start multiple tunnels at the
same time; otherwise, an existing tunnel is stopped when a new one is started.
Note that it is always possible, regardless of this key, to start multiple
tunnels using `wireguard /installtunnelservice`; this controls only the semantics
of tunnel start requests coming from the UI. If all goes well, this key will be
removed and the logic of whether to stop existing tunnels will be based on
overlapping routes, but for now, this key provides a manual override.

```
> reg add HKLM\Software\WireGuard /v MultipleSimultaneousTunnels /t REG_DWORD /d 1 /f
```

#### `HKLM\Software\WireGuard\ExperimentalKernelDriver`

When this key is set to `DWORD(1)`, an experimental kernel driver from the
Expand Down
69 changes: 37 additions & 32 deletions manager/ipc_server.go
Expand Up @@ -108,44 +108,49 @@ func (s *ManagerService) RuntimeConfig(tunnelName string) (*conf.Config, error)
}

func (s *ManagerService) Start(tunnelName string) error {
// TODO: Rather than being lazy and gating this behind a knob (yuck!), we should instead keep track of the routes
// of each tunnel, and only deactivate in the case of a tunnel with identical routes being added.
if !conf.AdminBool("MultipleSimultaneousTunnels") {
trackedTunnelsLock.Lock()
tt := make([]string, 0, len(trackedTunnels))
var inTransition string
for t, state := range trackedTunnels {
tt = append(tt, t)
if len(t) > 0 && (state == TunnelStarting || state == TunnelUnknown) {
inTransition = t
break
}
c, err := conf.LoadFromName(tunnelName)
if err != nil {
return err
}

// Figure out which tunnels have intersecting addresses/routes and stop those.
trackedTunnelsLock.Lock()
tt := make([]string, 0, len(trackedTunnels))
var inTransition string
for t, state := range trackedTunnels {
c2, err := conf.LoadFromName(t)
if err != nil || !c.IntersectsWith(c2) {
// If we can't get the config, assume it doesn't intersect.
continue
}
tt = append(tt, t)
if len(t) > 0 && (state == TunnelStarting || state == TunnelUnknown) {
inTransition = t
break
}
trackedTunnelsLock.Unlock()
if len(inTransition) != 0 {
return fmt.Errorf("Please allow the tunnel ‘%s’ to finish activating", inTransition)
}
trackedTunnelsLock.Unlock()
if len(inTransition) != 0 {
return fmt.Errorf("Please allow the tunnel ‘%s’ to finish activating", inTransition)
}

// Stop those intersecting tunnels asynchronously.
go func() {
for _, t := range tt {
s.Stop(t)
}
go func() {
for _, t := range tt {
for _, t := range tt {
state, err := s.State(t)
if err == nil && (state == TunnelStarted || state == TunnelStarting) {
log.Printf("[%s] Trying again to stop zombie tunnel", t)
s.Stop(t)
time.Sleep(time.Millisecond * 100)
}
for _, t := range tt {
state, err := s.State(t)
if err == nil && (state == TunnelStarted || state == TunnelStarting) {
log.Printf("[%s] Trying again to stop zombie tunnel", t)
s.Stop(t)
time.Sleep(time.Millisecond * 100)
}
}
}()
}
}
}()
time.AfterFunc(time.Second*10, cleanupStaleNetworkInterfaces)

// After that process is started -- it's somewhat asynchronous -- we install the new one.
c, err := conf.LoadFromName(tunnelName)
if err != nil {
return err
}
// After the stop process has begun, but before it's finished, we install the new one.
path, err := c.Path()
if err != nil {
return err
Expand Down

0 comments on commit 9f92a33

Please sign in to comment.