Initial commit (clatd v1.0)
This commit is contained in:
5
LICENCE
Normal file
5
LICENCE
Normal file
@@ -0,0 +1,5 @@
|
||||
Copyright (c) 2014 Tore Anderson <tore@fud.no>
|
||||
|
||||
As long as you retain this notice, you may use this piece of software as
|
||||
you wish. If you like it, and we happen to meet one day, you can buy me
|
||||
a beer in return. If you really like it, make it an IPA.
|
||||
20
Makefile
Normal file
20
Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
install:
|
||||
# Install the main script to /usr/sbin
|
||||
install -m0755 clatd /usr/sbin/clatd
|
||||
# Install manual page if pod2man is installed
|
||||
pod2man --name clatd --center "clatd - a CLAT implementation for Linux" --section 8 README.pod /usr/share/man/man8/clatd.8 && gzip -f9 /usr/share/man/man8/clatd.8 || echo "pod2man is required to generate manual page"
|
||||
# Install systemd service file if applicable for this system
|
||||
if test -x /usr/bin/systemctl && test -d "/etc/systemd/system"; then install -m0644 scripts/clatd.systemd /etc/systemd/system/clatd.service && systemctl daemon-reload; fi
|
||||
if test -e "/etc/systemd/system/clatd.service" && test ! -e "/etc/systemd/system/multi-user.target.wants/clatd.service"; then systemctl enable clatd.service; fi
|
||||
# Install upstart service file if applicable for this system
|
||||
if test -x /sbin/initctl && test -d "/etc/init"; then install -m0644 scripts/clatd.upstart /etc/init/clatd.conf; fi
|
||||
# Install NetworkManager dispatcher script if applicable
|
||||
if test -d /etc/NetworkManager/dispatcher.d; then install -m0755 scripts/clatd.networkmanager /etc/NetworkManager/dispatcher.d/50-clatd; fi
|
||||
|
||||
installdeps:
|
||||
# .deb/apt-get based distros
|
||||
if test -x /usr/bin/apt-get; then apt-get -y install perl-base perl-modules libnet-ip-perl libnet-dns-perl libio-socket-inet6-perl iproute iptables tayga; fi
|
||||
# .rpm/YUM-based distros
|
||||
if test -x /usr/bin/yum; then yum -y install perl perl-Net-IP perl-Net-DNS perl-IO-Socket-INET6 perl-File-Temp iproute iptables; fi
|
||||
# to get TAYGA on .rpm/YUM-based distros, we unfortunately need to install from source
|
||||
if test -x /usr/bin/yum && test ! -x /usr/sbin/tayga; then echo "TAYGA isn't packaged for YUM-based distros, will download and compile the source in 5 seconds (^C interrupts)" && sleep 5 && yum -y install gcc tar wget bzip2 && wget http://www.litech.org/tayga/tayga-0.9.2.tar.bz2 && bzcat tayga-0.9.2.tar.bz2 | tar x && cd tayga-0.9.2 && ./configure --prefix=/usr && make && make install && rm -rf ../tayga-0.9.2.tar.bz2 ../tayga-0.9.2; fi
|
||||
327
README.pod
Normal file
327
README.pod
Normal file
@@ -0,0 +1,327 @@
|
||||
=head1 NAME
|
||||
|
||||
B<clatd> - a CLAT implementation for Linux
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<clatd> implements the CLAT component of the 464XLAT network architecture
|
||||
specified in I<RFC 6877>. It allows an IPv6-only host to have IPv4 connectivity
|
||||
that is translated to IPv6 before being routed to an upstream PLAT (which is
|
||||
typically a Stateful NAT64 operated by the ISP) and there translated back to
|
||||
IPv4 before being routed to the IPv4 internet. This is especially useful when
|
||||
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 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
|
||||
downloaded from its home page at L<http://www.litech.org/tayga/>.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
B<clatd> [options]
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item -q
|
||||
|
||||
Quiet mode; suppress normal output This is the same as setting B<quiet=1>.
|
||||
Warnings and errors are still outputted, to silence those too, repeat I<-q>.
|
||||
|
||||
=item -d
|
||||
|
||||
Enable debugging output. This is the same as setting B<debug=1>. Repeat for
|
||||
even more debugging output, which is the
|
||||
equivalent of setting B<debug=2>.
|
||||
|
||||
=item -c conf-file
|
||||
|
||||
Read configuration settings from B<conf-file>. See section B<CONFIGURATION>
|
||||
below for more info.
|
||||
|
||||
=item -h, --help
|
||||
|
||||
Print a brief usage help and exit.
|
||||
|
||||
=item key=value
|
||||
|
||||
Set configuration B<key> to I<value>, overriding any setting found in the
|
||||
configuration file. Refer to the section B<CONFIGURATION> below for more info.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INVOCATION
|
||||
|
||||
B<clatd> is meant to be run under a daemonising control process such as
|
||||
systemd, upstart, or similar. It is further meant to be (re)started whenever a
|
||||
network interface goes up/down as this might mean a change in the PLAT
|
||||
availability or which prefixes/addresses needs to be used for the CLAT to work.
|
||||
It may also be run directly from the command line. It will run until killed
|
||||
with SIGINT (^C) or SIGTERM, at which point it will clean up after itself and
|
||||
exit gracefully.
|
||||
|
||||
See the I<scripts/> directory in the source distribution for some examples on
|
||||
how to invoke it it.
|
||||
|
||||
=head1 INSTALLATION
|
||||
|
||||
The following commands will quickly download and install the latest version
|
||||
of B<clatd> and its dependencies:
|
||||
|
||||
=over
|
||||
|
||||
=item git clone https://github.com/toreanderson/clatd
|
||||
|
||||
=item sudo make -C clatd install installdeps
|
||||
|
||||
=back
|
||||
|
||||
This will install B<clatd> to /usr/sbin, plus install systemd, upstart, and/or
|
||||
NetworkManager scripts if your distribution appears to be using them, and
|
||||
install all the dependencies. Note that TAYGA isn't available in RPM format,
|
||||
so on RedHat/Fedora the installdeps target will install gcc and attempt to
|
||||
compile TAYGA from source.
|
||||
|
||||
=head1 CONFIGURATION
|
||||
|
||||
B<clatd> is designed to be able to run without any user-supplied configuration
|
||||
in most cases. However, user-specified onfiguration settings may be added to
|
||||
the configuration file, the path to which may be given on the command line
|
||||
using the I<-c> option, or if it is not, the default location
|
||||
I</etc/clatd.conf> is used. Configuration settings may also be given directly
|
||||
on the command line when starting B<clatd>, which takes precedence over settings
|
||||
in the configuration file.
|
||||
|
||||
Settings are of the form B<key=value>. A list of recogniced keys and their
|
||||
possible values follow below:
|
||||
|
||||
=over
|
||||
|
||||
=item B<quiet=integer> (default: I<0>)
|
||||
|
||||
Set this to 1 to suppress normal output from B<clatd>. This is the same as
|
||||
providing the command line option I<-q>. Set it to 2 to additionally
|
||||
suppress warnings and errors. Note that this does not suppress debugging
|
||||
output.
|
||||
|
||||
=item B<debug=integer> (default: I<0>)
|
||||
|
||||
Set this to 1 to get debugging output from B<clatd>, or 2 to get even more of
|
||||
the stuff. These are the equivalent of providing the command line option I<-d>
|
||||
the specified number of times.
|
||||
|
||||
=item B<clat-dev=string> (default: I<clat>)
|
||||
|
||||
The name of the network device used by the CLAT. There should be no reason to
|
||||
change the default, unless you plan on running multiple instances of B<clatd>
|
||||
simultaneously.
|
||||
|
||||
=item B<clat-v4-addr=ipv4-address> (default: I<192.0.0.1>)
|
||||
|
||||
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.
|
||||
|
||||
The default address is one from I<I-D.draft-byrne-v6ops-clatip>.
|
||||
|
||||
=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.
|
||||
|
||||
=item B<dns64-servers=srv1,[srv2,..]> (default: use system resolver)
|
||||
|
||||
Comma-separated list of DNS64 servers to use when discovering the PLAT prefix
|
||||
using the method described in RFC 7050. By default, the system resolver is
|
||||
used, but it might be useful to override this in case your ISP doesn't provide
|
||||
you with a DNS64-enabled name server, and you want to test B<clatd> using any of
|
||||
the public DNS64/NAT64 instances on the internet. The first PLAT prefix
|
||||
encountered will be used.
|
||||
|
||||
=item B<cmd-ip=path> (default: assume in $PATH)
|
||||
|
||||
Path to the B<ip> binary from the iproute2 package available at
|
||||
L<https://www.kernel.org/pub/linux/utils/net/iproute2>. Required.
|
||||
|
||||
=item B<cmd-ip6tables=path> (default: assume in $PATH)
|
||||
|
||||
Path to the B<ip6tables> binary from the netfilter package available at
|
||||
L<http://netfilter.org>. Only required for adding ip6tables rules
|
||||
(see the B<ip6tables-enable> configuration setting).
|
||||
|
||||
=item B<cmd-tayga=path> (default: assume in $PATH)
|
||||
|
||||
Path to the B<tayga> binary from the TAYGA package available at
|
||||
L<http://www.litech.org/tayga>. Required.
|
||||
|
||||
=item B<forwarding-enable=bool> (default: I<yes>)
|
||||
|
||||
Controls whether or not B<clatd> should enable IPv6 forwarding if necessary. IPv6
|
||||
forwarding is necessary for B<clatd> to work correctly. It will also ensure that
|
||||
the I<accept_ra> sysctl is to '2' for all devices have it set to '1', in order
|
||||
to prevent any connectivity loss as a result of enabling forwarding.
|
||||
|
||||
All sysctls that are modified will be restored to their original values when
|
||||
B<clatd> is shutting down.
|
||||
|
||||
=item B<ip6tables-enable=bool> (default: see below)
|
||||
|
||||
Controls whether or not B<clatd> should insert ip6tables rules that permit the
|
||||
forwarding of IPv6 traffic between the CLAT and PLAT devices. Such forwarding
|
||||
must be permitted for B<clatd> to work correctly. Any rules added will be removed
|
||||
when B<clatd> is shutting down.
|
||||
|
||||
The default is I<yes> if the ip6tables_filter kernel module is loaded, I<no>
|
||||
if it is not.
|
||||
|
||||
=item B<plat-dev> (default: auto-detect)
|
||||
|
||||
Which network device is facing the PLAT (NAT64). By default, this is
|
||||
auto-detecting by performing a route table lookup towards the PLAT prefix.
|
||||
This setting is used when setting up generating the CLAT IPv6 address, and
|
||||
when setting up ip6tables rules and Proxy-ND entries.
|
||||
|
||||
=item B<plat-prefix> (default: auto-detect)
|
||||
|
||||
The IPv6 translation prefix into which the PLAT maps the IPv4 internet. See
|
||||
I<RFC 6052> for a closer description. By default, this is auto-detected from
|
||||
DNS64 answers using the method in I<RFC 7050>.
|
||||
|
||||
=item B<proxynd-enable> (default: I<yes>)
|
||||
|
||||
Controls whether or not B<clatd> should add a Proxy-ND entry for the CLAT IPv6
|
||||
address on the network device facing the PLAT. This is probably necessary
|
||||
on Ethernet networks (otherwise the upstream IPv6 router won't know where to
|
||||
send packets to the CLAT's IPv6 adderss), but likely not necessary on
|
||||
point-to-point links like PPP or 3GPP mobile broadband, as in those cases
|
||||
IPv6 ND isn't used. However it doesn't hurt to add Proxy-ND entries in that
|
||||
case, either.
|
||||
|
||||
Any entries added wil be removed when B<clatd> is shutting down.
|
||||
|
||||
=item B<tayga-conffile> (default: use a temporary file)
|
||||
|
||||
Where to write the TAYGA configuration file. By default, a temporary file will
|
||||
be created (and also deleted when B<clatd> is shutting down), but you may also
|
||||
specify an explicit configuration file here, which will not be deleted on
|
||||
shutdown.
|
||||
|
||||
=item B<tayga-v4-addr> (default: I<192.0.0.2>)
|
||||
|
||||
The IPv4 address assigned to the TAYGA process. This is used for emitting
|
||||
ICMPv4 errors back to the host (i.e., it will show up as the first hop when
|
||||
tracerouting to IPv4 destinations), and you may also ping it to verify that
|
||||
the TAYGA process is still alive and well.
|
||||
|
||||
The default address is one from I<I-D.draft-byrne-v6ops-clatip>.
|
||||
|
||||
=item B<v4-conncheck-enable=bool> (default: I<yes>)
|
||||
|
||||
Whether or not to check if the system has IPv4 connectivity before starting
|
||||
the CLAT. If it does, then B<clatd> will simply exit without doing anything.
|
||||
This is meant so that you can always enable B<clatd> to the system startup
|
||||
scripts or network-up event scripts (such as NetworkManager's dispatcher
|
||||
scripts), but not have B<clatd> interfering with native IPv4 connectivity when
|
||||
this is present.
|
||||
|
||||
If you want to always start the CLAT whenever possible, even though the
|
||||
system has IPv4 connectivity, disable this setting. You may instead use the
|
||||
B<v4-defaultroute-enable> and B<v4-defaultroute-metric> settings to prevent
|
||||
B<clatd> from interfering with native IPv4 connectivity.
|
||||
|
||||
=item B<v4-conncheck-delay=seconds> (default: I<10>)
|
||||
|
||||
When performing an IPv4 connectivity check, wait this number of seconds
|
||||
before actually doing anything. This is to avoid a race condition where for
|
||||
example IPv6 SLAAC finshes and triggers a network-up event script to start
|
||||
B<clatd>, while IPv4 DHCPv4 is still running in the background. This is at
|
||||
least a likely scenario when using NetworkManager, as it will start the
|
||||
dispatcher scripts as soon as either IPv4 or IPv6 has completed, and
|
||||
IPv6 SLAAC is typically faster than IPv4 DHCPv4.
|
||||
|
||||
Set it to 0 to perform the check immediately.
|
||||
|
||||
=item B<v4-defaultroute-enable=bool> (default: I<yes>)
|
||||
|
||||
Whether or not to add an IPv4 default route pointing to the CLAT. In a
|
||||
typical 464XLAT environment, you want this. However when using B<clatd> in
|
||||
an environment where native IPv4 connectivity is also present, you might want
|
||||
to disable this and instead control manually which IPv4 destinations is
|
||||
reached through the CLAT and which are not.
|
||||
|
||||
=item B<v4-defaultroute-metric=integer> (default: I<2048>)
|
||||
|
||||
The metric of the IPv4 default route pointing to the CLAT. The default is
|
||||
chosen because it is higher than that of a native IPv4 default route added by
|
||||
NetworkManager, which makes it so that the native IPv4 connectivity is
|
||||
preferred if present.
|
||||
|
||||
=item B<v4-defaultroute-mtu=integer> (default: I<1260>)
|
||||
|
||||
The MTU of the default route pointing to the CLAT. The default is the default
|
||||
IPv6 MTU used by TAYGA (1280, which in turn comes from I<RFC 6145>) minus 20 to
|
||||
compensate for the difference in header size between IPv4 and IPv6. This
|
||||
prevents outbound packets from having to be fragmented by TAYGA, and also
|
||||
makes local applications advertise a TCP MSS to their remote peers that
|
||||
prevent them from sending packets beck to us that would require fragmentation.
|
||||
|
||||
If you know that the IPv6 Path MTU between the host and the PLAT is larger
|
||||
than 1280, you may increase this, but then you should also recompile TAYGA
|
||||
with a larger B<ipv6_offlink_mtu> setting in I<conffile.c>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 LIMITATIONS
|
||||
|
||||
B<clatd> will not be able to acquire an IPv6 address for the CLAT if SLAAC
|
||||
isn't used. I<RFC 6877> suggests DHCPv6 IA_PD should be attempted in this
|
||||
case, but this isn't currently implemented.
|
||||
|
||||
B<clatd> will not attempt to perform Duplicate Address Detection for the IPv6
|
||||
address it generates. This is a violation of I<RFC 6877>.
|
||||
|
||||
B<clatd> will not attempt to perform a connectivity check to a discovered PLAT
|
||||
prefix before setting up the CLAT, as I<RFC 7050> suggest it should.
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
If you are experiencing any bugs or have any feature requests, head over to
|
||||
L<https://github.com/toreanderson/clatd/issues> and submit a new issue (if
|
||||
someone else hasn't already done so). Please make sure to include logs with
|
||||
full debugging output (using I<-d -d> on the command line or B<debug=2> in the
|
||||
configuration file) when reporting a bug.
|
||||
|
||||
=head1 LICENCE
|
||||
|
||||
Copyright (c) 2014 Tore Anderson <tore@fud.no>
|
||||
|
||||
As long as you retain this notice, you may use this piece of software as
|
||||
you wish. If you like it, and we happen to meet one day, you can buy me
|
||||
a beer in return. If you really like it, make it an IPA.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
ip(8), ip6tables(8), tayga(8), tayga.conf(5)
|
||||
|
||||
RFC 6052, RFC 6145, RFC 6146, RFC 6877, RFC 7050
|
||||
|
||||
I-D.anderson-siit-dc, I-D.byrne-v6ops-clatip
|
||||
|
||||
=cut
|
||||
792
clatd
Executable file
792
clatd
Executable file
@@ -0,0 +1,792 @@
|
||||
#! /usr/bin/perl -w
|
||||
#
|
||||
# Copyright (c) 2014 Tore Anderson <tore@fud.no>
|
||||
#
|
||||
# As long as you retain this notice, you may use this piece of software as
|
||||
# you wish. If you like it, and we happen to meet one day, you can buy me
|
||||
# a beer in return. If you really like it, make it an IPA.
|
||||
#
|
||||
# See the file 'README.pod' in the source distribution or the manual page
|
||||
# clatd(8) for more information.
|
||||
#
|
||||
use strict;
|
||||
use Net::IP;
|
||||
|
||||
my $VERSION = "1.0";
|
||||
|
||||
#
|
||||
# Populate the global config hash with the default values
|
||||
#
|
||||
my %CFG;
|
||||
$CFG{"quiet"} = 0; # suppress normal output
|
||||
$CFG{"debug"} = 0; # debugging output level
|
||||
$CFG{"clat-dev"} = "clat"; # TUN interface name to use
|
||||
$CFG{"clat-v4-addr"} = "192.0.0.1"; # from I-D.draft-byrne-v6ops-clatip
|
||||
$CFG{"clat-v6-addr"} = undef; # derive from existing SLAAC addr
|
||||
$CFG{"dns64-servers"} = undef; # use system resolver by default
|
||||
$CFG{"cmd-ip"} = "ip"; # assume in $PATH
|
||||
$CFG{"cmd-ip6tables"} = "ip6tables"; # assume in $PATH
|
||||
$CFG{"cmd-tayga"} = "tayga"; # assume in $PATH
|
||||
$CFG{"forwarding-enable"} = 1; # enable ipv6 forwarding?
|
||||
$CFG{"ip6tables-enable"} = undef; # allow clat<->plat traffic?
|
||||
$CFG{"plat-dev"} = undef; # PLAT-facing device, default detect
|
||||
$CFG{"plat-prefix"} = undef; # detect using DNS64 by default
|
||||
$CFG{"proxynd-enable"} = 1; # add proxy-nd entry for clat?
|
||||
$CFG{"tayga-conffile"} = undef; # make a temporary one by default
|
||||
$CFG{"tayga-v4-addr"} = "192.0.0.2"; # from I-D.draft-byrne-v6ops-clatip
|
||||
$CFG{"v4-conncheck-enable"} = 1; # exit if there's already a defroute
|
||||
$CFG{"v4-conncheck-delay"} = 10; # seconds before checking for v4 conn.
|
||||
$CFG{"v4-defaultroute-enable"} = 1; # add a v4 defaultroute via the CLAT?
|
||||
$CFG{"v4-defaultroute-metric"} = 2048; # metric for the IPv4 defaultroute
|
||||
$CFG{"v4-defaultroute-mtu"} = 1260; # MTU for the IPv4 defaultroute
|
||||
|
||||
|
||||
#
|
||||
# helper functions for various modes of output and error handling
|
||||
#
|
||||
sub p {
|
||||
print join("", @_), "\n" unless($CFG{"quiet"} >= 1);
|
||||
}
|
||||
sub d {
|
||||
print join("", @_), "\n" if($CFG{"debug"} >= 1);
|
||||
}
|
||||
sub d2 {
|
||||
print join("", @_), "\n" if($CFG{"debug"} >= 2);
|
||||
}
|
||||
sub w {
|
||||
print "<warn> ", join("", @_), "\n" unless($CFG{"quiet"} >= 2);
|
||||
}
|
||||
sub err {
|
||||
print "<error> ", join("", @_), "\n" unless($CFG{"quiet"} >= 2);
|
||||
cleanup_and_exit(1);
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Runs a command. First argument is what subroutine to call to a message if
|
||||
# the command doesn't exit successfully, second is the command itself, and
|
||||
# any more is the command line arguments.
|
||||
#
|
||||
sub cmd {
|
||||
my $msgsub = shift;
|
||||
my $command = shift;
|
||||
my @cmdline = @_;
|
||||
|
||||
d("cmd($command @cmdline)");
|
||||
|
||||
if(system($command, @cmdline)) {
|
||||
if($? == -1) {
|
||||
&{$msgsub}("cmd($command @cmdline) failed to execute");
|
||||
} elsif($? & 127) {
|
||||
&{$msgsub}("cmd($command @cmdline) died with signal ", ($? & 127));
|
||||
} else {
|
||||
&{$msgsub}("cmd($command @cmdline) returned ", ($? >> 127));
|
||||
}
|
||||
}
|
||||
return $?;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Reads in key=value pairs from a configuration file, overwriting the default
|
||||
# setting in the %CFG hash. The key must exist, or we
|
||||
#
|
||||
sub readconf {
|
||||
d("readconf('@_')");
|
||||
open(my $fd, "@_") or err("readconf('@_') failed: $!");
|
||||
while(<$fd>) {
|
||||
chomp;
|
||||
next if m,^\s*(;|#|//|$),; # strip out comments and empty lines
|
||||
if(m|^\s*([\w-]+)\s*=\s*(.*)\s*$|) {
|
||||
if(!exists($CFG{$1})) {
|
||||
w("Unknown key '$1' defined in config file ignored");
|
||||
} else {
|
||||
$CFG{$1} = $2;
|
||||
}
|
||||
} else {
|
||||
w("Unknown line '$_' in config file ignored");
|
||||
}
|
||||
}
|
||||
close($fd) or err($!);
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# gets a boolean value from the config hash - fails if unset or syntactically
|
||||
# invalid
|
||||
#
|
||||
sub cfgbool {
|
||||
my ($key) = @_;
|
||||
d2("cfgbool($key)");
|
||||
if(!exists($CFG{$key})) {
|
||||
err("key '$key' doesn't exist in config hash");
|
||||
}
|
||||
my $val = lc($CFG{$key});
|
||||
return 1 if($val eq "1" or $val eq "true" or $val eq "on" or $val eq "yes");
|
||||
return 0 if($val eq "0" or $val eq "false" or $val eq "off" or $val eq "no");
|
||||
err("$key: boolean value (1/0/true/false/on/off/yes/no) expected");
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# gets an integer value from the config hash - fails if unset or syntactically
|
||||
# invalid
|
||||
#
|
||||
sub cfgint {
|
||||
my ($key) = @_;
|
||||
d2("cfgstr($key)");
|
||||
if(!exists($CFG{$key})) {
|
||||
err("key '$key' doesn't exist in config hash");
|
||||
}
|
||||
my $val = $CFG{$key};
|
||||
$val =~ m|^\d+$| or err("$key=$val - integer expected");
|
||||
return $val;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# gets a scalar value from the config hash - fails if unset
|
||||
#
|
||||
sub cfg {
|
||||
my ($key) = @_;
|
||||
d2("cfgstr($key)");
|
||||
if(!exists($CFG{$key})) {
|
||||
err("key '$key' doesn't exist in config hash");
|
||||
}
|
||||
return $CFG{$key};
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# read sysctl in the first argument, or set it to value in second argument
|
||||
# if provided
|
||||
#
|
||||
sub sysctl {
|
||||
my ($sysctl, $new_value) = @_;
|
||||
|
||||
$sysctl =~ s|^/proc/sys/||;
|
||||
|
||||
if(defined($new_value)) {
|
||||
d("Setting sysctl /proc/sys/$sysctl=$new_value");
|
||||
my $fd;
|
||||
open($fd, ">/proc/sys/$sysctl");
|
||||
if(!defined($fd)) {
|
||||
w("Failed to open /proc/sys/$sysctl for writing: $!");
|
||||
return;
|
||||
}
|
||||
print $fd "$new_value\n";
|
||||
if(!close($fd)) {
|
||||
w("Failed to close /proc/sys/$sysctl after writing: $!");
|
||||
return;
|
||||
}
|
||||
return $new_value;
|
||||
} else {
|
||||
d("Reading sysctl /proc/sys/$sysctl");
|
||||
my $fd;
|
||||
open($fd, "/proc/sys/$sysctl");
|
||||
if(!defined($fd)) {
|
||||
w("Failed to open /proc/sys/$sysctl for reading: $!");
|
||||
return;
|
||||
}
|
||||
my $value = <$fd>;
|
||||
chomp($value);
|
||||
if(!close($fd)) {
|
||||
w("Failed to close /proc/sys/$sysctl after reading: $!");
|
||||
}
|
||||
d("/proc/sys/$sysctl is set to '$value'");
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Look for either of the WKAs for ipv4only.arpa (192.0.0.170 and .171) in an
|
||||
# IPv6 address at all of the locations RFC 6052 says it can occur. If it's
|
||||
# present at any of those locations (but no more than once), return the
|
||||
# inferred translation prefix.
|
||||
#
|
||||
sub find_rfc7050_wka {
|
||||
my $AAAA = shift;
|
||||
d("check_wka(): Testing to see if $AAAA was DNS64-synthesised");
|
||||
my $ip = Net::IP->new($AAAA, 6);
|
||||
if(!$ip) {
|
||||
w("Net::IP->new($AAAA, 6) failed: ", Net::IP::Error());
|
||||
return;
|
||||
}
|
||||
|
||||
my %rfc6052table;
|
||||
$rfc6052table{"32"}{"mask"} = "0:0:ffff:ffff::";
|
||||
$rfc6052table{"32"}{"wkas"} = [qw(0:0:c000:aa:: 0:0:c000:ab::)];
|
||||
$rfc6052table{"40"}{"mask"} = "0:0:ff:ffff:ff::";
|
||||
$rfc6052table{"40"}{"wkas"} = [qw(0:0:c0:0:aa:: 0:0:c0:0:ab::)];
|
||||
$rfc6052table{"48"}{"mask"} = "::ffff:ff:ff00:0:0";
|
||||
$rfc6052table{"48"}{"wkas"} = [qw(::c000:0:aa00:0:0 ::c000:0:ab00:0:0)];
|
||||
$rfc6052table{"56"}{"mask"} = "::ff:ff:ffff:0:0";
|
||||
$rfc6052table{"56"}{"wkas"} = [qw(::c0:0:aa:0:0 ::c0:0:ab:0:0)];
|
||||
$rfc6052table{"64"}{"mask"} = "::ff:ffff:ff00:0";
|
||||
$rfc6052table{"64"}{"wkas"} = [qw(::c0:0:aa00:0 ::c0:0:ab00:0)];
|
||||
$rfc6052table{"96"}{"mask"} = "::ffff:ffff";
|
||||
$rfc6052table{"96"}{"wkas"} = [qw(::c000:aa ::c000:ab)];
|
||||
|
||||
my $discovered_pfx_len;
|
||||
|
||||
for my $len (keys(%rfc6052table)) {
|
||||
d2("Looking for Well-Known Addresses at prefix length /$len");
|
||||
my $maskedip = $ip->intip();
|
||||
my $mask = Net::IP->new($rfc6052table{"$len"}{"mask"}, 6);
|
||||
if(!$mask) {
|
||||
w('Net::IP->new(', $rfc6052table{"$len"}{"mask"}, ', 6) failed: ',
|
||||
Net::IP::Error());
|
||||
return;
|
||||
}
|
||||
|
||||
$maskedip &= $mask->intip();
|
||||
|
||||
for my $wka (@{$rfc6052table{"$len"}{"wkas"}}) {
|
||||
d2("Looking for WKA $wka");
|
||||
my $wkaint = Net::IP->new($wka, 6);
|
||||
if(!$wkaint) {
|
||||
w("Net::IP->new($wka, 6) failed: ", Net::IP::Error());
|
||||
next;
|
||||
}
|
||||
|
||||
if($maskedip == $wkaint->intip) {
|
||||
if($discovered_pfx_len) {
|
||||
w("Found WKA at two locations in ", $ip->sort,
|
||||
"(/$discovered_pfx_len and /$len) - ignoring");
|
||||
return;
|
||||
}
|
||||
d2("Found it!");
|
||||
$discovered_pfx_len = $len;
|
||||
} else {
|
||||
d2("Didn't find it");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$discovered_pfx_len) {
|
||||
d2("Did not locate any WKAs in ", $ip->short);
|
||||
return;
|
||||
}
|
||||
|
||||
# Yay, we have found a prefix! Zero the host bits manually, as Net::IP-new()
|
||||
# unfortunately doesn't accept an address with a prefix length. That would
|
||||
# have made the rest of this function so much easier...
|
||||
$ip = $ip->intip;
|
||||
$ip >>= (128-$discovered_pfx_len);
|
||||
$ip <<= (128-$discovered_pfx_len);
|
||||
|
||||
# Now convert that bigint back to an IPv6 address. Net::IP doesn't have
|
||||
# a function to convert directly from a bigint to an IPv6 address (or
|
||||
# to create a new instance directly from a bigint), so we'll have to take
|
||||
# a detour via a binary string...
|
||||
my $binip = Net::IP::ip_inttobin($ip, 6);
|
||||
unless($binip) {
|
||||
w("Failed to convert integer $ip to a binary string");
|
||||
return;
|
||||
}
|
||||
unless($ip = Net::IP::ip_bintoip($binip, 6)) {
|
||||
w("Failed to convert binary string $binip to an IPv6 address");
|
||||
return;
|
||||
}
|
||||
|
||||
# Now make sure we have a valid prefix, and return it in pretty (compact)
|
||||
# format
|
||||
$ip = Net::IP->new("$ip/$discovered_pfx_len", 6);
|
||||
if(!$ip) {
|
||||
w("Net::IP->new($ip, 6) failed: ", Net::IP::Error());
|
||||
return;
|
||||
}
|
||||
|
||||
d("Inferred PLAT prefix ", $ip->short(), "/", $ip->prefixlen(),
|
||||
" from AAAA record $AAAA");
|
||||
|
||||
return $ip->short() . "/" . $ip->prefixlen();
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# This function attempts to implement RFC 7050: Discovery of the IPv6 Prefix
|
||||
# Used for IPv6 Address Synthesis. It tries to infer a PLAT prefix by looking
|
||||
# up to see if the well-known hostname 'ipv4only.arpa' resolves to an IPv6
|
||||
# address, if so there is a high chance of DNS64 being used.
|
||||
#
|
||||
sub get_plat_prefix {
|
||||
p("Performing DNS64-based PLAT prefix discovery (cf. RFC 7050)");
|
||||
|
||||
require IO::Socket::INET6; # needed by Net::DNS for querying IPv6 servers
|
||||
require Net::DNS;
|
||||
|
||||
my @dns64_servers = split(",", cfg("dns64-servers") || "");
|
||||
my @prefixes;
|
||||
|
||||
while (1) {
|
||||
my $dns64 = shift(@dns64_servers);
|
||||
my $res;
|
||||
if($dns64) {
|
||||
d("Looking up 'ipv4only.arpa' using DNS64 server $dns64");
|
||||
$res = Net::DNS::Resolver->new(nameservers => [$dns64]);
|
||||
} else {
|
||||
d("Looking up 'ipv4only.arpa' using system resolver");
|
||||
$res = Net::DNS::Resolver->new();
|
||||
}
|
||||
$res->dnssec(0); # RFC 7050 section 3
|
||||
my $pkt = $res->query('ipv4only.arpa', 'AAAA');
|
||||
if(!$pkt) {
|
||||
d("No AAAA records was returned for 'ipv4only.arpa'");
|
||||
next;
|
||||
}
|
||||
for my $rr ($pkt->answer) {
|
||||
if($rr->type ne "AAAA") {
|
||||
w("Got an non-AAAA RR? That's unexpected... Type=", $rr->type);
|
||||
next;
|
||||
}
|
||||
my $prefix = find_rfc7050_wka($rr->address);
|
||||
if(grep { $_ eq "$prefix" } @prefixes) {
|
||||
# we've seen this prefix already, ignore it (in most cases this will
|
||||
# happen at least once, since ipv4only.arpa has two A records)
|
||||
} else {
|
||||
push(@prefixes, $prefix);
|
||||
}
|
||||
}
|
||||
} continue { last unless @dns64_servers };
|
||||
|
||||
if(@prefixes > 1) {
|
||||
# Cool! More than one prefix! Here we might at some point implement a
|
||||
# connectivity check which tests that the prefixes actually work, and
|
||||
# skips to the next one if so...
|
||||
w("Multiple PLAT prefixes discovered (@prefixes), using the first seen");
|
||||
}
|
||||
if(@prefixes) {
|
||||
return $prefixes[0];
|
||||
} else {
|
||||
p("No PLAT prefix could be discovered. Your ISP probably doesn't provide",
|
||||
" NAT64/DNS64 PLAT service. Exiting.");
|
||||
cleanup_and_exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# This function figures out which network interface on the system faces the
|
||||
# PLAT/NAT64. We need this when generating an IPv6 address for the CLAT, when
|
||||
# installing Proxy-ND entries, and when setting up ip6tables rules.
|
||||
#
|
||||
sub get_plat_dev {
|
||||
d("get_plat_dev(): finding which network dev faces the PLAT");
|
||||
my $plat_dev;
|
||||
my $plat_prefix = cfg("plat-prefix");
|
||||
if(!$plat_prefix) {
|
||||
err("get_plat_dev(): No PLAT prefix to work with");
|
||||
}
|
||||
open(my $fd, '-|', cfg("cmd-ip"), qw(-6 route get), $plat_prefix)
|
||||
or err("get_plat_dev(): 'ip -6 route get $plat_prefix' failed to execute");
|
||||
while(<$fd>) {
|
||||
if(/ dev (\S+) /) {
|
||||
d("get_plat_dev(): Found PLAT-facing device: $1");
|
||||
$plat_dev = $1;
|
||||
}
|
||||
}
|
||||
close($fd) or err("get_plat_dev(): 'ip -6 route get $plat_prefix' failed");
|
||||
return $plat_dev;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Determines if an address is contructed using the Modified EUI-64 algorithm,
|
||||
# by extension that it was configured using SLAAC (in which case we're at
|
||||
# liberty to grab another address in that same /64 for the CLAT).
|
||||
#
|
||||
# This isn't a 100% foolproof check, as it is certainly possible to configure
|
||||
# such an address statically, or to hand it out using DHCPv6 IA_NA, but as
|
||||
# we can't easliy know with 100% certainty that SLAAC is being used, it'll
|
||||
# have to do. The function checks three things which are known to be true for
|
||||
# IPv6 addresses with Interface IDs based on Modified EUI-64:
|
||||
# 1) bits 24 through 38 in the Interface ID are 1
|
||||
# 2) bit 39 in the Interface ID is 0
|
||||
# Return true if all of the above is the case, false otherwise.
|
||||
#
|
||||
sub is_modified_eui64 {
|
||||
my $ip = shift;
|
||||
$ip = Net::IP->new($ip) or return;
|
||||
$ip = $ip->intip();
|
||||
|
||||
# Check 1) - return false if check fails
|
||||
my $mask = Net::IP->new("::ff:fe00:0");
|
||||
$mask = $mask->intip();
|
||||
return unless ($ip & $mask) == $mask;
|
||||
|
||||
# Check 2) and return
|
||||
$mask = Net::IP->new("::100:0");
|
||||
$mask = $mask->intip();
|
||||
return ($ip & $mask) != $mask;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
sub get_clat_v6_addr {
|
||||
my $plat_dev = cfg("plat-dev");
|
||||
if(!$plat_dev) {
|
||||
err("get_clat_v6_addr(): No PLAT device to work with");
|
||||
}
|
||||
p("Attempting to derive a CLAT IPv6 address from a EUI-64 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 |) {
|
||||
my $candidate = $1;
|
||||
next unless(is_modified_eui64($candidate));
|
||||
d2("Saw EUI-64 based address: $candidate");
|
||||
my $ip = Net::IP->new($candidate, 6) or next;
|
||||
$ip = $ip->intip();
|
||||
|
||||
# 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 and return
|
||||
$mask = Net::IP->new("::c1:a700:0", 6) or next;
|
||||
$mask = $mask->intip();
|
||||
$ip |= $mask;
|
||||
|
||||
$ip = Net::IP->new(Net::IP::ip_bintoip(Net::IP::ip_inttobin($ip, 6), 6));
|
||||
return $ip->short() if $ip;
|
||||
}
|
||||
}
|
||||
close($fd)
|
||||
or err("'ip -6 address list scope global dev $plat_dev' failed");
|
||||
err("Failed to generate a CLAT IPv6 address (try setting 'clat-v6-addr')");
|
||||
}
|
||||
|
||||
#
|
||||
# This subroutine is called when we are exiting, for whatever reason. It
|
||||
# tries to clean up any temporary changes we've made first. The variables
|
||||
# below gets set as we go along, so that the cleanup subroutine can restore
|
||||
# stuff if necessary.
|
||||
#
|
||||
my $cleanup_remove_clat_dev; # true if having created it
|
||||
my $cleanup_delete_taygaconf; # true if having made a temp confile
|
||||
my $cleanup_zero_forwarding_sysctl; # zero forwarding sysctl if set
|
||||
my @cleanup_accept_ra_sysctls; # accept_ra sysctls to be reset to '1'
|
||||
my $cleanup_zero_proxynd_sysctl; # zero proxy_ndp sysctl if set
|
||||
my $cleanup_remove_proxynd_entry, # true if having added proxynd entry
|
||||
my $cleanup_remove_ip6tables_rules; # true if having added ip6tables rules
|
||||
|
||||
sub cleanup_and_exit {
|
||||
my $exitcode = shift;
|
||||
|
||||
if(defined($cleanup_remove_clat_dev)) {
|
||||
d("Cleanup: Removing CLAT device");
|
||||
cmd(\&w, cfg("cmd-tayga"), "--config", cfg("tayga-conffile"), "--rmtun");
|
||||
}
|
||||
if(defined($cleanup_delete_taygaconf)) {
|
||||
d("Cleanup: Deleting TAYGA config file '", cfg("tayga-conffile"), "'");
|
||||
unlink(cfg("tayga-conffile"))
|
||||
or w("unlink('", cfg("tayga-conffile"), "') failed");
|
||||
}
|
||||
if(defined($cleanup_zero_forwarding_sysctl)) {
|
||||
d("Cleanup: Resetting forwarding sysctl to 0");
|
||||
sysctl("net/ipv6/conf/all/forwarding", 0);
|
||||
}
|
||||
for my $sysctl (@cleanup_accept_ra_sysctls) {
|
||||
d("Cleanup: Resetting $sysctl to 1");
|
||||
sysctl($sysctl, 1);
|
||||
}
|
||||
if(defined($cleanup_zero_proxynd_sysctl)) {
|
||||
d("Cleanup: Resetting proxy_ndp sysctl to 0");
|
||||
sysctl("net/ipv6/conf/" . cfg("plat-dev") . "/proxy_ndp", 0);
|
||||
}
|
||||
if(defined($cleanup_remove_proxynd_entry)) {
|
||||
d("Cleanup: Removing Proxy-ND entry for ", cfg("clat-v6-addr"), "on ",
|
||||
cfg("plat-dev"));
|
||||
cmd(\&w, cfg("cmd-ip"), qw(-6 neighbour delete proxy), cfg("clat-v6-addr"),
|
||||
"dev", cfg("plat-dev"));
|
||||
}
|
||||
if(defined($cleanup_remove_ip6tables_rules)) {
|
||||
d("Cleanup: Removing ip6tables rules allowing traffic between the CLAT ",
|
||||
"and PLAT devices");
|
||||
cmd(\&w, cfg("cmd-ip6tables"), qw(-D FORWARD -i), cfg("clat-dev"),
|
||||
"-o", cfg("plat-dev"), qw(-j ACCEPT));
|
||||
cmd(\&w, cfg("cmd-ip6tables"), qw(-D FORWARD -i), cfg("plat-dev"),
|
||||
"-o", cfg("clat-dev"), qw(-j ACCEPT));
|
||||
}
|
||||
|
||||
exit($exitcode);
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Ok, we're done defining helper functions, and are ready to start doing some
|
||||
# real work here. First parse option arguments from command line, config
|
||||
# overrides we do in a second pass below. We do it in two passes to ensure we
|
||||
# have read in any config from the config file before possibly overriding with
|
||||
# config supplied on the command line
|
||||
#
|
||||
#
|
||||
for (my $i = 0; $i < @ARGV;) {
|
||||
if($ARGV[$i] eq "-q") {
|
||||
$CFG{"quiet"}++;
|
||||
splice(@ARGV, $i, 1);
|
||||
next;
|
||||
} elsif($ARGV[$i] eq "-d") {
|
||||
$CFG{"debug"}++;
|
||||
splice(@ARGV, $i, 1);
|
||||
next;
|
||||
} elsif($ARGV[$i] eq "-c") {
|
||||
if(!defined($ARGV[$i+1])) {
|
||||
err("Command line option '-c' given without an argument");
|
||||
}
|
||||
if(!defined(&readconf)) {
|
||||
err("Command line option '-c' given more than once");
|
||||
}
|
||||
readconf($ARGV[$i+1]);
|
||||
undef(&readconf);
|
||||
splice(@ARGV, $i, 2);
|
||||
next;
|
||||
} elsif($ARGV[$i] =~ /^(-h|--help)$/) {
|
||||
print "clatd v$VERSION - a 464XLAT (RFC 6877) CLAT implementation for ",
|
||||
"Linux\n";
|
||||
print "\n";
|
||||
print " Usage: clatd [-q] [-d [-d]] [-c config-file] ",
|
||||
"[conf-key=val ...]\n";
|
||||
print " Author: Tore Anderson <tore\@fud.no>\n";
|
||||
print " Homepage: https://github.com/toreanderson/clatd\n";
|
||||
print "\n";
|
||||
print "For more documentation and information, see 'man 8 clatd'.\n";
|
||||
exit 0;
|
||||
} elsif($ARGV[$i] =~ /^-/) {
|
||||
err("Unrecognised command line option '$ARGV[$i]'");
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Read in config from default location if we haven't already due to
|
||||
# '-c "somefile"' having been supplied on command line (if so, &readconf
|
||||
# will have been undefined. However if it doesn't exit, that's OK - we'll
|
||||
# just proceed with defaults + any command line overrides
|
||||
#
|
||||
if(defined(&readconf) && -e "/etc/clatd.conf") {
|
||||
readconf("/etc/clatd.conf");
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Finally, deal with config settings from command line. This is done last so
|
||||
# that the command line takes precedence over all other sources of config
|
||||
#
|
||||
for (@ARGV) {
|
||||
if(m|^([\w-]+)=(.*)$|) {
|
||||
if(!exists($CFG{$1})) {
|
||||
err("Unknown config key '$1' given on command line");
|
||||
}
|
||||
$CFG{$1} = $2;
|
||||
} else {
|
||||
err("Unrecognised command line argument '$_'");
|
||||
}
|
||||
}
|
||||
d("Configuration successfully read, dumping it:");
|
||||
for my $key (sort(keys(%CFG))) {
|
||||
d(" $key=", defined($CFG{$key}) ? $CFG{$key} : "<undefined>");
|
||||
}
|
||||
|
||||
p("Starting clatd v$VERSION by Tore Anderson <tore\@fud.no>");
|
||||
|
||||
#
|
||||
# Step 1: Fill in any essential blanks in the configuration by auto-detecting
|
||||
# any missing values.
|
||||
$CFG{"plat-prefix"} ||= get_plat_prefix();
|
||||
if(!$CFG{"plat-prefix"}) {
|
||||
w("No PLAT prefix was discovered or specified; 464XLAT cannot work.");
|
||||
exit 0;
|
||||
} else {
|
||||
# Do some basic sanity checking on the PLAT prefix
|
||||
my $ip = Net::IP->new($CFG{"plat-prefix"}, 6);
|
||||
if(!$ip) {
|
||||
d2("Net::IP::Error()=" . Net::IP::Error()) if(Net::IP::Error());
|
||||
err("PLAT prefix $CFG{'plat-prefix'} is not a valid IPv6 prefix");
|
||||
}
|
||||
if($ip->prefixlen() != 96 and
|
||||
$ip->prefixlen() != 64 and
|
||||
$ip->prefixlen() != 56 and
|
||||
$ip->prefixlen() != 48 and
|
||||
$ip->prefixlen() != 32) {
|
||||
err("PLAT prefix $CFG{'plat-prefix'} has an invalid prefix length ",
|
||||
"(see RFC 6052 section 2.2)");
|
||||
}
|
||||
p("Using PLAT (NAT64) prefix: $CFG{'plat-prefix'}");
|
||||
}
|
||||
$CFG{"plat-dev"} ||= get_plat_dev();
|
||||
p("Device facing the PLAT: ", $CFG{"plat-dev"});
|
||||
$CFG{"clat-v6-addr"} ||= get_clat_v6_addr();
|
||||
p("Using CLAT IPv4 address: ", $CFG{"clat-v4-addr"});
|
||||
p("Using CLAT IPv6 address: ", $CFG{"clat-v6-addr"});
|
||||
if(!defined($CFG{"ip6tables-enable"})) {
|
||||
$CFG{"ip6tables-enable"} = -e "/sys/module/ip6table_filter" ? 1 : 0;
|
||||
}
|
||||
|
||||
#
|
||||
# Step 1: Detect if there is an IPv4 default route on the system from before.
|
||||
# If so we have no need for 464XLAT, and we can just exit straight away
|
||||
#
|
||||
if(cfgbool("v4-conncheck-enable")) {
|
||||
my $delay = cfgint("v4-conncheck-delay");
|
||||
p("Checking if this system already has IPv4 connectivity ",
|
||||
$delay ? "in $delay sec(s)" : "now");
|
||||
sleep($delay);
|
||||
open(my $fd, '-|', cfg("cmd-ip"), qw(-4 route list default))
|
||||
or err("'", cfg("cmd-ip"), " -4 route list default' failed to execute");
|
||||
while(<$fd>) {
|
||||
if(/^default /) {
|
||||
p("This system already has IPv4 connectivity; no need for a CLAT.");
|
||||
exit_and_cleanup(0);
|
||||
}
|
||||
}
|
||||
close($fd) or err("cmd(ip -4 route list default) failed");
|
||||
} else {
|
||||
d("Skipping IPv4 connectivity check at user request");
|
||||
}
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Write out the TAYGA config file, either to the user-specified location,
|
||||
# or to a temporary file (which we'll delete later)
|
||||
#
|
||||
my $tayga_conffile = cfg("tayga-conffile");
|
||||
my $tayga_conffile_fh;
|
||||
if(!$tayga_conffile) {
|
||||
require File::Temp;
|
||||
($tayga_conffile_fh, $tayga_conffile) = File::Temp::tempfile();
|
||||
d2("Using temporary conffile for TAYGA: $tayga_conffile");
|
||||
$CFG{"tayga-conffile"} = $tayga_conffile;
|
||||
$cleanup_delete_taygaconf = 1;
|
||||
} else {
|
||||
open($tayga_conffile_fh, ">$tayga_conffile") or
|
||||
err("Could not open TAYGA config file '$tayga_conffile' for writing");
|
||||
}
|
||||
|
||||
print $tayga_conffile_fh "# Ephemeral TAYGA config file written by $0\n";
|
||||
print $tayga_conffile_fh "# This file may be safely deleted at any time.\n";
|
||||
print $tayga_conffile_fh "tun-device ", cfg("clat-dev"), "\n";
|
||||
print $tayga_conffile_fh "prefix ", cfg("plat-prefix"), "\n";
|
||||
print $tayga_conffile_fh "ipv4-addr ", cfg("tayga-v4-addr"), "\n";
|
||||
print $tayga_conffile_fh "map ", cfg("clat-v4-addr"), " ",
|
||||
cfg("clat-v6-addr"),"\n";
|
||||
|
||||
close($tayga_conffile_fh) or err("close($tayga_conffile_fh: $!");
|
||||
|
||||
#
|
||||
# Enable IPv6 forwarding if necessary
|
||||
#
|
||||
if(cfgbool("forwarding-enable")) {
|
||||
if(sysctl("net/ipv6/conf/all/forwarding") == 0) {
|
||||
p("Enabling IPv6 forwarding");
|
||||
for my $ctl (glob("/proc/sys/net/ipv6/conf/*/accept_ra")) {
|
||||
|
||||
# Don't touch the ctl for the "all" interface, as that will probably
|
||||
# change interfaces that have accept_ra set to 0 also.
|
||||
next if($ctl eq "/proc/sys/net/ipv6/conf/all/accept_ra");
|
||||
|
||||
if(sysctl($ctl) == 1) {
|
||||
d("Changing $ctl from 1 to 2 to prevent connectivity loss after ",
|
||||
"enabling IPv6 forwarding");
|
||||
sysctl($ctl, 2);
|
||||
push(@cleanup_accept_ra_sysctls, $ctl);
|
||||
}
|
||||
}
|
||||
sysctl("net/ipv6/conf/all/forwarding", 1);
|
||||
$cleanup_zero_forwarding_sysctl = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#
|
||||
# Add ip6tables rules permitting traffic between the PLAT and the CLAT
|
||||
#
|
||||
if(cfgbool("ip6tables-enable")) {
|
||||
p("Adding ip6tables rules allowing traffic between the CLAT ",
|
||||
"and PLAT devices");
|
||||
cmd(\&w, cfg("cmd-ip6tables"), qw(-I FORWARD -i), cfg("clat-dev"),
|
||||
"-o", cfg("plat-dev"), qw(-j ACCEPT));
|
||||
cmd(\&w, cfg("cmd-ip6tables"), qw(-I FORWARD -i), cfg("plat-dev"),
|
||||
"-o", cfg("clat-dev"), qw(-j ACCEPT));
|
||||
$cleanup_remove_ip6tables_rules = 1;
|
||||
}
|
||||
|
||||
#
|
||||
# Enable ND proxy for the CLAT's IPv6 address on the interface facing the PLAT
|
||||
#
|
||||
if(cfgbool("proxynd-enable")) {
|
||||
my $plat_dev = cfg("plat-dev");
|
||||
my $clat_v6_addr = cfg("clat-v6-addr");
|
||||
p("Enabling Proxy-ND for $clat_v6_addr on $plat_dev");
|
||||
if(sysctl("net/ipv6/conf/$plat_dev/proxy_ndp") == 0) {
|
||||
sysctl("net/ipv6/conf/$plat_dev/proxy_ndp", 1);
|
||||
$cleanup_zero_proxynd_sysctl = 1;
|
||||
d("Enabled Proxy-ND sysctl for $plat_dev");
|
||||
}
|
||||
cmd(\&w, cfg("cmd-ip"), qw(-6 neighbour add proxy), cfg("clat-v6-addr"),
|
||||
"dev", cfg("plat-dev"));
|
||||
|
||||
$cleanup_remove_proxynd_entry = 1;
|
||||
}
|
||||
|
||||
#
|
||||
# Create the CLAT tun interface, add the IPv4 address to it as well as the
|
||||
# route to the corresponding IPv6 address, and possibly an IPv4 default route
|
||||
#
|
||||
p("Creating and configuring up CLAT device '", cfg("clat-dev"), "'");
|
||||
cmd(\&err, cfg("cmd-tayga"), "--config", cfg("tayga-conffile"), "--mktun",
|
||||
cfgint("debug") ? "-d" : "");
|
||||
$cleanup_remove_clat_dev = 1;
|
||||
cmd(\&err, cfg("cmd-ip"), qw(link set up dev), cfg("clat-dev"));
|
||||
cmd(\&err, cfg("cmd-ip"), qw(-4 address add), cfg("clat-v4-addr"),
|
||||
"dev", cfg("clat-dev"));
|
||||
cmd(\&err, cfg("cmd-ip"), qw(-6 route add), cfg("clat-v6-addr"),
|
||||
"dev", cfg("clat-dev"));
|
||||
if(cfgbool("v4-defaultroute-enable")) {
|
||||
my @cmdline = (qw(-4 route add default dev), cfg("clat-dev"));
|
||||
if(cfgint("v4-defaultroute-metric")) {
|
||||
push(@cmdline, ("metric", cfgint("v4-defaultroute-metric")))
|
||||
}
|
||||
if(cfgint("v4-defaultroute-mtu")) {
|
||||
push(@cmdline, ("mtu", cfgint("v4-defaultroute-mtu")))
|
||||
}
|
||||
p("Adding IPv4 default route via the CLAT");
|
||||
cmd(\&err, cfg("cmd-ip"), @cmdline);
|
||||
}
|
||||
|
||||
#
|
||||
# All preparation done! We can now start TAYGA, which will handle the actual
|
||||
# translation of IP packets.
|
||||
#
|
||||
p("Starting up TAYGA, using config file '$tayga_conffile'");
|
||||
|
||||
# We don't want systemd etc. to actually kill this script when stopping the
|
||||
# service, just TAYGA (so that we can get around to cleaning up after
|
||||
# ourselves)
|
||||
$SIG{'INT'} = 'IGNORE';
|
||||
$SIG{'TERM'} = 'IGNORE';
|
||||
|
||||
cmd(\&err, cfg("cmd-tayga"), "--config", cfg("tayga-conffile"), "--nodetach",
|
||||
cfgint("debug") ? "-d" : "");
|
||||
p("TAYGA terminated, cleaning up and exiting");
|
||||
|
||||
$SIG{'INT'} = 'DEFAULT';
|
||||
$SIG{'TERM'} = 'DEFAULT';
|
||||
|
||||
#
|
||||
# TAYGA exited, probably because we're shutting down. Cleanup and exit.
|
||||
#
|
||||
cleanup_and_exit(0);
|
||||
24
scripts/clatd.networkmanager
Normal file
24
scripts/clatd.networkmanager
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# clatd dispatcher script for NetworkManager
|
||||
#
|
||||
# Install it to: /etc/NetworkManager/dispatcher.d/50-clatd
|
||||
#
|
||||
# Written by Tore Anderson <tore@fud.no>
|
||||
#
|
||||
|
||||
# We simply restart clatd in all situations, as no matter if an interface
|
||||
# goes up or down, it may mean that the PLAT devices changes, it may mean
|
||||
# native IPv4 appearing or disappearing, or it may mean that DNS64 became
|
||||
# available or unavailable...it's far easier to simply restart always and
|
||||
# start from scratch than to figure out if a restart is truly necessary
|
||||
|
||||
# systemd-based distros
|
||||
if test -x /usr/bin/systemctl; then
|
||||
/usr/bin/systemctl restart clatd.service
|
||||
fi
|
||||
|
||||
# upstart-based distros
|
||||
if test -x /sbin/initctl; then
|
||||
/sbin/initctl restart clatd
|
||||
fi
|
||||
21
scripts/clatd.systemd
Normal file
21
scripts/clatd.systemd
Normal file
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# clatd service file for systemd
|
||||
#
|
||||
# Install it to: /etc/systemd/system/clatd.service
|
||||
# Enable it with: systemctl enable clatd.service
|
||||
# Start it with: systemctl start clatd.service
|
||||
#
|
||||
# Written by Tore Anderson <tore@fud.no>
|
||||
#
|
||||
|
||||
[Unit]
|
||||
Description=464XLAT CLAT daemon
|
||||
Documentation=man:clatd(8)
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/sbin/clatd
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
16
scripts/clatd.upstart
Normal file
16
scripts/clatd.upstart
Normal file
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# clatd service file for upstart
|
||||
#
|
||||
# Install it to: /etc/init/clatd.conf
|
||||
# Start it with: initctl start clatd
|
||||
#
|
||||
# Written by Tore Anderson <tore@fud.no>
|
||||
#
|
||||
|
||||
description "464XLAT CLAT daemon"
|
||||
author "Tore Anderson <tore@fud.no>"
|
||||
|
||||
start on net-device-up
|
||||
stop on runlevel [!2345]
|
||||
|
||||
exec /usr/sbin/clatd
|
||||
Reference in New Issue
Block a user