Просмотр исходного кода

wgengine/router_windows: support toggling local lan access when using
exit nodes.

Signed-off-by: Maisem Ali <[email protected]>

Maisem Ali 4 лет назад
Родитель
Сommit
ec52760a3d
3 измененных файлов с 77 добавлено и 44 удалено
  1. 18 14
      cmd/tailscaled/tailscaled_windows.go
  2. 1 1
      ipn/ipnlocal/local.go
  3. 58 29
      wgengine/router/router_windows.go

+ 18 - 14
cmd/tailscaled/tailscaled_windows.go

@@ -19,6 +19,7 @@ package main // import "tailscale.com/cmd/tailscaled"
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 	"log"
 	"os"
@@ -27,6 +28,7 @@ import (
 	"golang.org/x/sys/windows"
 	"golang.org/x/sys/windows/svc"
 	"golang.zx2c4.com/wireguard/windows/tunnel/winipcfg"
+	"inet.af/netaddr"
 	"tailscale.com/ipn/ipnserver"
 	"tailscale.com/logpolicy"
 	"tailscale.com/net/dns"
@@ -126,16 +128,6 @@ func beFirewallKillswitch() bool {
 	log.SetFlags(0)
 	log.Printf("killswitch subprocess starting, tailscale GUID is %s", os.Args[2])
 
-	go func() {
-		b := make([]byte, 16)
-		for {
-			_, err := os.Stdin.Read(b)
-			if err != nil {
-				log.Fatalf("parent process died or requested exit, exiting (%v)", err)
-			}
-		}
-	}()
-
 	guid, err := windows.GUIDFromString(os.Args[2])
 	if err != nil {
 		log.Fatalf("invalid GUID %q: %v", os.Args[2], err)
@@ -147,13 +139,25 @@ func beFirewallKillswitch() bool {
 	}
 
 	start := time.Now()
-	if _, err := wf.New(uint64(luid)); err != nil {
-		log.Fatalf("filewall creation failed: %v", err)
+	fw, err := wf.New(uint64(luid))
+	if err != nil {
+		log.Fatalf("failed to enable firewall: %v", err)
 	}
 	log.Printf("killswitch enabled, took %s", time.Since(start))
 
-	// Block until the monitor goroutine shuts us down.
-	select {}
+	// Note(maisem): when local lan access toggled, tailscaled needs to
+	// inform the firewall to let local routes through. The set of routes
+	// is passed in via stdin encoded in json.
+	dcd := json.NewDecoder(os.Stdin)
+	for {
+		var routes []netaddr.IPPrefix
+		if err := dcd.Decode(&routes); err != nil {
+			log.Fatalf("parent process died or requested exit, exiting (%v)", err)
+		}
+		if err := fw.UpdatePermittedRoutes(routes); err != nil {
+			log.Fatalf("failed to update routes (%v)", err)
+		}
+	}
 }
 
 func startIPNServer(ctx context.Context, logid string) error {

+ 1 - 1
ipn/ipnlocal/local.go

@@ -2108,7 +2108,7 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs *ipn.Prefs) *router
 		if !default6 {
 			rs.Routes = append(rs.Routes, ipv6Default)
 		}
-		if runtime.GOOS == "linux" || runtime.GOOS == "darwin" {
+		if runtime.GOOS == "linux" || runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
 			// Only allow local lan access on linux machines for now.
 			ips, _, err := interfaceRoutes()
 			if err != nil {

+ 58 - 29
wgengine/router/router_windows.go

@@ -7,6 +7,7 @@ package router
 import (
 	"bufio"
 	"context"
+	"encoding/json"
 	"fmt"
 	"io"
 	"os"
@@ -73,7 +74,7 @@ func (r *winRouter) Set(cfg *Config) error {
 	for _, la := range cfg.LocalAddrs {
 		localAddrs = append(localAddrs, la.String())
 	}
-	r.firewall.set(localAddrs, cfg.Routes)
+	r.firewall.set(localAddrs, cfg.Routes, cfg.LocalRoutes)
 
 	err := configureInterface(cfg, r.nativeTun)
 	if err != nil {
@@ -121,12 +122,16 @@ type firewallTweaker struct {
 	logf    logger.Logf
 	tunGUID windows.GUID
 
-	mu             sync.Mutex
-	didProcRule    bool
-	running        bool     // doAsyncSet goroutine is running
-	known          bool     // firewall is in known state (in lastVal)
-	wantLocal      []string // next value we want, or "" to delete the firewall rule
-	lastLocal      []string // last set value, if known
+	mu          sync.Mutex
+	didProcRule bool
+	running     bool     // doAsyncSet goroutine is running
+	known       bool     // firewall is in known state (in lastVal)
+	wantLocal   []string // next value we want, or "" to delete the firewall rule
+	lastLocal   []string // last set value, if known
+
+	localRoutes     []netaddr.IPPrefix
+	lastLocalRoutes []netaddr.IPPrefix
+
 	wantKillswitch bool
 	lastKillswitch bool
 
@@ -138,16 +143,17 @@ type firewallTweaker struct {
 	// non-nil any number of times during the process's lifetime.
 	fwProc *exec.Cmd
 	// stop makes fwProc exit when closed.
-	stop io.Closer
+	fwProcWriter  io.WriteCloser
+	fwProcEncoder *json.Encoder
 }
 
-func (ft *firewallTweaker) clear() { ft.set(nil, nil) }
+func (ft *firewallTweaker) clear() { ft.set(nil, nil, nil) }
 
 // set takes CIDRs to allow, and the routes that point into the Tailscale tun interface.
 // Empty slices remove firewall rules.
 //
 // set takes ownership of cidrs, but not routes.
-func (ft *firewallTweaker) set(cidrs []string, routes []netaddr.IPPrefix) {
+func (ft *firewallTweaker) set(cidrs []string, routes, localRoutes []netaddr.IPPrefix) {
 	ft.mu.Lock()
 	defer ft.mu.Unlock()
 
@@ -157,6 +163,7 @@ func (ft *firewallTweaker) set(cidrs []string, routes []netaddr.IPPrefix) {
 		ft.logf("marking allowed %v", cidrs)
 	}
 	ft.wantLocal = cidrs
+	ft.localRoutes = localRoutes
 	ft.wantKillswitch = hasDefaultRoute(routes)
 	if ft.running {
 		// The doAsyncSet goroutine will check ft.wantLocal/wantKillswitch
@@ -184,7 +191,7 @@ func (ft *firewallTweaker) doAsyncSet() {
 	ft.mu.Lock()
 	for { // invariant: ft.mu must be locked when beginning this block
 		val := ft.wantLocal
-		if ft.known && strsEqual(ft.lastLocal, val) && ft.wantKillswitch == ft.lastKillswitch {
+		if ft.known && strsEqual(ft.lastLocal, val) && ft.wantKillswitch == ft.lastKillswitch && routesEqual(ft.localRoutes, ft.lastLocalRoutes) {
 			ft.running = false
 			ft.logf("ending netsh goroutine")
 			ft.mu.Unlock()
@@ -193,13 +200,18 @@ func (ft *firewallTweaker) doAsyncSet() {
 		wantKillswitch := ft.wantKillswitch
 		needClear := !ft.known || len(ft.lastLocal) > 0 || len(val) == 0
 		needProcRule := !ft.didProcRule
+		localRoutes := ft.localRoutes
 		ft.mu.Unlock()
 
-		err := ft.doSet(val, wantKillswitch, needClear, needProcRule)
+		err := ft.doSet(val, wantKillswitch, needClear, needProcRule, localRoutes)
+		if err != nil {
+			ft.logf("set failed: %v", err)
+		}
 		bo.BackOff(ctx, err)
 
 		ft.mu.Lock()
 		ft.lastLocal = val
+		ft.lastLocalRoutes = localRoutes
 		ft.lastKillswitch = wantKillswitch
 		ft.known = (err == nil)
 	}
@@ -218,7 +230,7 @@ func (ft *firewallTweaker) doAsyncSet() {
 // process to dial out as it pleases.
 //
 // Must only be invoked from doAsyncSet.
-func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, procRule bool) error {
+func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, procRule bool, allowedRoutes []netaddr.IPPrefix) error {
 	if clear {
 		ft.logf("clearing Tailscale-In firewall rules...")
 		// We ignore the error here, because netsh returns an error for
@@ -271,24 +283,29 @@ func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, pr
 		ft.logf("added Tailscale-In rule to allow %v in %v", cidr, d)
 	}
 
-	if killswitch && ft.fwProc == nil {
+	if !killswitch {
+		if ft.fwProc != nil {
+			ft.fwProcWriter.Close()
+			ft.fwProcWriter = nil
+			ft.fwProc.Wait()
+			ft.fwProc = nil
+			ft.fwProcEncoder = nil
+		}
+		return nil
+	}
+	if ft.fwProc == nil {
 		exe, err := os.Executable()
 		if err != nil {
 			return err
 		}
 		proc := exec.Command(exe, "/firewall", ft.tunGUID.String())
-		var (
-			out io.ReadCloser
-			in  io.WriteCloser
-		)
-		out, err = proc.StdoutPipe()
+		in, err := proc.StdinPipe()
 		if err != nil {
 			return err
 		}
-		proc.Stderr = proc.Stdout
-		in, err = proc.StdinPipe()
+		out, err := proc.StdoutPipe()
 		if err != nil {
-			out.Close()
+			in.Close()
 			return err
 		}
 
@@ -305,20 +322,32 @@ func (ft *firewallTweaker) doSet(local []string, killswitch bool, clear bool, pr
 				}
 			}
 		}(out)
+		proc.Stderr = proc.Stdout
 
 		if err := proc.Start(); err != nil {
 			return err
 		}
-		ft.stop = in
+		ft.fwProcWriter = in
 		ft.fwProc = proc
-	} else if !killswitch && ft.fwProc != nil {
-		ft.stop.Close()
-		ft.stop = nil
-		ft.fwProc.Wait()
-		ft.fwProc = nil
+		ft.fwProcEncoder = json.NewEncoder(in)
 	}
+	// Note(maisem): when local lan access toggled, we need to inform the
+	// firewall to let the local routes through. The set of routes is passed
+	// in via stdin encoded in json.
+	return ft.fwProcEncoder.Encode(allowedRoutes)
+}
 
-	return nil
+func routesEqual(a, b []netaddr.IPPrefix) bool {
+	if len(a) != len(b) {
+		return false
+	}
+	// Routes are pre-sorted.
+	for i := range a {
+		if a[i] != b[i] {
+			return false
+		}
+	}
+	return true
 }
 
 func strsEqual(a, b []string) bool {