Compare commits

...

1 Commits

Author SHA1 Message Date
Brad Fitzpatrick
aa5360a295 all: support exporting your whole IPv4 LAN via 4via6
Updates #experiment

Change-Id: Iccde4fb193f700816a9b989a91de06e3a860b784
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2023-10-06 14:57:17 -07:00
7 changed files with 64 additions and 20 deletions

View File

@@ -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()})

View File

@@ -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()

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)