From ed7b4602b53035d8db09628e2c0df6422e1432f6 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 17 Dec 2021 15:03:47 +0800 Subject: [PATCH 01/16] [CLATJ#1] libclat: move makeChecksumNeutral() from netd to apex Introduce the library libclat to implement clat functions for jni. It is helpful for unit test. makeChecksumNeutral() and its test are moved from ClatdController without behavior change. Bug: 212345928 Test: build and boot atest libclat_test Change-Id: I1c0981d98141fc1cc07b2d3a0f3cbddf38683ff3 --- service/native/libs/libclat/Android.bp | 42 +++++++++ service/native/libs/libclat/clatutils.cpp | 51 ++++++++++ .../native/libs/libclat/clatutils_test.cpp | 93 +++++++++++++++++++ .../libs/libclat/include/libclat/clatutils.h | 26 ++++++ 4 files changed, 212 insertions(+) create mode 100644 service/native/libs/libclat/Android.bp create mode 100644 service/native/libs/libclat/clatutils.cpp create mode 100644 service/native/libs/libclat/clatutils_test.cpp create mode 100644 service/native/libs/libclat/include/libclat/clatutils.h diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp new file mode 100644 index 0000000000..a45ffa6153 --- /dev/null +++ b/service/native/libs/libclat/Android.bp @@ -0,0 +1,42 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_static { + name: "libclat", + defaults: ["netd_defaults"], + srcs: ["clatutils.cpp"], + stl: "libc++_static", + static_libs: ["libip_checksum"], + export_include_dirs: ["include"], + min_sdk_version: "30", + apex_available: ["com.android.tethering"], +} + +cc_test { + name: "libclat_test", + defaults: ["netd_defaults"], + test_suites: ["device-tests"], + srcs: [ + "clatutils_test.cpp", + ], + static_libs: [ + "libbase", + "libclat", + "libip_checksum", + ], +} \ No newline at end of file diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp new file mode 100644 index 0000000000..2a53ba8079 --- /dev/null +++ b/service/native/libs/libclat/clatutils.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "libclat/clatutils.h" + +#include + +extern "C" { +#include "checksum.h" +} + +namespace android { +namespace net { +namespace clat { + +// Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix. +void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) { + // Fill last 8 bytes of IPv6 address with random bits. + arc4random_buf(&v6->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 = (v6->s6_addr[11] << 8) + v6->s6_addr[12]; + + uint32_t c1 = ip_checksum_add(0, &v4, sizeof(v4)); + uint32_t c2 = ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) + + ip_checksum_add(0, v6, sizeof(*v6)); + + uint16_t delta = ip_checksum_adjust(middlebytes, c1, c2); + v6->s6_addr[11] = delta >> 8; + v6->s6_addr[12] = delta & 0xff; +} + +} // namespace clat +} // namespace net +} // namespace android diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp new file mode 100644 index 0000000000..1900abf5e2 --- /dev/null +++ b/service/native/libs/libclat/clatutils_test.cpp @@ -0,0 +1,93 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "libclat/clatutils.h" + +#include +#include +#include + +extern "C" { +#include "checksum.h" +} + +// Default translation parameters. +static const char kIPv4LocalAddr[] = "192.0.0.4"; + +namespace android { +namespace net { +namespace clat { + +using base::StringPrintf; + +class ClatUtils : public ::testing::Test {}; + +TEST_F(ClatUtils, MakeChecksumNeutral) { + // We can't test generateIPv6Address here since it requires manipulating routing, which we can't + // do without talking to the real netd on the system. + uint32_t rand = arc4random_uniform(0xffffffff); + uint16_t rand1 = rand & 0xffff; + uint16_t rand2 = (rand >> 16) & 0xffff; + std::string v6PrefixStr = StringPrintf("2001:db8:%x:%x", rand1, rand2); + std::string v6InterfaceAddrStr = StringPrintf("%s::%x:%x", v6PrefixStr.c_str(), rand2, rand1); + std::string nat64PrefixStr = StringPrintf("2001:db8:%x:%x::", rand2, rand1); + + in_addr v4 = {inet_addr(kIPv4LocalAddr)}; + in6_addr v6InterfaceAddr; + ASSERT_TRUE(inet_pton(AF_INET6, v6InterfaceAddrStr.c_str(), &v6InterfaceAddr)); + in6_addr nat64Prefix; + ASSERT_TRUE(inet_pton(AF_INET6, nat64PrefixStr.c_str(), &nat64Prefix)); + + // Generate a boatload of random IIDs. + int onebits = 0; + uint64_t prev_iid = 0; + for (int i = 0; i < 100000; i++) { + in6_addr v6 = v6InterfaceAddr; + makeChecksumNeutral(&v6, v4, nat64Prefix); + + // Check the generated IP address is in the same prefix as the interface IPv6 address. + EXPECT_EQ(0, memcmp(&v6, &v6InterfaceAddr, 8)); + + // Check that consecutive IIDs are not the same. + uint64_t iid = *(uint64_t*)(&v6.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. + uint16_t c1 = ip_checksum_finish(ip_checksum_add(0, &v4, sizeof(v4))); + uint16_t c2 = ip_checksum_finish(ip_checksum_add(0, &nat64Prefix, sizeof(nat64Prefix)) + + ip_checksum_add(0, &v6, sizeof(v6))); + + if (c1 != c2) { + char v6Str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, &v6, v6Str, sizeof(v6Str)); + FAIL() << "Bad IID: " << v6Str << " not checksum-neutral with " << kIPv4LocalAddr + << " and " << nat64PrefixStr.c_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 += __builtin_popcountll(*(uint64_t*)&iid); + } + EXPECT_LE(3190000, onebits); + EXPECT_GE(3210000, onebits); +} + +} // namespace clat +} // namespace net +} // namespace android diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h new file mode 100644 index 0000000000..05c99df66d --- /dev/null +++ b/service/native/libs/libclat/include/libclat/clatutils.h @@ -0,0 +1,26 @@ +// Copyright (C) 2022 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once +#include +#include + +namespace android { +namespace net { +namespace clat { + +void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix); +} // namespace clat +} // namespace net +} // namespace android From aaa8bd8b749fa8b0963cc067318c1a5394d23076 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Mon, 20 Dec 2021 16:43:40 +0800 Subject: [PATCH 02/16] [CLATJ#2] libclat: Add libclat_test to postsubmit Add native library libclat test to postsubmit. Bug: 212345928 Test: atest libclat_test Change-Id: Ied5e0796b01c384b3eee2d40c0c735859709be33 --- TEST_MAPPING | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TEST_MAPPING b/TEST_MAPPING index 6996ad9349..9f2ea35cd5 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -32,6 +32,9 @@ // TODO: move to presubmit when known green. { "name": "bpf_existence_test" + }, + { + "name": "libclat_test" } ], "mainline-presubmit": [ From 6139d87623705116a6cff7da5fdb9c88831ca6ae Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 17 Dec 2021 15:47:26 +0800 Subject: [PATCH 03/16] [CLATJ#3] libclat: move generateIpv6Address() from netd to apex generateIpv6Address() is moved from ClatdController without behavior change. The unique_fd for socket fd is replaced by primitive int because libbase is not supported in mainline. Bug: 212345928 Test: build and boot atest libclat_test Change-Id: I42e152e6fe3ad577a0274e1d55e737318f61176d --- service/native/libs/libclat/clatutils.cpp | 40 +++++++++++++++++++ .../libs/libclat/include/libclat/clatutils.h | 3 ++ 2 files changed, 43 insertions(+) diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp index 2a53ba8079..69076c5542 100644 --- a/service/native/libs/libclat/clatutils.cpp +++ b/service/native/libs/libclat/clatutils.cpp @@ -14,7 +14,10 @@ #include "libclat/clatutils.h" +#include #include +#include +#include extern "C" { #include "checksum.h" @@ -46,6 +49,43 @@ void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Pr v6->s6_addr[12] = delta & 0xff; } +// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix. +int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix, + in6_addr* v6) { + int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (s == -1) return -errno; + + if (setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, iface, strlen(iface) + 1) == -1) { + close(s); + return -errno; + } + + sockaddr_in6 sin6 = {.sin6_family = AF_INET6, .sin6_addr = nat64Prefix}; + if (connect(s, reinterpret_cast(&sin6), sizeof(sin6)) == -1) { + close(s); + return -errno; + } + + socklen_t len = sizeof(sin6); + if (getsockname(s, reinterpret_cast(&sin6), &len) == -1) { + close(s); + return -errno; + } + + *v6 = sin6.sin6_addr; + + if (IN6_IS_ADDR_UNSPECIFIED(v6) || IN6_IS_ADDR_LOOPBACK(v6) || IN6_IS_ADDR_LINKLOCAL(v6) || + IN6_IS_ADDR_SITELOCAL(v6) || IN6_IS_ADDR_ULA(v6)) { + close(s); + return -ENETUNREACH; + } + + makeChecksumNeutral(v6, v4, nat64Prefix); + close(s); + + return 0; +} + } // namespace clat } // namespace net } // namespace android diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h index 05c99df66d..b86ee88e20 100644 --- a/service/native/libs/libclat/include/libclat/clatutils.h +++ b/service/native/libs/libclat/include/libclat/clatutils.h @@ -21,6 +21,9 @@ namespace net { namespace clat { void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix); +int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix, + in6_addr* v6); + } // namespace clat } // namespace net } // namespace android From 2f623f381c823c6c3fe3446993089bee78c65d4d Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 17 Dec 2021 17:24:58 +0800 Subject: [PATCH 04/16] [CLATJ#4] libclat: move selectIpv4Address() and isIpv4AddressFree() from netd to apex isIpv4AddressFree() is moved from ClatdController without behavior change. selectIpv4Address() and its test are moved from ClatdController as well. They have been refactored slightly for testing because function pointer of isIpv4AddressFreeFunc is not a global variable anymore. We can't set global function pointer to change selectIpv4Address() behavior for testing. Instead isIpv4AddressFree is sent as an argument of function pointer. For public caller, selectIpv4Address() has no behavior change. For test caller, they can call an internal selectIpv4AddressInternal (.., fn) to change isIpv4AddrFreeFn function pointer for testing. Bug: 212345928 Test: build and boot atest libclat_test Change-Id: Iab5e6fd5ebbccf10e7b3be2251b45949cf3f3464 --- service/native/libs/libclat/clatutils.cpp | 62 +++++++++++++++++++ .../native/libs/libclat/clatutils_test.cpp | 62 +++++++++++++++++++ .../libs/libclat/include/libclat/clatutils.h | 6 ++ 3 files changed, 130 insertions(+) diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp index 69076c5542..9ceed75093 100644 --- a/service/native/libs/libclat/clatutils.cpp +++ b/service/native/libs/libclat/clatutils.cpp @@ -27,6 +27,68 @@ namespace android { namespace net { namespace clat { +bool isIpv4AddressFree(in_addr_t addr) { + int s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (s == -1) { + return 0; + } + + // Attempt to connect to the address. If the connection succeeds and getsockname returns the + // same then the address is already assigned to the system and we can't use it. + struct sockaddr_in sin = { + .sin_family = AF_INET, + .sin_port = htons(53), + .sin_addr = {addr}, + }; + socklen_t len = sizeof(sin); + bool inuse = connect(s, (struct sockaddr*)&sin, sizeof(sin)) == 0 && + getsockname(s, (struct sockaddr*)&sin, &len) == 0 && (size_t)len >= sizeof(sin) && + sin.sin_addr.s_addr == addr; + + close(s); + return !inuse; +} + +// Picks a free IPv4 address, starting from ip and trying all addresses in the prefix in order. +// ip - the IP address from the configuration file +// prefixlen - the length of the prefix from which addresses may be selected. +// returns: the IPv4 address, or INADDR_NONE if no addresses were available +in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen) { + return selectIpv4AddressInternal(ip, prefixlen, isIpv4AddressFree); +} + +// Only allow testing to use this function directly. Otherwise call selectIpv4Address(ip, pfxlen) +// which has applied valid isIpv4AddressFree function pointer. +in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, + isIpv4AddrFreeFn isIpv4AddressFreeFunc) { + // Impossible! Only test allows to apply fn. + if (isIpv4AddressFreeFunc == nullptr) { + return INADDR_NONE; + } + + // Don't accept prefixes that are too large because we scan addresses one by one. + if (prefixlen < 16 || prefixlen > 32) { + return INADDR_NONE; + } + + // All these are in host byte order. + in_addr_t mask = 0xffffffff >> (32 - prefixlen) << (32 - prefixlen); + in_addr_t ipv4 = ntohl(ip.s_addr); + in_addr_t first_ipv4 = ipv4; + in_addr_t prefix = ipv4 & mask; + + // Pick the first IPv4 address in the pool, wrapping around if necessary. + // So, for example, 192.0.0.4 -> 192.0.0.5 -> 192.0.0.6 -> 192.0.0.7 -> 192.0.0.0. + do { + if (isIpv4AddressFreeFunc(htonl(ipv4))) { + return htonl(ipv4); + } + ipv4 = prefix | ((ipv4 + 1) & ~mask); + } while (ipv4 != first_ipv4); + + return INADDR_NONE; +} + // Alters the bits in the IPv6 address to make them checksum neutral with v4 and nat64Prefix. void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix) { // Fill last 8 bytes of IPv6 address with random bits. diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp index 1900abf5e2..0b38bce82c 100644 --- a/service/native/libs/libclat/clatutils_test.cpp +++ b/service/native/libs/libclat/clatutils_test.cpp @@ -33,6 +33,68 @@ using base::StringPrintf; class ClatUtils : public ::testing::Test {}; +// Mock functions for isIpv4AddressFree. +bool neverFree(in_addr_t /* addr */) { + return 0; +} +bool alwaysFree(in_addr_t /* addr */) { + return 1; +} +bool only2Free(in_addr_t addr) { + return (ntohl(addr) & 0xff) == 2; +} +bool over6Free(in_addr_t addr) { + return (ntohl(addr) & 0xff) >= 6; +} +bool only10Free(in_addr_t addr) { + return (ntohl(addr) & 0xff) == 10; +} + +// Apply mocked isIpv4AddressFree function for selectIpv4Address test. +in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen, + isIpv4AddrFreeFn fn /* mocked function */) { + // Call internal function to replace isIpv4AddressFreeFn for testing. + return selectIpv4AddressInternal(ip, prefixlen, fn); +} + +TEST_F(ClatUtils, SelectIpv4Address) { + struct in_addr addr; + + inet_pton(AF_INET, kIPv4LocalAddr, &addr); + + // If no addresses are free, return INADDR_NONE. + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, neverFree)); + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 16, neverFree)); + + // If the configured address is free, pick that. But a prefix that's too big is invalid. + EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 29, alwaysFree)); + EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 20, alwaysFree)); + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 15, alwaysFree)); + + // A prefix length of 32 works, but anything above it is invalid. + EXPECT_EQ(inet_addr(kIPv4LocalAddr), selectIpv4Address(addr, 32, alwaysFree)); + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 33, alwaysFree)); + + // If another address is free, pick it. + EXPECT_EQ(inet_addr("192.0.0.6"), selectIpv4Address(addr, 29, over6Free)); + + // Check that we wrap around to addresses that are lower than the first address. + EXPECT_EQ(inet_addr("192.0.0.2"), selectIpv4Address(addr, 29, only2Free)); + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 30, only2Free)); + + // If a free address exists outside the prefix, we don't pick it. + EXPECT_EQ(INADDR_NONE, selectIpv4Address(addr, 29, only10Free)); + EXPECT_EQ(inet_addr("192.0.0.10"), selectIpv4Address(addr, 24, only10Free)); + + // Now try using the real function which sees if IP addresses are free using bind(). + // Assume that the machine running the test has the address 127.0.0.1, but not 8.8.8.8. + addr.s_addr = inet_addr("8.8.8.8"); + EXPECT_EQ(inet_addr("8.8.8.8"), selectIpv4Address(addr, 29)); + + addr.s_addr = inet_addr("127.0.0.1"); + EXPECT_EQ(inet_addr("127.0.0.2"), selectIpv4Address(addr, 29)); +} + TEST_F(ClatUtils, MakeChecksumNeutral) { // We can't test generateIPv6Address here since it requires manipulating routing, which we can't // do without talking to the real netd on the system. diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h index b86ee88e20..d1e22776b2 100644 --- a/service/native/libs/libclat/include/libclat/clatutils.h +++ b/service/native/libs/libclat/include/libclat/clatutils.h @@ -20,10 +20,16 @@ namespace android { namespace net { namespace clat { +bool isIpv4AddressFree(in_addr_t addr); +in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen); void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix); int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix, in6_addr* v6); +// For testing +typedef bool (*isIpv4AddrFreeFn)(in_addr_t); +in_addr_t selectIpv4AddressInternal(const in_addr ip, int16_t prefixlen, isIpv4AddrFreeFn fn); + } // namespace clat } // namespace net } // namespace android From 86a56de6096ba081940cc57598e46bac29299253 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 17 Dec 2021 19:40:59 +0800 Subject: [PATCH 05/16] [CLATJ#5] libclat: move detect_mtu() from netd to apex detect_mtu() is moved from ClatdController without behavior change. The unique_fd for socket fd is replaced by primitive int because libbase is not supported in mainline. Bug: 212345928 Test: build and boot atest libclat_test Change-Id: Ib9c1a9d4b9e1c141d88164e8489c5044fdf70685 --- service/native/libs/libclat/Android.bp | 2 + service/native/libs/libclat/clatutils.cpp | 55 +++++++++++++++++++ .../native/libs/libclat/clatutils_test.cpp | 5 ++ .../libs/libclat/include/libclat/clatutils.h | 1 + 4 files changed, 63 insertions(+) diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp index a45ffa6153..9fe441ac70 100644 --- a/service/native/libs/libclat/Android.bp +++ b/service/native/libs/libclat/Android.bp @@ -22,6 +22,7 @@ cc_library_static { srcs: ["clatutils.cpp"], stl: "libc++_static", static_libs: ["libip_checksum"], + shared_libs: ["liblog"], export_include_dirs: ["include"], min_sdk_version: "30", apex_available: ["com.android.tethering"], @@ -39,4 +40,5 @@ cc_test { "libclat", "libip_checksum", ], + shared_libs: ["liblog"], } \ No newline at end of file diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp index 9ceed75093..8f037ad07e 100644 --- a/service/native/libs/libclat/clatutils.cpp +++ b/service/native/libs/libclat/clatutils.cpp @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#define LOG_TAG "clatutils" + #include "libclat/clatutils.h" #include +#include #include #include #include @@ -23,6 +26,9 @@ extern "C" { #include "checksum.h" } +// Sync from system/netd/include/netid_client.h. +#define MARK_UNSET 0u + namespace android { namespace net { namespace clat { @@ -148,6 +154,55 @@ int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat return 0; } +int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark) { + // Create an IPv6 UDP socket. + int s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (s < 0) { + int ret = errno; + ALOGE("socket(AF_INET6, SOCK_DGRAM, 0) failed: %s", strerror(errno)); + return -ret; + } + + // Socket's mark affects routing decisions (network selection) + if ((mark != MARK_UNSET) && setsockopt(s, SOL_SOCKET, SO_MARK, &mark, sizeof(mark))) { + int ret = errno; + ALOGE("setsockopt(SOL_SOCKET, SO_MARK) failed: %s", strerror(errno)); + close(s); + return -ret; + } + + // Try to connect udp socket to plat_subnet(96 bits):plat_suffix(32 bits) + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = *plat_subnet, + }; + dst.sin6_addr.s6_addr32[3] = plat_suffix; + if (connect(s, (struct sockaddr*)&dst, sizeof(dst))) { + int ret = errno; + ALOGE("connect() failed: %s", strerror(errno)); + close(s); + return -ret; + } + + // Fetch the socket's IPv6 mtu - this is effectively fetching mtu from routing table + int mtu; + socklen_t sz_mtu = sizeof(mtu); + if (getsockopt(s, SOL_IPV6, IPV6_MTU, &mtu, &sz_mtu)) { + int ret = errno; + ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) failed: %s", strerror(errno)); + close(s); + return -ret; + } + if (sz_mtu != sizeof(mtu)) { + ALOGE("getsockopt(SOL_IPV6, IPV6_MTU) returned unexpected size: %d", sz_mtu); + close(s); + return -EFAULT; + } + close(s); + + return mtu; +} + } // namespace clat } // namespace net } // namespace android diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp index 0b38bce82c..bb9678d665 100644 --- a/service/native/libs/libclat/clatutils_test.cpp +++ b/service/native/libs/libclat/clatutils_test.cpp @@ -150,6 +150,11 @@ TEST_F(ClatUtils, MakeChecksumNeutral) { EXPECT_GE(3210000, onebits); } +TEST_F(ClatUtils, DetectMtu) { + // ::1 with bottom 32 bits set to 1 is still ::1 which routes via lo with mtu of 64KiB + ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536); +} + } // namespace clat } // namespace net } // namespace android diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h index d1e22776b2..c8f83f16b4 100644 --- a/service/native/libs/libclat/include/libclat/clatutils.h +++ b/service/native/libs/libclat/include/libclat/clatutils.h @@ -25,6 +25,7 @@ in_addr_t selectIpv4Address(const in_addr ip, int16_t prefixlen); void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Prefix); int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix, in6_addr* v6); +int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark); // For testing typedef bool (*isIpv4AddrFreeFn)(in_addr_t); From 8ff032b990d514ead8ea7ea06bd412fcd0289248 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Fri, 17 Dec 2021 21:12:53 +0800 Subject: [PATCH 06/16] [CLATJ#6] libclat: move configure_packet_socket() from netd to apex configure_packet_socket() is moved from ClatdController. Only change the failure return value from errno to -errno. The remaining parts are the same. The test is modified from ClatdControllerTest::ConfigureIpv6Address. Simply set socket filter to an interface and 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. Bug: 212345928 Test: build and boot atest libclat_test Change-Id: Ia33c21051b040829c7d2aa55ed5b7de8766c98c5 --- service/native/libs/libclat/Android.bp | 7 ++- service/native/libs/libclat/clatutils.cpp | 60 +++++++++++++++++++ .../native/libs/libclat/clatutils_test.cpp | 27 +++++++++ .../libs/libclat/include/libclat/clatutils.h | 1 + 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/service/native/libs/libclat/Android.bp b/service/native/libs/libclat/Android.bp index 9fe441ac70..85407871f2 100644 --- a/service/native/libs/libclat/Android.bp +++ b/service/native/libs/libclat/Android.bp @@ -39,6 +39,11 @@ cc_test { "libbase", "libclat", "libip_checksum", + "libnetd_test_tun_interface", ], - shared_libs: ["liblog"], + shared_libs: [ + "liblog", + "libnetutils", + ], + require_root: true, } \ No newline at end of file diff --git a/service/native/libs/libclat/clatutils.cpp b/service/native/libs/libclat/clatutils.cpp index 8f037ad07e..4a125ba9ff 100644 --- a/service/native/libs/libclat/clatutils.cpp +++ b/service/native/libs/libclat/clatutils.cpp @@ -17,6 +17,9 @@ #include "libclat/clatutils.h" #include +#include +#include +#include #include #include #include @@ -26,6 +29,10 @@ extern "C" { #include "checksum.h" } +// Sync from external/android-clat/clatd.h +#define MAXMTU 65536 +#define PACKETLEN (MAXMTU + sizeof(struct tun_pi)) + // Sync from system/netd/include/netid_client.h. #define MARK_UNSET 0u @@ -203,6 +210,59 @@ int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_ return mtu; } +/* function: configure_packet_socket + * Binds the packet socket and attaches the receive filter to it. + * sock - the socket to configure + * addr - the IP address to filter + * ifindex - index of interface to add the filter to + * returns: 0 on success, -errno on failure + */ +int configure_packet_socket(int sock, in6_addr* addr, int ifindex) { + uint32_t* ipv6 = addr->s6_addr32; + + // clang-format off + struct sock_filter filter_code[] = { + // Load the first four bytes of the IPv6 destination address (starts 24 bytes in). + // Compare it against the first four bytes of our IPv6 address, in host byte order (BPF loads + // are always in host byte order). If it matches, continue with next instruction (JMP 0). If it + // doesn't match, jump ahead to statement that returns 0 (ignore packet). Repeat for the other + // three words of the IPv6 address, and if they all match, return PACKETLEN (accept packet). + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 24), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[0]), 0, 7), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 28), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[1]), 0, 5), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 32), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[2]), 0, 3), + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, 36), + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, htonl(ipv6[3]), 0, 1), + BPF_STMT(BPF_RET | BPF_K, PACKETLEN), + BPF_STMT(BPF_RET | BPF_K, 0), + }; + // clang-format on + struct sock_fprog filter = {sizeof(filter_code) / sizeof(filter_code[0]), filter_code}; + + if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter))) { + int res = errno; + ALOGE("attach packet filter failed: %s", strerror(errno)); + return -res; + } + + struct sockaddr_ll sll = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IPV6), + .sll_ifindex = ifindex, + .sll_pkttype = + PACKET_OTHERHOST, // The 464xlat IPv6 address is not assigned to the kernel. + }; + if (bind(sock, (struct sockaddr*)&sll, sizeof(sll))) { + int res = errno; + ALOGE("binding packet socket: %s", strerror(errno)); + return -res; + } + + return 0; +} + } // namespace clat } // namespace net } // namespace android diff --git a/service/native/libs/libclat/clatutils_test.cpp b/service/native/libs/libclat/clatutils_test.cpp index bb9678d665..4153e19d37 100644 --- a/service/native/libs/libclat/clatutils_test.cpp +++ b/service/native/libs/libclat/clatutils_test.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include "tun_interface.h" extern "C" { #include "checksum.h" @@ -29,6 +32,7 @@ namespace android { namespace net { namespace clat { +using android::net::TunInterface; using base::StringPrintf; class ClatUtils : public ::testing::Test {}; @@ -155,6 +159,29 @@ TEST_F(ClatUtils, DetectMtu) { ASSERT_EQ(detect_mtu(&in6addr_loopback, htonl(1), 0 /*MARK_UNSET*/), 65536); } +TEST_F(ClatUtils, ConfigurePacketSocket) { + // Create an interface for configure_packet_socket to attach socket filter to. + TunInterface v6Iface; + ASSERT_EQ(0, v6Iface.init()); + + int s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, htons(ETH_P_IPV6)); + EXPECT_LE(0, s); + struct in6_addr addr6; + EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::f00", &addr6)); + EXPECT_EQ(0, configure_packet_socket(s, &addr6, v6Iface.ifindex())); + + // 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(s, reinterpret_cast(&sll), &len)); + EXPECT_EQ(htons(ETH_P_IPV6), sll.sll_protocol); + EXPECT_EQ(sll.sll_ifindex, v6Iface.ifindex()); + + close(s); + v6Iface.destroy(); +} + } // namespace clat } // namespace net } // namespace android diff --git a/service/native/libs/libclat/include/libclat/clatutils.h b/service/native/libs/libclat/include/libclat/clatutils.h index c8f83f16b4..812c86eb21 100644 --- a/service/native/libs/libclat/include/libclat/clatutils.h +++ b/service/native/libs/libclat/include/libclat/clatutils.h @@ -26,6 +26,7 @@ void makeChecksumNeutral(in6_addr* v6, const in_addr v4, const in6_addr& nat64Pr int generateIpv6Address(const char* iface, const in_addr v4, const in6_addr& nat64Prefix, in6_addr* v6); int detect_mtu(const struct in6_addr* plat_subnet, uint32_t plat_suffix, uint32_t mark); +int configure_packet_socket(int sock, in6_addr* addr, int ifindex); // For testing typedef bool (*isIpv4AddrFreeFn)(in_addr_t); From e2cf0555903b3b54d0682c97c9064b4ee49d4f1a Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 17:05:41 +0800 Subject: [PATCH 07/16] [CLATJ#7] ClatCoordinator: select IPv4 address for clatd Introduce ClatCoordinator to replace netd/ClatdController. This is a startup commit. Starting clatd needs to prepare local 464xlat ipv4 address, local 464xlat ipv6 address, tun interface, clat read/write sockets. These will be implemented from this and followup commits. This commit implements that prepare local 464xlat ipv4 address. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I486db8aef0181ee1aedd7cd7702f95d5a1cef812 --- service/Android.bp | 6 ++ ...id_server_connectivity_ClatCoordinator.cpp | 69 ++++++++++++ service/jni/onload.cpp | 5 + .../server/connectivity/ClatCoordinator.java | 100 ++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 service/jni/com_android_server_connectivity_ClatCoordinator.cpp create mode 100644 service/src/com/android/server/connectivity/ClatCoordinator.java diff --git a/service/Android.bp b/service/Android.bp index 76f9153e06..5ba6a0099e 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -57,12 +57,18 @@ cc_library_shared { ], srcs: [ "jni/com_android_server_TestNetworkService.cpp", + "jni/com_android_server_connectivity_ClatCoordinator.cpp", "jni/onload.cpp", ], stl: "libc++_static", header_libs: [ "libbase_headers", ], + static_libs: [ + "libclat", + "libip_checksum", + "libnetjniutils", + ], shared_libs: [ "liblog", "libnativehelper", diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp new file mode 100644 index 0000000000..e042bf3db9 --- /dev/null +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "libclat/clatutils.h" +#include "nativehelper/scoped_utf_chars.h" + +namespace android { +jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env, + jobject clazz, + jstring v4addr, + jint prefixlen) { + ScopedUtfChars address(env, v4addr); + in_addr ip; + if (inet_pton(AF_INET, address.c_str(), &ip) != 1) { + return nullptr; + } + + // Pick an IPv4 address. + // TODO: this picks the address based on other addresses that are assigned to interfaces, but + // the address is only actually assigned to an interface once clatd starts up. So we could end + // up with two clatd instances with the same IPv4 address. + // Stop doing this and instead pick a free one from the kV4Addr pool. + in_addr v4 = {net::clat::selectIpv4Address(ip, prefixlen)}; + if (v4.s_addr == INADDR_NONE) { + jniThrowExceptionFmt(env, "java/io/IOException", "No free IPv4 address in %s/%d", + address.c_str(), prefixlen); + return nullptr; + } + + char addrstr[INET_ADDRSTRLEN]; + if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) { + return nullptr; + } + return env->NewStringUTF(addrstr); +} + +/* + * JNI registration. + */ +static const JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + {"selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;", + (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address}, +}; + +int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { + return jniRegisterNativeMethods(env, "com/android/server/connectivity/ClatCoordinator", + gMethods, NELEM(gMethods)); +} + +}; // namespace android diff --git a/service/jni/onload.cpp b/service/jni/onload.cpp index 00128794bc..04d9671fad 100644 --- a/service/jni/onload.cpp +++ b/service/jni/onload.cpp @@ -20,6 +20,7 @@ namespace android { int register_android_server_TestNetworkService(JNIEnv* env); +int register_android_server_connectivity_ClatCoordinator(JNIEnv* env); extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv *env; @@ -32,6 +33,10 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void*) { return JNI_ERR; } + if (register_android_server_connectivity_ClatCoordinator(env) < 0) { + return JNI_ERR; + } + return JNI_VERSION_1_6; } diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java new file mode 100644 index 0000000000..cf22e24284 --- /dev/null +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.net.INetd; +import android.net.IpPrefix; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.IOException; + +/** + * This coordinator is responsible for providing clat relevant functionality. + * + * {@hide} + */ +public class ClatCoordinator { + private static final String TAG = ClatCoordinator.class.getSimpleName(); + + // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses + // in 192.0.0.0/29 (RFC 7335). + @VisibleForTesting + static final String INIT_V4ADDR_STRING = "192.0.0.4"; + @VisibleForTesting + static final int INIT_V4ADDR_PREFIX_LEN = 29; + + private static final int INVALID_PID = 0; + + @NonNull + private final INetd mNetd; + @NonNull + private final Dependencies mDeps; + @Nullable + private String mIface = null; + private int mPid = INVALID_PID; + + @VisibleForTesting + abstract static class Dependencies { + /** + * Get netd. + */ + @NonNull + public abstract INetd getNetd(); + + /** + * Pick an IPv4 address for clat. + */ + @NonNull + public String jniSelectIpv4Address(@NonNull String v4addr, int prefixlen) + throws IOException { + return selectIpv4Address(v4addr, prefixlen); + } + } + + public ClatCoordinator(@NonNull Dependencies deps) { + mDeps = deps; + mNetd = mDeps.getNetd(); + } + + /** + * Start clatd for a given interface and NAT64 prefix. + */ + public String clatStart(final String iface, final int netId, + @NonNull final IpPrefix nat64Prefix) + throws IOException { + if (nat64Prefix.getPrefixLength() != 96) { + throw new IOException("Prefix must be 96 bits long: " + nat64Prefix); + } + + // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 .. + final String v4; + try { + v4 = mDeps.jniSelectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN); + } catch (IOException e) { + throw new IOException("no IPv4 addresses were available for clat: " + e); + } + + // TODO: start clatd and returns local xlat464 v6 address. + return null; + } + + private static native String selectIpv4Address(String v4addr, int prefixlen) + throws IOException; +} From 884c0a122c9a660b10807341ab68f8a348bcef8e Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 18:20:08 +0800 Subject: [PATCH 08/16] [CLATJ#8] ClatCoordinator: select IPv6 address with checksum-neutral IID Select a local 464xlat IPv6 address for clatd. The IPv6 address has checksum-neutral IID. The native function is moved from netd to libclat. Its test is moved from netd as well. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I11520e5c086e475999847d829dd6341b14199b39 --- ...id_server_connectivity_ClatCoordinator.cpp | 43 +++++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 20 +++++++++ 2 files changed, 63 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index e042bf3db9..e3e5000385 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -52,6 +52,46 @@ jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv return env->NewStringUTF(addrstr); } +// Picks a random interface ID that is checksum neutral with the IPv4 address and the NAT64 prefix. +jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address( + JNIEnv* env, jobject clazz, jstring ifaceStr, jstring v4Str, jstring prefix64Str) { + ScopedUtfChars iface(env, ifaceStr); + ScopedUtfChars addr4(env, v4Str); + ScopedUtfChars prefix64(env, prefix64Str); + + if (iface.c_str() == nullptr) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid null interface name"); + return nullptr; + } + + in_addr v4; + if (inet_pton(AF_INET, addr4.c_str(), &v4) != 1) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid clat v4 address %s", + addr4.c_str()); + return nullptr; + } + + in6_addr nat64Prefix; + if (inet_pton(AF_INET6, prefix64.c_str(), &nat64Prefix) != 1) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid prefix %s", prefix64.c_str()); + return nullptr; + } + + in6_addr v6; + if (net::clat::generateIpv6Address(iface.c_str(), v4, nat64Prefix, &v6)) { + jniThrowExceptionFmt(env, "java/io/IOException", + "Unable to find global source address on %s for %s", iface.c_str(), + prefix64.c_str()); + return nullptr; + } + + char addrstr[INET6_ADDRSTRLEN]; + if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) { + return nullptr; + } + return env->NewStringUTF(addrstr); +} + /* * JNI registration. */ @@ -59,6 +99,9 @@ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ {"selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;", (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address}, + {"generateIpv6Address", + "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index cf22e24284..11deb78d92 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -66,6 +66,15 @@ public class ClatCoordinator { throws IOException { return selectIpv4Address(v4addr, prefixlen); } + + /** + * Generate a checksum-neutral IID. + */ + @NonNull + public String jniGenerateIpv6Address(@NonNull String iface, @NonNull String v4, + @NonNull String prefix64) throws IOException { + return generateIpv6Address(iface, v4, prefix64); + } } public ClatCoordinator(@NonNull Dependencies deps) { @@ -91,10 +100,21 @@ public class ClatCoordinator { throw new IOException("no IPv4 addresses were available for clat: " + e); } + // [2] Generate a checksum-neutral IID. + final String pfx96 = nat64Prefix.getAddress().getHostAddress(); + final String v6; + try { + v6 = mDeps.jniGenerateIpv6Address(iface, v4, pfx96); + } catch (IOException e) { + throw new IOException("no IPv6 addresses were available for clat: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } private static native String selectIpv4Address(String v4addr, int prefixlen) throws IOException; + private static native String generateIpv6Address(String iface, String v4, String prefix64) + throws IOException; } From 46bbee1bfede2512652487695167ea3b03cf04a7 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 19:11:24 +0800 Subject: [PATCH 09/16] [CLATJ#9] ClatCoordinator: open tun interface Open the v4-... tun interface for clat. The native function is moved from netd to jni. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I3f3c587275125ce3fc7481006947211f17dbb2d4 --- ...id_server_connectivity_ClatCoordinator.cpp | 34 ++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 40 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index e3e5000385..d3895b3ff2 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -15,7 +15,11 @@ */ #include +#include +#include +#include #include +#include #include @@ -92,6 +96,34 @@ jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address( return env->NewStringUTF(addrstr); } +static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(JNIEnv* env, + jobject clazz, + jstring tuniface) { + ScopedUtfChars v4interface(env, tuniface); + + // open the tun device in non blocking mode as required by clatd + jint fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (fd == -1) { + jniThrowExceptionFmt(env, "java/io/IOException", "open tun device failed (%s)", + strerror(errno)); + return -1; + } + + struct ifreq ifr = { + .ifr_flags = IFF_TUN, + }; + strlcpy(ifr.ifr_name, v4interface.c_str(), sizeof(ifr.ifr_name)); + + if (ioctl(fd, TUNSETIFF, &ifr, sizeof(ifr))) { + close(fd); + jniThrowExceptionFmt(env, "java/io/IOException", "ioctl(TUNSETIFF) failed (%s)", + strerror(errno)); + return -1; + } + + return fd; +} + /* * JNI registration. */ @@ -102,6 +134,8 @@ static const JNINativeMethod gMethods[] = { {"generateIpv6Address", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address}, + {"createTunInterface", "(Ljava/lang/String;)I", + (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index 11deb78d92..22edf1b09c 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -20,6 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.net.INetd; import android.net.IpPrefix; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -33,6 +37,9 @@ import java.io.IOException; public class ClatCoordinator { private static final String TAG = ClatCoordinator.class.getSimpleName(); + // This must match the interface prefix in clatd.c. + private static final String CLAT_PREFIX = "v4-"; + // For historical reasons, start with 192.0.0.4, and after that, use all subsequent addresses // in 192.0.0.0/29 (RFC 7335). @VisibleForTesting @@ -58,6 +65,21 @@ public class ClatCoordinator { @NonNull public abstract INetd getNetd(); + /** + * @see ParcelFileDescriptor#adoptFd(int). + */ + @NonNull + public ParcelFileDescriptor adoptFd(int fd) { + return ParcelFileDescriptor.adoptFd(fd); + } + + /** + * Create tun interface for a given interface name. + */ + public int jniCreateTunInterface(@NonNull String tuniface) throws IOException { + return createTunInterface(tuniface); + } + /** * Pick an IPv4 address for clat. */ @@ -109,6 +131,23 @@ public class ClatCoordinator { throw new IOException("no IPv6 addresses were available for clat: " + e); } + // [3] Open, configure and bring up the tun interface. + // Create the v4-... tun interface. + final String tunIface = CLAT_PREFIX + iface; + final ParcelFileDescriptor tunFd; + try { + tunFd = mDeps.adoptFd(mDeps.jniCreateTunInterface(tunIface)); + } catch (IOException e) { + throw new IOException("Create tun interface " + tunIface + " failed: " + e); + } + + // disable IPv6 on it - failing to do so is not a critical error + try { + mNetd.interfaceSetEnableIPv6(tunIface, false /* enabled */); + } catch (RemoteException | ServiceSpecificException e) { + Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -117,4 +156,5 @@ public class ClatCoordinator { throws IOException; private static native String generateIpv6Address(String iface, String v4, String prefix64) throws IOException; + private static native int createTunInterface(String tuniface) throws IOException; } From 7de1c49b7400cf46470c52408443619447ae327a Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 20:00:05 +0800 Subject: [PATCH 10/16] [CLATJ#10] ClatCoordinator: detect mtu Detect the available mtu for clat. The native mtu detect function is moved from netd to libclat which is called by jni. The java adjust mtu function is moved from netd native code. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: Icf5f57c7e7b947997b76152722e46acdd9d95c1a --- ...id_server_connectivity_ClatCoordinator.cpp | 23 ++++++++ .../server/connectivity/ClatCoordinator.java | 52 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index d3895b3ff2..eefb94bf88 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -124,6 +124,27 @@ static jint com_android_server_connectivity_ClatCoordinator_createTunInterface(J return fd; } +static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* env, jobject clazz, + jstring platSubnet, + jint plat_suffix, jint mark) { + ScopedUtfChars platSubnetStr(env, platSubnet); + + in6_addr plat_subnet; + if (inet_pton(AF_INET6, platSubnetStr.c_str(), &plat_subnet) != 1) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid plat prefix address %s", + platSubnetStr.c_str()); + return -1; + } + + int ret = net::clat::detect_mtu(&plat_subnet, plat_suffix, mark); + if (ret < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", "detect mtu failed: %s", strerror(-ret)); + return -1; + } + + return ret; +} + /* * JNI registration. */ @@ -136,6 +157,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address}, {"createTunInterface", "(Ljava/lang/String;)I", (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface}, + {"detectMtu", "(Ljava/lang/String;II)I", + (void*)com_android_server_connectivity_ClatCoordinator_detectMtu}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index 22edf1b09c..1702930e27 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -16,6 +16,10 @@ package com.android.server.connectivity; +import static android.net.INetd.PERMISSION_SYSTEM; + +import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; + import android.annotation.NonNull; import android.annotation.Nullable; import android.net.INetd; @@ -28,6 +32,8 @@ import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import java.io.IOException; +import java.net.InetAddress; +import java.nio.ByteBuffer; /** * This coordinator is responsible for providing clat relevant functionality. @@ -37,6 +43,13 @@ import java.io.IOException; public class ClatCoordinator { private static final String TAG = ClatCoordinator.class.getSimpleName(); + // Sync from external/android-clat/clatd.c + // 40 bytes IPv6 header - 20 bytes IPv4 header + 8 bytes fragment header. + @VisibleForTesting + static final int MTU_DELTA = 28; + @VisibleForTesting + static final int CLAT_MAX_MTU = 65536; + // This must match the interface prefix in clatd.c. private static final String CLAT_PREFIX = "v4-"; @@ -46,6 +59,7 @@ public class ClatCoordinator { static final String INIT_V4ADDR_STRING = "192.0.0.4"; @VisibleForTesting static final int INIT_V4ADDR_PREFIX_LEN = 29; + private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8"); private static final int INVALID_PID = 0; @@ -97,6 +111,35 @@ public class ClatCoordinator { @NonNull String prefix64) throws IOException { return generateIpv6Address(iface, v4, prefix64); } + + /** + * Detect MTU. + */ + public int jniDetectMtu(@NonNull String platSubnet, int platSuffix, int mark) + throws IOException { + return detectMtu(platSubnet, platSuffix, mark); + } + } + + @VisibleForTesting + static int getFwmark(int netId) { + // See union Fwmark in system/netd/include/Fwmark.h + return (netId & 0xffff) + | 0x1 << 16 // protectedFromVpn: true + | 0x1 << 17 // explicitlySelected: true + | (PERMISSION_SYSTEM & 0x3) << 18; + } + + @VisibleForTesting + static int adjustMtu(int mtu) { + // clamp to minimum ipv6 mtu - this probably cannot ever trigger + if (mtu < IPV6_MIN_MTU) mtu = IPV6_MIN_MTU; + // clamp to buffer size + if (mtu > CLAT_MAX_MTU) mtu = CLAT_MAX_MTU; + // decrease by ipv6(40) + ipv6 fragmentation header(8) vs ipv4(20) overhead of 28 bytes + mtu -= MTU_DELTA; + + return mtu; } public ClatCoordinator(@NonNull Dependencies deps) { @@ -148,6 +191,13 @@ public class ClatCoordinator { Log.e(TAG, "Disable IPv6 on " + tunIface + " failed: " + e); } + // Detect ipv4 mtu. + final Integer fwmark = getFwmark(netId); + final int detectedMtu = mDeps.jniDetectMtu(pfx96, + ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark); + final int mtu = adjustMtu(detectedMtu); + Log.i(TAG, "ipv4 mtu is " + mtu); + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -157,4 +207,6 @@ public class ClatCoordinator { private static native String generateIpv6Address(String iface, String v4, String prefix64) throws IOException; private static native int createTunInterface(String tuniface) throws IOException; + private static native int detectMtu(String platSubnet, int platSuffix, int mark) + throws IOException; } From 397ca65abb55dfa2dc536cc3897c5cfde429881b Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 20:18:02 +0800 Subject: [PATCH 11/16] [CLATJ#11] ClatCoordinator: config tun interface and bring up Apply mtu and selected IPv4 address to tun interface. Then, bring up the tun interface. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I24a2a91e2b084be28820a7a63b265c5f56293334 --- .../server/connectivity/ClatCoordinator.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index 1702930e27..d122f3c76c 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -16,6 +16,7 @@ package com.android.server.connectivity; +import static android.net.INetd.IF_STATE_UP; import static android.net.INetd.PERMISSION_SYSTEM; import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; @@ -23,6 +24,7 @@ import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU; import android.annotation.NonNull; import android.annotation.Nullable; import android.net.INetd; +import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -198,6 +200,27 @@ public class ClatCoordinator { final int mtu = adjustMtu(detectedMtu); Log.i(TAG, "ipv4 mtu is " + mtu); + // TODO: add setIptablesDropRule + + // Config tun interface mtu, address and bring up. + try { + mNetd.interfaceSetMtu(tunIface, mtu); + } catch (RemoteException | ServiceSpecificException e) { + throw new IOException("Set MTU " + mtu + " on " + tunIface + " failed: " + e); + } + final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel(); + ifConfig.ifName = tunIface; + ifConfig.ipv4Addr = v4; + ifConfig.prefixLength = 32; + ifConfig.hwAddr = ""; + ifConfig.flags = new String[] {IF_STATE_UP}; + try { + mNetd.interfaceSetCfg(ifConfig); + } catch (RemoteException | ServiceSpecificException e) { + throw new IOException("Setting IPv4 address to " + ifConfig.ipv4Addr + "/" + + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } From 79d34befb15228fe432cb67211ae27368c97ad8d Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 20:48:51 +0800 Subject: [PATCH 12/16] [CLATJ#12] ClatCoordinator: open packet socket with a given mark Move the packet socket initialization from netd to jni because mainline module is going to launch clatd. Need to provide packet socket for reading local 464xlat IPv6 packets. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I1cef6e099403f883d668081fb5e917863f5a3c2a --- ...id_server_connectivity_ClatCoordinator.cpp | 18 ++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 21 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index eefb94bf88..c658464286 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -27,6 +27,10 @@ #include "nativehelper/scoped_utf_chars.h" namespace android { +static void throwIOException(JNIEnv* env, const char* msg, int error) { + jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error)); +} + jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv* env, jobject clazz, jstring v4addr, @@ -145,6 +149,18 @@ static jint com_android_server_connectivity_ClatCoordinator_detectMtu(JNIEnv* en return ret; } +static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNIEnv* env, + jobject clazz) { + // Will eventually be bound to htons(ETH_P_IPV6) protocol, + // but only after appropriate bpf filter is attached. + int sock = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sock < 0) { + throwIOException(env, "packet socket failed", errno); + return -1; + } + return sock; +} + /* * JNI registration. */ @@ -159,6 +175,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface}, {"detectMtu", "(Ljava/lang/String;II)I", (void*)com_android_server_connectivity_ClatCoordinator_detectMtu}, + {"openPacketSocket", "()I", + (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index d122f3c76c..bc635ed866 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -121,6 +121,13 @@ public class ClatCoordinator { throws IOException { return detectMtu(platSubnet, platSuffix, mark); } + + /** + * Open packet socket. + */ + public int jniOpenPacketSocket() throws IOException { + return openPacketSocket(); + } } @VisibleForTesting @@ -221,6 +228,19 @@ public class ClatCoordinator { + ifConfig.prefixLength + " failed on " + ifConfig.ifName + ": " + e); } + // [4] Open and configure local 464xlat read/write sockets. + // Opens a packet socket to receive IPv6 packets in clatd. + final ParcelFileDescriptor readSock6; + try { + // Use a JNI call to get native file descriptor instead of Os.socket() because we would + // like to use ParcelFileDescriptor to close file descriptor automatically. But ctor + // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file + // descriptor to initialize ParcelFileDescriptor object instead. + readSock6 = mDeps.adoptFd(mDeps.jniOpenPacketSocket()); + } catch (IOException e) { + throw new IOException("Open packet socket failed: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -232,4 +252,5 @@ public class ClatCoordinator { private static native int createTunInterface(String tuniface) throws IOException; private static native int detectMtu(String platSubnet, int platSuffix, int mark) throws IOException; + private static native int openPacketSocket() throws IOException; } From d292f45e1689cfd4bd09826a25ae5d9943468fb9 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 21:05:49 +0800 Subject: [PATCH 13/16] [CLATJ#13] ClatCoordinator: open raw socket Move the raw socket initialization from netd to mainline because mainline module is going to launch clatd. Need to provide raw socket for writing local 464xlat IPv6 packets. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I6d1da4f1b400eeed87771ae8197b0c58ec50804b --- ...id_server_connectivity_ClatCoordinator.cpp | 24 +++++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 18 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index c658464286..562b1a2787 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -26,6 +26,9 @@ #include "libclat/clatutils.h" #include "nativehelper/scoped_utf_chars.h" +// Sync from system/netd/include/netid_client.h +#define MARK_UNSET 0u + namespace android { static void throwIOException(JNIEnv* env, const char* msg, int error) { jniThrowExceptionFmt(env, "java/io/IOException", "%s: %s", msg, strerror(error)); @@ -161,6 +164,25 @@ static jint com_android_server_connectivity_ClatCoordinator_openPacketSocket(JNI return sock; } +static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEnv* env, + jobject clazz, + jint mark) { + int sock = socket(AF_INET6, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_RAW); + if (sock < 0) { + throwIOException(env, "raw socket failed", errno); + return -1; + } + + // TODO: check the mark validation + if (mark != MARK_UNSET && setsockopt(sock, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) < 0) { + throwIOException(env, "could not set mark on raw socket", errno); + close(sock); + return -1; + } + + return sock; +} + /* * JNI registration. */ @@ -177,6 +199,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_connectivity_ClatCoordinator_detectMtu}, {"openPacketSocket", "()I", (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket}, + {"openRawSocket6", "(I)I", + (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index bc635ed866..cf956c59e3 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -128,6 +128,13 @@ public class ClatCoordinator { public int jniOpenPacketSocket() throws IOException { return openPacketSocket(); } + + /** + * Open IPv6 raw socket and set SO_MARK. + */ + public int jniOpenRawSocket6(int mark) throws IOException { + return openRawSocket6(mark); + } } @VisibleForTesting @@ -241,6 +248,16 @@ public class ClatCoordinator { throw new IOException("Open packet socket failed: " + e); } + // Opens a raw socket with a given fwmark to send IPv6 packets in clatd. + final ParcelFileDescriptor writeSock6; + try { + // Use a JNI call to get native file descriptor instead of Os.socket(). See above + // reason why we use jniOpenPacketSocket6(). + writeSock6 = mDeps.adoptFd(mDeps.jniOpenRawSocket6(fwmark)); + } catch (IOException e) { + throw new IOException("Open raw socket failed: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -253,4 +270,5 @@ public class ClatCoordinator { private static native int detectMtu(String platSubnet, int platSuffix, int mark) throws IOException; private static native int openPacketSocket() throws IOException; + private static native int openRawSocket6(int mark) throws IOException; } From 2c3acb01c91f75c75ba9c331f668b55d39aec868 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 21:21:47 +0800 Subject: [PATCH 14/16] [CLATJ#14] ClatCoordinator: add anycast to raw socket Start translating packets to the new prefix. Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit. Change-Id: I38729c23fce72428919b33ac531ede9593337a0a --- ...id_server_connectivity_ClatCoordinator.cpp | 28 ++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 33 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index 562b1a2787..84c4cff90f 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -183,6 +183,32 @@ static jint com_android_server_connectivity_ClatCoordinator_openRawSocket6(JNIEn return sock; } +static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt( + JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) { + int sock = netjniutils::GetNativeFileDescriptor(env, javaFd); + if (sock < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor"); + return; + } + + ScopedUtfChars addrStr(env, addr6); + + in6_addr addr; + if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s", + addrStr.c_str()); + return; + } + + struct ipv6_mreq mreq = {addr, ifindex}; + int ret = setsockopt(sock, SOL_IPV6, IPV6_JOIN_ANYCAST, &mreq, sizeof(mreq)); + if (ret) { + jniThrowExceptionFmt(env, "java/io/IOException", "setsockopt IPV6_JOIN_ANYCAST failed: %s", + strerror(errno)); + return; + } +} + /* * JNI registration. */ @@ -201,6 +227,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket}, {"openRawSocket6", "(I)I", (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6}, + {"addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", + (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index cf956c59e3..578379d74e 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -32,7 +32,9 @@ import android.os.ServiceSpecificException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.net.module.util.InterfaceParams; +import java.io.FileDescriptor; import java.io.IOException; import java.net.InetAddress; import java.nio.ByteBuffer; @@ -63,6 +65,7 @@ public class ClatCoordinator { static final int INIT_V4ADDR_PREFIX_LEN = 29; private static final InetAddress GOOGLE_DNS_4 = InetAddress.parseNumericAddress("8.8.8.8"); + private static final int INVALID_IFINDEX = 0; private static final int INVALID_PID = 0; @NonNull @@ -89,6 +92,14 @@ public class ClatCoordinator { return ParcelFileDescriptor.adoptFd(fd); } + /** + * Get interface index for a given interface. + */ + public int getInterfaceIndex(String ifName) { + final InterfaceParams params = InterfaceParams.getByName(ifName); + return params != null ? params.index : INVALID_IFINDEX; + } + /** * Create tun interface for a given interface name. */ @@ -135,6 +146,14 @@ public class ClatCoordinator { public int jniOpenRawSocket6(int mark) throws IOException { return openRawSocket6(mark); } + + /** + * Add anycast setsockopt. + */ + public void jniAddAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex) + throws IOException { + addAnycastSetsockopt(sock, v6, ifindex); + } } @VisibleForTesting @@ -258,6 +277,18 @@ public class ClatCoordinator { throw new IOException("Open raw socket failed: " + e); } + final int ifaceIndex = mDeps.getInterfaceIndex(iface); + if (ifaceIndex == INVALID_IFINDEX) { + throw new IOException("Fail to get interface index for interface " + iface); + } + + // Start translating packets to the new prefix. + try { + mDeps.jniAddAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex); + } catch (IOException e) { + throw new IOException("add anycast sockopt failed: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -271,4 +302,6 @@ public class ClatCoordinator { throws IOException; private static native int openPacketSocket() throws IOException; private static native int openRawSocket6(int mark) throws IOException; + private static native void addAnycastSetsockopt(FileDescriptor sock, String v6, int ifindex) + throws IOException; } From 847b2ce4f976fef7eb1aa7ea122bbca270fe0410 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Sat, 25 Dec 2021 21:27:23 +0800 Subject: [PATCH 15/16] [CLATJ#15] ClatCoordinator: add socket filter to packet socket Update our packet socket filter to reflect the new 464xlat IP address Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit Change-Id: Ic50dc122731f311ad00ab8bff5472cb3bc41f5f1 --- ...id_server_connectivity_ClatCoordinator.cpp | 26 +++++++++++++++++++ .../server/connectivity/ClatCoordinator.java | 17 ++++++++++++ 2 files changed, 43 insertions(+) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index 84c4cff90f..b37827ea28 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -209,6 +209,30 @@ static void com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt } } +static void com_android_server_connectivity_ClatCoordinator_configurePacketSocket( + JNIEnv* env, jobject clazz, jobject javaFd, jstring addr6, jint ifindex) { + ScopedUtfChars addrStr(env, addr6); + + int sock = netjniutils::GetNativeFileDescriptor(env, javaFd); + if (sock < 0) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid file descriptor"); + return; + } + + in6_addr addr; + if (inet_pton(AF_INET6, addrStr.c_str(), &addr) != 1) { + jniThrowExceptionFmt(env, "java/io/IOException", "Invalid IPv6 address %s", + addrStr.c_str()); + return; + } + + int ret = net::clat::configure_packet_socket(sock, &addr, ifindex); + if (ret < 0) { + throwIOException(env, "configure packet socket failed", -ret); + return; + } +} + /* * JNI registration. */ @@ -229,6 +253,8 @@ static const JNINativeMethod gMethods[] = { (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6}, {"addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt}, + {"configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", + (void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket}, }; int register_android_server_connectivity_ClatCoordinator(JNIEnv* env) { diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index 578379d74e..2623c50988 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -154,6 +154,14 @@ public class ClatCoordinator { throws IOException { addAnycastSetsockopt(sock, v6, ifindex); } + + /** + * Configure packet socket. + */ + public void jniConfigurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex) + throws IOException { + configurePacketSocket(sock, v6, ifindex); + } } @VisibleForTesting @@ -289,6 +297,13 @@ public class ClatCoordinator { throw new IOException("add anycast sockopt failed: " + e); } + // Update our packet socket filter to reflect the new 464xlat IP address. + try { + mDeps.jniConfigurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex); + } catch (IOException e) { + throw new IOException("configure packet socket failed: " + e); + } + // TODO: start clatd and returns local xlat464 v6 address. return null; } @@ -304,4 +319,6 @@ public class ClatCoordinator { private static native int openRawSocket6(int mark) throws IOException; private static native void addAnycastSetsockopt(FileDescriptor sock, String v6, int ifindex) throws IOException; + private static native void configurePacketSocket(FileDescriptor sock, String v6, int ifindex) + throws IOException; } From 050b6529c43c8c63187ca4584226a8a0fb27a656 Mon Sep 17 00:00:00 2001 From: Hungming Chen Date: Thu, 20 Jan 2022 22:13:19 +0800 Subject: [PATCH 16/16] [CLATJ#16] Rename JNI/Dependencies function and improve JNI error handling - rename JNI functions with prefix native_* - throw exception while JNI function gets invalid address argument - throw exception while JNI function returns invalid address Bug: 212345928 Test: flash and boot Run "atest ClatCoordinatorTest" in a followup commit Change-Id: I6745a61525c6f72a61fbde0b21b3f7abcdf2446a --- ...id_server_connectivity_ClatCoordinator.cpp | 20 +++--- .../server/connectivity/ClatCoordinator.java | 70 +++++++++---------- 2 files changed, 47 insertions(+), 43 deletions(-) diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp index b37827ea28..a9d794697a 100644 --- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp +++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp @@ -15,6 +15,7 @@ */ #include +#include #include #include #include @@ -41,6 +42,7 @@ jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv ScopedUtfChars address(env, v4addr); in_addr ip; if (inet_pton(AF_INET, address.c_str(), &ip) != 1) { + throwIOException(env, "invalid address", EINVAL); return nullptr; } @@ -58,6 +60,7 @@ jstring com_android_server_connectivity_ClatCoordinator_selectIpv4Address(JNIEnv char addrstr[INET_ADDRSTRLEN]; if (!inet_ntop(AF_INET, (void*)&v4, addrstr, sizeof(addrstr))) { + throwIOException(env, "invalid address", EADDRNOTAVAIL); return nullptr; } return env->NewStringUTF(addrstr); @@ -98,6 +101,7 @@ jstring com_android_server_connectivity_ClatCoordinator_generateIpv6Address( char addrstr[INET6_ADDRSTRLEN]; if (!inet_ntop(AF_INET6, (void*)&v6, addrstr, sizeof(addrstr))) { + throwIOException(env, "invalid address", EADDRNOTAVAIL); return nullptr; } return env->NewStringUTF(addrstr); @@ -238,22 +242,22 @@ static void com_android_server_connectivity_ClatCoordinator_configurePacketSocke */ static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;", + {"native_selectIpv4Address", "(Ljava/lang/String;I)Ljava/lang/String;", (void*)com_android_server_connectivity_ClatCoordinator_selectIpv4Address}, - {"generateIpv6Address", + {"native_generateIpv6Address", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void*)com_android_server_connectivity_ClatCoordinator_generateIpv6Address}, - {"createTunInterface", "(Ljava/lang/String;)I", + {"native_createTunInterface", "(Ljava/lang/String;)I", (void*)com_android_server_connectivity_ClatCoordinator_createTunInterface}, - {"detectMtu", "(Ljava/lang/String;II)I", + {"native_detectMtu", "(Ljava/lang/String;II)I", (void*)com_android_server_connectivity_ClatCoordinator_detectMtu}, - {"openPacketSocket", "()I", + {"native_openPacketSocket", "()I", (void*)com_android_server_connectivity_ClatCoordinator_openPacketSocket}, - {"openRawSocket6", "(I)I", + {"native_openRawSocket6", "(I)I", (void*)com_android_server_connectivity_ClatCoordinator_openRawSocket6}, - {"addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", + {"native_addAnycastSetsockopt", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", (void*)com_android_server_connectivity_ClatCoordinator_addAnycastSetsockopt}, - {"configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", + {"native_configurePacketSocket", "(Ljava/io/FileDescriptor;Ljava/lang/String;I)V", (void*)com_android_server_connectivity_ClatCoordinator_configurePacketSocket}, }; diff --git a/service/src/com/android/server/connectivity/ClatCoordinator.java b/service/src/com/android/server/connectivity/ClatCoordinator.java index 2623c50988..4d243c482a 100644 --- a/service/src/com/android/server/connectivity/ClatCoordinator.java +++ b/service/src/com/android/server/connectivity/ClatCoordinator.java @@ -103,64 +103,64 @@ public class ClatCoordinator { /** * Create tun interface for a given interface name. */ - public int jniCreateTunInterface(@NonNull String tuniface) throws IOException { - return createTunInterface(tuniface); + public int createTunInterface(@NonNull String tuniface) throws IOException { + return native_createTunInterface(tuniface); } /** * Pick an IPv4 address for clat. */ @NonNull - public String jniSelectIpv4Address(@NonNull String v4addr, int prefixlen) + public String selectIpv4Address(@NonNull String v4addr, int prefixlen) throws IOException { - return selectIpv4Address(v4addr, prefixlen); + return native_selectIpv4Address(v4addr, prefixlen); } /** * Generate a checksum-neutral IID. */ @NonNull - public String jniGenerateIpv6Address(@NonNull String iface, @NonNull String v4, + public String generateIpv6Address(@NonNull String iface, @NonNull String v4, @NonNull String prefix64) throws IOException { - return generateIpv6Address(iface, v4, prefix64); + return native_generateIpv6Address(iface, v4, prefix64); } /** * Detect MTU. */ - public int jniDetectMtu(@NonNull String platSubnet, int platSuffix, int mark) + public int detectMtu(@NonNull String platSubnet, int platSuffix, int mark) throws IOException { - return detectMtu(platSubnet, platSuffix, mark); + return native_detectMtu(platSubnet, platSuffix, mark); } /** * Open packet socket. */ - public int jniOpenPacketSocket() throws IOException { - return openPacketSocket(); + public int openPacketSocket() throws IOException { + return native_openPacketSocket(); } /** * Open IPv6 raw socket and set SO_MARK. */ - public int jniOpenRawSocket6(int mark) throws IOException { - return openRawSocket6(mark); + public int openRawSocket6(int mark) throws IOException { + return native_openRawSocket6(mark); } /** * Add anycast setsockopt. */ - public void jniAddAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex) + public void addAnycastSetsockopt(@NonNull FileDescriptor sock, String v6, int ifindex) throws IOException { - addAnycastSetsockopt(sock, v6, ifindex); + native_addAnycastSetsockopt(sock, v6, ifindex); } /** * Configure packet socket. */ - public void jniConfigurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex) + public void configurePacketSocket(@NonNull FileDescriptor sock, String v6, int ifindex) throws IOException { - configurePacketSocket(sock, v6, ifindex); + native_configurePacketSocket(sock, v6, ifindex); } } @@ -203,7 +203,7 @@ public class ClatCoordinator { // [1] Pick an IPv4 address from 192.0.0.4, 192.0.0.5, 192.0.0.6 .. final String v4; try { - v4 = mDeps.jniSelectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN); + v4 = mDeps.selectIpv4Address(INIT_V4ADDR_STRING, INIT_V4ADDR_PREFIX_LEN); } catch (IOException e) { throw new IOException("no IPv4 addresses were available for clat: " + e); } @@ -212,7 +212,7 @@ public class ClatCoordinator { final String pfx96 = nat64Prefix.getAddress().getHostAddress(); final String v6; try { - v6 = mDeps.jniGenerateIpv6Address(iface, v4, pfx96); + v6 = mDeps.generateIpv6Address(iface, v4, pfx96); } catch (IOException e) { throw new IOException("no IPv6 addresses were available for clat: " + e); } @@ -222,7 +222,7 @@ public class ClatCoordinator { final String tunIface = CLAT_PREFIX + iface; final ParcelFileDescriptor tunFd; try { - tunFd = mDeps.adoptFd(mDeps.jniCreateTunInterface(tunIface)); + tunFd = mDeps.adoptFd(mDeps.createTunInterface(tunIface)); } catch (IOException e) { throw new IOException("Create tun interface " + tunIface + " failed: " + e); } @@ -236,7 +236,7 @@ public class ClatCoordinator { // Detect ipv4 mtu. final Integer fwmark = getFwmark(netId); - final int detectedMtu = mDeps.jniDetectMtu(pfx96, + final int detectedMtu = mDeps.detectMtu(pfx96, ByteBuffer.wrap(GOOGLE_DNS_4.getAddress()).getInt(), fwmark); final int mtu = adjustMtu(detectedMtu); Log.i(TAG, "ipv4 mtu is " + mtu); @@ -270,7 +270,7 @@ public class ClatCoordinator { // like to use ParcelFileDescriptor to close file descriptor automatically. But ctor // ParcelFileDescriptor(FileDescriptor fd) is a @hide function. Need to use native file // descriptor to initialize ParcelFileDescriptor object instead. - readSock6 = mDeps.adoptFd(mDeps.jniOpenPacketSocket()); + readSock6 = mDeps.adoptFd(mDeps.openPacketSocket()); } catch (IOException e) { throw new IOException("Open packet socket failed: " + e); } @@ -280,7 +280,7 @@ public class ClatCoordinator { try { // Use a JNI call to get native file descriptor instead of Os.socket(). See above // reason why we use jniOpenPacketSocket6(). - writeSock6 = mDeps.adoptFd(mDeps.jniOpenRawSocket6(fwmark)); + writeSock6 = mDeps.adoptFd(mDeps.openRawSocket6(fwmark)); } catch (IOException e) { throw new IOException("Open raw socket failed: " + e); } @@ -292,14 +292,14 @@ public class ClatCoordinator { // Start translating packets to the new prefix. try { - mDeps.jniAddAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex); + mDeps.addAnycastSetsockopt(writeSock6.getFileDescriptor(), v6, ifaceIndex); } catch (IOException e) { throw new IOException("add anycast sockopt failed: " + e); } // Update our packet socket filter to reflect the new 464xlat IP address. try { - mDeps.jniConfigurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex); + mDeps.configurePacketSocket(readSock6.getFileDescriptor(), v6, ifaceIndex); } catch (IOException e) { throw new IOException("configure packet socket failed: " + e); } @@ -308,17 +308,17 @@ public class ClatCoordinator { return null; } - private static native String selectIpv4Address(String v4addr, int prefixlen) + private static native String native_selectIpv4Address(String v4addr, int prefixlen) throws IOException; - private static native String generateIpv6Address(String iface, String v4, String prefix64) - throws IOException; - private static native int createTunInterface(String tuniface) throws IOException; - private static native int detectMtu(String platSubnet, int platSuffix, int mark) - throws IOException; - private static native int openPacketSocket() throws IOException; - private static native int openRawSocket6(int mark) throws IOException; - private static native void addAnycastSetsockopt(FileDescriptor sock, String v6, int ifindex) - throws IOException; - private static native void configurePacketSocket(FileDescriptor sock, String v6, int ifindex) + private static native String native_generateIpv6Address(String iface, String v4, + String prefix64) throws IOException; + private static native int native_createTunInterface(String tuniface) throws IOException; + private static native int native_detectMtu(String platSubnet, int platSuffix, int mark) throws IOException; + private static native int native_openPacketSocket() throws IOException; + private static native int native_openRawSocket6(int mark) throws IOException; + private static native void native_addAnycastSetsockopt(FileDescriptor sock, String v6, + int ifindex) throws IOException; + private static native void native_configurePacketSocket(FileDescriptor sock, String v6, + int ifindex) throws IOException; }