IPv6 using Charter’s 6RD on Ubuntu behind an IPv4 only router.

Finally figured out how to get a 6rd tunnel setup with Charter. My problem was that 1) I wasn’t paying attention to the examples and 2) I have my IPv6 router behind an IPv4 only router. So unlike the examples, I needed to use the private IP address instead of the public for the tunnel. (Like you do for 6in4.)

You can find my setup script here.

In my /etc/network/interface I added the following to my eth0 interface.

post-up /etc/network/6rd || echo 1;
pre-down ip tunnel del tun6rd || echo 1;

In the original script, the author has PREFIX:0::1/32 assigned to the external interface and PREFIX:1::1/64 assigned to the inside. I’m not sure the reasoning for this as both reside on the same /64 subnet. To me, it would make more sense to use PREFIX:: for the outside and PREFIX::1 for the internal so they are right next to each other.

I hope this helps other Charter customers figure out their own ‘native’ IPv6 connectivity.

AAAA tale of two DNS

Since getting IPv6 up and running, I’ve been trying to figure out a way to map domain names to hosts no matter if they have statically assigned addresses or dhcp/radv generated ones. Additionally, I didn’t want to purchase a new domain. Instead I opted to create a new subdomain and delegated authority to my home IPv6 router’s name server.

In my first attempt, I was using a public FQDN. This presented a problem when using BIND’s allow-update as the private IPv4 range was now public and doesn’t help when trying to access my home computers. After some digging I found update-policy, but this required that each host made use of DNSSEC/TSIG/SIG…something I couldn’t guarantee on my network…yet. So it was back to allow-update.

A couple days later after some further thought, I settled on using a .local domain and via a script, copy the AAAA records to a public domain. This solution gives me easy access to my servers, without exposing the private IPv4 addresses. Even better, the script can be extended to include additional records or rules. For instance, don’t want to map android phones? Cisco switches? Cross reference the IPs to macs and filter away.

Zone config

//public
zone "home.example.com" {
	type master;
	file "/var/lib/bind/master/home.example.com.conf";
	//Only allow the updates from the local machine.
	allow-update { localhost; };
	//Only allow the axfr from the local machine.
	allow-transfer { localhost; };
};

//private
zone "home.example.local" {
	type master;
	file "/var/lib/bind/master/home.example.local.conf";
	//Allow local network hosts with static addresses to update the zone.
	allow-update { LocalIPv6/64; LocalIPv4/24; localhost; };
	//Only allow the axfr from the local machine.
	allow-transfer { localhost; };
};

The actual zone files are your regular zone files, nothing special.

Script

The script to update the public zone with AAAA records in the private. This is a cron job that only runs every hour as I’m not motivated enough at the moment to create higher res crontab.

#!/usr/bin/php
<?php
//get the aaaa records that have been registered with the local domain...minus the ns records.
$c = "dig @::1 home.example.local. axfr | grep AAAA | grep -v ns.home.example.local";
$out = array();
exec($c, $out);
$hosts = array();

//build array with hostnames for keys pointing to an array of associated ipv6 addresses.
foreach($out as $v) {
	$hn = substr($v,0,strpos($v,'.'));
	if(!isset($hosts[$hn])) {
		$hosts[$hn] = array();
	}
	$ipv6 = preg_split("/( |\t){1,}/", $v);
	$ipv6 = $ipv6[count($ipv6)-1];
	$hosts[$hn][] = $ipv6;
}

//Now take that array and pump it into nsupdate.
foreach($hosts as $k => $v) {
	$cmds = array("echo server ::1", "echo zone home.example.com", "echo update delete {$k}.home.example.com. AAAA");

	foreach($v as $ipv6) {
		$cmds[] = "echo update add {$k}.home.example.com. 86400 AAAA {$ipv6}";
	}
	$cmds[] = "echo send";
	$cmd = implode("\r\n", $cmds);
	exec("(". $cmd . ") | nsupdate", $out2);
	//var_dump("(". $cmd . ") | nsupdate");
	//var_dump(implode("\r\n", $out2));
}
?>

IPv6 in the home and me

