From 09f45fbdbf57ba8a68b2a9569c1942eb5a7b8baa Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Thu, 15 Mar 2018 11:11:31 +0000 Subject: [PATCH] system: rewrite the default route handling With this, there is consistency in setting the default route and associated marker file. Nothing is done before the decision is final, so we may not end up with a missing route after an attempt to reconfigure for something that doesn't work anyway. DHCPv4 and PPP still do this manually, but we will have to route them through the same logic instead of them manually handling their routes. 6rd and 6to4 may have a similar issue as well. While here, swap the system_routing_configure() arguments to align with the way other configure functions present their arguments (verbose print flag first). Gateway monitoring and default switching improvement since 2016. Push $keep into default handling, it may be useful in general to avoid spurious switches. It could also be a default probably. --- src/etc/inc/gwlb.inc | 22 +----- src/etc/inc/interfaces.inc | 6 +- src/etc/inc/system.inc | 139 +++++++++++++++++++---------------- src/etc/rc.bootup | 2 +- src/etc/rc.newwanip | 4 +- src/etc/rc.newwanipv6 | 2 +- src/etc/rc.routing_configure | 2 +- 7 files changed, 88 insertions(+), 89 deletions(-) diff --git a/src/etc/inc/gwlb.inc b/src/etc/inc/gwlb.inc index 7ce4b8f27..d4d67035f 100644 --- a/src/etc/inc/gwlb.inc +++ b/src/etc/inc/gwlb.inc @@ -1,6 +1,7 @@ Copyright (C) 2008 Bill Marquette Copyright (C) 2008 Seth Mos Copyright (C) 2010 Ermal Luçi @@ -769,25 +770,8 @@ function fixup_default_gateway($gateways_status, $gateways_arr) continue; } - $defaultif = get_real_interface($gateways_arr[$dfltgwname]['friendlyiface']); - $gwipmatch = $dfltgwip; - if (is_linklocal($dfltgwip)) { - /* correct match in IPv6 case */ - $gwipmatch .= "%{$defaultif}"; - } - - $tmpcmd = "/sbin/route -n get -{$ipprotocol} default 2>/dev/null | /usr/bin/awk '/gateway:/ {print $2}'"; - $defaultgw = trim(exec($tmpcmd), " \n"); - if ($defaultgw != $gwipmatch) { - foreach (glob('/tmp/*_defaultgw' . ($ipprotocol == 'inet' ? '' : 'v6')) as $to_delete) { - log_error("GATEWAYS: removing {$to_delete}"); - @unlink($to_delete); - } - log_error("Switching default gateway to $dfltgwname ($dfltgwip on $defaultif)"); - @file_put_contents("/tmp/{$defaultif}_defaultgw" . ($ipprotocol == 'inet' ? '' : 'v6'), $dfltgwip); - /* XXX fargw and IPv6 should be cleaned up to make it easier to read */ - system_default_route($dfltgwip, !isset($gateways_arr[$dfltgwname]['fargw']) && $ipprotocol == 'inet' ? null : $defaultif); - } + /* switch only if another gateway is set aa the default */ + system_default_route($dfltgwip, $ipprotocol, $gateways_arr[$dfltgwname]['friendlyiface'], isset($gateways_arr[$dfltgwname]['fargw']), true); } } diff --git a/src/etc/inc/interfaces.inc b/src/etc/inc/interfaces.inc index 7e042ef74..41adfb996 100644 --- a/src/etc/inc/interfaces.inc +++ b/src/etc/inc/interfaces.inc @@ -995,7 +995,7 @@ function interface_gif_configure(&$gif, $gifkey = "") foreach ($iflist as $ifname) { if ($config['interfaces'][$ifname]['if'] == $gifif) { if (get_interface_gateway($ifname) || get_interface_gateway_v6($ifname)) { - system_routing_configure($ifname); + system_routing_configure(false, $ifname); break; } } @@ -1076,7 +1076,7 @@ function interfaces_configure($verbose = false) interfaces_group_setup(); if ($reload) { - system_routing_configure('', $verbose); + system_routing_configure($verbose); ipsec_configure_do($verbose); plugins_configure('dns', $verbose); services_dhcpd_configure('all', array(), $verbose); @@ -2713,7 +2713,7 @@ function interface_configure($interface = 'wan', $reloadall = false, $linkupeven } if ($reloadall == true) { - system_routing_configure($interface); + system_routing_configure(false, $interface); ipsec_configure_do(); plugins_configure('dns'); services_dhcpd_configure(); diff --git a/src/etc/inc/system.inc b/src/etc/inc/system.inc index dcac9923e..216b900a6 100644 --- a/src/etc/inc/system.inc +++ b/src/etc/inc/system.inc @@ -417,30 +417,67 @@ function system_host_route($host, $gateway, $delete = true, $add = true) } } -function system_default_route($gateway, $interface = null) +function system_default_route($gateway, $family, $interface, $far = false, $keep = false) { - if (is_ipaddrv4($gateway)) { - $family = 'inet'; - } elseif (is_ipaddrv6($gateway)) { - $family = 'inet6'; - if (is_linklocal($gateway)) { - $gateway .= "%{$interface}"; + $realif = get_real_interface($interface); + + switch ($family) { + case 'inet': + break; + case 'inet6': + if (is_linklocal($gateway)) { + $gateway .= "%{$realif}"; + } + break; + default: + log_error("ROUTING: unknown address family '{$family}'"); + return; + } + + if ($keep) { + $tmpcmd = "/sbin/route -n get -{$family} default 2>/dev/null | /usr/bin/awk '/gateway:/ {print $2}'"; + $current = trim(exec($tmpcmd), " \n"); + if ($current == $gateway) { + log_error("ROUTING: keeping current default gateway '{$gateway}'"); + return; + } + } + + if ($family == 'inet') { + foreach (glob('/tmp/*_defaultgw', GLOB_BRACE) as $to_delete) { + log_error("ROUTING: removing {$to_delete}"); + @unlink($to_delete); + } + + log_error("ROUTING: creating /tmp/{$realif}_defaultgw using '{$gateway}'"); + @file_put_contents("/tmp/{$realif}_defaultgw", $gateway); + + if (!$far) { + $realif = null; } - /* IPv6 does not support far gateway notion */ - $interface = null; } else { - return; + foreach (glob('/tmp/*_defaultgwv6', GLOB_BRACE) as $to_delete) { + log_error("ROUTING: removing {$to_delete}"); + @unlink($to_delete); + } + + log_error("ROUTING: creating /tmp/{$realif}_defaultgwv6 using '{$gateway}'"); + @file_put_contents("/tmp/{$realif}_defaultgwv6", $gateway); + + /* IPv6 does not support far gateway notion */ + $realif = null; + $far = false; } mwexecf('/sbin/route delete -%s default', array($family), true); - if (!empty($interface)) { - mwexecf('/sbin/route delete -%s %s -interface %s', array($family, $gateway, $interface), true); - mwexecf('/sbin/route add -%s %s -interface %s', array($family, $gateway, $interface)); + if (!empty($realif)) { + mwexecf('/sbin/route delete -%s %s -interface %s', array($family, $gateway, $realif), true); + mwexecf('/sbin/route add -%s %s -interface %s', array($family, $gateway, $realif)); } mwexecf('/sbin/route add -%s default %s', array($family, $gateway)); } -function system_routing_configure($interface = '', $verbose = false) +function system_routing_configure($verbose = false, $interface = '') { global $config; @@ -449,58 +486,45 @@ function system_routing_configure($interface = '', $verbose = false) flush(); } - $gatewayip = ""; - $interfacegw = ""; + $interfacegw = ''; $foundgw = false; - $gatewayipv6 = ""; - $interfacegwv6 = ""; - $foundgwv6 = false; + $gatewayip = ''; $fargw = false; + $interfacegwv6 = ''; + $foundgwv6 = false; + $gatewayipv6 = ''; + if (!empty($interface)) { log_error("ROUTING: entering configure using '${interface}'"); } else { log_error("ROUTING: entering configure using defaults"); } - /* tack on all the hard defined gateways as well */ if (isset($config['gateways']['gateway_item'])) { - foreach (glob('/tmp/*_defaultgw{,v6}', GLOB_BRACE) as $to_delete) { - log_error("ROUTING: removing {$to_delete}"); - @unlink($to_delete); - } - foreach ($config['gateways']['gateway_item'] as $gateway) { if (isset($gateway['defaultgw'])) { - if ($foundgw == false && $gateway['ipprotocol'] != "inet6" && (is_ipaddrv4($gateway['gateway']) || $gateway['gateway'] == "dynamic")) { - if ($gateway['gateway'] == "dynamic") { + if ($foundgw == false && $gateway['ipprotocol'] != 'inet6') { + if ($gateway['gateway'] == 'dynamic') { $gateway['gateway'] = get_interface_gateway($gateway['interface']); } + + $interfacegw = $gateway['interface']; $fargw = isset($gateway['fargw']); $gatewayip = $gateway['gateway']; - $interfacegw = $gateway['interface']; - if (!empty($gateway['interface'])) { - $defaultif = get_real_interface($gateway['interface']); - if ($defaultif && is_ipaddrv4($gatewayip)) { - log_error("ROUTING: creating /tmp/{$defaultif}_defaultgw"); - @file_put_contents("/tmp/{$defaultif}_defaultgw", $gatewayip); - } - } $foundgw = true; - } elseif ($foundgwv6 == false && $gateway['ipprotocol'] == "inet6" && (is_ipaddrv6($gateway['gateway']) || $gateway['gateway'] == "dynamic")) { - if ($gateway['gateway'] == "dynamic") { + + log_error("ROUTING: IPv4 default gateway set to {$interfacegw}"); + } elseif ($foundgwv6 == false && $gateway['ipprotocol'] == 'inet6') { + if ($gateway['gateway'] == 'dynamic') { $gateway['gateway'] = get_interface_gateway_v6($gateway['interface']); } - $gatewayipv6 = $gateway['gateway']; + $interfacegwv6 = $gateway['interface']; - if (!empty($gateway['interface'])) { - $defaultifv6 = get_real_interface($gateway['interface']); - if ($defaultifv6 && is_ipaddrv6($gatewayipv6)) { - log_error("ROUTING: creating /tmp/{$defaultifv6}_defaultgwv6"); - @file_put_contents("/tmp/{$defaultifv6}_defaultgwv6", $gatewayipv6); - } - } + $gatewayipv6 = $gateway['gateway']; $foundgwv6 = true; + + log_error("ROUTING: IPv6 default gateway set to {$interfacegwv6}"); } } } @@ -516,43 +540,34 @@ function system_routing_configure($interface = '', $verbose = false) * In fact the following code is a mini gateway switcher * facility which can only switch one hardcoded gateway and * may obscure a problem with the setup for a long time... :( + * + * XXX Find a way to infer an upstream-capable interface or + * maybe make gateway switching the hardcoded behaviour, or + * at least the new default. */ if (!$foundgw) { $interfacegw = 'wan'; - $defaultif = get_real_interface($interfacegw); + log_error("ROUTING: no IPv4 default gateway set, assuming {$interfacegw}"); $gatewayip = get_interface_gateway($interfacegw); - if (is_ipaddrv4($gatewayip)) { - log_error("ROUTING: no IPv4 default gateway set, assuming {$interfacegw} on '{$defaultif}' ({$gatewayip})"); - @file_put_contents("/tmp/{$defaultif}_defaultgw", $gatewayip); - } else { - log_error("ROUTING: no IPv4 default gateway set, skipping {$interfacegw} on '{$defaultif}'"); - } } if (!$foundgwv6) { $interfacegwv6 = 'wan'; - $defaultifv6 = get_real_interface($interfacegwv6); + log_error("ROUTING: no IPv6 default gateway set, assuming {$interfacegwv6}"); $gatewayipv6 = get_interface_gateway_v6($interfacegwv6); - if (is_ipaddrv6($gatewayipv6)) { - log_error("ROUTING: no IPv6 default gateway set, trying {$interfacegwv6} on '{$defaultifv6}' ({$gatewayipv6})"); - @file_put_contents("/tmp/{$defaultifv6}_defaultgwv6", $gatewayipv6); - } else { - log_error("ROUTING: no IPv6 default gateway set, skipping {$interfacegwv6} on '{$defaultifv6}'"); - $interfacegwv6 = ''; - } } if ((empty($interface) || $interface == $interfacegw) && is_ipaddrv4($gatewayip)) { log_error("ROUTING: setting IPv4 default route to {$gatewayip}"); - system_default_route($gatewayip, $fargw ? $defaultif : null); + system_default_route($gatewayip, 'inet', $interfacegw, $fargw); } else { log_error("ROUTING: skipping IPv4 default route"); } if ((empty($interface) || $interface == $interfacegwv6) && is_ipaddrv6($gatewayipv6)) { log_error("ROUTING: setting IPv6 default route to {$gatewayipv6}"); - system_default_route($gatewayipv6, $defaultifv6); + system_default_route($gatewayipv6, 'inet6', $interfacegwv6, false); } else { log_error("ROUTING: skipping IPv6 default route"); } diff --git a/src/etc/rc.bootup b/src/etc/rc.bootup index 7a7ad21b8..afb26e0ad 100755 --- a/src/etc/rc.bootup +++ b/src/etc/rc.bootup @@ -104,7 +104,7 @@ filter_pflog_start(true); setup_gateways_monitor(true); plugins_configure('earlybootup', true); system_cron_configure(true, true); -system_routing_configure('', true); +system_routing_configure(true); plugins_configure('dns', true); services_dhcpd_configure('all', array(), true); diff --git a/src/etc/rc.newwanip b/src/etc/rc.newwanip index 1a92ddad0..dcf3c78e3 100755 --- a/src/etc/rc.newwanip +++ b/src/etc/rc.newwanip @@ -144,7 +144,7 @@ if (isset($config['gifs']['gif']) && is_array($config['gifs']['gif'])){ if (!empty($confif)) { interface_configure($confif); } - system_routing_configure($ifname); + system_routing_configure(false, $ifname); } } } @@ -161,7 +161,7 @@ $cacheip = @file_get_contents($cacheip_file); if (!is_ipaddr($cacheip) || $ip != $cacheip || !is_ipaddr($configip)) { @unlink($cacheip_file); - system_routing_configure($interface); + system_routing_configure(false, $interface); setup_gateways_monitor(); plugins_configure('vpn', false, array($interface)); filter_configure_sync(); diff --git a/src/etc/rc.newwanipv6 b/src/etc/rc.newwanipv6 index 2c357ce20..1b05ac821 100755 --- a/src/etc/rc.newwanipv6 +++ b/src/etc/rc.newwanipv6 @@ -142,7 +142,7 @@ if (!is_ipaddr($cacheip) || $ip != $cacheip || !is_ipaddr($configip)) { @unlink($cacheip_file); - system_routing_configure($interface); + system_routing_configure(false, $interface); setup_gateways_monitor(); plugins_configure('vpn', false, array($interface)); filter_configure_sync(); diff --git a/src/etc/rc.routing_configure b/src/etc/rc.routing_configure index 20d2681ff..3d9a7a964 100755 --- a/src/etc/rc.routing_configure +++ b/src/etc/rc.routing_configure @@ -42,6 +42,6 @@ foreach (glob("/tmp/delete_route_*.todo") as $filename) { unlink($filename); } -system_routing_configure('', true); +system_routing_configure(true); setup_gateways_monitor(true); filter_configure_sync(true);