Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(WIP) Adding support for DS-lite (IPv4 over IPv6) #4335

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Updated version of the DS-lite patch with UI to configure the DS-lite…
… parameters, such as AFTR address and DHCPv6 retrieval of the address if available. The patch is not tested yet and only updated to the current source base, which is very different from what it was in 2018.
  • Loading branch information
noctarius committed Sep 8, 2020
commit 1e5e9b1416bdce11e12eac137c57e0c7dd121599
93 changes: 93 additions & 0 deletions src/etc/inc/dslite.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

/*
* Copyright (C) 2018 Christoph Engelbert <me@noctarius.com>
* 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);
}
}
16 changes: 15 additions & 1 deletion src/etc/inc/interfaces.inc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
*/

require_once("interfaces.lib.inc");
require_once("dslite.php");

/*
* converts a string like "a,b,c,d"
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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)) {
Expand Down
2 changes: 1 addition & 1 deletion src/etc/inc/system.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
26 changes: 26 additions & 0 deletions src/etc/rc.newwanipv6
Original file line number Diff line number Diff line change
Expand Up @@ -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}'");
Expand All @@ -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}).");
Expand All @@ -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";

Expand Down Expand Up @@ -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');
Expand Down
4 changes: 4 additions & 0 deletions src/opnsense/mvc/app/library/OPNsense/Routing/Gateways.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/opnsense/service/conf/actions.d/actions_interface.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
71 changes: 69 additions & 2 deletions src/www/interfaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -703,6 +705,25 @@ 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 (strtolower($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");
}
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']) {
Expand Down Expand Up @@ -915,6 +936,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
*/
Expand Down Expand Up @@ -1126,6 +1162,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
Expand Down Expand Up @@ -1410,7 +1450,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 {
Expand Down Expand Up @@ -1817,7 +1857,7 @@ function toggle_allcfg() {
<td>
<select name="type" class="selectpicker" data-style="btn-default" id="type">
<?php
$types4 = array("none" => gettext("None"), "staticv4" => gettext("Static IPv4"), "dhcp" => gettext("DHCP"), "ppp" => gettext("PPP"), "pppoe" => gettext("PPPoE"), "pptp" => gettext("PPTP"), "l2tp" => gettext("L2TP"));
$types4 = array("none" => gettext("None"), "staticv4" => gettext("Static IPv4"), "dhcp" => gettext("DHCP"), "ppp" => gettext("PPP"), "pppoe" => gettext("PPPoE"), "pptp" => gettext("PPTP"), "l2tp" => gettext("L2TP"), "dslite" => gettext("DS-Lite"));
foreach ($types4 as $key => $opt):?>
<option value="<?=$key;?>" <?=$key == $pconfig['type'] ? "selected=\"selected\"" : "";?> ><?=$opt;?></option>
<?php
Expand Down Expand Up @@ -2198,6 +2238,33 @@ function toggle_allcfg() {
</table>
</div>
</div>
<!-- Section : DS-Lite -->
<div class="tab-content content-box col-xs-12 __mb" id="dslite" style="display:none">
<div class="table-responsive">
<table class="table table-striped opnsense_standard_table_form">
<thead>
<tr>
<th colspan="2"><?=gettext("DS-Lite configuration"); ?></th>
</tr>
</thead>
<tbody>
<tr>
<td style="width:22%"><a id="help_for_dslite_config_aftr_addr" href="#" class="showhelp"><i class="fa fa-info-circle"></i></a> <?=gettext("AFTR Address"); ?></td>
<td style="width:78%">
<input name="dslite_aftr_addr" type="text" id="dslite_aftr_addr" value="<?=$pconfig['dslite_aftr_addr']; ?>" />
<div class="hidden" data-for="help_for_dslite_config_aftr_addr">
<?=gettext("The value in this field is the provider's AFTR (CNAT service) address. ".
"Both a valid IPv6 address as well as a DNS name can be set. If configured using a ".
"DNS name, the name will be resolved just before creating the DS-Lite tunnel. If the".
"field is left blank, the address is tried to be discovered automatically by use ".
"of DHCPv6."); ?>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Section : PPP -->
<div class="tab-content content-box col-xs-12 __mb" id="ppp" style="display:none">
<div class="table-responsive">
Expand Down
18 changes: 17 additions & 1 deletion src/www/status_interfaces.php
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,28 @@ interface_configure(false, $interface, true);
endif;
if ($ifinfo['status'] != "down"):
if ($ifinfo['dhcplink'] != "down" && $ifinfo['pppoelink'] != "down" && $ifinfo['pptplink'] != "down"):
if (!empty($ifinfo['ipaddr'])):?>
if ($ifinfo['ipaddr']):
if ($ifinfo['ipaddr'] == "dslite"):?>
<tr>
<td><?=gettext("IPv4 connectivity") ?></td>
<td><?=gettext("Dual-Stack Lite (DS-Lite)") ?></td>
</tr>
<tr>
<td><?=gettext("AFTR dddress") ?></td>
<td><?=htmlspecialchars($ifinfo['dslite_aftr']) ?></td>
</tr>
<tr>
<td><?=gettext("DS-Lite tunnel interface") ?></td>
<td><?=htmlspecialchars($ifinfo['dslite_gif']) ?></td>
</tr>
<?php
else:?>
<tr>
<td><?= gettext("IPv4 address") ?></td>
<td>
<?=$ifinfo['ipaddr'];?> / <?=$ifinfo['subnet'];?>
<?php
endif;
foreach($ifinfo['ipv4'] as $ipv4):
if ($ipv4['ipaddr'] != $ifinfo['ipaddr']):?>
<br/>
Expand Down
8 changes: 7 additions & 1 deletion src/www/widgets/widgets/interface_list.widget.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,13 @@ function interface_widget_update(sender, data)
<?= empty($ifinfo['media']) ? htmlspecialchars($ifinfo['cell_mode']) : htmlspecialchars($ifinfo['media']) ?>
</td>
<td style="width:45%; word-break: break-word;">
<?= htmlspecialchars($ifinfo['ipaddr']) ?>
<? if ($ifinfo['ipaddr'] == "dslite") {
$aftr = $ifinfo['dslite_aftr'];
$gifif = $ifinfo['dslite_gif'];
echo htmlspecialchars(sprintf("DS-Lite via AFTR '%s' on tunnel interface %s", $aftr, $gifif));
} else {
echo htmlspecialchars($ifinfo['ipaddr']);
}?>
<?= !empty($ifinfo['ipaddr']) ? '<br/>' : '' ?>
<?= htmlspecialchars(isset($config['interfaces'][$ifdescr]['dhcp6prefixonly']) ? $ifinfo['linklocal'] : $ifinfo['ipaddrv6']) ?>
</td>
Expand Down