Allow passing in the IPv4 and IPv6 addresses on the command line.
This allows the address to be determined by netd and passed in,
which makes it possible for the rest of the OS to know what the
clatd IPv6 address is.
Bug: 65674744
Test: atest clatd_test
Test: IPv4 on IPv6-only networks continues to work
Test: clatd continues to avoid existing IPv4 addresses in 192.0.0.0/29
Test: passing NAT64 prefix, IPv4/IPv6 addresses on command line works
Change-Id: I8519c2f01b44022ef036c80aa0df32cd76003055
diff --git a/clatd.c b/clatd.c
index bcbd9c5..3ccceea 100644
--- a/clatd.c
+++ b/clatd.c
@@ -110,13 +110,11 @@
return 1;
}
-/* function: configure_tun_ip
- * configures the ipv4 and ipv6 addresses on the tunnel interface
- * tunnel - tun device data
+/* function: ipv4_address_generate
+ * picks a free IPv4 address from the local subnet or exits if there are no free addresses
+ * returns: the IPv4 address as an in_addr_t
*/
-void configure_tun_ip(const struct tun_data *tunnel) {
- int status;
-
+static in_addr_t ipv4_address_generate() {
// Pick an IPv4 address to use by finding a free address in the configured prefix. Technically,
// there is a race here - if another clatd calls config_select_ipv4_address after we do, but
// before we call add_address, it can end up having the same IP address as we do. But the time
@@ -131,22 +129,49 @@
Global_Clatd_Config.ipv4_local_prefixlen);
exit(1);
}
- Global_Clatd_Config.ipv4_local_subnet.s_addr = localaddr;
+ return localaddr;
+}
- // Configure the interface before bringing it up. As soon as we bring the interface up, the
- // framework will be notified and will assume the interface's configuration has been finalized.
- status = add_address(tunnel->device4, AF_INET, &Global_Clatd_Config.ipv4_local_subnet, 32,
- &Global_Clatd_Config.ipv4_local_subnet);
- if (status < 0) {
- logmsg(ANDROID_LOG_FATAL, "configure_tun_ip/if_address(4) failed: %s", strerror(-status));
+/* function: ipv4_address_from_cmdline
+ * configures the IPv4 address specified on the command line, or exits if the address is not valid
+ * v4_addr - a string, the IPv4 address
+ * returns: the IPv4 address as an in_addr_t
+ */
+static in_addr_t ipv4_address_from_cmdline(const char *v4_addr) {
+ in_addr_t localaddr;
+ if (!inet_pton(AF_INET, v4_addr, &localaddr)) {
+ logmsg(ANDROID_LOG_FATAL, "Invalid IPv4 address %s", v4_addr);
exit(1);
}
+ return localaddr;
+}
+
+/* function: configure_tun_ip
+ * configures the ipv4 and ipv6 addresses on the tunnel interface
+ * tunnel - tun device data
+ */
+void configure_tun_ip(const struct tun_data *tunnel, const char *v4_addr) {
+ if (v4_addr) {
+ Global_Clatd_Config.ipv4_local_subnet.s_addr = ipv4_address_from_cmdline(v4_addr);
+ } else {
+ Global_Clatd_Config.ipv4_local_subnet.s_addr = ipv4_address_generate();
+ }
char addrstr[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &Global_Clatd_Config.ipv4_local_subnet, addrstr, sizeof(addrstr));
logmsg(ANDROID_LOG_INFO, "Using IPv4 address %s on %s", addrstr, tunnel->device4);
- if ((status = if_up(tunnel->device4, Global_Clatd_Config.ipv4mtu)) < 0) {
+ // Configure the interface before bringing it up. As soon as we bring the interface up, the
+ // framework will be notified and will assume the interface's configuration has been finalized.
+ int status = add_address(tunnel->device4, AF_INET, &Global_Clatd_Config.ipv4_local_subnet, 32,
+ &Global_Clatd_Config.ipv4_local_subnet);
+ if (status < 0) {
+ logmsg(ANDROID_LOG_FATAL, "configure_tun_ip/if_address(4) failed: %s", strerror(-status));
+ exit(1);
+ }
+
+ status = if_up(tunnel->device4, Global_Clatd_Config.ipv4mtu);
+ if (status < 0) {
logmsg(ANDROID_LOG_FATAL, "configure_tun_ip/if_up(4) failed: %s", strerror(-status));
exit(1);
}
@@ -251,15 +276,13 @@
}
}
-/* function: configure_clat_ipv6_address
- * picks the clat IPv6 address and configures packet translation to use it.
- * tunnel - tun device data
+/* function: clat_ipv6_address_from_interface
+ * picks the clat IPv6 address based on the interface address
* interface - uplink interface name
* returns: 1 on success, 0 on failure
*/
-int configure_clat_ipv6_address(const struct tun_data *tunnel, const char *interface) {
+static int clat_ipv6_address_from_interface(const char *interface) {
union anyip *interface_ip;
- char addrstr[INET6_ADDRSTRLEN];
// TODO: check that the prefix length is /64.
interface_ip = getinterface_ip(interface, AF_INET6);
@@ -270,13 +293,48 @@
// Generate an interface ID.
config_generate_local_ipv6_subnet(&interface_ip->ip6);
- inet_ntop(AF_INET6, &interface_ip->ip6, addrstr, sizeof(addrstr));
+
+ Global_Clatd_Config.ipv6_local_subnet = interface_ip->ip6;
+ free(interface_ip);
+ return 1;
+}
+
+/* function: clat_ipv6_address_from_cmdline
+ * parses the clat IPv6 address from the command line
+ * v4_addr - a string, the IPv6 address
+ * returns: 1 on success, 0 on failure
+ */
+static int clat_ipv6_address_from_cmdline(const char *v6_addr) {
+ if (!inet_pton(AF_INET6, v6_addr, &Global_Clatd_Config.ipv6_local_subnet)) {
+ logmsg(ANDROID_LOG_FATAL, "Invalid source address %s", v6_addr);
+ return 0;
+ }
+
+ return 1;
+}
+
+/* function: configure_clat_ipv6_address
+ * picks the clat IPv6 address and configures packet translation to use it.
+ * tunnel - tun device data
+ * interface - uplink interface name
+ * returns: 1 on success, 0 on failure
+ */
+int configure_clat_ipv6_address(const struct tun_data *tunnel, const char *interface,
+ const char *v6_addr) {
+ int ret;
+ if (v6_addr) {
+ ret = clat_ipv6_address_from_cmdline(v6_addr);
+ } else {
+ ret = clat_ipv6_address_from_interface(interface);
+ }
+ if (!ret) return 0;
+
+ char addrstr[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &Global_Clatd_Config.ipv6_local_subnet, addrstr, sizeof(addrstr));
logmsg(ANDROID_LOG_INFO, "Using IPv6 address %s on %s", addrstr, interface);
// Start translating packets to the new prefix.
- Global_Clatd_Config.ipv6_local_subnet = interface_ip->ip6;
add_anycast_address(tunnel->write_fd6, &Global_Clatd_Config.ipv6_local_subnet, interface);
- free(interface_ip);
// Update our packet socket filter to reflect the new 464xlat IP address.
if (!configure_packet_socket(tunnel->read_fd6)) {
@@ -295,8 +353,8 @@
* tunnel - tun device data
* net_id - NetID to use, NETID_UNSET indicates use of default network
*/
-void configure_interface(const char *uplink_interface, const char *plat_prefix,
- struct tun_data *tunnel, unsigned net_id) {
+void configure_interface(const char *uplink_interface, const char *plat_prefix, const char *v4_addr,
+ const char *v6_addr, struct tun_data *tunnel, unsigned net_id) {
int error;
if (!read_config("/system/etc/clatd.conf", uplink_interface, plat_prefix, net_id)) {
@@ -335,9 +393,9 @@
exit(1);
}
- configure_tun_ip(tunnel);
+ configure_tun_ip(tunnel, v4_addr);
- if (!configure_clat_ipv6_address(tunnel, uplink_interface)) {
+ if (!configure_clat_ipv6_address(tunnel, uplink_interface, v6_addr)) {
exit(1);
}
}
diff --git a/clatd.h b/clatd.h
index 67ba990..f7f7315 100644
--- a/clatd.h
+++ b/clatd.h
@@ -35,13 +35,15 @@
#define NO_TRAFFIC_INTERFACE_POLL_FREQUENCY 90
void stop_loop();
+void configure_tun_ip(const struct tun_data *tunnel, const char *v4_addr);
void set_capability(uint64_t target_cap);
void drop_root_but_keep_caps();
void open_sockets(struct tun_data *tunnel, uint32_t mark);
int ipv6_address_changed(const char *interface);
-int configure_clat_ipv6_address(const struct tun_data *tunnel, const char *interface);
-void configure_interface(const char *uplink_interface, const char *plat_prefix,
- struct tun_data *tunnel, unsigned net_id);
+int configure_clat_ipv6_address(const struct tun_data *tunnel, const char *interface,
+ const char *src_addr);
+void configure_interface(const char *uplink_interface, const char *plat_prefix, const char *v4_addr,
+ const char *v6, struct tun_data *tunnel, unsigned net_id);
void event_loop(struct tun_data *tunnel);
int parse_unsigned(const char *str, unsigned *out);
diff --git a/clatd_test.cpp b/clatd_test.cpp
index 81af41d..7705a36 100644
--- a/clatd_test.cpp
+++ b/clatd_test.cpp
@@ -294,8 +294,8 @@
void reassemble_packet(const uint8_t **fragments, const size_t lengths[], int numpackets,
uint8_t *reassembled, size_t *reassembled_len, const char *msg) {
- struct iphdr *ip = NULL;
- struct ip6_hdr *ip6 = NULL;
+ struct iphdr *ip = nullptr;
+ struct ip6_hdr *ip6 = nullptr;
size_t total_length, pos = 0;
uint8_t protocol = 0;
uint8_t version = ip_version(fragments[0]);
@@ -560,6 +560,21 @@
}
}
+static tun_data makeTunData() {
+ // Create some fake but realistic-looking sockets so update_clat_ipv6_address doesn't balk.
+ return {
+ .write_fd6 = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW),
+ .read_fd6 = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)),
+ .fd4 = socket(AF_UNIX, SOCK_DGRAM, 0),
+ };
+}
+
+void freeTunData(tun_data *tunnel) {
+ close(tunnel->write_fd6);
+ close(tunnel->read_fd6);
+ close(tunnel->fd4);
+}
+
struct clat_config Global_Clatd_Config;
class ClatdTest : public ::testing::Test {
@@ -757,6 +772,55 @@
EXPECT_EQ(inet_addr("127.0.0.2"), config_select_ipv4_address(&addr, 29));
}
+TEST_F(ClatdTest, ConfigureTunIp) {
+ addr_free_func orig_config_is_ipv4_address_free = config_is_ipv4_address_free;
+ config_is_ipv4_address_free = over6_free;
+
+ Global_Clatd_Config.ipv4_local_prefixlen = 29;
+ Global_Clatd_Config.ipv4mtu = 1472;
+
+ // Create an interface for configure_tun_ip to configure and bring up.
+ TunInterface v4Iface;
+ ASSERT_EQ(0, v4Iface.init());
+ struct tun_data tunnel = makeTunData();
+ strlcpy(tunnel.device4, v4Iface.name().c_str(), sizeof(tunnel.device4));
+
+ configure_tun_ip(&tunnel, nullptr /* v4_addr */);
+ EXPECT_EQ(inet_addr("192.0.0.6"), Global_Clatd_Config.ipv4_local_subnet.s_addr);
+
+ union anyip *ip = getinterface_ip(v4Iface.name().c_str(), AF_INET);
+ EXPECT_EQ(inet_addr("192.0.0.6"), ip->ip4.s_addr);
+ free(ip);
+
+ config_is_ipv4_address_free = orig_config_is_ipv4_address_free;
+ v4Iface.destroy();
+}
+
+TEST_F(ClatdTest, ConfigureTunIpManual) {
+ addr_free_func orig_config_is_ipv4_address_free = config_is_ipv4_address_free;
+ config_is_ipv4_address_free = over6_free;
+
+ Global_Clatd_Config.ipv4_local_prefixlen = 29;
+ Global_Clatd_Config.ipv4mtu = 1472;
+
+ // Create an interface for configure_tun_ip to configure and bring up.
+ TunInterface v4Iface;
+ ASSERT_EQ(0, v4Iface.init());
+ struct tun_data tunnel = makeTunData();
+ strlcpy(tunnel.device4, v4Iface.name().c_str(), sizeof(tunnel.device4));
+
+ configure_tun_ip(&tunnel, "192.0.2.1" /* v4_addr */);
+ EXPECT_EQ(inet_addr("192.0.2.1"), Global_Clatd_Config.ipv4_local_subnet.s_addr);
+
+ union anyip *ip = getinterface_ip(v4Iface.name().c_str(), AF_INET);
+ ASSERT_NE(nullptr, ip);
+ EXPECT_EQ(inet_addr("192.0.2.1"), ip->ip4.s_addr);
+ free(ip);
+
+ config_is_ipv4_address_free = orig_config_is_ipv4_address_free;
+ v4Iface.destroy();
+}
+
TEST_F(ClatdTest, DataSanitycheck) {
// Sanity checks the data.
uint8_t v4_header[] = { IPV4_UDP_HEADER };
@@ -965,16 +1029,22 @@
expect_ipv6_addr_equal(&expected, &actual);
}
+void expectSocketBound(int ifindex, int sock) {
+ // Check that the packet socket is bound to the interface. We can't check the socket filter
+ // because there is no way to fetch it from the kernel.
+ sockaddr_ll sll;
+ socklen_t len = sizeof(sll);
+ ASSERT_EQ(0, getsockname(sock, reinterpret_cast<sockaddr *>(&sll), &len));
+ EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
+ EXPECT_EQ(ifindex, sll.sll_ifindex);
+}
+
TEST_F(ClatdTest, ConfigureIpv6Address) {
- // Create some fake but realistic-looking sockets so update_clat_ipv6_address doesn't balk.
- struct tun_data tunnel = {
- .write_fd6 = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW),
- .read_fd6 = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)),
- };
+ struct tun_data tunnel = makeTunData();
// Run configure_clat_ipv6_address.
ASSERT_TRUE(IN6_IS_ADDR_UNSPECIFIED(&Global_Clatd_Config.ipv6_local_subnet));
- ASSERT_EQ(1, configure_clat_ipv6_address(&tunnel, sTun.name().c_str()));
+ ASSERT_EQ(1, configure_clat_ipv6_address(&tunnel, sTun.name().c_str(), nullptr /* v6_addr */));
// Check that it generated an IID in the same prefix as the address assigned to the interface,
// and that the IID is not the default IID.
@@ -984,6 +1054,26 @@
EXPECT_NE(htonl((uint32_t)0x00000464), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
EXPECT_NE((uint32_t)0, Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+ expectSocketBound(sTun.ifindex(), tunnel.read_fd6);
+
+ freeTunData(&tunnel);
+}
+
+TEST_F(ClatdTest, ConfigureIpv6AddressCommandLine) {
+ struct tun_data tunnel = makeTunData();
+
+ ASSERT_TRUE(IN6_IS_ADDR_UNSPECIFIED(&Global_Clatd_Config.ipv6_local_subnet));
+
+ const char *addrStr = "2001:db8::f00";
+ in6_addr addr;
+ ASSERT_EQ(1, inet_pton(AF_INET6, addrStr, &addr));
+ ASSERT_EQ(1, configure_clat_ipv6_address(&tunnel, sTun.name().c_str(), addrStr));
+
+ EXPECT_EQ(htonl(0x20010db8), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[0]);
+ EXPECT_EQ(htonl(0x00000000), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[1]);
+ EXPECT_EQ(htonl(0x00000000), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[2]);
+ EXPECT_EQ(htonl(0x00000f00), Global_Clatd_Config.ipv6_local_subnet.s6_addr32[3]);
+
// Check that the packet socket is bound to the interface. We can't check the socket filter
// because there is no way to fetch it from the kernel.
sockaddr_ll sll;
@@ -991,6 +1081,10 @@
ASSERT_EQ(0, getsockname(tunnel.read_fd6, reinterpret_cast<sockaddr *>(&sll), &len));
EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol);
EXPECT_EQ(sll.sll_ifindex, sTun.ifindex());
+
+ expectSocketBound(sTun.ifindex(), tunnel.read_fd6);
+
+ freeTunData(&tunnel);
}
TEST_F(ClatdTest, Ipv6AddressChanged) {
@@ -1000,7 +1094,7 @@
.read_fd6 = socket(AF_PACKET, SOCK_DGRAM, htons(ETH_P_IPV6)),
};
const char *ifname = sTun.name().c_str();
- ASSERT_EQ(1, configure_clat_ipv6_address(&tunnel, ifname));
+ ASSERT_EQ(1, configure_clat_ipv6_address(&tunnel, ifname, nullptr));
EXPECT_EQ(0, ipv6_address_changed(ifname));
EXPECT_EQ(0, ipv6_address_changed(ifname));
diff --git a/main.c b/main.c
index 11c51a5..54d12d1 100644
--- a/main.c
+++ b/main.c
@@ -42,6 +42,8 @@
printf("android-clat arguments:\n");
printf("-i [uplink interface]\n");
printf("-p [plat prefix]\n");
+ printf("-4 [IPv4 address]\n");
+ printf("-6 [IPv6 address]\n");
printf("-n [NetId]\n");
printf("-m [socket mark]\n");
}
@@ -53,11 +55,12 @@
struct tun_data tunnel;
int opt;
char *uplink_interface = NULL, *plat_prefix = NULL, *net_id_str = NULL, *mark_str = NULL;
+ char *v4_addr = NULL, *v6_addr = NULL;
unsigned net_id = NETID_UNSET;
uint32_t mark = MARK_UNSET;
unsigned len;
- while ((opt = getopt(argc, argv, "i:p:n:m:h")) != -1) {
+ while ((opt = getopt(argc, argv, "i:p:4:6:n:m:h")) != -1) {
switch (opt) {
case 'i':
uplink_interface = optarg;
@@ -65,6 +68,12 @@
case 'p':
plat_prefix = optarg;
break;
+ case '4':
+ v4_addr = optarg;
+ break;
+ case '6':
+ v6_addr = optarg;
+ break;
case 'n':
net_id_str = optarg;
break;
@@ -101,8 +110,10 @@
exit(1);
}
- logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s netid=%s mark=%s", CLATD_VERSION,
- uplink_interface, net_id_str ? net_id_str : "(none)", mark_str ? mark_str : "(none)");
+ logmsg(ANDROID_LOG_INFO, "Starting clat version %s on %s netid=%s mark=%s plat=%s v4=%s v6=%s",
+ CLATD_VERSION, uplink_interface, net_id_str ? net_id_str : "(none)",
+ mark_str ? mark_str : "(none)", plat_prefix ? plat_prefix : "(none)",
+ v4_addr ? v4_addr : "(none)", v6_addr ? v6_addr : "(none)");
// run under a regular user but keep needed capabilities
drop_root_but_keep_caps();
@@ -125,7 +136,7 @@
// following line causes XLAT failure in permissive mode.
unsetenv("ANDROID_DNS_MODE");
- configure_interface(uplink_interface, plat_prefix, &tunnel, net_id);
+ configure_interface(uplink_interface, plat_prefix, v4_addr, v6_addr, &tunnel, net_id);
// Drop all remaining capabilities.
set_capability(0);