CONNMARK patch for two WAN connections

Hello.

I got into well-known problem with two inbound ISP connections.

Here’s my little patch wich allows to CONNMARK inbound connections and to create a policy table rule to handle them.

What the patch does.

It adds two new commands to VyOS firewall.

  1. set policy enable-connmark-restore-mark
    This command adds CONNMARK restore-mark rules for ESTABLISHED and RELATED connections to PREROUTING (not sure about rules ordering, please correct me if it should be the first rule in chain, not the last) and OUTPUT chains.

  2. set policy … rule … set connmark
    This rule actually sets the CONNMARK and MARK for NEW incoming connections and creates corresponding ip route rule for them.

After creating policy with CONNMARK rules just apply them to inbound interfaces. Here’s my test config:

firewall {
    state-policy {
        established {
            action accept
        }
        related {
            action accept
        }
    }
    syn-cookies enable
}
interfaces {
    ethernet eth0 {
        address 192.168.3.1/24
        duplex auto
        hw-id 08:00:27:dd:a1:da
        policy {
            route ISP_eth0
        }
        smp_affinity auto
        speed auto
    }
    ethernet eth1 {
        address 192.168.129.1/24
        duplex auto
        hw-id 08:00:27:aa:dd:a3
        policy {
            route ISP_eth1
        }
        smp_affinity auto
        speed auto
    }
    loopback lo {
    }
}
policy {
    enable-connmark-restore-mark
    route ISP_eth0 {
        rule 10 {
            set {
                connmark 1
            }
        }
    }
    route ISP_eth1 {
        rule 10 {
            set {
                connmark 2
            }
        }
    }
}
protocols {
    static {
        route 0.0.0.0/0 {
            next-hop 192.168.3.254 {
                distance 100
            }
            next-hop 192.168.129.254 {
                distance 100
            }
        }
        table 1 {
            route 0.0.0.0/0 {
                next-hop 192.168.3.254 {
                }
            }
        }
        table 2 {
            route 0.0.0.0/0 {
                next-hop 192.168.129.254 {
                }
            }
        }
    }
}

And here’s the patch itself (I can’t attach it as a file, the board says that this type of files isn’t allowd)

