diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp index 8cf46efa9e..cf2848f4b3 100644 --- a/Tethering/apex/Android.bp +++ b/Tethering/apex/Android.bp @@ -69,7 +69,10 @@ apex { "libservice-connectivity", "libandroid_net_connectivity_com_android_net_module_util_jni", ], - native_shared_libs: ["libnetd_updatable"], + native_shared_libs: [ + "libcom.android.tethering.connectivity_native", + "libnetd_updatable", + ], }, both: { jni_libs: [ diff --git a/service/Android.bp b/service/Android.bp index d850015ad6..b13f615f74 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -22,7 +22,6 @@ package { aidl_interface { name: "connectivity_native_aidl_interface", local_include_dir: "binder", - vendor_available: true, srcs: [ "binder/android/net/connectivity/aidl/*.aidl", ], diff --git a/service/libconnectivity/Android.bp b/service/libconnectivity/Android.bp new file mode 100644 index 0000000000..391ceacab9 --- /dev/null +++ b/service/libconnectivity/Android.bp @@ -0,0 +1,57 @@ +// +// 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 { + // See: http://go/android-license-faq + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library_shared { + name: "libcom.android.tethering.connectivity_native", + srcs: [ + "src/**/*.cpp", + ], + min_sdk_version: "30", + static_libs: [ + "connectivity_native_aidl_interface-V1-ndk", + ], + export_include_dirs: ["include"], + cflags: [ + "-Wall", + "-Werror", + "-Wno-error=deprecated-declarations", + ], + + shared_libs: [ + "libbinder_ndk", + ], + llndk: { + symbol_file: "libconnectivity_native.map.txt", + }, + stubs: { + symbol_file: "libconnectivity_native.map.txt", + versions: [ + "current", + ], + }, + header_abi_checker: { + enabled: true, + symbol_file: "libconnectivity_native.map.txt", + }, + apex_available: [ + "com.android.tethering", + ], +} diff --git a/service/libconnectivity/include/connectivity_native.h b/service/libconnectivity/include/connectivity_native.h new file mode 100644 index 0000000000..5a2509af4c --- /dev/null +++ b/service/libconnectivity/include/connectivity_native.h @@ -0,0 +1,87 @@ +/* + * 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. + */ + +#ifndef LIBCONNECTIVITY_CONNECTIVITY_NATIVE_H_ +#define LIBCONNECTIVITY_CONNECTIVITY_NATIVE_H_ + +#include +#include + +// For branches that do not yet have __ANDROID_API_U__ defined, like module +// release branches. +#ifndef __ANDROID_API_U__ +#define __ANDROID_API_U__ 34 +#endif + +__BEGIN_DECLS + +/** + * Blocks a port from being assigned during bind(). The caller is responsible for updating + * /proc/sys/net/ipv4/ip_local_port_range with the port being blocked so that calls to connect() + * will not automatically assign one of the blocked ports. + * Will return success even if port was already blocked. + * + * Returns 0 on success, or a POSIX error code (see errno.h) on failure: + * - EINVAL for invalid port number + * - EPERM if the UID of the client doesn't have network stack permission + * - Other errors as per https://man7.org/linux/man-pages/man2/bpf.2.html + * + * @param port Int corresponding to port number. + */ +int AConnectivityNative_blockPortForBind(in_port_t port) __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Unblocks a port that has previously been blocked. + * Will return success even if port was already unblocked. + * + * Returns 0 on success, or a POSIX error code (see errno.h) on failure: + * - EINVAL for invalid port number + * - EPERM if the UID of the client doesn't have network stack permission + * - Other errors as per https://man7.org/linux/man-pages/man2/bpf.2.html + * + * @param port Int corresponding to port number. + */ +int AConnectivityNative_unblockPortForBind(in_port_t port) __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Unblocks all ports that have previously been blocked. + * + * Returns 0 on success, or a POSIX error code (see errno.h) on failure: + * - EINVAL for invalid port number + * - EPERM if the UID of the client doesn't have network stack permission + * - Other errors as per https://man7.org/linux/man-pages/man2/bpf.2.html + */ +int AConnectivityNative_unblockAllPortsForBind() __INTRODUCED_IN(__ANDROID_API_U__); + +/** + * Gets the list of ports that have been blocked. + * + * Returns 0 on success, or a POSIX error code (see errno.h) on failure: + * - EINVAL for invalid port number + * - EPERM if the UID of the client doesn't have network stack permission + * - Other errors as per https://man7.org/linux/man-pages/man2/bpf.2.html + * + * @param ports Array of ports that will be filled with the port numbers. + * @param count Pointer to the size of the ports array; the value will be set to the total number of + * blocked ports, which may be larger than the ports array that was filled. + */ +int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count) + __INTRODUCED_IN(__ANDROID_API_U__); + +__END_DECLS + + +#endif diff --git a/service/libconnectivity/libconnectivity_native.map.txt b/service/libconnectivity/libconnectivity_native.map.txt new file mode 100644 index 0000000000..19b1074bd1 --- /dev/null +++ b/service/libconnectivity/libconnectivity_native.map.txt @@ -0,0 +1,24 @@ +# +# 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. +# +LIBCONNECTIVITY_NATIVE { + global: + AConnectivityNative_blockPortForBind; # apex llndk + AConnectivityNative_getPortsBlockedForBind; # apex llndk + AConnectivityNative_unblockPortForBind; # apex llndk + AConnectivityNative_unblockAllPortsForBind; # apex llndk + local: + *; +}; diff --git a/service/libconnectivity/src/connectivity_native.cpp b/service/libconnectivity/src/connectivity_native.cpp new file mode 100644 index 0000000000..9545ed1ec0 --- /dev/null +++ b/service/libconnectivity/src/connectivity_native.cpp @@ -0,0 +1,74 @@ +/* + * 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 "connectivity_native.h" + +#include +#include + +using aidl::android::net::connectivity::aidl::IConnectivityNative; + + +static std::shared_ptr getBinder() { + static ndk::SpAIBinder sBinder = ndk::SpAIBinder(reinterpret_cast( + AServiceManager_getService("connectivity_native"))); + return aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder); +} + +static int getErrno(const ::ndk::ScopedAStatus& status) { + switch (status.getExceptionCode()) { + case EX_NONE: + return 0; + case EX_ILLEGAL_ARGUMENT: + return EINVAL; + case EX_SECURITY: + return EPERM; + case EX_SERVICE_SPECIFIC: + return status.getServiceSpecificError(); + default: + return EPROTO; + } +} + +int AConnectivityNative_blockPortForBind(in_port_t port) { + std::shared_ptr c = getBinder(); + return getErrno(c->blockPortForBind(port)); +} + +int AConnectivityNative_unblockPortForBind(in_port_t port) { + std::shared_ptr c = getBinder(); + return getErrno(c->unblockPortForBind(port)); +} + +int AConnectivityNative_unblockAllPortsForBind() { + std::shared_ptr c = getBinder(); + return getErrno(c->unblockAllPortsForBind()); +} + +int AConnectivityNative_getPortsBlockedForBind(in_port_t *ports, size_t *count) { + std::shared_ptr c = getBinder(); + std::vector actualBlockedPorts; + int err = getErrno(c->getPortsBlockedForBind(&actualBlockedPorts)); + if (err) { + return err; + } + + for (int i = 0; i < *count && i < actualBlockedPorts.size(); i++) { + ports[i] = actualBlockedPorts[i]; + } + *count = actualBlockedPorts.size(); + return 0; +} diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp index 7d43aa8f4b..8825aa41e6 100644 --- a/tests/native/connectivity_native_test/Android.bp +++ b/tests/native/connectivity_native_test/Android.bp @@ -10,7 +10,7 @@ cc_test { "vts", ], test_config_template: "AndroidTestTemplate.xml", - min_sdk_version: "31", + min_sdk_version: "34", tidy: false, srcs: [ "connectivity_native_test.cpp", diff --git a/tests/native/connectivity_native_test/connectivity_native_test.cpp b/tests/native/connectivity_native_test/connectivity_native_test.cpp index 3db5265f85..27a9d35518 100644 --- a/tests/native/connectivity_native_test/connectivity_native_test.cpp +++ b/tests/native/connectivity_native_test/connectivity_native_test.cpp @@ -14,62 +14,81 @@ * limitations under the License. */ -#include #include #include #include #include // FIRST_APPLICATION_UID +#include #include #include #include "bpf/BpfUtils.h" -using aidl::android::net::connectivity::aidl::IConnectivityNative; +typedef int (*GetPortsBlockedForBind)(in_port_t*, size_t*); +GetPortsBlockedForBind getPortsBlockedForBind; +typedef int (*BlockPortForBind)(in_port_t); +BlockPortForBind blockPortForBind; +typedef int (*UnblockPortForBind)(in_port_t); +UnblockPortForBind unblockPortForBind; +typedef int (*UnblockAllPortsForBind)(); +UnblockAllPortsForBind unblockAllPortsForBind; class ConnectivityNativeBinderTest : public ::testing::Test { public: - std::vector mActualBlockedPorts; - - ConnectivityNativeBinderTest() { - AIBinder* binder = AServiceManager_getService("connectivity_native"); - ndk::SpAIBinder sBinder = ndk::SpAIBinder(binder); - mService = aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder); - } + in_port_t mActualBlockedPorts[65535]; + size_t mActualBlockedPortsCount = 65535; + bool restoreBlockedPorts; void SetUp() override { - // Skip test case if not on T. - if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << + restoreBlockedPorts = false; + // Skip test case if not on U. + if (!android::modules::sdklevel::IsAtLeastU()) GTEST_SKIP() << "Should be at least T device."; // Skip test case if not on 5.4 kernel which is required by bpf prog. if (!android::bpf::isAtLeastKernelVersion(5, 4, 0)) GTEST_SKIP() << "Kernel should be at least 5.4."; - ASSERT_NE(nullptr, mService.get()); + // Necessary to use dlopen/dlsym since the lib is only available on U and there + // is no Sdk34ModuleController in tradefed yet. + // TODO: link against the library directly and add Sdk34ModuleController to + // AndroidTest.txml when available. + void* nativeLib = dlopen("libcom.android.tethering.connectivity_native.so", RTLD_NOW); + ASSERT_NE(nullptr, nativeLib); + getPortsBlockedForBind = reinterpret_cast( + dlsym(nativeLib, "AConnectivityNative_getPortsBlockedForBind")); + ASSERT_NE(nullptr, getPortsBlockedForBind); + blockPortForBind = reinterpret_cast( + dlsym(nativeLib, "AConnectivityNative_blockPortForBind")); + ASSERT_NE(nullptr, blockPortForBind); + unblockPortForBind = reinterpret_cast( + dlsym(nativeLib, "AConnectivityNative_unblockPortForBind")); + ASSERT_NE(nullptr, unblockPortForBind); + unblockAllPortsForBind = reinterpret_cast( + dlsym(nativeLib, "AConnectivityNative_unblockAllPortsForBind")); + ASSERT_NE(nullptr, unblockAllPortsForBind); // If there are already ports being blocked on device unblockAllPortsForBind() store // the currently blocked ports and add them back at the end of the test. Do this for // every test case so additional test cases do not forget to add ports back. - ndk::ScopedAStatus status = mService->getPortsBlockedForBind(&mActualBlockedPorts); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - + int err = getPortsBlockedForBind(mActualBlockedPorts, &mActualBlockedPortsCount); + EXPECT_EQ(err, 0); + restoreBlockedPorts = true; } void TearDown() override { - ndk::ScopedAStatus status; - if (mActualBlockedPorts.size() > 0) { - for (int i : mActualBlockedPorts) { - mService->blockPortForBind(i); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + int err; + if (mActualBlockedPortsCount > 0 && restoreBlockedPorts) { + for (int i=0; i < mActualBlockedPortsCount; i++) { + err = blockPortForBind(mActualBlockedPorts[i]); + EXPECT_EQ(err, 0); } } } protected: - std::shared_ptr mService; - void runSocketTest (sa_family_t family, const int type, bool blockPort) { - ndk::ScopedAStatus status; + int err; in_port_t port = 0; int sock, sock2; // Open two sockets with SO_REUSEADDR and expect they can both bind to port. @@ -79,16 +98,16 @@ class ConnectivityNativeBinderTest : public ::testing::Test { int blockedPort = 0; if (blockPort) { blockedPort = ntohs(port); - status = mService->blockPortForBind(blockedPort); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + err = blockPortForBind(blockedPort); + EXPECT_EQ(err, 0); } int sock3 = openSocket(&port, family, type, blockPort /* expectBindFail */); if (blockPort) { EXPECT_EQ(-1, sock3); - status = mService->unblockPortForBind(blockedPort); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + err = unblockPortForBind(blockedPort); + EXPECT_EQ(err, 0); } else { EXPECT_NE(-1, sock3); } @@ -177,110 +196,74 @@ TEST_F(ConnectivityNativeBinderTest, BlockPort6Tcp) { } TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) { - ndk::ScopedAStatus status = mService->blockPortForBind(5555); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - status = mService->blockPortForBind(5555); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - status = mService->unblockPortForBind(5555); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + int err = blockPortForBind(5555); + EXPECT_EQ(err, 0); + err = blockPortForBind(5555); + EXPECT_EQ(err, 0); + err = unblockPortForBind(5555); + EXPECT_EQ(err, 0); } TEST_F(ConnectivityNativeBinderTest, GetBlockedPorts) { - ndk::ScopedAStatus status; - std::vector blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; + int err; + in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; + + if (mActualBlockedPortsCount > 0) { + err = unblockAllPortsForBind(); + } + for (int i : blockedPorts) { - status = mService->blockPortForBind(i); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + err = blockPortForBind(i); + EXPECT_EQ(err, 0); + } + size_t actualBlockedPortsCount = 8; + in_port_t actualBlockedPorts[actualBlockedPortsCount]; + err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount); + EXPECT_EQ(err, 0); + EXPECT_NE(actualBlockedPortsCount, 0); + for (int i=0; i < actualBlockedPortsCount; i++) { + EXPECT_EQ(blockedPorts[i], actualBlockedPorts[i]); } - std::vector actualBlockedPorts; - status = mService->getPortsBlockedForBind(&actualBlockedPorts); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - EXPECT_FALSE(actualBlockedPorts.empty()); - EXPECT_EQ(blockedPorts, actualBlockedPorts); // Remove the ports we added. - status = mService->unblockAllPortsForBind(); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - status = mService->getPortsBlockedForBind(&actualBlockedPorts); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - EXPECT_TRUE(actualBlockedPorts.empty()); + err = unblockAllPortsForBind(); + EXPECT_EQ(err, 0); + err = getPortsBlockedForBind(actualBlockedPorts, &actualBlockedPortsCount); + EXPECT_EQ(err, 0); + EXPECT_EQ(actualBlockedPortsCount, 0); } TEST_F(ConnectivityNativeBinderTest, UnblockAllPorts) { - ndk::ScopedAStatus status; - std::vector blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; + int err; + in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; - if (mActualBlockedPorts.size() > 0) { - status = mService->unblockAllPortsForBind(); + if (mActualBlockedPortsCount > 0) { + err = unblockAllPortsForBind(); } for (int i : blockedPorts) { - status = mService->blockPortForBind(i); - EXPECT_TRUE(status.isOk()) << status.getDescription (); + err = blockPortForBind(i); + EXPECT_EQ(err, 0); } - std::vector actualBlockedPorts; - status = mService->getPortsBlockedForBind(&actualBlockedPorts); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - EXPECT_FALSE(actualBlockedPorts.empty()); + size_t actualBlockedPortsCount = 8; + in_port_t actualBlockedPorts[actualBlockedPortsCount]; + err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount); + EXPECT_EQ(err, 0); + EXPECT_EQ(actualBlockedPortsCount, 8); - status = mService->unblockAllPortsForBind(); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - status = mService->getPortsBlockedForBind(&actualBlockedPorts); - EXPECT_TRUE(status.isOk()) << status.getDescription (); - EXPECT_TRUE(actualBlockedPorts.empty()); + err = unblockAllPortsForBind(); + EXPECT_EQ(err, 0); + err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount); + EXPECT_EQ(err, 0); + EXPECT_EQ(actualBlockedPortsCount, 0); // If mActualBlockedPorts is not empty, ports will be added back in teardown. } -TEST_F(ConnectivityNativeBinderTest, BlockNegativePort) { - int retry = 0; - ndk::ScopedAStatus status; - do { - status = mService->blockPortForBind(-1); - // TODO: find out why transaction failed is being thrown on the first attempt. - } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); - EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); -} - -TEST_F(ConnectivityNativeBinderTest, UnblockNegativePort) { - int retry = 0; - ndk::ScopedAStatus status; - do { - status = mService->unblockPortForBind(-1); - // TODO: find out why transaction failed is being thrown on the first attempt. - } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); - EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); -} - -TEST_F(ConnectivityNativeBinderTest, BlockMaxPort) { - int retry = 0; - ndk::ScopedAStatus status; - do { - status = mService->blockPortForBind(65536); - // TODO: find out why transaction failed is being thrown on the first attempt. - } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); - EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); -} - -TEST_F(ConnectivityNativeBinderTest, UnblockMaxPort) { - int retry = 0; - ndk::ScopedAStatus status; - do { - status = mService->unblockPortForBind(65536); - // TODO: find out why transaction failed is being thrown on the first attempt. - } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); - EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); -} - TEST_F(ConnectivityNativeBinderTest, CheckPermission) { - int retry = 0; int curUid = getuid(); EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno); - ndk::ScopedAStatus status; - do { - status = mService->blockPortForBind(5555); - // TODO: find out why transaction failed is being thrown on the first attempt. - } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); - EXPECT_EQ(EX_SECURITY, status.getExceptionCode()); + int err = blockPortForBind((in_port_t) 5555); + EXPECT_EQ(EPERM, err); EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno); }