Last November we moved from a rental home into a home of our own. Unfortunately, the old woman before us never had cable television or internet and so we had to wait a month before the ISP was able to dig in a line and hook us up. Now that we have internet, I’ve finally been able to resurrect my network.

While our ISP does have 6RD, I’m a huge fan of Hurricane Electric’s TunnelBroker.net. I find it easy to use and I don’t need to worry about changing ISP and keeping the same address range.

From before the move I had a dual-stack with an old laptop working as the IPv6 tunnel/router/firewall. (Because I don’t yet have a PCMCIA ethernet card and it’s so old I can’t get vlan trunking working so it has to function behind my Buffalo router/AP.) Now that we have internet, I just powered it on, ran “sudo ifup he-ipv6”, and voila! IPv6 was up and running.

What to do with 2^64 addresses at my disposal? Well, one thing I’m wanting to get working is getting my media anywhere I have a cellular signal and data. This is a problem as my phone only seems to support IPv4 on cellular networks and I don’t really want to go the dynamic DNS route as it seems too…archaic.

IPv6 router using TunnelBroker

/etc/network/interfaces

auto he-ipv6
iface he-ipv6 inet6 v4tunnel
	pre-up wget -q --no-check-certificate -O - 'https://ipv4.tunnelbroker.net/ipv4_end.php?ip=AUTO&amp;pass={PASSWORD}&amp;apikey={APIKEY}&amp;tid={TUNNELID}'
	address {LOCAL_TUNNEL_IPV6}
	netmask 64 endpoint {HE_ENDPOINT}
	up `ip -6 route add default via {HE_TUNNEL_ENDPOINT_IPV6} dev he-ipv6`
	up `echo 1 &gt; /proc/sys/net/ipv6/conf/all/forwarding`
	down `echo 0 &gt; /proc/sys/net/ipv6/conf/all/forwarding` 

Hurricane Electric 6in4 Windows startup script

Updates HE public IP records for your tunnel (for instance at the coffee shop), finds the first available interface that is connected and creates a v6v4tunnel using that IP.

See script for where you need to edit.

function fastpingtest {
    $ping = New-Object System.Net.NetworkInformation.Ping;
    $ping.Send("8.8.8.8", 1000).status -eq "success";
}
$endtime = [datetime]::Now.AddMinutes(1);
$mapipv6 = $false; 
while([datetime]::Now -lt $endtime) {
    if(fastpingtest) { $mapipv6 = $true; break; }
}

if($mapipv6) {
    $wc = New-Object net.webclient;
    $url= "https://ipv4.tunnelbroker.net/ipv4_end.php?ip=AUTO&pass={1}&apikey={0}&tid={2}";

    $values = "USERID", "PASSWORDMD5HASH", TUNNELID;
    $wc.DownloadString(($url -f $values));
    
    #get connected interface
    $interface = netsh interface ipv4 show interface | findstr /c:" connected" | ?{!$_.contains("Loopback");} | %{[regex]::Split($_, "( )+") | ?{$_.trim().length -gt 0} | %{$_.trim()}; }
    $interface_ip = (netsh interface ipv4 show address $interface[0] | findstr /c:"IP Address" | select -First 1).split(":")[1].trim()
    netsh interface teredo set state disabled
    netsh interface ipv6 add v6v4tunnel IP6Tunnel $interface_ip HEIPv4ENDPOINT
    netsh interface ipv6 add address IP6Tunnel YOURIPv6ADDRESS
    netsh interface ipv6 add route ::/0 IP6Tunnel HEIPv6ADDRESS
}

IPv6 range/subnet calculater for powershell

From what I can tell, there aren’t many…if any, IPv6 address range calculators. While I read it is recommended that you use the entire /64 block, I don’t think it’s always necessary to do so. Besides, what happens when you want to route only a portion of the block to one place?

Example usage:

ipv6range.ps1 2001:470:1f10:60::10 64 | ft ipaddress*
ipv6range.ps1 2003:5:1f:fa0::10 104 | ft ipaddress* #ipv4 /8 equivalent
ipv6range.ps1 2003:5:1f:fa0::10 112 | ft ipaddress* #ipv4 /16 equivalent
ipv6range.ps1 2003:5:1f:fa0::10 120 | ft ipaddress* #ipv4 /24 equivalent
ipv6range.ps1 2003:5:1f:fa0::10 124 | ft ipaddress* #ipv4 /28 equivalent

