diff --git a/src/etc/inc/dslite.php b/src/etc/inc/dslite.php new file mode 100644 index 00000000000..8af7b5faee9 --- /dev/null +++ b/src/etc/inc/dslite.php @@ -0,0 +1,93 @@ + + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +require_once("interfaces.inc"); +require_once("util.inc"); +require_once("system.inc"); + +function dslite_find_tunnel_interface($parent) { + $dslite_tunnel_file = sprintf("/tmp/dslite_%s.state", $parent); + $gifif = @file_get_contents($dslite_tunnel_file); + return ($gifif); +} + +function dslite_find_aftr_address($parent) { + $dslite_aftr_file = sprintf("/tmp/dslite_%s_aftr.state", $parent); + $aftr_addr = @file_get_contents($dslite_aftr_file); + return ($aftr_addr); +} + +function setup_dslite_tunnel($verbose, $parent, $aftr) { + global $config; + + // Should be tested outside, but let's make sure we won't mess around + if ($config['interfaces'][$parent]['ipaddr'] != "dslite") { + return; + } + + $wanconfig = $config['interfaces'][$parent]; + $mtu = !empty($wanconfig['dslite_tunnel_mtu']) ? $wanconfig['dslite_tunnel_mtu'] : ''; + $ip = get_interface_ipv6($parent); + + if ($verbose) { + log_error(sprintf("Starting DS-Lite tunnel with AFTR '%s' on interface %s with external IP '%s'", $aftr, $parent, $ip)); + } + + $gifif = legacy_interface_create('gif'); + + $pmtu = ''; + if (!empty($mtu)) { + $pmtu = sprintf('mtu %s', $mtu); + } + + mwexecf('/sbin/ifconfig %s inet6 tunnel %s %s %s -accept_rtadv ifdisabled', array($gifif, $ip, $aftr, $pmtu)); + mwexecf("/sbin/ifconfig %s inet 192.0.0.2 192.0.0.1 netmask 255.255.255.248", array($gifif)); + + $dslite_tunnel_file = sprintf("/tmp/dslite_%s.state", $parent); + @file_put_contents($dslite_tunnel_file, $gifif); + + $dslite_aftr_file = sprintf("/tmp/dslite_%s_aftr.state", $parent); + @file_put_contents($dslite_aftr_file, $aftr); +} + +function destroy_dslite_tunnel($verbose, $parent) { + global $config; + + // Should be tested outside, but let's make sure we won't mess around + if ($config['interfaces'][$parent]['ipaddr'] != "dslite") { + return; + } + + $gifif = dslite_find_tunnel_interface($parent); + if (!empty($gifif)) { + if ($verbose) { + log_error(sprintf("Destroying DS-Lite tunnel %s on interface %s", $gifif, $parent)); + } + mwexecf("/sbin/ifconfig %s destroy", $gifif); + } +} diff --git a/src/etc/inc/interfaces.inc b/src/etc/inc/interfaces.inc index b7e25bbd695..d060ed1413c 100644 --- a/src/etc/inc/interfaces.inc +++ b/src/etc/inc/interfaces.inc @@ -31,6 +31,7 @@ */ require_once("interfaces.lib.inc"); +require_once("dslite.php"); /* * converts a string like "a,b,c,d" @@ -2962,7 +2963,7 @@ function interface_dhcpv6_prepare($interface = 'wan', $wancfg, $linkdownevent = $dhcp6cscript .= "\t\techo \${PDINFO} > /tmp/{$wanif}_pdinfo\n"; $dhcp6cscript .= "\tfi\n"; $dhcp6cscript .= "\t/usr/bin/logger -t dhcp6c \"dhcp6c \$REASON on {$wanif} - running newipv6\"\n"; - $dhcp6cscript .= "\t/usr/local/opnsense/service/configd_ctl.py interface newipv6 {$wanif}\n"; + $dhcp6cscript .= "\t/usr/local/opnsense/service/configd_ctl.py interface newipv6 {$wanif} \$raw_dhcp_option_64\n"; $dhcp6cscript .= "\t;;\n"; $dhcp6cscript .= "EXIT|RELEASE)\n"; $dhcp6cscript .= "\t/usr/bin/logger -t dhcp6c \"dhcp6c \$REASON on {$wanif} - running newipv6\"\n"; @@ -3063,6 +3064,11 @@ function DHCP6_Config_File_Basic($interface, $wancfg, $wanif, $id = 0) $dhcp6cconf .= " request domain-name-servers;\n"; $dhcp6cconf .= " request domain-name;\n"; + + if ($wancfg['ipaddr'] == "dslite") { + $dhcp6cconf .= " request raw-option 64 00;\n"; + } + $dhcp6cconf .= " script \"/var/etc/dhcp6c_{$interface}_script.sh\"; # we'd like some nameservers please\n"; $dhcp6cconf .= "};\n"; @@ -4159,6 +4165,14 @@ function get_interfaces_info() $ifinfo['subnet'] = $ifinfo['ipv4'][0]['subnetbits']; } + if ($config['interfaces'][$ifdescr]['ipaddr'] == "dslite") { + $gifif = dslite_find_tunnel_interface($ifdescr); + $aftr = dslite_find_aftr_address($ifdescr); + $ifinfo['ipaddr'] = 'dslite'; + $ifinfo['dslite_aftr'] = $aftr; + $ifinfo['dslite_gif'] = $gifif; + } + /* XXX there are more magic files */ $aux = @file_get_contents("/tmp/{$ifinfo['ifv6']}_pdinfo"); if (!empty($aux)) { diff --git a/src/etc/inc/system.inc b/src/etc/inc/system.inc index 2d25dc6802a..1f30c4734e0 100644 --- a/src/etc/inc/system.inc +++ b/src/etc/inc/system.inc @@ -443,7 +443,7 @@ function system_default_route($gateway, $family, $interface, $far = false) @unlink($to_delete); } - log_error("ROUTING: creating /tmp/{$realif}_defaultgw using '{$gateway}'"); + log_error("ROUTING: creating /tmp/{$realif}_defaultgw/tmp/{$realif}_defaultgw using '{$gateway}'"); @file_put_contents("/tmp/{$realif}_defaultgw", $gateway); if (!$far) { diff --git a/src/etc/rc.newwanipv6 b/src/etc/rc.newwanipv6 index d8e92a20dcf..58bc302ecca 100755 --- a/src/etc/rc.newwanipv6 +++ b/src/etc/rc.newwanipv6 @@ -38,6 +38,7 @@ require_once("system.inc"); require_once("interfaces.inc"); $argument = isset($argv[1]) ? trim($argv[1]) : ''; +$aftr = isset($argv[2]) ? trim($argv[2]) : ''; if (file_exists('/var/run/booting')) { log_error("IP renewal deferred during boot on '{$argument}'"); @@ -62,6 +63,20 @@ if (!isset($config['interfaces'][$interface]['enable'])) { return; } +/* Check that we actually have a valid AFTR address either by auto-discovery from DHCPv6 or configured manually */ +if ($config['interfaces'][$interface]['ipaddr'] == "dslite") { + // convert the raw argument, which is hex, to a string + if (!empty($aftr)) { + $aftr = pack("*H", $aftr); + } + if (!empty(trim($config['interfaces'][$interface]['dslite_aftr_addr']))) { + $aftr = trim($config['interfaces'][$interface]['dslite_aftr_addr']); + } + if (empty($aftr)) { + log_error("DS-Lite selected on interface '{$interface}' but no AFTR address is discovered or configured, ignoring tunnel for now."); + } +} + $interface_descr = convert_friendly_interface_to_friendly_descr($interface); log_error("On (IP address: {$ip}) (interface: {$interface_descr}[{$interface}]) (real interface: {$interface_real})."); @@ -80,6 +95,7 @@ $configip = $config['interfaces'][$interface]['ipaddrv6']; $searchdomain_file = "/var/etc/searchdomain_v6{$interface_real}"; $nameserver_file = "/var/etc/nameserver_v6{$interface_real}"; +$dslite_tunnel_file = "/tmp/dslite_${interface}.state"; $cacheip_file = "/var/db/{$interface}_cacheipv6"; $ip_file = "/var/db/{$interface}_ipv6"; @@ -132,7 +148,17 @@ if (!is_ipaddr($cacheip) || $ip != $cacheip || !is_ipaddr($configip)) { } } + // Make sure to recreate the DS-Lite tunnel before we forge the new tunnel and routes + if ($config['interfaces'][$interface]['ipaddr'] == "dslite") { + destroy_dslite_tunnel(false, $interface); + } + @unlink($cacheip_file); + @unlink($dslite_tunnel_file); + + if ($config['interfaces'][$interface]['ipaddr'] == "dslite" && !empty($aftr)) { + setup_dslite_tunnel(false, $interface, $aftr); + } system_routing_configure(false, $interface); plugins_configure('monitor'); diff --git a/src/opnsense/mvc/app/library/OPNsense/Routing/Gateways.php b/src/opnsense/mvc/app/library/OPNsense/Routing/Gateways.php index dd2567238cd..a9e0eb8b88c 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Routing/Gateways.php +++ b/src/opnsense/mvc/app/library/OPNsense/Routing/Gateways.php @@ -265,6 +265,10 @@ public function getGateways() } elseif (empty($thisconf['virtual'])) { // skipped dynamic gateway from config, add to $dynamic_gw to handle defunct $dynamic_gw[$ifname][] = $thisconf; + } elseif ($ipproto == 'inet' && $ifcfg['ipaddr'] == 'dslite') { + // handle default gateway ip in DS-lite + $thisconf['gateway'] = '192.0.0.1'; + $this->cached_gateways[$gwkey] = $thisconf; } } } diff --git a/src/opnsense/service/conf/actions.d/actions_interface.conf b/src/opnsense/service/conf/actions.d/actions_interface.conf index c481e5b2491..2188f68a59a 100644 --- a/src/opnsense/service/conf/actions.d/actions_interface.conf +++ b/src/opnsense/service/conf/actions.d/actions_interface.conf @@ -13,7 +13,7 @@ message:New IPv4 on %s [newipv6] command:/usr/local/etc/rc.newwanipv6 -parameters:%s +parameters:%s %s type:script message:New IPv6 on %s diff --git a/src/www/interfaces.php b/src/www/interfaces.php index b2a9e2951d4..5485462dcdc 100644 --- a/src/www/interfaces.php +++ b/src/www/interfaces.php @@ -388,6 +388,8 @@ function get_wireless_channel_info($interface) { 'subnetv6', 'track6-interface', 'track6-prefix-id', + 'dslite_aftr_addr', + 'dslite_tunnel_mtu' ); foreach ($std_copy_fieldnames as $fieldname) { $pconfig[$fieldname] = isset($a_interfaces[$if][$fieldname]) ? $a_interfaces[$if][$fieldname] : null; @@ -703,6 +705,26 @@ interface_sync_wireless_clones($a_interfaces[$if], false); } do_input_validation($pconfig, $reqdfields, $reqdfieldsn, $input_errors); break; + case "dslite": + if (empty($pconfig['type6'])) { + $input_errors[] = gettext("DS-Lite requires IPv6 configuration to be present."); + } else { + switch ($pconfig['type6']) { + case "staticv6": + // No further checks as the IPv6 address is checked in the next step + break; + case "dhcp6": + if (!empty($pconfig['dhcp6prefixonly'])) { + $input_errors[] = gettext("DS-Lite requires a public IPv6 on the WAN interface to create a 4in6 tunnel, therefore 'Request only an IPv6 prefix' does not work"); + } + break; + default: + // Just fail, no other option allowed for the time being + $input_errors[] = gettext("DS-Lite requires either of static IPv6 or DHCPv6 to be configured."); + break; + } + } + break; } switch ($pconfig['type6']) { @@ -915,6 +937,21 @@ interface_sync_wireless_clones($a_interfaces[$if], false); if (!empty($pconfig['mss']) && $pconfig['mss'] < 576) { $input_errors[] = gettext("The MSS must be greater than 576 bytes."); } + if (!empty($pconfig['dslite_aftr_addr'])) { + if (!is_ipaddrv6($pconfig['dslite_aftr_addr'])) { + if (is_ipaddrv4($pconfig['dslite_aftr_addr'])) { + $input_errors[] = gettext("A valid IPv6 address or a DNS name. An IPv4 address cannot be specified."); + } + } + } + if (!empty($pconfig['dslite_tunnel_mtu'])) { + if ($pconfig['dslite_tunnel_mtu'] < 576 || $pconfig['dslite_tunnel_mtu'] > 9000) { + $input_errors[] = gettext("The DS-Lite tunnel MTU must be greater than 576 bytes and less than 9000."); + } + if (!empty($pconfig['mtu']) && $pconfig['mtu'] < $pconfig['dslite_tunnel_mtu']) { + $input_errors[] = gettext("The DS-Lite tunnel MTU cannot be bigger than the MTU of the parent interface."); + } + } /* Wireless interface */ @@ -1126,6 +1163,10 @@ interface_sync_wireless_clones($a_interfaces[$if], false); $new_ppp_config['idletimeout'] = $pconfig['pptp_idletimeout']; } break; + case "dslite": + $new_config['dslite_aftr_addr'] = $pconfig['dslite_aftr_addr']; + $new_config['dslite_tunnel_mtu'] = $pconfig['dslite_tunnel_mtu']; + break; } // switch ipv6 config by type @@ -1410,7 +1451,7 @@ function toggle_allcfg() { // $("#type").change(function(){ - $('#staticv4, #dhcp, #pppoe, #pptp, #ppp').hide(); + $('#staticv4, #dhcp, #pppoe, #pptp, #ppp, #dslite').hide(); if ($(this).val() == "l2tp") { $("#pptp").show(); } else { @@ -1817,7 +1858,7 @@ function toggle_allcfg() { + + + + + + +