|
|
@@ -71,12 +71,26 @@ func (e *KernelEngine) Sync(groups []*SDWANGroup) error {
|
|
|
e.ensureMangleJump("PREROUTING")
|
|
|
e.ensureMangleJump("OUTPUT")
|
|
|
|
|
|
- // Bypass private RFC1918, loopback, link-local, and multicast
|
|
|
+ // 1. Bypass inbound traffic from all member WAN interfaces so return traffic is never classified/marked
|
|
|
+ seenInIfaces := make(map[string]bool)
|
|
|
+ for _, g := range groups {
|
|
|
+ for _, m := range g.Members {
|
|
|
+ if m.Interface != "" && !seenInIfaces[m.Interface] {
|
|
|
+ seenInIfaces[m.Interface] = true
|
|
|
+ _ = exec.Command("iptables", "-t", "mangle", "-A", "DEBTIK_SDWAN", "-i", m.Interface, "-j", "RETURN").Run()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. Bypass private RFC1918, loopback, link-local, and multicast
|
|
|
privateNets := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4"}
|
|
|
for _, net := range privateNets {
|
|
|
_ = exec.Command("iptables", "-t", "mangle", "-A", "DEBTIK_SDWAN", "-d", net, "-j", "RETURN").Run()
|
|
|
}
|
|
|
|
|
|
+ // 3. Ensure high-priority policy routing rules for private subnets so return traffic and LAN traffic always lookup main table
|
|
|
+ e.ensurePrivateRoutingRules()
|
|
|
+
|
|
|
// Restore connection mark: if a flow has already been classified, keep its mark (Session Stickiness!)
|
|
|
_ = exec.Command("iptables", "-t", "mangle", "-A", "DEBTIK_SDWAN", "-j", "CONNMARK", "--restore-mark").Run()
|
|
|
_ = exec.Command("iptables", "-t", "mangle", "-A", "DEBTIK_SDWAN", "-m", "mark", "!", "--mark", "0", "-j", "RETURN").Run()
|
|
|
@@ -168,9 +182,14 @@ func (e *KernelEngine) applyMemberRoute(m *SDWANMember) {
|
|
|
// Flush table first
|
|
|
_ = exec.Command("ip", "route", "flush", "table", tableStr).Run()
|
|
|
|
|
|
+ gw := m.Gateway
|
|
|
+ if gw == "" {
|
|
|
+ gw = e.detectInterfaceGateway(m.Interface)
|
|
|
+ }
|
|
|
+
|
|
|
args := []string{"route", "replace", "default"}
|
|
|
- if m.Gateway != "" {
|
|
|
- args = append(args, "via", m.Gateway)
|
|
|
+ if gw != "" {
|
|
|
+ args = append(args, "via", gw)
|
|
|
}
|
|
|
args = append(args, "dev", m.Interface, "table", tableStr)
|
|
|
|
|
|
@@ -179,9 +198,26 @@ func (e *KernelEngine) applyMemberRoute(m *SDWANMember) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+func (e *KernelEngine) detectInterfaceGateway(ifaceName string) string {
|
|
|
+ link, err := netlink.LinkByName(ifaceName)
|
|
|
+ if err != nil {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ routes, err := netlink.RouteList(link, netlink.FAMILY_V4)
|
|
|
+ if err != nil {
|
|
|
+ return ""
|
|
|
+ }
|
|
|
+ for _, r := range routes {
|
|
|
+ if r.Gw != nil && !r.Gw.IsUnspecified() {
|
|
|
+ return r.Gw.String()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return ""
|
|
|
+}
|
|
|
+
|
|
|
func (e *KernelEngine) applyMemberRule(m *SDWANMember) {
|
|
|
tableStr := strconv.Itoa(m.TableID)
|
|
|
- prefStr := strconv.Itoa(2000 + m.TableID%1000)
|
|
|
+ prefStr := strconv.Itoa(2100 + m.TableID%1000)
|
|
|
|
|
|
// Delete matching rule if already present
|
|
|
_ = exec.Command("ip", "rule", "del", "fwmark", m.MarkHex, "table", tableStr).Run()
|
|
|
@@ -354,7 +390,19 @@ func (e *KernelEngine) cleanupMangle() {
|
|
|
_ = exec.Command("iptables", "-t", "mangle", "-X", "DEBTIK_SDWAN").Run()
|
|
|
}
|
|
|
|
|
|
+func (e *KernelEngine) ensurePrivateRoutingRules() {
|
|
|
+ privateNets := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
|
|
|
+ for _, net := range privateNets {
|
|
|
+ _ = exec.Command("ip", "rule", "del", "to", net, "table", "main", "pref", "2000").Run()
|
|
|
+ _ = exec.Command("ip", "rule", "add", "to", net, "table", "main", "pref", "2000").Run()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func (e *KernelEngine) cleanupIPRules() {
|
|
|
+ privateNets := []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"}
|
|
|
+ for _, net := range privateNets {
|
|
|
+ _ = exec.Command("ip", "rule", "del", "to", net, "table", "main", "pref", "2000").Run()
|
|
|
+ }
|
|
|
out, err := exec.Command("ip", "rule", "show").CombinedOutput()
|
|
|
if err != nil {
|
|
|
return
|