Generate random IIDs if no EUI-64 address is found

This allows clatd to work correctly on 3GPP mobile networks, where the
IID is assigned from the network, rather than being generated using
EUI-64. We still prefer the old method, though, the random one is only
used if no EUI-64 address exists on the PLAT device. Update docs
accordingly.

Also upgrade docs to better describe usage as a SIIT-DC Host Agent.
This commit is contained in:
Tore Anderson
2014-10-05 20:14:01 +02:00
parent 54dd5ac854
commit 81f2c61364
2 changed files with 128 additions and 43 deletions

View File

@@ -1,6 +1,6 @@
=head1 NAME
B<clatd> - a CLAT implementation for Linux
B<clatd> - a CLAT / SIIT-DC Host Agent implementation for Linux
=head1 DESCRIPTION
@@ -13,11 +13,12 @@ local applications on the host requires actual IPv4 connectivity or cannot
make use of DNS64 (for example because they use legacy AF_INET socket calls,
or if they are simply not using DNS64).
It may also be used in combination with a stateless PLAT as defined by
I<I-D.anderson-siit-dc> to give the otherwise IPv6-only host a public IPv4
address with connectivity to the IPv4 internet. This may be useful in a
server environment that are using legacy IPv4-only applications as described
above.
It may also be used to implement an SIIT-DC Host Agent as defined by
I<I-D.anderson-v6ops-siit-dc-2xlat>. In this scenario, the PLAT is a SIIT-DC
Gateway (see I<I-D.anderson-v6ops-siit-dc>) instead of a Stateful NAT64 (see
I<RFC6146>). When used as a SIIT-DC Host Agent, you will probably want to
manually configure the settings I<clat-v4-addr>, I<clat-v6-addr>, and
I<plat-prefix> to mirror the SIIT-DC Gateway's configuration.
It relies on the software package TAYGA by Nathan Lutchansky for the actual
translation of packets between IPv4 and IPv6 (I<RFC 6145>) TAYGA may be
@@ -129,22 +130,41 @@ simultaneously.
The IPv4 address that will be assigned to the CLAT device. Local applications
will bind to this address when communicating with external IPv4 destinations.
In a standard 464XLAT environment with a stateful NAT64 serving as the PLAT,
there should be no need to change the default, but if the PLAT is a stateless
translator (a la I-D.draft-anderson-siit-dc), you might want to set this to
the true external address used externally, so the the local applications can
correctly identify which public address they'll be using on the IPv4 internet.
there should be no need to change the default.
When using B<clatd> as an SIIT-DC Host Agent (cf.
I-D.draft-anderson-v6ops-siit-dc-2xlat), you will want to set this to the
IPv4 Service Address configured in the SIIT-DC Gateway. This way, local
applications can correctly identify which public address they'll be using on
the IPv4 internet, and will be able to provide fully functional references to
it in application-level payload, and so on.
The default address is one from I<RFC 7335>.
=item B<clat-v6-addr=ipv6-address> (default: auto-generated)
The IPv6 address of the CLAT. Traffic to/from the B<clat-v4-addr> will be
translated into this address. By default, B<clatd> will attempt to figure out
which network device will be used for traffic towards the PLAT, see if there
is any SLAAC-configured addresses on it, and if so substitute the '0xfffe'
value in the middle of the Interface ID for '0xc1a7' to generate a new
address for the CLAT. If you're not using SLAAC you will have to set this
manually.
translated into this address. When using B<clatd> as an SIIT-DC Host Agent,
you will want to set this to the IPv6 address in the Static Address Mapping
configured in the SIIT-DC Gateway.
By default, B<clatd> will attempt to figure out which network device will be
used for traffic towards the PLAT, see if there is any SLAAC-based globally
scoped addresses on it (i.e., a /64 with '0xfffe' in the middle of the
Interface ID), and will if so substitute that '0xfffe' value with '0xc1a7'
("clat") to generate a CLAT IPv6 address.
If only a non-SLAAC global address is found on the PLAT-facing device,
B<clatd> will substitute its Interface ID with a random integer and use the
result as the CLAT IPv6 address. It will only do so if the prefix length is
/120 or smaller, as otherwise the risk of IID collisions is considered to be
too high. Note that on most Perl platforms, the I<rand()> function is limited
to 48 bits, which means that for longer IIDs, the least significant bits will
be all 0.
If multiple addresses are found in either category, the one that shares the
longest common prefix with the PLAT prefix will be preferred when deriving
the CLAT IPv6 address according to the algorithm described above.
=item B<dns64-servers=srv1,[srv2,..]> (default: use system resolver)
@@ -323,6 +343,6 @@ ip(8), ip6tables(8), tayga(8), tayga.conf(5)
RFC 6052, RFC 6145, RFC 6146, RFC 6877, RFC 7050, RFC 7335
I-D.anderson-siit-dc
I-D.anderson-v6ops-siit-dc, I-D.anderson-v6ops-siit-dc-2xlat
=cut

