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
This commit is contained in:
Lorenzo Colitti 2014-10-09 22:29:45 +09:00
commit 9808952bdf
7 changed files with 270 additions and 20 deletions

View file

@ -30,7 +30,7 @@ include $(CLEAR_VARS)
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

View file

@ -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);

View file

@ -164,16 +164,16 @@ void interface_poll(const struct tun_data *tunnel) {
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)) {

View file

@ -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

View file

@ -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 @@ void reassemble_packet(const uint8_t **fragments, const size_t lengths[], int nu
*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 @@ void check_data_matches(const uint8_t *expected, const uint8_t *actual, size_t l
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 @@ void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *
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 @@ void do_translate_packet(const uint8_t *original, size_t original_len, uint8_t *
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 @@ void check_fragment_translation(const uint8_t *original[], const size_t original
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 @@ class ClatdTest : public ::testing::Test {
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 @@ TEST_F(ClatdTest, Fragmentation) {
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");
}

View file

@ -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 @@ void free_config() {
}
}
/* 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 dns64_detection(unsigned net_id) {
}
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 dns64_detection(unsigned net_id) {
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 @@ void config_generate_local_ipv6_subnet(struct in6_addr *interface_ip) {
*/
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 @@ int subnet_from_interface(cnode *root, const char *interface) {
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 @@ int read_config(const char *file, const char *uplink_interface, const char *plat
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 @@ int read_config(const char *file, const char *uplink_interface, const char *plat
}
}
if(!subnet_from_interface(root,Global_Clatd_Config.default_pdp_interface))
goto failed;
return 1;

View file

@ -40,5 +40,6 @@ extern struct clat_config Global_Clatd_Config;
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__ */