mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-09 09:33:42 +00:00
wgengine/router: add ip rules for unifi udm-pro
Fixes: #4038 Signed-off-by: Jason Barnett <J@sonBarnett.com>
This commit is contained in:
parent
10d4057a64
commit
8d4ea4d90c
@ -9,6 +9,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"tailscale.com/types/lazy"
|
"tailscale.com/types/lazy"
|
||||||
"tailscale.com/util/lineiter"
|
"tailscale.com/util/lineiter"
|
||||||
@ -30,6 +31,7 @@ const (
|
|||||||
WDMyCloud = Distro("wdmycloud")
|
WDMyCloud = Distro("wdmycloud")
|
||||||
Unraid = Distro("unraid")
|
Unraid = Distro("unraid")
|
||||||
Alpine = Distro("alpine")
|
Alpine = Distro("alpine")
|
||||||
|
UDMPro = Distro("udmpro")
|
||||||
)
|
)
|
||||||
|
|
||||||
var distro lazy.SyncValue[Distro]
|
var distro lazy.SyncValue[Distro]
|
||||||
@ -75,6 +77,9 @@ func linuxDistro() Distro {
|
|||||||
case have("/usr/local/bin/freenas-debug"):
|
case have("/usr/local/bin/freenas-debug"):
|
||||||
// TrueNAS Scale runs on debian
|
// TrueNAS Scale runs on debian
|
||||||
return TrueNAS
|
return TrueNAS
|
||||||
|
case isUDMPro():
|
||||||
|
// UDM-Pro runs on debian
|
||||||
|
return UDMPro
|
||||||
case have("/etc/debian_version"):
|
case have("/etc/debian_version"):
|
||||||
return Debian
|
return Debian
|
||||||
case have("/etc/arch-release"):
|
case have("/etc/arch-release"):
|
||||||
@ -147,3 +152,44 @@ func DSMVersion() int {
|
|||||||
return 0
|
return 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isUDMPro checks a couple of files known to exist on a UDM-Pro and returns
|
||||||
|
// true if the expected content exists in the files.
|
||||||
|
func isUDMPro() bool {
|
||||||
|
// This is a performance guardrail against trying to load both
|
||||||
|
// /etc/board.info and /sys/firmware/devicetree/base/soc/board-cfg/id when
|
||||||
|
// not running on Debian so we don't make unnecessary calls in situations
|
||||||
|
// where we definitely are NOT on a UDM Pro. In other words, the have() call
|
||||||
|
// is much cheaper than the two os.ReadFile() in fileContainsAnyString().
|
||||||
|
// That said, on Debian systems we will still be making the two
|
||||||
|
// os.ReadFile() in fileContainsAnyString().
|
||||||
|
if !have("/etc/debian_version") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if exists, err := fileContainsAnyString("/etc/board.info", "UDMPRO", "Dream Machine PRO"); err == nil && exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if exists, err := fileContainsAnyString("/sys/firmware/devicetree/base/soc/board-cfg/id", "udm pro"); err == nil && exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fileContainsAnyString is used to determine if one or more of the provided
|
||||||
|
// strings exists in a file. This is not efficient for larger files. If you want
|
||||||
|
// to use this function to parse large files, please refactor to use
|
||||||
|
// `io.LimitedReader`.
|
||||||
|
func fileContainsAnyString(filePath string, searchStrings ...string) (bool, error) {
|
||||||
|
data, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
for _, searchString := range searchStrings {
|
||||||
|
if strings.Contains(content, searchString) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
@ -32,6 +32,8 @@ import (
|
|||||||
"tailscale.com/version/distro"
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var getDistroFunc = distro.Get
|
||||||
|
|
||||||
const (
|
const (
|
||||||
netfilterOff = preftype.NetfilterOff
|
netfilterOff = preftype.NetfilterOff
|
||||||
netfilterNoDivert = preftype.NetfilterNoDivert
|
netfilterNoDivert = preftype.NetfilterNoDivert
|
||||||
@ -222,7 +224,7 @@ func busyboxParseVersion(output string) (major, minor, patch int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func useAmbientCaps() bool {
|
func useAmbientCaps() bool {
|
||||||
if distro.Get() != distro.Synology {
|
if getDistroFunc() != distro.Synology {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return distro.DSMVersion() >= 7
|
return distro.DSMVersion() >= 7
|
||||||
@ -438,7 +440,7 @@ func (r *linuxRouter) Set(cfg *Config) error {
|
|||||||
|
|
||||||
// Issue 11405: enable IP forwarding on gokrazy.
|
// Issue 11405: enable IP forwarding on gokrazy.
|
||||||
advertisingRoutes := len(cfg.SubnetRoutes) > 0
|
advertisingRoutes := len(cfg.SubnetRoutes) > 0
|
||||||
if distro.Get() == distro.Gokrazy && advertisingRoutes {
|
if getDistroFunc() == distro.Gokrazy && advertisingRoutes {
|
||||||
r.enableIPForwarding()
|
r.enableIPForwarding()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1181,7 +1183,9 @@ var (
|
|||||||
tailscaleRouteTable = newRouteTable("tailscale", 52)
|
tailscaleRouteTable = newRouteTable("tailscale", 52)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ipRules are the policy routing rules that Tailscale uses.
|
// baseIPRules are the policy routing rules that Tailscale uses, when not
|
||||||
|
// running on a UDM-Pro.
|
||||||
|
//
|
||||||
// The priority is the value represented here added to r.ipPolicyPrefBase,
|
// The priority is the value represented here added to r.ipPolicyPrefBase,
|
||||||
// which is usually 5200.
|
// which is usually 5200.
|
||||||
//
|
//
|
||||||
@ -1196,7 +1200,7 @@ var (
|
|||||||
// and 'ip rule' implementations (including busybox), don't support
|
// and 'ip rule' implementations (including busybox), don't support
|
||||||
// checking for the lack of a fwmark, only the presence. The technique
|
// checking for the lack of a fwmark, only the presence. The technique
|
||||||
// below works even on very old kernels.
|
// below works even on very old kernels.
|
||||||
var ipRules = []netlink.Rule{
|
var baseIPRules = []netlink.Rule{
|
||||||
// Packets from us, tagged with our fwmark, first try the kernel's
|
// Packets from us, tagged with our fwmark, first try the kernel's
|
||||||
// main routing table.
|
// main routing table.
|
||||||
{
|
{
|
||||||
@ -1232,6 +1236,34 @@ var ipRules = []netlink.Rule{
|
|||||||
// usual rules (pref 32766 and 32767, ie. main and default).
|
// usual rules (pref 32766 and 32767, ie. main and default).
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// udmProIPRules are the policy routing rules that Tailscale uses, when running
|
||||||
|
// on a UDM-Pro.
|
||||||
|
//
|
||||||
|
// The priority is the value represented here added to
|
||||||
|
// r.ipPolicyPrefBase, which is usually 5200.
|
||||||
|
//
|
||||||
|
// This represents an experiment that will be used to gather more information.
|
||||||
|
// If this goes well, Tailscale may opt to use this for all of Linux.
|
||||||
|
var udmProIPRules = []netlink.Rule{
|
||||||
|
// non-fwmark packets fall through to the usual rules (pref 32766 and 32767,
|
||||||
|
// ie. main and default).
|
||||||
|
{
|
||||||
|
Priority: 70,
|
||||||
|
Invert: true,
|
||||||
|
Mark: linuxfw.TailscaleBypassMarkNum,
|
||||||
|
Table: tailscaleRouteTable.Num,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ipRules returns the appropriate list of ip rules to be used by Tailscale. See
|
||||||
|
// comments on baseIPRules and udmProIPRules for more details.
|
||||||
|
func ipRules() []netlink.Rule {
|
||||||
|
if getDistroFunc() == distro.UDMPro {
|
||||||
|
return udmProIPRules
|
||||||
|
}
|
||||||
|
return baseIPRules
|
||||||
|
}
|
||||||
|
|
||||||
// justAddIPRules adds policy routing rule without deleting any first.
|
// justAddIPRules adds policy routing rule without deleting any first.
|
||||||
func (r *linuxRouter) justAddIPRules() error {
|
func (r *linuxRouter) justAddIPRules() error {
|
||||||
if !r.ipRuleAvailable {
|
if !r.ipRuleAvailable {
|
||||||
@ -1243,7 +1275,7 @@ func (r *linuxRouter) justAddIPRules() error {
|
|||||||
var errAcc error
|
var errAcc error
|
||||||
for _, family := range r.addrFamilies() {
|
for _, family := range r.addrFamilies() {
|
||||||
|
|
||||||
for _, ru := range ipRules {
|
for _, ru := range ipRules() {
|
||||||
// Note: r is a value type here; safe to mutate it.
|
// Note: r is a value type here; safe to mutate it.
|
||||||
ru.Family = family.netlinkInt()
|
ru.Family = family.netlinkInt()
|
||||||
if ru.Mark != 0 {
|
if ru.Mark != 0 {
|
||||||
@ -1272,7 +1304,7 @@ func (r *linuxRouter) addIPRulesWithIPCommand() error {
|
|||||||
rg := newRunGroup(nil, r.cmd)
|
rg := newRunGroup(nil, r.cmd)
|
||||||
|
|
||||||
for _, family := range r.addrFamilies() {
|
for _, family := range r.addrFamilies() {
|
||||||
for _, rule := range ipRules {
|
for _, rule := range ipRules() {
|
||||||
args := []string{
|
args := []string{
|
||||||
"ip", family.dashArg(),
|
"ip", family.dashArg(),
|
||||||
"rule", "add",
|
"rule", "add",
|
||||||
@ -1320,7 +1352,7 @@ func (r *linuxRouter) delIPRules() error {
|
|||||||
}
|
}
|
||||||
var errAcc error
|
var errAcc error
|
||||||
for _, family := range r.addrFamilies() {
|
for _, family := range r.addrFamilies() {
|
||||||
for _, ru := range ipRules {
|
for _, ru := range ipRules() {
|
||||||
// Note: r is a value type here; safe to mutate it.
|
// Note: r is a value type here; safe to mutate it.
|
||||||
// When deleting rules, we want to be a bit specific (mention which
|
// When deleting rules, we want to be a bit specific (mention which
|
||||||
// table we were routing to) but not *too* specific (fwmarks, etc).
|
// table we were routing to) but not *too* specific (fwmarks, etc).
|
||||||
@ -1363,7 +1395,7 @@ func (r *linuxRouter) delIPRulesWithIPCommand() error {
|
|||||||
// That leaves us some flexibility to change these values in later
|
// That leaves us some flexibility to change these values in later
|
||||||
// versions without having ongoing hacks for every possible
|
// versions without having ongoing hacks for every possible
|
||||||
// combination.
|
// combination.
|
||||||
for _, rule := range ipRules {
|
for _, rule := range ipRules() {
|
||||||
args := []string{
|
args := []string{
|
||||||
"ip", family.dashArg(),
|
"ip", family.dashArg(),
|
||||||
"rule", "del",
|
"rule", "del",
|
||||||
@ -1500,7 +1532,7 @@ func normalizeCIDR(cidr netip.Prefix) string {
|
|||||||
// platformCanNetfilter reports whether the current distro/environment supports
|
// platformCanNetfilter reports whether the current distro/environment supports
|
||||||
// running iptables/nftables commands.
|
// running iptables/nftables commands.
|
||||||
func platformCanNetfilter() bool {
|
func platformCanNetfilter() bool {
|
||||||
switch distro.Get() {
|
switch getDistroFunc() {
|
||||||
case distro.Synology:
|
case distro.Synology:
|
||||||
// Synology doesn't support iptables or nftables. Attempting to run it
|
// Synology doesn't support iptables or nftables. Attempting to run it
|
||||||
// just blocks for a long time while it logs about failures.
|
// just blocks for a long time while it logs about failures.
|
||||||
@ -1526,7 +1558,7 @@ func cleanUp(logf logger.Logf, interfaceName string) {
|
|||||||
// of the config file being present as well as a policy rule with a specific
|
// of the config file being present as well as a policy rule with a specific
|
||||||
// priority (2000 + 1 - first interface mwan3 manages) and non-zero mark.
|
// priority (2000 + 1 - first interface mwan3 manages) and non-zero mark.
|
||||||
func checkOpenWRTUsingMWAN3() (bool, error) {
|
func checkOpenWRTUsingMWAN3() (bool, error) {
|
||||||
if distro.Get() != distro.OpenWrt {
|
if getDistroFunc() != distro.OpenWrt {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"tailscale.com/tstest"
|
"tailscale.com/tstest"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/util/linuxfw"
|
"tailscale.com/util/linuxfw"
|
||||||
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRouterStates(t *testing.T) {
|
func TestRouterStates(t *testing.T) {
|
||||||
@ -1231,3 +1232,24 @@ func adjustFwmask(t *testing.T, s string) string {
|
|||||||
|
|
||||||
return fwmaskAdjustRe.ReplaceAllString(s, "$1")
|
return fwmaskAdjustRe.ReplaceAllString(s, "$1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIPRulesForUDMPro(t *testing.T) {
|
||||||
|
// Override the global getDistroFunc
|
||||||
|
getDistroFunc = func() distro.Distro {
|
||||||
|
return distro.UDMPro
|
||||||
|
}
|
||||||
|
defer func() { getDistroFunc = distro.Get }() // Restore original after the test
|
||||||
|
|
||||||
|
expected := udmProIPRules
|
||||||
|
actual := ipRules()
|
||||||
|
|
||||||
|
if len(expected) != len(actual) {
|
||||||
|
t.Fatalf("Expected %d rules, got %d", len(expected), len(actual))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rule := range expected {
|
||||||
|
if rule != actual[i] {
|
||||||
|
t.Errorf("Rule mismatch at index %d: expected %+v, got %+v", i, rule, actual[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user