diff --git a/Makefile b/Makefile index 08efbaa..bdca32e 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,8 @@ install: installdeps: # .deb/apt-get based distros - if test -x "$(APT_GET)"; then $(APT_GET) -y install perl-base perl-modules libnet-ip-perl libnet-dns-perl iproute2 nftables tayga; fi + if test -x "$(APT_GET)"; then $(APT_GET) -y install perl-base perl-modules libnet-ip-perl libnet-dns-perl libjson-perl iproute2 nftables tayga; fi # .rpm/DNF/YUM-based distros - if test -x "$(DNF_OR_YUM)"; then $(DNF_OR_YUM) -y install perl perl-IPC-Cmd perl-Net-IP perl-Net-DNS perl-File-Temp iproute nftables; fi + if test -x "$(DNF_OR_YUM)"; then $(DNF_OR_YUM) -y install perl perl-IPC-Cmd perl-Net-IP perl-Net-DNS perl-File-Temp perl-JSON iproute nftables; fi # If necessary, try to install the TAYGA .rpm using dnf/yum. It is unfortunately not available in all .rpm based distros (in particular CentOS/RHEL). if test -x "$(DNF_OR_YUM)" && test ! -x "$(TAYGA)"; then $(DNF_OR_YUM) -y install tayga || echo "ERROR: Failed to install TAYGA using dnf/yum, the package is probably not included in your distro. Try enabling the EPEL repo and try again, or install TAYGA directly from source."; exit 1; fi diff --git a/README.pod b/README.pod index 88ea2d2..321231e 100644 --- a/README.pod +++ b/README.pod @@ -241,17 +241,32 @@ with using B as a SIIT-DC Edge Relay (I). =item B (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 using any of -the public DNS64/NAT64 instances on the internet. The first PLAT prefix -encountered will be used. +using the method described in I. By default, B will first try +to determine if systemd-networkd is aware of a PLAT prefix (learned from the +PREF64 Router Advertisement option, cf. I), falling back on using +DNS64 discovery towards the system resolver if it isn't. + +It might be useful to override this in case your network does not advertise the +PREF64 RA option, your ISP doesn't provide you with a DNS64-enabled name +server, and you want to test B using any of the public DNS64/NAT64 +instances on the internet. The first PLAT prefix encountered will be used. =item B (default: assume in $PATH) Path to the B binary from the iproute2 package available at L. Required. +=item B (default: assume in $PATH) + +Path to the B binary from systemd-networkd. Required in order to +use any PLAT prefix discovered by systemd-networkd from the PREF64 Router +Advertisement option (see I and the I option in +I for more information). The first prefix returned is used, +any others are ignored. + +To prevent PLAT prefix discovery via systemd-networkd from being attempted, set +this to an empty string. + =item B (default: assume in $PATH) Path to the B binary from the nftables package available at @@ -493,9 +508,9 @@ SOFTWARE. =head1 SEE ALSO -ip(8), nft(8), tayga(8), tayga.conf(5) +ip(8), nft(8), systemd.network(5), tayga(8), tayga.conf(5) RFC 6052, RFC 6145, RFC 6146, RFC 6877, RFC 7050, RFC 7335 RFC 7755, RFC 7756, -RFC 7757 +RFC 7757, RFC 8781 =cut diff --git a/clatd b/clatd index 69f0e03..f3e8ff7 100755 --- a/clatd +++ b/clatd @@ -42,6 +42,7 @@ $CFG{"clat-v4-addr"} = "192.0.0.1"; # from RFC 7335 $CFG{"clat-v6-addr"} = "shared"; # re-use primary address from host OS $CFG{"dns64-servers"} = undef; # use system resolver by default $CFG{"cmd-ip"} = "ip"; # assume in $PATH +$CFG{"cmd-networkctl"} = "networkctl"; # assume in $PATH $CFG{"cmd-nft"} = "nft"; # assume in $PATH $CFG{"cmd-tayga"} = "tayga"; # assume in $PATH $CFG{"cmd-ufw"} = "ufw"; # assume in $PATH @@ -329,7 +330,7 @@ sub find_rfc7050_wka { # 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 { +sub get_plat_prefix_from_dns64 { p("Performing DNS64-based PLAT prefix discovery (cf. RFC 7050)"); require Net::DNS; @@ -397,6 +398,62 @@ sub get_plat_prefix { } +# +# This function attempts request a PLAT prefix from systemd-networkd, which +# systemd-networkd will know about if the Router Advertisements contain the +# PREF64 option defined in RFC 8781, and systemd-networkd is configured with +# UsePREF64=true (not the default). The first prefix seen is used, subsequent +# ones are ignored. +# +sub get_plat_prefix_from_networkd { + if(!can_run(cfg("cmd-networkctl"))) { + d(cfg("cmd-networkctl"), " is not installed or not exectutable, skipping"); + return; + } + p("Attempting to query systemd-networkd for PLAT prefix (cf. RFC 8781)"); + open(my $fd, '-|', cfg("cmd-networkctl"), qw(--json=short status)) + or err(cfg("cmd-networkctl"), " failed to execute"); + my @out = <$fd>; + if(!close($fd)) { + p("'networkctl status' failed, trying DNS64 PLAT prefix discovery"); + return; + } + + require JSON; + my $status = JSON::decode_json("@out"); + + return unless($status->{"Interfaces"}); + + for my $interface (@{$status->{"Interfaces"}}) { + next if(!$interface->{"NDisc"}->{"PREF64"}); + + d2($interface->{"Name"}, " PREF64 bytes: ", + join(" ", @{$interface->{"NDisc"}->{"PREF64"}->[0]->{"Prefix"}}), + " / ", $interface->{"NDisc"}->{"PREF64"}->[0]->{"PrefixLength"}); + + # PREF64 is an array of bytes, convert to IPv6 string representation + my @bytes = @{$interface->{"NDisc"}->{"PREF64"}->[0]->{"Prefix"}}; + my $ipstr; + for (my $i = 0; $i < @bytes;) { + $ipstr .= sprintf("%02x%02x", $bytes[$i++], $bytes[$i++]); + $ipstr .= ":" if($i < @bytes); + } + $ipstr .= "/" . $interface->{"NDisc"}->{"PREF64"}->[0]->{"PrefixLength"}; + + d2("String representation: $ipstr"); + + # Run the result through Net::IP to ensure we have a valid IPv6 string + # and also to convert it to compact format. + my $ip = Net::IP->new($ipstr, 6) + or err("Failed to convert PREF64 bytes array to IPv6 string format"); + + my $prefix = $ip->short() . "/" . $ip->prefixlen(); + d("Obtained PLAT prefix $prefix from systemd-networkd"); + return $prefix; + } +} + + # # 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 @@ -770,7 +827,9 @@ p("Starting clatd v$VERSION by Tore Anderson "); # # Step 1: Fill in any essential blanks in the configuration by auto-detecting # any missing values. -$CFG{"plat-prefix"} ||= get_plat_prefix(); +$CFG{"plat-prefix"} ||= get_plat_prefix_from_networkd() + unless($CFG{"dns64-servers"}); +$CFG{"plat-prefix"} ||= get_plat_prefix_from_dns64(); if(!$CFG{"plat-prefix"}) { w("No PLAT prefix was discovered or specified; 464XLAT cannot work."); exit 0;