Patch for multiwan setup via one interface



  • Hi. I came across a setup lately, where a 3 port appliance had only one available interface to talk to 3 adsl modem/routers. VLANs where out of the question, so I had to add 2 virtual IPs on the WAN interface and 2 more gateways. So, interface WAN had IP 192.168.2.10/24 and gw 192.168.2.1, 2 virtual IPs, 192.168.1.10/24 and 192.168.3.10/24, and two more gateways, 192.168.1.1 and 192.168.3.1.

    
    --------┐
     pfSense|
     router |=╦> 192.168.2.10/24---------192.168.2.1/24 (DSL Modem/Router 1)
            | ╠>(192.168.1.10/24)----┐
    --------┘ ╚>(192.168.3.10/24)-┐  |
                                  |  └---192.168.1.1/24 (DSL Modem/Router 2)
                                  |
                                  |
                                  └------192.168.3.1/24 (DSL Modem/Router 3)
    
    

    The rules created should be like:

    
    # let out anything from the firewall host itself and decrypted IPsec traffic
    pass out  inet all keep state allow-opts tracker 1000003715 label "let out anything IPv4 from firewall host itself"
    pass out  inet6 all keep state allow-opts tracker 1000003716 label "let out anything IPv6 from firewall host itself"
    pass out  route-to ( igb0 192.168.2.1 ) from 192.168.2.10 to !192.168.2.0/24 tracker 1000003811 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 192.168.3.1 ) from 192.168.3.10 to !192.168.3.0/24 tracker 1000003812 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 192.168.1.1 ) from 192.168.1.10 to !192.168.1.0/24 tracker 1000003813 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 fe80::1 ) inet6 from 2a02:587:2517:9300:20d:b9ff:fe47:29e0 to !2a02:587:2517:9300:20d:b9ff:fe47:29e0/64 tracker 1000003814 keep state allow-opts label "let out anything from firewall host itself"
    
    

    but were instead:

    
    # let out anything from the firewall host itself and decrypted IPsec traffic
    pass out  inet all keep state allow-opts tracker 1000003715 label "let out anything IPv4 from firewall host itself"
    pass out  inet6 all keep state allow-opts tracker 1000003716 label "let out anything IPv6 from firewall host itself"
    pass out  route-to ( igb0 192.168.2.1 ) from 192.168.2.10 to !192.168.2.0/24 tracker 1000003811 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 192.168.2.1 ) from 192.168.3.10 to !192.168.3.0/24 tracker 1000003812 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 192.168.2.1 ) from 192.168.1.10 to !192.168.1.0/24 tracker 1000003813 keep state allow-opts label "let out anything from firewall host itself"
    pass out  route-to ( igb0 fe80::1 ) inet6 from 2a02:587:2517:9300:20d:b9ff:fe47:29e0 to !2a02:587:2517:9300:20d:b9ff:fe47:29e0/64 tracker 1000003814 keep state allow-opts label "let out anything from firewall host itself"
    
    

    The problem is the "route-to" part, which takes the pair (interface gateway) from the interface definition, sending all traffic to the same gateway, where instead they should use an appropriate gateway for each virtual IP. The solution I found was to check if there's an available gateway in the virtual IP's subnet and use this, otherwise fall back to the default. The following patch seems to work, so I leave it here in case it actually is correct.

    
    diff -ru a/etc/inc/filter.inc b/etc/inc/filter.inc
    --- a/etc/inc/filter.inc	2017-07-23 13:15:00.590858022 +0300
    +++ b/etc/inc/filter.inc	2017-07-23 13:16:05.287896427 +0300
    @@ -3584,6 +3584,10 @@
     					if (ip_in_subnet($vip['ip'], "{$ifcfg['sa']}/{$ifcfg['sn']}")) {
     						$ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !{$ifcfg['sa']}/{$ifcfg['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
     					} else {
    +						$gw_temp = match_vip_to_gw($vip['ip'], gen_subnet($vip['ip'], $vip['sn']).'/'.$vip['sn']);
    +						if ($gw_temp != "") {
    +							$gw = $gw_temp;
    +						}
     						$ipfrules .= "pass out {$log['pass']} route-to ( {$ifcfg['if']} {$gw} ) from {$vip['ip']} to !" . gen_subnet($vip['ip'], $vip['sn']) . "/{$vip['sn']} tracker {$increment_tracker($tracker)} keep state allow-opts label \"let out anything from firewall host itself\"\n";
     					}
     				}
    diff -ru a/etc/inc/gwlb.inc b/etc/inc/gwlb.inc
    --- a/etc/inc/gwlb.inc	2017-07-23 13:15:01.302869525 +0300
    +++ b/etc/inc/gwlb.inc	2017-07-23 13:16:05.415898468 +0300
    @@ -1340,4 +1340,20 @@
    
     	return $members;
     }
    +
    +function match_vip_to_gw($ipaddr, $subnet) {
    +	global $config;
    +
    +	if (is_array($config['gateways']['gateway_item'])) {
    +		$gws = $config['gateways']['gateway_item'];
    +	} else {
    +		return false;
    +	}
    +
    +	foreach ($gws as $gw) {
    +		if (ip_in_subnet($gw['gateway'], $subnet)) {
    +			return $gw['gateway'];
    +		}
    +	}
    +}
     ?>