Tyler Wear | b37f551 | 2021-10-01 13:22:00 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright (C) 2022 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include <android-modules-utils/sdk_level.h> |
| 18 | #include <cutils/misc.h> // FIRST_APPLICATION_UID |
| 19 | #include <gtest/gtest.h> |
| 20 | #include <netinet/in.h> |
| 21 | #include <android/binder_manager.h> |
| 22 | #include <android/binder_process.h> |
| 23 | |
| 24 | #include <aidl/android/net/connectivity/aidl/ConnectivityNative.h> |
| 25 | |
| 26 | using aidl::android::net::connectivity::aidl::IConnectivityNative; |
| 27 | |
| 28 | class ConnectivityNativeBinderTest : public ::testing::Test { |
| 29 | public: |
| 30 | std::vector<int32_t> mActualBlockedPorts; |
| 31 | |
| 32 | ConnectivityNativeBinderTest() { |
| 33 | AIBinder* binder = AServiceManager_getService("connectivity_native"); |
| 34 | ndk::SpAIBinder sBinder = ndk::SpAIBinder(binder); |
| 35 | mService = aidl::android::net::connectivity::aidl::IConnectivityNative::fromBinder(sBinder); |
| 36 | } |
| 37 | |
| 38 | void SetUp() override { |
| 39 | // Skip test case if not on T. |
| 40 | if (!android::modules::sdklevel::IsAtLeastT()) GTEST_SKIP() << |
| 41 | "Should be at least T device."; |
| 42 | |
| 43 | ASSERT_NE(nullptr, mService.get()); |
| 44 | |
| 45 | // If there are already ports being blocked on device unblockAllPortsForBind() store |
| 46 | // the currently blocked ports and add them back at the end of the test. Do this for |
| 47 | // every test case so additional test cases do not forget to add ports back. |
| 48 | ndk::ScopedAStatus status = mService->getPortsBlockedForBind(&mActualBlockedPorts); |
| 49 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 50 | |
| 51 | } |
| 52 | |
| 53 | void TearDown() override { |
| 54 | ndk::ScopedAStatus status; |
| 55 | if (mActualBlockedPorts.size() > 0) { |
| 56 | for (int i : mActualBlockedPorts) { |
| 57 | mService->blockPortForBind(i); |
| 58 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | protected: |
| 64 | std::shared_ptr<IConnectivityNative> mService; |
| 65 | |
| 66 | void runSocketTest (sa_family_t family, const int type, bool blockPort) { |
| 67 | ndk::ScopedAStatus status; |
| 68 | in_port_t port = 0; |
| 69 | int sock, sock2; |
| 70 | // Open two sockets with SO_REUSEADDR and expect they can both bind to port. |
| 71 | sock = openSocket(&port, family, type, false /* expectBindFail */); |
| 72 | sock2 = openSocket(&port, family, type, false /* expectBindFail */); |
| 73 | |
| 74 | int blockedPort = 0; |
| 75 | if (blockPort) { |
| 76 | blockedPort = ntohs(port); |
| 77 | status = mService->blockPortForBind(blockedPort); |
| 78 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 79 | } |
| 80 | |
| 81 | int sock3 = openSocket(&port, family, type, blockPort /* expectBindFail */); |
| 82 | |
| 83 | if (blockPort) { |
| 84 | EXPECT_EQ(-1, sock3); |
| 85 | status = mService->unblockPortForBind(blockedPort); |
| 86 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 87 | } else { |
| 88 | EXPECT_NE(-1, sock3); |
| 89 | } |
| 90 | |
| 91 | close(sock); |
| 92 | close(sock2); |
| 93 | close(sock3); |
| 94 | } |
| 95 | |
| 96 | /* |
| 97 | * Open the socket and update the port. |
| 98 | */ |
| 99 | int openSocket(in_port_t* port, sa_family_t family, const int type, bool expectBindFail) { |
| 100 | int ret = 0; |
| 101 | int enable = 1; |
| 102 | const int sock = socket(family, type, 0); |
| 103 | ret = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); |
| 104 | EXPECT_EQ(0, ret); |
| 105 | |
| 106 | if (family == AF_INET) { |
| 107 | struct sockaddr_in addr4 = { .sin_family = family, .sin_port = htons(*port) }; |
| 108 | ret = bind(sock, (struct sockaddr*) &addr4, sizeof(addr4)); |
| 109 | } else { |
| 110 | struct sockaddr_in6 addr6 = { .sin6_family = family, .sin6_port = htons(*port) }; |
| 111 | ret = bind(sock, (struct sockaddr*) &addr6, sizeof(addr6)); |
| 112 | } |
| 113 | |
| 114 | if (expectBindFail) { |
| 115 | EXPECT_NE(0, ret); |
| 116 | // If port is blocked, return here since the port is not needed |
| 117 | // for subsequent sockets. |
| 118 | close(sock); |
| 119 | return -1; |
| 120 | } |
| 121 | EXPECT_EQ(0, ret) << "bind unexpectedly failed, errno: " << errno; |
| 122 | |
| 123 | if (family == AF_INET) { |
| 124 | struct sockaddr_in sin; |
| 125 | socklen_t len = sizeof(sin); |
| 126 | EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len)); |
| 127 | EXPECT_NE(0, ntohs(sin.sin_port)); |
| 128 | if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin_port)); |
| 129 | *port = ntohs(sin.sin_port); |
| 130 | } else { |
| 131 | struct sockaddr_in6 sin; |
| 132 | socklen_t len = sizeof(sin); |
| 133 | EXPECT_NE(-1, getsockname(sock, (struct sockaddr *)&sin, &len)); |
| 134 | EXPECT_NE(0, ntohs(sin.sin6_port)); |
| 135 | if (*port != 0) EXPECT_EQ(*port, ntohs(sin.sin6_port)); |
| 136 | *port = ntohs(sin.sin6_port); |
| 137 | } |
| 138 | return sock; |
| 139 | } |
| 140 | }; |
| 141 | |
| 142 | TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Udp) { |
| 143 | runSocketTest(AF_INET, SOCK_DGRAM, false); |
| 144 | } |
| 145 | |
| 146 | TEST_F(ConnectivityNativeBinderTest, PortUnblockedV4Tcp) { |
| 147 | runSocketTest(AF_INET, SOCK_STREAM, false); |
| 148 | } |
| 149 | |
| 150 | TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Udp) { |
| 151 | runSocketTest(AF_INET6, SOCK_DGRAM, false); |
| 152 | } |
| 153 | |
| 154 | TEST_F(ConnectivityNativeBinderTest, PortUnblockedV6Tcp) { |
| 155 | runSocketTest(AF_INET6, SOCK_STREAM, false); |
| 156 | } |
| 157 | |
| 158 | TEST_F(ConnectivityNativeBinderTest, BlockPort4Udp) { |
| 159 | runSocketTest(AF_INET, SOCK_DGRAM, true); |
| 160 | } |
| 161 | |
| 162 | TEST_F(ConnectivityNativeBinderTest, BlockPort4Tcp) { |
| 163 | runSocketTest(AF_INET, SOCK_STREAM, true); |
| 164 | } |
| 165 | |
| 166 | TEST_F(ConnectivityNativeBinderTest, BlockPort6Udp) { |
| 167 | runSocketTest(AF_INET6, SOCK_DGRAM, true); |
| 168 | } |
| 169 | |
| 170 | TEST_F(ConnectivityNativeBinderTest, BlockPort6Tcp) { |
| 171 | runSocketTest(AF_INET6, SOCK_STREAM, true); |
| 172 | } |
| 173 | |
| 174 | TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) { |
| 175 | ndk::ScopedAStatus status = mService->blockPortForBind(5555); |
| 176 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 177 | status = mService->blockPortForBind(5555); |
| 178 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 179 | status = mService->unblockPortForBind(5555); |
| 180 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 181 | } |
| 182 | |
| 183 | TEST_F(ConnectivityNativeBinderTest, GetBlockedPorts) { |
| 184 | ndk::ScopedAStatus status; |
| 185 | std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; |
| 186 | for (int i : blockedPorts) { |
| 187 | status = mService->blockPortForBind(i); |
| 188 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 189 | } |
| 190 | std::vector<int32_t> actualBlockedPorts; |
| 191 | status = mService->getPortsBlockedForBind(&actualBlockedPorts); |
| 192 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 193 | EXPECT_FALSE(actualBlockedPorts.empty()); |
| 194 | EXPECT_EQ(blockedPorts, actualBlockedPorts); |
| 195 | |
| 196 | // Remove the ports we added. |
| 197 | status = mService->unblockAllPortsForBind(); |
| 198 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 199 | status = mService->getPortsBlockedForBind(&actualBlockedPorts); |
| 200 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 201 | EXPECT_TRUE(actualBlockedPorts.empty()); |
| 202 | } |
| 203 | |
| 204 | TEST_F(ConnectivityNativeBinderTest, UnblockAllPorts) { |
| 205 | ndk::ScopedAStatus status; |
| 206 | std::vector<int> blockedPorts{1, 100, 1220, 1333, 2700, 5555, 5600, 65000}; |
| 207 | |
| 208 | if (mActualBlockedPorts.size() > 0) { |
| 209 | status = mService->unblockAllPortsForBind(); |
| 210 | } |
| 211 | |
| 212 | for (int i : blockedPorts) { |
| 213 | status = mService->blockPortForBind(i); |
| 214 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 215 | } |
| 216 | |
| 217 | std::vector<int32_t> actualBlockedPorts; |
| 218 | status = mService->getPortsBlockedForBind(&actualBlockedPorts); |
| 219 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 220 | EXPECT_FALSE(actualBlockedPorts.empty()); |
| 221 | |
| 222 | status = mService->unblockAllPortsForBind(); |
| 223 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 224 | status = mService->getPortsBlockedForBind(&actualBlockedPorts); |
| 225 | EXPECT_TRUE(status.isOk()) << status.getDescription (); |
| 226 | EXPECT_TRUE(actualBlockedPorts.empty()); |
| 227 | // If mActualBlockedPorts is not empty, ports will be added back in teardown. |
| 228 | } |
| 229 | |
| 230 | TEST_F(ConnectivityNativeBinderTest, BlockNegativePort) { |
| 231 | int retry = 0; |
| 232 | ndk::ScopedAStatus status; |
| 233 | do { |
| 234 | status = mService->blockPortForBind(-1); |
| 235 | // TODO: find out why transaction failed is being thrown on the first attempt. |
| 236 | } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); |
| 237 | EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); |
| 238 | } |
| 239 | |
| 240 | TEST_F(ConnectivityNativeBinderTest, UnblockNegativePort) { |
| 241 | int retry = 0; |
| 242 | ndk::ScopedAStatus status; |
| 243 | do { |
| 244 | status = mService->unblockPortForBind(-1); |
| 245 | // TODO: find out why transaction failed is being thrown on the first attempt. |
| 246 | } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); |
| 247 | EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); |
| 248 | } |
| 249 | |
| 250 | TEST_F(ConnectivityNativeBinderTest, BlockMaxPort) { |
| 251 | int retry = 0; |
| 252 | ndk::ScopedAStatus status; |
| 253 | do { |
| 254 | status = mService->blockPortForBind(65536); |
| 255 | // TODO: find out why transaction failed is being thrown on the first attempt. |
| 256 | } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); |
| 257 | EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); |
| 258 | } |
| 259 | |
| 260 | TEST_F(ConnectivityNativeBinderTest, UnblockMaxPort) { |
| 261 | int retry = 0; |
| 262 | ndk::ScopedAStatus status; |
| 263 | do { |
| 264 | status = mService->unblockPortForBind(65536); |
| 265 | // TODO: find out why transaction failed is being thrown on the first attempt. |
| 266 | } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); |
| 267 | EXPECT_EQ(EX_ILLEGAL_ARGUMENT, status.getExceptionCode()); |
| 268 | } |
| 269 | |
| 270 | TEST_F(ConnectivityNativeBinderTest, CheckPermission) { |
| 271 | int retry = 0; |
| 272 | int curUid = getuid(); |
| 273 | EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno); |
| 274 | ndk::ScopedAStatus status; |
| 275 | do { |
| 276 | status = mService->blockPortForBind(5555); |
| 277 | // TODO: find out why transaction failed is being thrown on the first attempt. |
| 278 | } while (status.getExceptionCode() == EX_TRANSACTION_FAILED && retry++ < 5); |
| 279 | EXPECT_EQ(EX_SECURITY, status.getExceptionCode()); |
| 280 | EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno); |
| 281 | } |