wgengine/router: add ip rules for unifi udm-pro

Fixes: #4038

Signed-off-by: Jason Barnett <J@sonBarnett.com>
This commit is contained in:
Jason Barnett 2024-01-11 17:36:12 -07:00
parent ff1391a97e
commit c097c31135
3 changed files with 103 additions and 6 deletions

View File

@ -10,6 +10,7 @@ import (
"os"
"runtime"
"strconv"
"strings"
"tailscale.com/types/lazy"
"tailscale.com/util/lineread"
@ -31,6 +32,7 @@ const (
WDMyCloud = Distro("wdmycloud")
Unraid = Distro("unraid")
Alpine = Distro("alpine")
UDMPro = Distro("udmpro")
)
var distro lazy.SyncValue[Distro]
@ -76,6 +78,9 @@ func linuxDistro() Distro {
case have("/usr/local/bin/freenas-debug"):
// TrueNAS Scale runs on debian
return TrueNAS
case isUDMPro():
// UDM-Pro runs on debian
return UDMPro
case have("/etc/debian_version"):
return Debian
case have("/etc/arch-release"):
@ -147,3 +152,25 @@ func DSMVersion() int {
return v
})
}
func isUDMPro() bool {
if exists, err := fileContainsString("/etc/board.info", "UDMPRO"); err == nil && exists {
return true
}
if exists, err := fileContainsString("/etc/board.info", "Dream Machine PRO"); err == nil && exists {
return true
}
if exists, err := fileContainsString("/sys/firmware/devicetree/base/soc/board-cfg/id", "udm pro"); err == nil && exists {
return true
}
return false
}
func fileContainsString(filePath, searchString string) (bool, error) {
data, err := os.ReadFile(filePath)
if err != nil {
return false, err
}
return strings.Contains(string(data), searchString), nil
}

View File

@ -1031,6 +1031,10 @@ func mustRouteTable(num int) RouteTable {
var (
mainRouteTable = newRouteTable("main", 254)
defaultRouteTable = newRouteTable("default", 253)
// Port 9 - WAN
udmProRouteTable1 = newRouteTable("201", 201)
// Port 10 - SFP+ WAN 2
udmProRouteTable2 = newRouteTable("202", 202)
// tailscaleRouteTable is the routing table number for Tailscale
// network routes. See addIPRules for the detailed policy routing
@ -1051,7 +1055,7 @@ var (
tailscaleRouteTable = newRouteTable("tailscale", 52)
)
// ipRules are the policy routing rules that Tailscale uses.
// _ipRules are the policy routing rules that Tailscale uses.
// The priority is the value represented here added to r.ipPolicyPrefBase,
// which is usually 5200.
//
@ -1066,7 +1070,7 @@ var (
// and 'ip rule' implementations (including busybox), don't support
// checking for the lack of a fwmark, only the presence. The technique
// below works even on very old kernels.
var ipRules = []netlink.Rule{
var _ipRules = []netlink.Rule{
// Packets from us, tagged with our fwmark, first try the kernel's
// main routing table.
{
@ -1102,6 +1106,17 @@ var ipRules = []netlink.Rule{
// usual rules (pref 32766 and 32767, ie. main and default).
}
var addedUDMProIPRules = false
func ipRules() []netlink.Rule {
// lazy add UDM Pro rules
if distro.Get() == distro.UDMPro {
addUDMProIpRules()
}
return _ipRules
}
// justAddIPRules adds policy routing rule without deleting any first.
func (r *linuxRouter) justAddIPRules() error {
if !r.ipRuleAvailable {
@ -1113,7 +1128,7 @@ func (r *linuxRouter) justAddIPRules() error {
var errAcc error
for _, family := range r.addrFamilies() {
for _, ru := range ipRules {
for _, ru := range ipRules() {
// Note: r is a value type here; safe to mutate it.
ru.Family = family.netlinkInt()
if ru.Mark != 0 {
@ -1142,7 +1157,7 @@ func (r *linuxRouter) addIPRulesWithIPCommand() error {
rg := newRunGroup(nil, r.cmd)
for _, family := range r.addrFamilies() {
for _, rule := range ipRules {
for _, rule := range ipRules() {
args := []string{
"ip", family.dashArg(),
"rule", "add",
@ -1190,7 +1205,7 @@ func (r *linuxRouter) delIPRules() error {
}
var errAcc error
for _, family := range r.addrFamilies() {
for _, ru := range ipRules {
for _, ru := range ipRules() {
// Note: r is a value type here; safe to mutate it.
// When deleting rules, we want to be a bit specific (mention which
// table we were routing to) but not *too* specific (fwmarks, etc).
@ -1233,7 +1248,7 @@ func (r *linuxRouter) delIPRulesWithIPCommand() error {
// That leaves us some flexibility to change these values in later
// versions without having ongoing hacks for every possible
// combination.
for _, rule := range ipRules {
for _, rule := range ipRules() {
args := []string{
"ip", family.dashArg(),
"rule", "del",
@ -1395,3 +1410,22 @@ func nlAddrOfPrefix(p netip.Prefix) *netlink.Addr {
IPNet: netipx.PrefixIPNet(p),
}
}
// Adds necessary ip rules for UDM-Pro. See issue 4038.
func addUDMProIpRules() {
if addedUDMProIPRules {
return
}
_ipRules = append(_ipRules, netlink.Rule{
Priority: 21,
Mark: linuxfw.TailscaleBypassMarkNum,
Table: udmProRouteTable1.Num,
}, netlink.Rule{
Priority: 22,
Mark: linuxfw.TailscaleBypassMarkNum,
Table: udmProRouteTable2.Num,
})
addedUDMProIPRules = true
}

View File

@ -19,6 +19,7 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
ts_netlink "github.com/tailscale/netlink"
"github.com/tailscale/wireguard-go/tun"
"github.com/vishvananda/netlink"
"go4.org/netipx"
@ -1132,3 +1133,38 @@ func adjustFwmask(t *testing.T, s string) string {
return fwmaskAdjustRe.ReplaceAllString(s, "$1")
}
func TestAddUDMProIpRules(t *testing.T) {
originalIpRules := make([]ts_netlink.Rule, len(_ipRules))
copy(originalIpRules, _ipRules)
addUDMProIpRules()
expectedRules := []ts_netlink.Rule{
{
Priority: 21,
Mark: linuxfw.TailscaleBypassMarkNum,
Table: udmProRouteTable1.Num,
},
{
Priority: 22,
Mark: linuxfw.TailscaleBypassMarkNum,
Table: udmProRouteTable2.Num,
},
}
for _, expected := range expectedRules {
found := false
for _, actual := range ipRules() {
if reflect.DeepEqual(actual, expected) {
found = true
break
}
}
if !found {
t.Errorf("Expected rule %+v; not found in ipRules", expected)
}
}
_ipRules = originalIpRules
}