net/tsaddr, wgengine/netstack: add IPv6 range that forwards to site-relative IPv4

This defines a new magic IPv6 prefix, fd7a:115c:a1e0:b1a::/64, a
subset of our existing /48, where the final 32 bits are an IPv4
address, and the middle 32 bits are a user-chosen "site ID". (which
must currently be 0000:00xx; the top 3 bytes must be zero for now)

e.g., I can say my home LAN's "site ID" is "0000:00bb" and then
advertise its 10.2.0.0/16 IPv4 range via IPv6, like:

    tailscale up --advertise-routes=fd7a:115c:a1e0:b1a::bb:10.2.0.0/112

(112 being /128 minuse the /96 v6 prefix length)

Then people in my tailnet can:

     $ curl '[fd7a:115c:a1e0:b1a::bb:10.2.0.230]'
     <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ....

Updates #3616, etc

RELNOTE=initial support for TS IPv6 addresses to route v4 "via" specific nodes

Change-Id: I9b49b6ad10410a24b5866b9fbc69d3cae1f600ef
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick
2022-03-30 08:47:16 -07:00
committed by Brad Fitzpatrick
parent f992749b98
commit 3ae701f0eb
6 changed files with 156 additions and 4 deletions

View File

@@ -659,6 +659,39 @@ func TestPrefsFromUpArgs(t *testing.T) {
NoSNAT: true,
},
},
{
name: "via_route_good",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a::bb:10.0.0.0/112",
netfilterMode: "off",
},
want: &ipn.Prefs{
WantRunning: true,
NoSNAT: true,
AdvertiseRoutes: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("fd7a:115c:a1e0:b1a::bb:10.0.0.0/112"),
},
},
},
{
name: "via_route_short_prefix",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a::/64",
netfilterMode: "off",
},
wantErr: "fd7a:115c:a1e0:b1a::/64 4-in-6 prefix must be at least a /96",
},
{
name: "via_route_short_reserved_siteid",
goos: "linux",
args: upArgsT{
advertiseRoutes: "fd7a:115c:a1e0:b1a:1234:5678::/112",
netfilterMode: "off",
},
wantErr: "route fd7a:115c:a1e0:b1a:1234:5678::/112 contains invalid site ID 12345678; must be 0xff or less",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -7,6 +7,7 @@ package cli
import (
"context"
"encoding/base64"
"encoding/binary"
"encoding/json"
"errors"
"flag"
@@ -27,6 +28,7 @@ import (
"tailscale.com/envknob"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnstate"
"tailscale.com/net/tsaddr"
"tailscale.com/safesocket"
"tailscale.com/tailcfg"
"tailscale.com/types/logger"
@@ -201,6 +203,26 @@ var (
ipv6default = netaddr.MustParseIPPrefix("::/0")
)
func validateViaPrefix(ipp netaddr.IPPrefix) error {
if !tsaddr.IsViaPrefix(ipp) {
return fmt.Errorf("%v is not a 4-in-6 prefix", ipp)
}
if ipp.Bits() < (128 - 32) {
return fmt.Errorf("%v 4-in-6 prefix must be at least a /%v", ipp, 128-32)
}
a := ipp.IP().As16()
// The first 64 bits of a are the via prefix.
// The next 32 bits are the "site ID".
// The last 32 bits are the IPv4.
// For now, we reserve the top 3 bytes of the site ID,
// and only allow users to use site IDs 0-255.
siteID := binary.BigEndian.Uint32(a[8:12])
if siteID > 0xFF {
return fmt.Errorf("route %v contains invalid site ID %08x; must be 0xff or less", ipp, siteID)
}
return nil
}
func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]netaddr.IPPrefix, error) {
routeMap := map[netaddr.IPPrefix]bool{}
if advertiseRoutes != "" {
@@ -214,6 +236,11 @@ func calcAdvertiseRoutes(advertiseRoutes string, advertiseDefaultRoute bool) ([]
if ipp != ipp.Masked() {
return nil, fmt.Errorf("%s has non-address bits set; expected %s", ipp, ipp.Masked())
}
if tsaddr.IsViaPrefix(ipp) {
if err := validateViaPrefix(ipp); err != nil {
return nil, err
}
}
if ipp == ipv4default {
default4 = true
} else if ipp == ipv6default {