diff --git a/lib/Vyatta/IpTables/Rule.pm b/lib/Vyatta/IpTables/Rule.pm
index 3c266f6..b2b605b 100755
--- a/lib/Vyatta/IpTables/Rule.pm
+++ b/lib/Vyatta/IpTables/Rule.pm
@@ -25,6 +25,7 @@ my %fields = (
   _icmp_type   => undef,
   _icmp_name   => undef,
   _icmpv6_type => undef,
+  _mod_connmark => undef,
   _mod_mark    => undef,
   _mod_table   => undef,
   _mod_dscp    => undef,
@@ -78,6 +79,7 @@ my %dummy_rule = (
   _icmp_type   => undef,
   _icmp_name   => undef,
   _icmpv6_type => undef,
+  _mod_connmark => undef,
   _mod_mark    => undef,
   _mod_table   => undef,
   _mod_dscp    => undef,
@@ -172,6 +174,7 @@ sub setup_base {
   $self->{_icmp_type}   = $config->$val_func("icmp type");
   $self->{_icmp_name}   = $config->$val_func("icmp type-name");
   $self->{_icmpv6_type} = $config->$val_func("icmpv6 type");
+  $self->{_mod_connmark}    = $config->$val_func("set connmark");
   $self->{_mod_mark}    = $config->$val_func("set mark");
   $self->{_mod_table}   = $config->$val_func("set table");
   if ($self->{_mod_table} eq 'main') {
@@ -253,6 +256,7 @@ sub print {
   print "icmp type: $self->{_icmp_type}\n" if defined $self->{_icmp_type};
   print "icmpv6 type: $self->{_icmpv6_type}\n"
                                            if defined $self->{_icmpv6_type};
+  print "mod connmark: $self->{_mod_connmark}\n"   if defined $self->{_mod_connmark};
   print "mod mark: $self->{_mod_mark}\n"   if defined $self->{_mod_mark};
   print "mod table: $self->{_mod_table}\n"   if defined $self->{_mod_table};
   print "mod dscp: $self->{_mod_dscp}\n"   if defined $self->{_mod_dscp};
@@ -282,6 +286,11 @@ sub is_disabled {
   return 0;
 }
 
+sub is_connmark_table {
+  my $self = shift;
+  return $self->{_mod_connmark};
+}
+
 sub is_route_table {
   my $self = shift;
   return $self->{_mod_table};
@@ -598,6 +607,11 @@ first character capitalized eg. Mon,Thu,Sat For negation, add ! in front eg. !Mo
   } elsif ($self->{_comment} =~ m/^policy/) {
     # mangle actions
     my $count = 0;
+    if (defined($self->{_mod_connmark})) {
+      # CONNMARK
+      $rule .= "-j VYATTA_PBR_$self->{_mod_connmark} ";
+      $count++;
+    }
     if (defined($self->{_mod_mark})) {
       # MARK
       $rule .= "-j MARK --set-mark $self->{_mod_mark} ";
diff --git a/scripts/firewall/vyatta-firewall.pl b/scripts/firewall/vyatta-firewall.pl
index b29ded9..637f540 100755
--- a/scripts/firewall/vyatta-firewall.pl
+++ b/scripts/firewall/vyatta-firewall.pl
@@ -18,6 +18,7 @@ my $debug_flag = 0;
 # Enable sending debug output to syslog.
 my $syslog_flag = 1;
 
+
 my $fw_stateful_file = '/var/run/vyatta_fw_stateful';
 my $fw_tree_file     = '/var/run/vyatta_fw_trees';
 my $policy_ref_file  = '/var/run/vyatta_policy_ref';
@@ -30,13 +31,17 @@ my $max_rule = 10000;
 my (@setup, @updateints, @updaterules);
 my ($teardown, $teardown_ok);
 
+# CONNMARK restore
+my ($connmark_restore);
+
 GetOptions("setup=s{2}"        => \@setup,
            "teardown=s"        => \$teardown,
            "teardown-ok=s"     => \$teardown_ok,
            "update-rules=s{2}" => \@updaterules,
            "update-interfaces=s{5}" => \@updateints,
            "debug"             => \$debug_flag,
-           "syslog"            => \$syslog_flag
+           "syslog"            => \$syslog_flag,
+	   "connmark-restore=i"            => \$connmark_restore
 );
 
 # mapping from config node to iptables/ip6tables table
@@ -200,6 +205,16 @@ if (defined $teardown) {
   exit 0;
 }
 
+if(defined $connmark_restore) {
+	if($connmark_restore == 1) {
+		enable_fw_connmark_restoremark ();
+	}
+	else {
+		disable_fw_connmark_restoremark ();
+	}
+	exit 0;
+}
+
 help();
 exit 1;
 
@@ -326,7 +341,7 @@ sub is_tree_in_use {
 }
 
 sub add_route_table {
-  my ($table, $rule) = @_;
+  my ($table, $connmark, $rule) = @_;
   my $rule_found = 0;
   my $table_count = -1;
   my @newlines = ();
@@ -364,7 +379,15 @@ sub add_route_table {
     my $mark = 0x7FFFFFFF + $table;
     system("ip rule add pref $table fwmark $mark table $table");
     run_cmd("iptables -t mangle -N VYATTA_PBR_$table", 1);
-    run_cmd("iptables -t mangle -I VYATTA_PBR_$table 1 -j MARK --set-mark $mark", 1);
+	if($connmark == 0)
+	{
+		run_cmd("iptables -t mangle -I VYATTA_PBR_$table 1 -j MARK --set-mark $mark", 1);
+	}
+	else
+	{
+		# This is a CONNMARK table
+		run_cmd("iptables -t mangle -I VYATTA_PBR_$table 1 -m state --state NEW -j CONNMARK --set-mark $mark", 1);
+	}
     run_cmd("iptables -t mangle -I VYATTA_PBR_$table 2 -j ACCEPT", 1);
   }
 
@@ -581,7 +604,11 @@ sub update_rules {
       }
 
       if ($node->is_route_table) {
-        add_route_table($node->is_route_table, $name);
+        add_route_table($node->is_route_table, 0, $name);
+      }
+
+      if ($node->is_connmark_table) {
+	add_route_table($node->is_connmark_table, 1, $name);
       }
 
       my ($err_str, @rule_strs) = $node->rule();
@@ -829,6 +856,19 @@ sub disable_fw_conntrack {
   run_cmd("$iptables_cmd -t raw -R FW_CONNTRACK 1 -j RETURN", 1);
 }
 
+sub enable_fw_connmark_restoremark {
+  my $iptables_cmd = shift;
+  log_msg("enable_fw_connmark_restoremark");
+  run_cmd("iptables -t mangle -A PREROUTING -m state --state ESTABLISHED,RELATED -m connmark ! --mark 0 -j CONNMARK --restore-mark", 1);
+  run_cmd("iptables -t mangle -A OUTPUT -m state --state ESTABLISHED,RELATED -m connmark ! --mark 0 -j CONNMARK --restore-mark", 1);
+}
+
+sub disable_fw_connmark_restoremark {
+  my $iptables_cmd = shift;
+  log_msg("disable_fw_connmark_restoremark");
+  run_cmd("iptables -t mangle -D PREROUTING -m state --state ESTABLISHED,RELATED -m connmark ! --mark 0 -j CONNMARK --restore-mark", 1);
+  run_cmd("iptables -t mangle -D OUTPUT -m state --state ESTABLISHED,RELATED -m connmark ! --mark 0 -j CONNMARK --restore-mark", 1);
+}
 
 sub teardown_iptables {
   my ($table, $iptables_cmd) = @_;
diff --git a/templates/policy/enable-connmark-restore-mark/node.def b/templates/policy/enable-connmark-restore-mark/node.def
new file mode 100644
index 0000000..0ea1039
--- /dev/null
+++ b/templates/policy/enable-connmark-restore-mark/node.def
@@ -0,0 +1,5 @@
+create: sudo /opt/vyatta/sbin/vyatta-firewall.pl --connmark-restore 1
+
+delete: sudo /opt/vyatta/sbin/vyatta-firewall.pl --connmark-restore 0
+
+help: Option to CONNMARK auto restore-mark for all connections
diff --git a/templates/policy/ipv6-route/node.tag/rule/node.tag/set/connmark/node.def b/templates/policy/ipv6-route/node.tag/rule/node.tag/set/connmark/node.def
new file mode 100644
index 0000000..72007f1
--- /dev/null
+++ b/templates/policy/ipv6-route/node.tag/rule/node.tag/set/connmark/node.def
@@ -0,0 +1,16 @@
+type: txt
+help: Routing table to forward packet with (marked with CONNMARK)
+val_help: u32:1-200 ; Table number
+val_help: main ; Main table
+syntax:expression: exec "
+  if [[ $VAR(@) =~ ^-?[0-9]+$ ]] ; then 
+    if [ $VAR(@) -lt 1 -o $VAR(@) -gt 200 ] ; then
+        echo Table must be between 1-200 or table main
+        exit 1
+    fi
+  else
+    if [[ $VAR(@) != 'main' ]] ; then
+      echo Table must be between 1-200 or table main
+      exit 1
+    fi
+  fi"
diff --git a/templates/policy/ipv6-route/node.tag/rule/node.tag/set/dscp/node.def b/templates/policy/ipv6-route/node.tag/rule/node.tag/set/dscp/node.def
old mode 100755
new mode 100644
diff --git a/templates/policy/ipv6-route/node.tag/rule/node.tag/set/table/node.def b/templates/policy/ipv6-route/node.tag/rule/node.tag/set/table/node.def
old mode 100755
new mode 100644
diff --git a/templates/policy/route/node.tag/rule/node.tag/set/connmark/node.def b/templates/policy/route/node.tag/rule/node.tag/set/connmark/node.def
new file mode 100644
index 0000000..72007f1
--- /dev/null
+++ b/templates/policy/route/node.tag/rule/node.tag/set/connmark/node.def
@@ -0,0 +1,16 @@
+type: txt
+help: Routing table to forward packet with (marked with CONNMARK)
+val_help: u32:1-200 ; Table number
+val_help: main ; Main table
+syntax:expression: exec "
+  if [[ $VAR(@) =~ ^-?[0-9]+$ ]] ; then 
+    if [ $VAR(@) -lt 1 -o $VAR(@) -gt 200 ] ; then
+        echo Table must be between 1-200 or table main
+        exit 1
+    fi
+  else
+    if [[ $VAR(@) != 'main' ]] ; then
+      echo Table must be between 1-200 or table main
+      exit 1
+    fi
+  fi"
diff --git a/templates/policy/route/node.tag/rule/node.tag/set/dscp/node.def b/templates/policy/route/node.tag/rule/node.tag/set/dscp/node.def
old mode 100755
new mode 100644
diff --git a/templates/policy/route/node.tag/rule/node.tag/set/table/node.def b/templates/policy/route/node.tag/rule/node.tag/set/table/node.def
old mode 100755
new mode 100644
index bb97649..2a7a844
--- a/templates/policy/route/node.tag/rule/node.tag/set/table/node.def
+++ b/templates/policy/route/node.tag/rule/node.tag/set/table/node.def
@@ -1,5 +1,5 @@
 type: txt
-help: Routing table to forward packet with
+help: Routing table to forward packet with (marked with MARK)
 val_help: u32:1-200 ; Table number
 val_help: main ; Main table
 syntax:expression: exec "