117
clatd
View File

@@ -12,7 +12,7 @@
use strict;
use Net::IP;
my $VERSION = "1.1";
my $VERSION = "1.2";
#
# Populate the global config hash with the default values
@@ -425,12 +425,14 @@ sub is_modified_eui64 {
#
# This function considers any globally scoped /64 address on the PLAT-facing
# device, checks to see if it is base on Modified EUI-64, and generates a
# new address for the CLAT by substituting the "0xfffe" bits in the middle
# of the Interface ID with 0xc1a7 ("clat"). This keeps the last 24 bits
# unchanged, which has the added bonus of not requiring the host to join
# another Solicited-Node multicast group.
# This function considers any globally scoped IPv6 address on the PLAT-facing
# device, and derives an CLAT IPv6 address from the best match (longest
# common prefix with PLAT prefix). Addresses based on Modified EUI-64 are
# preferred, and if found, it generates a new address for the CLAT by
# substituting the "0xfffe" bits in the middle of the Interface ID with
# 0xc1a7 ("clat"). This keeps the last 24 bits unchanged, which has the added
# bonus of not requiring the host to join another Solicited-Node multicast
# group. If no EUI-64 address is seen, it'll use a random IID instead.
#
sub get_clat_v6_addr {
my $plat_dev = cfg("plat-dev");
@@ -446,47 +448,108 @@ sub get_clat_v6_addr {
err("Failed to convert plat prefix to bigint");
}
my $ip; # will contain the best candidate ip in bigint format
my $best_score;
my $ip_plen; # will contain the prefix length of the best candidate ip
my $best_score; # will contain the score of the best candidate seen
my $seen_eui64; # set if we've seen an eui-64 based address
p("Attempting to derive a CLAT IPv6 address from a EUI-64 address on ",
p("Attempting to derive a CLAT IPv6 address from an IPv6 address on ",
"'$plat_dev'");
open(my $fd, '-|', cfg("cmd-ip"), qw(-6 address list scope global dev),
$plat_dev)
or err("'ip -6 address list scope global dev $plat_dev' failed to execute");
while(<$fd>) {
if(m| inet6 (\S+)/64 scope global |) {
if(m| inet6 (\S+)/(\d{1,3}) scope global |) {
my $candidate = $1;
next unless(is_modified_eui64($candidate));
d2("Saw EUI-64 based address: $candidate");
my $plen = $2;
d2("Saw a candidate address on '$plat_dev': $candidate/$plen");
my $candidate_int = Net::IP->new($candidate, 6)->intip();
if(!$candidate_int) {
err("Failed to convert plat prefix to bigint");
}
if(!$best_score or $best_score > ($plat_prefix_int ^ $candidate_int)) {
d2("$candidate has so far the longest common prefix with plat prefix");
if($plen > 120) {
# We'll need a subnet with some space if we are to generate a random
# IID and don't have too large risk of collisions... /120 seems like
# an OK limit
d2("Refusing to use random IIDs for prefix lengths > /120");
next;
}
# True if the candidate under consideration is EUI-64 based
my $is_eui64 = ($plen == 64) && is_modified_eui64($candidate);
# If this is the first time we're considering an EUI-64 based address,
# we unconditionally prefer it (even if it doesn't have the longest
# matching prefix), because we consider deriving the CLAT IPv6
# address from an EUI-64 based candidate to be safer than generating
# a truly random CLAT IPv6 address.
if($is_eui64 and !$seen_eui64++) {
d2("Preferring $candidate/$plen; it's the first EUI-64 seen");
$best_score = $plat_prefix_int ^ $candidate_int;
$ip = $candidate_int;
$ip_plen = $plen;
next;
}
# If we already have found an EUI-64 based address, we can reject this
# candidate outright, as it is *not* EUI-64 based.
if(!$is_eui64 and $seen_eui64) {
d2("Rejecting $candidate/$plen; we have better EUI-64 candidates");
next;
}
# Otherwise, we'll be comparing EUI-64 to EUI-64, or non EUI-64 to
# non EUI-64. If so, we prefer the current candidate if it has a better
# score than the current best match (or if there is no current best
# match).
if(!$best_score or $best_score > ($plat_prefix_int ^ $candidate_int)) {
d2("Preferring $candidate/$plen; best match so far");
$best_score = $plat_prefix_int ^ $candidate_int;
$ip = $candidate_int;
$ip_plen = $plen;
next;
}
d2("Rejecting $candidate/$plen; we've seen better matches");
}
}
close($fd)
or err("'ip -6 address list scope global dev $plat_dev' failed");
if(!$ip) {
err("No Modified EUI-64-based address seen on $plat_dev; clatd cannot ",
"auto-generate a CLAT IPv6 address (try setting 'clat-v6-addr')");
err("Could not find a global IPv6 address on $plat_dev from which ",
"to derive a CLAT IPv6 address (try setting 'clat-v6-addr')");
}
# First clear the middle 0xfffe bits of the interface ID
my $mask = Net::IP->new("ffff:ffff:ffff:ffff:ffff:ff00:00ff:ffff");
$mask = $mask->intip();
$ip &= $mask;
if($seen_eui64) {
# If the chosen candidate IP is EUI-64 based, we derive a CLAT IPv6
# address by replacing the 0xffe in the middle of the Interface ID with
# 0xc1a7 ("CLAT").
# Next set them to the value 0xc1a7 and return
$mask = Net::IP->new("::c1:a700:0", 6) or err(Net::IP::Error());
$mask = $mask->intip();
$ip |= $mask;
# First clear the middle 0xfffe bits of the interface ID
my $mask = Net::IP->new("ffff:ffff:ffff:ffff:ffff:ff00:00ff:ffff");
$mask = $mask->intip();
$ip &= $mask;
# Next set them to the value 0xc1a7
$mask = Net::IP->new("::c1:a700:0", 6) or err(Net::IP::Error());
$mask = $mask->intip();
$ip |= $mask;
} else {
# If the chosen candidate IP is NOT EUI-64 based, we'll just make up a
# random interface ID. There is no guarantee that this will actually
# work, but it's the best thing we can try...
# First zero out the entire Interface ID
$ip >>= (128-$ip_plen);
$ip <<= (128-$ip_plen);
my $iid = int(rand(2**(128-$ip_plen)));
d2(sprintf("Using random interface ID: %x", $iid));
$ip |= $iid;
}
# Convert back the BigInt to a regular Net::IP object and return
$ip = Net::IP->new(Net::IP::ip_bintoip(Net::IP::ip_inttobin($ip, 6), 6));
return $ip->short() if $ip;
@@ -579,8 +642,10 @@ for (my $i = 0; $i < @ARGV;) {
splice(@ARGV, $i, 2);
next;
} elsif($ARGV[$i] =~ /^(-h|--help)$/) {
print "clatd v$VERSION - a 464XLAT (RFC 6877) CLAT implementation for ",
"Linux\n";
print <<"EOF";
clatd v$VERSION - a 464XLAT (RFC 6877) CLAT and SIIT-DC Host Agent
(I-D.anderson-v6ops-siit-dc-2xlat) implementation for Linux
EOF
print "\n";
print " Usage: clatd [-q] [-d [-d]] [-c config-file] ",
"[conf-key=val ...]\n";