summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-07-21 20:20:17 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-07-21 20:20:17 +0000
commit6bbb4dd1e04df9e96cfbfcddf8ffe95e89590c96 (patch)
tree13e706afe06f87ab77b6789fb29ddaf857748711
parent477f61976e838a5059f651aa0ebfb0393747b85e (diff)
parentca6312b6af2237edb421c282c6469777e5bc1390 (diff)
downloadandroid-clat-android13-mainline-go-extservices-release.tar.gz
Snap for 8857176 from ca6312b6af2237edb421c282c6469777e5bc1390 to mainline-go-extservices-releaseaml_go_ext_330912000android13-mainline-go-extservices-release
Change-Id: I70bf97852b0b11e7c9a72795ea32264f585380c6
-rw-r--r--clatd.c87
1 files changed, 87 insertions, 0 deletions
diff --git a/clatd.c b/clatd.c
index f72f431..9c3424e 100644
--- a/clatd.c
+++ b/clatd.c
@@ -40,6 +40,7 @@
#include <sys/uio.h>
#include "clatd.h"
+#include "checksum.h"
#include "config.h"
#include "dump.h"
#include "getaddr.h"
@@ -123,11 +124,97 @@ void read_packet(int read_fd, int write_fd, int to_ipv6) {
translate_packet(write_fd, 1 /* to_ipv6 */, packet, readlen);
}
+// IPv6 DAD packet format:
+// Ethernet header (if needed) will be added by the kernel:
+// u8[6] src_mac; u8[6] dst_mac '33:33:ff:XX:XX:XX'; be16 ethertype '0x86DD'
+// IPv6 header:
+// be32 0x60000000 - ipv6, tclass 0, flowlabel 0
+// be16 payload_length '32'; u8 nxt_hdr ICMPv6 '58'; u8 hop limit '255'
+// u128 src_ip6 '::'
+// u128 dst_ip6 'ff02::1:ffXX:XXXX'
+// ICMPv6 header:
+// u8 type '135'; u8 code '0'; u16 icmp6 checksum; u32 reserved '0'
+// ICMPv6 neighbour solicitation payload:
+// u128 tgt_ip6
+// ICMPv6 ND options:
+// u8 opt nr '14'; u8 length '1'; u8[6] nonce '6 random bytes'
+void send_dad(int fd, const struct in6_addr* tgt) {
+ struct {
+ struct ip6_hdr ip6h;
+ struct nd_neighbor_solicit ns;
+ uint8_t ns_opt_nr;
+ uint8_t ns_opt_len;
+ uint8_t ns_opt_nonce[6];
+ } dad_pkt = {
+ .ip6h = {
+ .ip6_flow = htonl(6 << 28), // v6, 0 tclass, 0 flowlabel
+ .ip6_plen = htons(sizeof(dad_pkt) - sizeof(struct ip6_hdr)), // payload length, ie. 32
+ .ip6_nxt = IPPROTO_ICMPV6, // 58
+ .ip6_hlim = 255,
+ .ip6_src = {}, // ::
+ .ip6_dst.s6_addr = {
+ 0xFF, 0x02, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 0, 1,
+ 0xFF, tgt->s6_addr[13], tgt->s6_addr[14], tgt->s6_addr[15],
+ }, // ff02::1:ffXX:XXXX - multicast group address derived from bottom 24-bits of tgt
+ },
+ .ns = {
+ .nd_ns_type = ND_NEIGHBOR_SOLICIT, // 135
+ .nd_ns_code = 0,
+ .nd_ns_cksum = 0, // will be calculated later
+ .nd_ns_reserved = 0,
+ .nd_ns_target = *tgt,
+ },
+ .ns_opt_nr = 14, // icmp6 option 'nonce' from RFC3971
+ .ns_opt_len = 1, // in units of 8 bytes, including option nr and len
+ .ns_opt_nonce = {}, // opt_len *8 - sizeof u8(opt_nr) - sizeof u8(opt_len) = 6 ranodmized bytes
+ };
+ arc4random_buf(&dad_pkt.ns_opt_nonce, sizeof(dad_pkt.ns_opt_nonce));
+
+ // 40 byte IPv6 header + 8 byte ICMPv6 header + 16 byte ipv6 target address + 8 byte nonce option
+ _Static_assert(sizeof(dad_pkt) == 40 + 8 + 16 + 8, "sizeof dad packet != 72");
+
+ // IPv6 header checksum is standard negated 16-bit one's complement sum over the icmpv6 pseudo
+ // header (which includes payload length, nextheader, and src/dst ip) and the icmpv6 payload.
+ //
+ // Src/dst ip immediately prefix the icmpv6 header itself, so can be handled along
+ // with the payload. We thus only need to manually account for payload len & next header.
+ //
+ // The magic '8' is simply the offset of the ip6_src field in the ipv6 header,
+ // ie. we're skipping over the ipv6 version, tclass, flowlabel, payload length, next header
+ // and hop limit fields, because they're not quite where we want them to be.
+ //
+ // ip6_plen is already in network order, while ip6_nxt is a single byte and thus needs htons().
+ uint32_t csum = dad_pkt.ip6h.ip6_plen + htons(dad_pkt.ip6h.ip6_nxt);
+ csum = ip_checksum_add(csum, &dad_pkt.ip6h.ip6_src, sizeof(dad_pkt) - 8);
+ dad_pkt.ns.nd_ns_cksum = ip_checksum_finish(csum);
+
+ const struct sockaddr_in6 dst = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = dad_pkt.ip6h.ip6_dst,
+ .sin6_scope_id = if_nametoindex(Global_Clatd_Config.native_ipv6_interface),
+ };
+
+ sendto(fd, &dad_pkt, sizeof(dad_pkt), 0 /*flags*/, (const struct sockaddr *)&dst, sizeof(dst));
+}
+
/* function: event_loop
* reads packets from the tun network interface and passes them down the stack
* tunnel - tun device data
*/
void event_loop(struct tun_data *tunnel) {
+ // Apparently some network gear will refuse to perform NS for IPs that aren't DAD'ed,
+ // this would then result in an ipv6-only network with working native ipv6, working
+ // IPv4 via DNS64, but non-functioning IPv4 via CLAT (ie. IPv4 literals + IPv4 only apps).
+ // The kernel itself doesn't do DAD for anycast ips (but does handle IPV6 MLD and handle ND).
+ // So we'll spoof dad here, and yeah, we really should check for a response and in
+ // case of failure pick a different IP. Seeing as 48-bits of the IP are utterly random
+ // (with the other 16 chosen to guarantee checksum neutrality) this seems like a remote
+ // concern...
+ // TODO: actually perform true DAD
+ send_dad(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet);
+
time_t last_interface_poll;
struct pollfd wait_fd[] = {
{ tunnel->read_fd6, POLLIN, 0 },