summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorBrad Fitzpatrick <bradfitz@tailscale.com>2023-10-06 14:57:17 -0700
committerBrad Fitzpatrick <bradfitz@tailscale.com>2023-10-06 14:57:17 -0700
commitaa5360a295b9290396d2a14ed9d1566bbddf1e31 (patch)
treeeab1b41d06f0ca62b45adf7e544102f7e7830ac6
parente6aa7b815d2dca9af1d803b3585eff96be045af3 (diff)
downloadtailscale-bm/4via6.tar.xz
tailscale-bm/4via6.zip
all: support exporting your whole IPv4 LAN via 4via6bm/4via6
Updates #experiment Change-Id: Iccde4fb193f700816a9b989a91de06e3a860b784 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
-rw-r--r--client/web/web.go2
-rw-r--r--cmd/tailscale/cli/set.go8
-rw-r--r--cmd/tailscale/cli/up.go2
-rw-r--r--ipn/ipnlocal/c2n.go5
-rw-r--r--net/dns/resolver/tsdns.go39
-rw-r--r--net/dns/resolver/tsdns_test.go13
-rw-r--r--net/netutil/routes.go15
7 files changed, 64 insertions, 20 deletions
diff --git a/client/web/web.go b/client/web/web.go
index c76cd4955..e42a61c58 100644
--- a/client/web/web.go
+++ b/client/web/web.go
@@ -474,7 +474,7 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) {
return
}
- routes, err := netutil.CalcAdvertiseRoutes(postData.AdvertiseRoutes, postData.AdvertiseExitNode)
+ routes, err := netutil.CalcAdvertiseRoutes(st.TailscaleIPs[0], postData.AdvertiseRoutes, postData.AdvertiseExitNode)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(mi{"error": err.Error()})
diff --git a/cmd/tailscale/cli/set.go b/cmd/tailscale/cli/set.go
index d589da3ae..1c8fc34a8 100644
--- a/cmd/tailscale/cli/set.go
+++ b/cmd/tailscale/cli/set.go
@@ -140,7 +140,7 @@ func runSet(ctx context.Context, args []string) (retErr error) {
return err
}
if maskedPrefs.AdvertiseRoutesSet {
- maskedPrefs.AdvertiseRoutes, err = calcAdvertiseRoutesForSet(advertiseExitNodeSet, advertiseRoutesSet, curPrefs, setArgs)
+ maskedPrefs.AdvertiseRoutes, err = calcAdvertiseRoutesForSet(st.TailscaleIPs[0], advertiseExitNodeSet, advertiseRoutesSet, curPrefs, setArgs)
if err != nil {
return err
}
@@ -174,13 +174,13 @@ func runSet(ctx context.Context, args []string) (retErr error) {
// advertiseRoutesSet is whether the --advertise-routes flag was set.
// curPrefs is the current Prefs.
// setArgs is the parsed command-line arguments.
-func calcAdvertiseRoutesForSet(advertiseExitNodeSet, advertiseRoutesSet bool, curPrefs *ipn.Prefs, setArgs setArgsT) (routes []netip.Prefix, err error) {
+func calcAdvertiseRoutesForSet(addrV4 netip.Addr, advertiseExitNodeSet, advertiseRoutesSet bool, curPrefs *ipn.Prefs, setArgs setArgsT) (routes []netip.Prefix, err error) {
if advertiseExitNodeSet && advertiseRoutesSet {
- return netutil.CalcAdvertiseRoutes(setArgs.advertiseRoutes, setArgs.advertiseDefaultRoute)
+ return netutil.CalcAdvertiseRoutes(addrV4, setArgs.advertiseRoutes, setArgs.advertiseDefaultRoute)
}
if advertiseRoutesSet {
- return netutil.CalcAdvertiseRoutes(setArgs.advertiseRoutes, curPrefs.AdvertisesExitNode())
+ return netutil.CalcAdvertiseRoutes(addrV4, setArgs.advertiseRoutes, curPrefs.AdvertisesExitNode())
}
if advertiseExitNodeSet {
alreadyAdvertisesExitNode := curPrefs.AdvertisesExitNode()
diff --git a/cmd/tailscale/cli/up.go b/cmd/tailscale/cli/up.go
index 850a56cf2..b55ae0cf1 100644
--- a/cmd/tailscale/cli/up.go
+++ b/cmd/tailscale/cli/up.go
@@ -228,7 +228,7 @@ func warnf(format string, args ...any) {
// function exists for testing and should have no side effects or
// outside interactions (e.g. no making Tailscale LocalAPI calls).
func prefsFromUpArgs(upArgs upArgsT, warnf logger.Logf, st *ipnstate.Status, goos string) (*ipn.Prefs, error) {
- routes, err := netutil.CalcAdvertiseRoutes(upArgs.advertiseRoutes, upArgs.advertiseDefaultRoute)
+ routes, err := netutil.CalcAdvertiseRoutes(netip.Addr{}, upArgs.advertiseRoutes, upArgs.advertiseDefaultRoute)
if err != nil {
return nil, err
}
diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go
index afe9f56ee..0a4da6113 100644
--- a/ipn/ipnlocal/c2n.go
+++ b/ipn/ipnlocal/c2n.go
@@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
+ "log"
"net"
"net/http"
"os"
@@ -283,7 +284,9 @@ func (b *LocalBackend) handleC2NWoL(w http.ResponseWriter, r *http.Request) {
http.Error(w, "bad method", http.StatusMethodNotAllowed)
return
}
- r.ParseForm()
+ err := r.ParseForm()
+ log.Printf("ParseForm=%v: Form=%q, PostForm=%q", err, r.Form, r.PostForm)
+
var macs []net.HardwareAddr
for _, macStr := range r.Form["mac"] {
mac, err := net.ParseMAC(macStr)
diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go
index ddb7b6cdb..9503dbe8f 100644
--- a/net/dns/resolver/tsdns.go
+++ b/net/dns/resolver/tsdns.go
@@ -8,6 +8,7 @@ package resolver
import (
"bufio"
"context"
+ "encoding/binary"
"encoding/hex"
"errors"
"fmt"
@@ -21,6 +22,7 @@ import (
"strings"
"sync"
"time"
+ "unicode"
dns "golang.org/x/net/dns/dnsmessage"
"tailscale.com/control/controlknobs"
@@ -680,17 +682,21 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr,
// (2022-06-02) to work around an issue in Chrome where it would treat
// "http://via-1.1.2.3.4" as a search string instead of a URL. We should rip out
// the old format in early 2023.
-func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr, bool) {
- fqdn := string(domain.WithoutTrailingDot())
+func (r *Resolver) parseViaDomain(domainFQDN dnsname.FQDN, typ dns.Type) (netip.Addr, bool) {
+ fqdn := string(domainFQDN.WithoutTrailingDot())
if typ != dns.TypeAAAA {
return netip.Addr{}, false
}
if len(fqdn) < len("via-X.0.0.0.0") {
return netip.Addr{}, false // too short to be valid
}
+ r.mu.Lock()
+ hosts := r.hostToIP
+ r.mu.Unlock()
var siteID string
var ip4Str string
+ var prefix uint32
switch {
case strings.Contains(fqdn, "-via-"):
// Format number 3: "192-168-1-2-via-7" or "192-168-1-2-via-7.foo.ts.net."
@@ -703,8 +709,24 @@ func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr
if !ok {
return netip.Addr{}, false
}
- siteID = suffix
ip4Str = strings.ReplaceAll(v4hyphens, "-", ".")
+ if strings.ContainsFunc(suffix, unicode.IsLetter) {
+ // Advertising a whole LAN case. IPv4 address via a specific named node
+ // ("10-0-0-1-via-appletv.foo.ts.net.") where suffix here is
+ // "appletv" and not a numeric site ID.
+ _, node, _ := strings.Cut(domainFQDN.WithTrailingDot(), "-via-")
+ for _, addr := range hosts[dnsname.FQDN(node)] {
+ if addr.Is4() {
+ a4 := addr.As4()
+ prefix = binary.BigEndian.Uint32(a4[:])
+ }
+ }
+ if prefix == 0 {
+ return netip.Addr{}, false
+ }
+ } else {
+ siteID = suffix
+ }
case strings.HasPrefix(fqdn, "via-"):
firstDot := strings.Index(fqdn, ".")
if firstDot < 0 {
@@ -730,13 +752,16 @@ func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr
return netip.Addr{}, false // badly formed, don't respond
}
- prefix, err := strconv.ParseUint(siteID, 0, 32)
- if err != nil {
- return netip.Addr{}, false // badly formed, don't respond
+ if prefix == 0 {
+ prefix64, err := strconv.ParseUint(siteID, 0, 32)
+ if err != nil {
+ return netip.Addr{}, false // badly formed, don't respond
+ }
+ prefix = uint32(prefix64)
}
// MapVia will never error when given an IPv4 netip.Prefix.
- out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen()))
+ out, _ := tsaddr.MapVia(prefix, netip.PrefixFrom(ip4, ip4.BitLen()))
return out.Addr(), true
}
diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go
index 882462012..b96c36ecb 100644
--- a/net/dns/resolver/tsdns_test.go
+++ b/net/dns/resolver/tsdns_test.go
@@ -32,8 +32,9 @@ import (
)
var (
- testipv4 = netip.MustParseAddr("1.2.3.4")
- testipv6 = netip.MustParseAddr("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f")
+ testipv4 = netip.MustParseAddr("1.2.3.4")
+ test3ipv4 = netip.MustParseAddr("5.6.7.8")
+ testipv6 = netip.MustParseAddr("0001:0203:0405:0607:0809:0a0b:0c0d:0e0f")
testipv4Arpa = dnsname.FQDN("4.3.2.1.in-addr.arpa.")
testipv6Arpa = dnsname.FQDN("f.0.e.0.d.0.c.0.b.0.a.0.9.0.8.0.7.0.6.0.5.0.4.0.3.0.2.0.1.0.0.0.ip6.arpa.")
@@ -43,8 +44,9 @@ var (
var dnsCfg = Config{
Hosts: map[dnsname.FQDN][]netip.Addr{
- "test1.ipn.dev.": {testipv4},
- "test2.ipn.dev.": {testipv6},
+ "test1.ipn.dev.": {testipv4},
+ "test2.ipn.dev.": {testipv6},
+ "test3.foo.ts.net.": {test3ipv4},
},
LocalDomains: []dnsname.FQDN{"ipn.dev.", "3.2.1.in-addr.arpa.", "1.0.0.0.ip6.arpa."},
}
@@ -352,7 +354,6 @@ func TestResolveLocal(t *testing.T) {
// Hyphenated 4via6 format.
// Without any suffix domain:
- {"via_form3_hex_bare", dnsname.FQDN("1-2-3-4-via-0xff."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:ff:1.2.3.4"), dns.RCodeSuccess},
{"via_form3_dec_bare", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
// With a Tailscale domain:
{"via_form3_dec_ts.net", dnsname.FQDN("1-2-3-4-via-1.foo.ts.net."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess},
@@ -361,6 +362,8 @@ func TestResolveLocal(t *testing.T) {
// suffixes are currently hard-coded and not plumbed via the netmap)
{"via_form3_dec_example.com", dnsname.FQDN("1-2-3-4-via-1.example.com."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
{"via_form3_dec_examplets.net", dnsname.FQDN("1-2-3-4-via-1.examplets.net."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused},
+
+ {"via_lan", dnsname.FQDN("10-20-30-40-via-test3.foo.ts.net."), dns.TypeAAAA, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0506:0708:10.20.30.40"), dns.RCodeSuccess},
}
for _, tt := range tests {
diff --git a/net/netutil/routes.go b/net/netutil/routes.go
index 83f29bf3a..e9a8c019c 100644
--- a/net/netutil/routes.go
+++ b/net/netutil/routes.go
@@ -41,12 +41,25 @@ func validateViaPrefix(ipp netip.Prefix) error {
// CalcAdvertiseRoutes calculates the requested routes to be advertised by a node.
// advertiseRoutes is the user-provided, comma-separated list of routes (IP addresses or CIDR prefixes) to advertise.
// advertiseDefaultRoute indicates whether the node should act as an exit node and advertise default routes.
-func CalcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netip.Prefix, error) {
+func CalcAdvertiseRoutes(selfIPv4 netip.Addr, advertiseRoutes string, advertiseDefaultRoute bool) ([]netip.Prefix, error) {
routeMap := map[netip.Prefix]bool{}
if advertiseRoutes != "" {
var default4, default6 bool
advroutes := strings.Split(advertiseRoutes, ",")
for _, s := range advroutes {
+ if s == "lan-via6" {
+ if !selfIPv4.IsValid() {
+ return nil, fmt.Errorf("cannot advertise lan-via6 route until you're connected to Tailscale")
+ }
+ a4 := selfIPv4.As4()
+ selfSiteID := binary.BigEndian.Uint32(a4[:])
+ for _, pfxStr := range []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"} {
+ pfx := netip.MustParsePrefix(pfxStr)
+ pfx6, _ := tsaddr.MapVia(selfSiteID, pfx)
+ routeMap[pfx6] = true
+ }
+ continue
+ }
ipp, err := netip.ParsePrefix(s)
if err != nil {
return nil, fmt.Errorf("%q is not a valid IP address or CIDR prefix", s)