….and the code….

param(
    [net.ipaddress]$Addr,
    [int]$netmask = 64,
    [switch]$ForceListing

)

if($Addr.AddressFamily -ne 'InterNetworkV6') { throw "`$Addr must be a valid IPv6 address."; }

<#
Create a subnet mask based on a CIDR input.
#>
function subnet {
    param(
        [int]$netmask
    )

    if($netmask -gt 128) { throw "`$netmask cannot be greater than 128"; }

    $mask = (@($true) * $netmask) + (@($false) * (128-$netmask));
    return New-Object Collections.BitArray @(,$mask);
}

<#
Convert a BitArray into a byte array for easy conversion into an IPAddress.
#>
function bit2byte {
    param(
        [Collections.BitArray]$bitArray
    )

    for($i = 0; $i -lt $bitArray.length; $i+=8) {
        [convert]::ToByte([string]::Join("", ([string[]][byte[]]($bitArray[$i..($i+7)]))), 2)
    }
}

<#
convert an ip into a BitArray for easy bitwise operations.
#>
function ip2bit {
    param([net.ipaddress]$addr)

    $b = $addr.GetAddressBytes();
    $bits = @();
    foreach($a in $b) {
        $t = [convert]::ToString($a,2).padleft(8,"0") #[7..0];
        $bits += [string]::join("",$t);
    }
    $nbits = ($bits | %{[char[]]$_} | %{[bool]::Parse("$_".replace("1","true").replace("0","false"))});
    return New-Object collections.bitarray @(,$nbits)
}

<#
Increment an ipv6 address.
#>
function inc {
    param([net.ipaddress]$addr)

    $b = $addr.GetAddressBytes();

    for($i = $b.length-1; $i -ge 0; $i--) {
        if($b[$i] -gt 254) { continue; }
        $b[$i]++;
        break;
    }
    New-Object net.ipaddress @(,$b);
}

<#
Decrement an ipv6 address.
#>
function dec {
    param([net.ipaddress]$addr)

    $b = $addr.GetAddressBytes();

    for($i = $b.length-1; $i -ge 0; $i--) {
        if($b[$i] -eq 0) { continue; }
        $b[$i]--;
        break;
    }
    New-Object net.ipaddress @(,$b);
}

$ipArr = new-object collections.bitarray @(,(ip2bit $Addr.GetAddressBytes()))

$netBits = New-Object collections.bitarray @(,(subnet $netmask))
$hostBits = (New-Object collections.bitarray $netBits).Xor((New-Object collections.bitarray 128, $true)) #.xor((New-Object collections.bitarray 128, $true))

$netId = New-Object net.ipaddress @(, (bit2byte (New-Object collections.bitarray $ipArr).And($netBits)));
$netBcast = New-Object net.ipaddress @(, (bit2byte (New-Object collections.bitarray $ipArr).Or($hostBits)));

$numHosts = [math]::Pow(2, ($hostBits | ?{$_} | measure).count)

if($numHosts -gt 256 -and !$ForceListing.ispresent) {
    #well, if we have more than this, just output the (ipv4 equivalent) network id and broadcast address.
    $netId
    $netBcast
} else {
    Add-Member -PassThru -Force -InputObject $netId -MemberType NoteProperty -Name IsUsable -Value $false;
    try {
        $lastAddr = $netId;
        for($i = 0; $i -lt $numHosts-2; $i ++) {
            $lastAddr = inc $lastAddr
            Add-Member -PassThru -Force -InputObject $lastAddr -MemberType NoteProperty -Name IsUsable -Value $true;
        }
    } catch {}
    Add-Member -PassThru -Force -InputObject $netBcast -MemberType NoteProperty -Name IsUsable -Value $false;
    #$res
}

Notes:

  • Not commented well. RTCFA (read the code for answers)! >:(
  • Only increments despite there being a decrement function.
  • If the number of host addresses is less then 256, it will always display the complete listing.