Generate a random IID if one is not specified.
- Add code to generate a random IPv6 address that's
checksum-neutral with the NAT64 prefix and clat IPv4 address.
- Only calculate the IP address after the NAT64 prefix is known.
- Because the clat IPv6 address is no longer determinisitic,
modify interface_poll so it checks whether the prefix has
changed instead of checking whether the IPv6 address has
changed.
- Add/update unit tests.
Change-Id: Ia53716ca5315ebdd0eaa3ad3a07552bf18e9dd5c
diff --git a/Android.mk b/Android.mk
index a640e06..b3b9fa3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -30,7 +30,7 @@
LOCAL_MODULE := clatd_test
LOCAL_CFLAGS := -Wall -Werror -Wunused-parameter
-LOCAL_SRC_FILES := clatd_test.cpp dump.c checksum.c translate.c icmp.c ipv4.c ipv6.c logging.c
+LOCAL_SRC_FILES := clatd_test.cpp checksum.c translate.c icmp.c ipv4.c ipv6.c logging.c config.c
LOCAL_MODULE_TAGS := eng tests
LOCAL_SHARED_LIBRARIES := liblog
diff --git a/checksum.h b/checksum.h
index 6195810..d0af88e 100644
--- a/checksum.h
+++ b/checksum.h
@@ -18,6 +18,10 @@
#ifndef __CHECKSUM_H__
#define __CHECKSUM_H__
+#include <stdint.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+
uint32_t ip_checksum_add(uint32_t current, const void *data, int len);
uint16_t ip_checksum_finish(uint32_t temp_sum);
uint16_t ip_checksum(const void *data, int len);
diff --git a/clatd.c b/clatd.c
index dbf725b..4b6b8cc 100644
--- a/clatd.c
+++ b/clatd.c
@@ -164,16 +164,16 @@
return;
}
- config_generate_local_ipv6_subnet(&interface_ip->ip6);
+ if(!ipv6_prefix_equal(&interface_ip->ip6, &Global_Clatd_Config.ipv6_local_subnet)) {
+ config_generate_local_ipv6_subnet(&interface_ip->ip6);
- if(!IN6_ARE_ADDR_EQUAL(&interface_ip->ip6, &Global_Clatd_Config.ipv6_local_subnet)) {
char from_addr[INET6_ADDRSTRLEN], to_addr[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &Global_Clatd_Config.ipv6_local_subnet, from_addr, sizeof(from_addr));
inet_ntop(AF_INET6, &interface_ip->ip6, to_addr, sizeof(to_addr));
- logmsg(ANDROID_LOG_WARN, "clat subnet changed from %s to %s", from_addr, to_addr);
+ logmsg(ANDROID_LOG_WARN, "clat IPv6 address changed from %s to %s", from_addr, to_addr);
// Start translating packets to the new prefix.
- memcpy(&Global_Clatd_Config.ipv6_local_subnet, &interface_ip->ip6, sizeof(struct in6_addr));
+ Global_Clatd_Config.ipv6_local_subnet = interface_ip->ip6;
// Update our packet socket filter to reflect the new 464xlat IP address.
if (!configure_packet_socket(tunnel->read_fd6)) {
diff --git a/clatd.conf b/clatd.conf
index b17b862..3805c6d 100644
--- a/clatd.conf
+++ b/clatd.conf
@@ -1,6 +1,7 @@
-# host ID to use as the source of CLAT traffic
-# this is a /128 taken out of the /64 routed to the phone
-ipv6_host_id ::464
+# Host IID to use as the source of CLAT traffic.
+# This is a /128 taken out of the /64 on the parent interface.
+# A host IID of :: means to generate a checksum-neutral, random IID.
+ipv6_host_id ::
# ipv4 subnet for the local traffic to use. This is a /32 host address
ipv4_local_subnet 192.0.0.4
diff --git a/clatd_test.cpp b/clatd_test.cpp
index b35bf70..171aecb 100644
--- a/clatd_test.cpp
+++ b/clatd_test.cpp
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <arpa/inet.h>
+#include <netinet/in6.h>
#include <sys/uio.h>
#include <gtest/gtest.h>
@@ -370,7 +371,7 @@
*reassembled_len = total_length;
}
-void check_data_matches(const uint8_t *expected, const uint8_t *actual, size_t len, const char *msg) {
+void check_data_matches(const void *expected, const void *actual, size_t len, const char *msg) {
if (memcmp(expected, actual, len)) {
// Hex dump, 20 bytes per line, one space between bytes (1 byte = 3 chars), indented by 4.
int hexdump_len = len * 3 + (len / 20 + 1) * 5;
@@ -382,11 +383,11 @@
sprintf(actual_hexdump + pos, "\n ");
pos += 4;
}
- sprintf(expected_hexdump + pos, " %02x", expected[i]);
- sprintf(actual_hexdump + pos, " %02x", actual[i]);
+ sprintf(expected_hexdump + pos, " %02x", ((uint8_t *) expected)[i]);
+ sprintf(actual_hexdump + pos, " %02x", ((uint8_t *) actual)[i]);
pos += 3;
}
- FAIL() << msg << ": Translated packet doesn't match"
+ FAIL() << msg << ": Data doesn't match"
<< "\n Expected:" << (char *) expected_hexdump
<< "\n Actual:" << (char *) actual_hexdump << "\n";
}
@@ -460,6 +461,7 @@
translate_packet(write_fd, (version == 4), original, original_len);
+ snprintf(foo, sizeof(foo), "%s: Invalid translated packet", msg);
if (version == 6) {
// Translating to IPv4. Expect a tun header.
struct tun_pi new_tun_header;
@@ -472,13 +474,15 @@
ASSERT_LT((size_t) len, *outlen) << msg << ": Translated packet buffer too small\n";
EXPECT_EQ(expected_proto, new_tun_header.proto) << msg << "Unexpected tun proto\n";
*outlen = len - sizeof(new_tun_header);
+ check_packet(out, *outlen, msg);
} else {
- FAIL() << msg << ": Packet was not translated";
+ FAIL() << msg << ": Packet was not translated: len=" << len;
*outlen = 0;
}
} else {
// Translating to IPv6. Expect raw packet.
*outlen = read(read_fd, out, *outlen);
+ check_packet(out, *outlen, msg);
}
}
@@ -514,6 +518,44 @@
check_packet(translated, translated_len, msg);
}
+int get_transport_checksum(const uint8_t *packet) {
+ struct iphdr *ip;
+ struct ip6_hdr *ip6;
+ uint8_t protocol;
+ const void *payload;
+
+ int version = ip_version(packet);
+ switch (version) {
+ case 4:
+ ip = (struct iphdr *) packet;
+ if (is_ipv4_fragment(ip)) {
+ return -1;
+ }
+ protocol = ip->protocol;
+ payload = ip + 1;
+ break;
+ case 6:
+ ip6 = (struct ip6_hdr *) packet;
+ protocol = ip6->ip6_nxt;
+ payload = ip6 + 1;
+ break;
+ default:
+ return -1;
+ }
+
+ switch (protocol) {
+ case IPPROTO_UDP:
+ return ((struct udphdr *) payload)->check;
+
+ case IPPROTO_TCP:
+ return ((struct tcphdr *) payload)->check;
+
+ case IPPROTO_FRAGMENT:
+ default:
+ return -1;
+ }
+}
+
struct clat_config Global_Clatd_Config;
class ClatdTest : public ::testing::Test {
@@ -522,10 +564,126 @@
inet_pton(AF_INET, kIPv4LocalAddr, &Global_Clatd_Config.ipv4_local_subnet);
inet_pton(AF_INET6, kIPv6PlatSubnet, &Global_Clatd_Config.plat_subnet);
inet_pton(AF_INET6, kIPv6LocalAddr, &Global_Clatd_Config.ipv6_local_subnet);
+ Global_Clatd_Config.ipv6_host_id = in6addr_any;
}
};
-TEST_F(ClatdTest, Sanitycheck) {
+void expect_ipv6_addr_equal(struct in6_addr *expected, struct in6_addr *actual) {
+ if (!IN6_ARE_ADDR_EQUAL(expected, actual)) {
+ char expected_str[INET6_ADDRSTRLEN], actual_str[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, expected, expected_str, sizeof(expected_str));
+ inet_ntop(AF_INET6, actual, actual_str, sizeof(actual_str));
+ FAIL()
+ << "Unexpected IPv6 address:: "
+ << "\n Expected: " << expected_str
+ << "\n Actual: " << actual_str
+ << "\n";
+ }
+}
+
+TEST_F(ClatdTest, TestIPv6PrefixEqual) {
+ EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet,
+ &Global_Clatd_Config.plat_subnet));
+ EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.plat_subnet,
+ &Global_Clatd_Config.ipv6_local_subnet));
+
+ struct in6_addr subnet2 = Global_Clatd_Config.ipv6_local_subnet;
+ EXPECT_TRUE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2));
+ EXPECT_TRUE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet));
+
+ subnet2.s6_addr[6] = 0xff;
+ EXPECT_FALSE(ipv6_prefix_equal(&Global_Clatd_Config.ipv6_local_subnet, &subnet2));
+ EXPECT_FALSE(ipv6_prefix_equal(&subnet2, &Global_Clatd_Config.ipv6_local_subnet));
+}
+
+int count_onebits(const void *data, size_t size) {
+ int onebits = 0;
+ for (size_t pos = 0; pos < size; pos++) {
+ uint8_t *byte = ((uint8_t*) data) + pos;
+ for (int shift = 0; shift < 8; shift++) {
+ onebits += (*byte >> shift) & 1;
+ }
+ }
+ return onebits;
+}
+
+TEST_F(ClatdTest, TestCountOnebits) {
+ uint64_t i;
+ i = 1;
+ ASSERT_EQ(1, count_onebits(&i, sizeof(i)));
+ i <<= 61;
+ ASSERT_EQ(1, count_onebits(&i, sizeof(i)));
+ i |= ((uint64_t) 1 << 33);
+ ASSERT_EQ(2, count_onebits(&i, sizeof(i)));
+ i = 0xf1000202020000f0;
+ ASSERT_EQ(5 + 1 + 1 + 1 + 4, count_onebits(&i, sizeof(i)));
+}
+
+TEST_F(ClatdTest, TestGenIIDConfigured) {
+ struct in6_addr myaddr, expected;
+ ASSERT_TRUE(inet_pton(AF_INET6, "::bad:ace:d00d", &Global_Clatd_Config.ipv6_host_id));
+ ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:0:bad:ace:d00d", &expected));
+ ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", &myaddr));
+ config_generate_local_ipv6_subnet(&myaddr);
+ expect_ipv6_addr_equal(&expected, &myaddr);
+}
+
+TEST_F(ClatdTest, TestGenIIDRandom) {
+ struct in6_addr interface_ipv6;
+ ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54", &interface_ipv6));
+ Global_Clatd_Config.ipv6_host_id = in6addr_any;
+
+ // Generate a boatload of random IIDs.
+ int onebits = 0;
+ uint64_t prev_iid = 0;
+ for (int i = 0; i < 100000; i++) {
+ struct in6_addr myaddr = interface_ipv6;
+
+ config_generate_local_ipv6_subnet(&myaddr);
+
+ // Check the generated IP address is in the same prefix as the interface IPv6 address.
+ EXPECT_TRUE(ipv6_prefix_equal(&interface_ipv6, &myaddr));
+
+ // Check that consecutive IIDs are not the same.
+ uint64_t iid = * (uint64_t*) (&myaddr.s6_addr[8]);
+ ASSERT_TRUE(iid != prev_iid)
+ << "Two consecutive random IIDs are the same: "
+ << std::showbase << std::hex
+ << iid << "\n";
+ prev_iid = iid;
+
+ // Check that the IID is checksum-neutral with the NAT64 prefix and the
+ // local prefix.
+ struct in_addr *ipv4addr = &Global_Clatd_Config.ipv4_local_subnet;
+ struct in6_addr *plat_subnet = &Global_Clatd_Config.plat_subnet;
+
+ uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, ipv4addr, sizeof(*ipv4addr)));
+ uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) +
+ ip_checksum_add(0, &myaddr, sizeof(myaddr)));
+
+ if (c1 != c2) {
+ char myaddr_str[INET6_ADDRSTRLEN], plat_str[INET6_ADDRSTRLEN], ipv4_str[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &myaddr, myaddr_str, sizeof(myaddr_str));
+ inet_ntop(AF_INET6, plat_subnet, plat_str, sizeof(plat_str));
+ inet_ntop(AF_INET, ipv4addr, ipv4_str, sizeof(ipv4_str));
+ FAIL()
+ << "Bad IID: " << myaddr_str
+ << " not checksum-neutral with " << ipv4_str << " and " << plat_str
+ << std::showbase << std::hex
+ << "\n IPv4 checksum: " << c1
+ << "\n IPv6 checksum: " << c2
+ << "\n";
+ }
+
+ // Check that IIDs are roughly random and use all the bits by counting the
+ // total number of bits set to 1 in a random sample of 100000 generated IIDs.
+ onebits += count_onebits(&iid, sizeof(iid));
+ }
+ EXPECT_LE(3190000, onebits);
+ EXPECT_GE(3210000, onebits);
+}
+
+TEST_F(ClatdTest, DataSanitycheck) {
// Sanity checks the data.
uint8_t v4_header[] = { IPV4_UDP_HEADER };
ASSERT_EQ(sizeof(struct iphdr), sizeof(v4_header)) << "Test IPv4 header: incorrect length\n";
@@ -681,3 +839,43 @@
kIPv4Fragments, kIPv4FragLengths,
ARRAYSIZE(kIPv6Fragments), "IPv6->IPv4 fragment translation");
}
+
+void check_translate_checksum_neutral(const uint8_t *original, size_t original_len,
+ size_t expected_len, const char *msg) {
+ uint8_t translated[MAXMTU];
+ size_t translated_len = sizeof(translated);
+ do_translate_packet(original, original_len, translated, &translated_len, msg);
+ EXPECT_EQ(expected_len, translated_len) << msg << ": Translated packet length incorrect\n";
+ // do_translate_packet already checks packets for validity and verifies the checksum.
+ int original_check = get_transport_checksum(original);
+ int translated_check = get_transport_checksum(translated);
+ ASSERT_NE(-1, original_check);
+ ASSERT_NE(-1, translated_check);
+ ASSERT_EQ(original_check, translated_check)
+ << "Not checksum neutral: original and translated checksums differ\n";
+}
+
+TEST_F(ClatdTest, TranslateChecksumNeutral) {
+ // Generate a random clat IPv6 address and check that translation is checksum-neutral.
+ Global_Clatd_Config.ipv6_host_id = in6addr_any;
+ ASSERT_TRUE(inet_pton(AF_INET6, "2001:db8:1:2:f076:ae99:124e:aa54",
+ &Global_Clatd_Config.ipv6_local_subnet));
+ config_generate_local_ipv6_subnet(&Global_Clatd_Config.ipv6_local_subnet);
+ ASSERT_NE((uint32_t) 0x00000464, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+ ASSERT_NE((uint32_t) 0, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+
+ // Check that translating UDP packets is checksum-neutral. First, IPv4.
+ uint8_t udp_ipv4[] = { IPV4_UDP_HEADER UDP_HEADER PAYLOAD };
+ fix_udp_checksum(udp_ipv4);
+ check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20,
+ "UDP/IPv4 -> UDP/IPv6 checksum neutral");
+
+ // Now try IPv6.
+ uint8_t udp_ipv6[] = { IPV6_UDP_HEADER UDP_HEADER PAYLOAD };
+ // The test packet uses the static IID, not the random IID. Fix up the source address.
+ struct ip6_hdr *ip6 = (struct ip6_hdr *) udp_ipv6;
+ memcpy(&ip6->ip6_src, &Global_Clatd_Config.ipv6_local_subnet, sizeof(ip6->ip6_src));
+ fix_udp_checksum(udp_ipv6);
+ check_translate_checksum_neutral(udp_ipv4, sizeof(udp_ipv4), sizeof(udp_ipv4) + 20,
+ "UDP/IPv4 -> UDP/IPv6 checksum neutral");
+}
diff --git a/config.c b/config.c
index 623a1b0..09d3df0 100644
--- a/config.c
+++ b/config.c
@@ -31,6 +31,7 @@
#include "logging.h"
#include "getaddr.h"
#include "clatd.h"
+#include "checksum.h"
struct clat_config Global_Clatd_Config;
@@ -149,6 +150,16 @@
}
}
+/* function: ipv6_prefix_equal
+ * compares the prefixes two ipv6 addresses. assumes the prefix lengths are both /64.
+ * a1 - first address
+ * a2 - second address
+ * returns: 0 if the subnets are different, 1 if they are the same.
+ */
+int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2) {
+ return !memcmp(a1, a2, 8);
+}
+
/* function: dns64_detection
* does dns lookups to set the plat subnet or exits on failure, waits forever for a dns response with a query backoff timer
* net_id - (optional) netId to use, NETID_UNSET indicates use of default network
@@ -175,6 +186,28 @@
}
+void gen_random_iid(struct in6_addr *myaddr, struct in_addr *ipv4_local_subnet,
+ struct in6_addr *plat_subnet) {
+ // Fill last 8 bytes of IPv6 address with random bits.
+ arc4random_buf(&myaddr->s6_addr[8], 8);
+
+ // Make the IID checksum-neutral. That is, make it so that:
+ // checksum(Local IPv4 | Remote IPv4) = checksum(Local IPv6 | Remote IPv6)
+ // in other words (because remote IPv6 = NAT64 prefix | Remote IPv4):
+ // checksum(Local IPv4) = checksum(Local IPv6 | NAT64 prefix)
+ // Do this by adjusting the two bytes in the middle of the IID.
+
+ uint16_t middlebytes = (myaddr->s6_addr[11] << 8) + myaddr->s6_addr[12];
+
+ uint32_t c1 = ip_checksum_add(0, ipv4_local_subnet, sizeof(*ipv4_local_subnet));
+ uint32_t c2 = ip_checksum_add(0, plat_subnet, sizeof(*plat_subnet)) +
+ ip_checksum_add(0, myaddr, sizeof(*myaddr));
+
+ uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2);
+ myaddr->s6_addr[11] = delta >> 8;
+ myaddr->s6_addr[12] = delta & 0xff;
+}
+
/* function: config_generate_local_ipv6_subnet
* generates the local ipv6 subnet when given the interface ip
* requires config.ipv6_host_id
@@ -183,8 +216,16 @@
void config_generate_local_ipv6_subnet(struct in6_addr *interface_ip) {
int i;
- for(i = 2; i < 4; i++) {
- interface_ip->s6_addr32[i] = Global_Clatd_Config.ipv6_host_id.s6_addr32[i];
+ if (IN6_IS_ADDR_UNSPECIFIED(&Global_Clatd_Config.ipv6_host_id)) {
+ /* Generate a random interface ID. */
+ gen_random_iid(interface_ip,
+ &Global_Clatd_Config.ipv4_local_subnet,
+ &Global_Clatd_Config.plat_subnet);
+ } else {
+ /* Use the specified interface ID. */
+ for(i = 2; i < 4; i++) {
+ interface_ip->s6_addr32[i] = Global_Clatd_Config.ipv6_host_id.s6_addr32[i];
+ }
}
}
@@ -195,10 +236,12 @@
*/
int subnet_from_interface(cnode *root, const char *interface) {
union anyip *interface_ip;
+ char addrstr[INET6_ADDRSTRLEN];
- if(!config_item_ip6(root, "ipv6_host_id", "::464", &Global_Clatd_Config.ipv6_host_id))
+ if(!config_item_ip6(root, "ipv6_host_id", "::", &Global_Clatd_Config.ipv6_host_id))
return 0;
+ // TODO: check that the prefix length is /64.
interface_ip = getinterface_ip(interface, AF_INET6);
if(!interface_ip) {
logmsg(ANDROID_LOG_FATAL,"unable to find an ipv6 ip on interface %s",interface);
@@ -210,6 +253,9 @@
config_generate_local_ipv6_subnet(&Global_Clatd_Config.ipv6_local_subnet);
+ inet_ntop(AF_INET6, &Global_Clatd_Config.ipv6_local_subnet, addrstr, sizeof(addrstr));
+ logmsg(ANDROID_LOG_INFO, "Using %s on %s", addrstr, interface);
+
return 1;
}
@@ -240,9 +286,6 @@
strncpy(Global_Clatd_Config.default_pdp_interface, uplink_interface, sizeof(Global_Clatd_Config.default_pdp_interface));
- if(!subnet_from_interface(root,Global_Clatd_Config.default_pdp_interface))
- goto failed;
-
if(!config_item_int16_t(root, "mtu", "-1", &Global_Clatd_Config.mtu))
goto failed;
@@ -275,6 +318,9 @@
}
}
+ if(!subnet_from_interface(root,Global_Clatd_Config.default_pdp_interface))
+ goto failed;
+
return 1;
diff --git a/config.h b/config.h
index da7446e..654e909 100644
--- a/config.h
+++ b/config.h
@@ -40,5 +40,6 @@
int read_config(const char *file, const char *uplink_interface, const char *plat_prefix,
unsigned net_id);
void config_generate_local_ipv6_subnet(struct in6_addr *interface_ip);
+int ipv6_prefix_equal(struct in6_addr *a1, struct in6_addr *a2);
#endif /* __CONFIG_H__ */