diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index 5e6584996b..7ae1425fca 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -324,6 +324,13 @@ default + + android.hardware.net.nlinterceptor + + IInterceptor + default + + android.hardware.oemlock 1 diff --git a/wifi/netlinkinterceptor/aidl/default/Android.bp b/wifi/netlinkinterceptor/aidl/default/Android.bp new file mode 100644 index 0000000000..686ff19c3d --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/Android.bp @@ -0,0 +1,39 @@ +// +// Copyright (C) 2021 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. +// + +cc_binary { + name: "android.hardware.net.nlinterceptor-service.default", + init_rc: ["nlinterceptor-default.rc"], + vintf_fragments: ["nlinterceptor-default.xml"], + vendor: true, + relative_install_path: "hw", + defaults: ["nlinterceptor@defaults"], + shared_libs: [ + "android.hardware.net.nlinterceptor-V1-ndk", + "libbase", + "libbinder_ndk", + ], + static_libs: [ + "libnlinterceptor", + "libnl++", + ], + srcs: [ + "InterceptorRelay.cpp", + "NetlinkInterceptor.cpp", + "service.cpp", + "util.cpp", + ], +} diff --git a/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.cpp b/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.cpp new file mode 100644 index 0000000000..ded9122a6f --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2021 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 "InterceptorRelay.h" + +#include +#include +#include + +#include + +#include "util.h" + +namespace android::nlinterceptor { +using namespace std::chrono_literals; + +static constexpr std::chrono::milliseconds kPollTimeout = 300ms; +static constexpr bool kSuperVerbose = true; + +InterceptorRelay::InterceptorRelay(uint32_t nlFamily, uint32_t clientNlPid, + const std::string& clientName) + : mClientName(clientName), + mNlSocket(std::make_optional(nlFamily, 0, 0)), + mClientNlPid(clientNlPid) {} + +InterceptorRelay::~InterceptorRelay() { + mRunning = false; + if (mRelayThread.joinable()) mRelayThread.join(); +} + +uint32_t InterceptorRelay::getPid() { + auto pidMaybe = mNlSocket->getPid(); + CHECK(pidMaybe.has_value()) << "Failed to get pid of nl::Socket!"; + return *pidMaybe; +} + +void InterceptorRelay::relayMessages() { + pollfd fds[] = { + mNlSocket->preparePoll(POLLIN), + }; + while (mRunning) { + if (poll(fds, countof(fds), kPollTimeout.count()) < 0) { + PLOG(FATAL) << "poll failed"; + return; + } + const auto nlsockEvents = fds[0].revents; + + if (isSocketBad(nlsockEvents)) { + LOG(ERROR) << "Netlink socket is bad"; + mRunning = false; + return; + } + if (!isSocketReadable(nlsockEvents)) continue; + + const auto [msgMaybe, sa] = mNlSocket->receiveFrom(); + if (!msgMaybe.has_value()) { + LOG(ERROR) << "Failed to receive Netlink data!"; + mRunning = false; + return; + } + const auto msg = *msgMaybe; + if (!msg.firstOk()) { + LOG(ERROR) << "Netlink packet is malformed!"; + // Test messages might be empty, this isn't fatal. + continue; + } + if constexpr (kSuperVerbose) { + LOG(VERBOSE) << "[" << mClientName + << "] nlMsg: " << nl::toString(msg, NETLINK_GENERIC); + } + + uint32_t destinationPid = 0; + if (sa.nl_pid == 0) { + destinationPid = mClientNlPid; + } + + if (!mNlSocket->send(msg, destinationPid)) { + LOG(ERROR) << "Failed to send Netlink message!"; + mRunning = false; + return; + } + } + LOG(VERBOSE) << "[" << mClientName << "] Exiting relay thread!"; +} + +bool InterceptorRelay::start() { + if (mRunning) { + LOG(ERROR) + << "Can't relay messages: InterceptorRelay is already running!"; + return false; + } + if (mRelayThread.joinable()) { + LOG(ERROR) << "relay thread is already running!"; + return false; + } + if (!mNlSocket.has_value()) { + LOG(ERROR) << "Netlink socket not initialized!"; + return false; + } + + mRunning = true; + mRelayThread = std::thread(&InterceptorRelay::relayMessages, this); + + LOG(VERBOSE) << "Relay threads initialized"; + return true; +} + +bool InterceptorRelay::subscribeGroup(uint32_t nlGroup) { + return mNlSocket->addMembership(nlGroup); +} + +bool InterceptorRelay::unsubscribeGroup(uint32_t nlGroup) { + return mNlSocket->dropMembership(nlGroup); +} + +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.h b/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.h new file mode 100644 index 0000000000..0178c90e1e --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/InterceptorRelay.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2021 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 +#include + +namespace android::nlinterceptor { + +class InterceptorRelay { + public: + /** + * Wrapper around the netlink socket and thread which relays messages. + * + * \param nlFamily - netlink family to use for the netlink socket. + * \param clientNlPid - pid of the client netlink socket. + * \param clientName - name of the client to be used for debugging. + */ + InterceptorRelay(uint32_t nlFamily, uint32_t clientNlPid, + const std::string& clientName); + + /** + * Stops the relay thread if running and destroys itself. + */ + ~InterceptorRelay(); + + /** + * Returns the PID of the internal Netlink socket. + * + * \return value of PID, + */ + uint32_t getPid(); + + /** + * Spawns relay thread. + */ + bool start(); + + /** + * Subscribes the internal socket to a single Netlink multicast group. + * + * \param nlGroup - Netlink group to subscribe to. + * \returns - true for success, false for failure. + */ + bool subscribeGroup(uint32_t nlGroup); + + /** + * Unsubscribes the internal socket from a single Netlink multicast group. + * + * \param nlGroup - Netlink group to unsubscribe from. + * \returns - true for success, false for failure. + */ + bool unsubscribeGroup(uint32_t nlGroup); + + private: + std::string mClientName; ///< Name of client (Wificond, for example). + std::optional mNlSocket; + const uint32_t mClientNlPid = 0; ///< pid of client NL socket. + + /** + * If set to true, the relay thread should be running. Setting this to false + * stops the relay thread. + */ + std::atomic_bool mRunning = false; + + /** + * Reads incoming Netlink messages destined for mNlSocket. If from the + * kernel, the message is relayed to the client specified in the + * constructor. Otherwise, the message is relayed to the kernel. This will + * run as long as mRunning is set to true. + */ + void relayMessages(); + + std::thread mRelayThread; +}; + +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.cpp b/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.cpp new file mode 100644 index 0000000000..908ecf22de --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.cpp @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2021 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 "NetlinkInterceptor.h" + +#include +#include + +namespace android::nlinterceptor { + +ndk::ScopedAStatus NetlinkInterceptor::createSocket( + int32_t nlFamilyAidl, int32_t clientNlPidAidl, + const std::string& clientName, AidlInterceptedSocket* interceptedSocket) { + auto nlFamily = static_cast(nlFamilyAidl); + auto clientNlPid = static_cast(clientNlPidAidl); + uint32_t interceptorNlPid = 0; + + std::unique_ptr interceptor = + std::make_unique(nlFamily, clientNlPid, clientName); + + interceptorNlPid = interceptor->getPid(); + + if (interceptorNlPid == 0) { + LOG(ERROR) << "Failed to create a Netlink socket for " << clientName + << ", " << nlFamily << ":" << clientNlPid; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + if (mClientMap.find({nlFamily, interceptorNlPid}) != mClientMap.end()) { + LOG(ERROR) << "A socket with pid " << interceptorNlPid + << " already exists!"; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + if (!interceptor->start()) { + LOG(ERROR) << "Failed to start interceptor thread!"; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + if (!mClientMap + .emplace(InterceptedSocket(nlFamily, interceptorNlPid), + std::move(interceptor)) + .second) { + // If this happens, it is very bad. + LOG(FATAL) << "Failed to insert interceptor instance with pid " + << interceptorNlPid << " into map!"; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + interceptedSocket->nlFamily = nlFamily; + interceptedSocket->portId = interceptorNlPid; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus NetlinkInterceptor::closeSocket( + const AidlInterceptedSocket& interceptedSocket) { + InterceptedSocket sock(interceptedSocket); + + auto interceptorIt = mClientMap.find(sock); + if (interceptorIt == mClientMap.end()) { + LOG(ERROR) << "closeSocket Failed! No such socket " << sock; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + mClientMap.erase(interceptorIt); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus NetlinkInterceptor::subscribeGroup( + const AidlInterceptedSocket& interceptedSocket, int32_t nlGroupAidl) { + InterceptedSocket sock(interceptedSocket); + auto nlGroup = static_cast(nlGroupAidl); + + auto interceptorIt = mClientMap.find(sock); + if (interceptorIt == mClientMap.end()) { + LOG(ERROR) << "subscribeGroup failed! No such socket " << sock; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + auto& interceptor = interceptorIt->second; + if (!interceptor->subscribeGroup(nlGroup)) { + LOG(ERROR) << "Failed to subscribe " << sock << " to " << nlGroup; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus NetlinkInterceptor::unsubscribeGroup( + const AidlInterceptedSocket& interceptedSocket, int32_t nlGroupAidl) { + InterceptedSocket sock(interceptedSocket); + auto nlGroup = static_cast(nlGroupAidl); + + auto interceptorIt = mClientMap.find(sock); + if (interceptorIt == mClientMap.end()) { + LOG(ERROR) << "unsubscribeGroup failed! No such socket " << sock; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + + auto& interceptor = interceptorIt->second; + if (!interceptor->unsubscribeGroup(nlGroup)) { + LOG(ERROR) << "Failed to unsubscribe " << sock << " from " << nlGroup; + return ndk::ScopedAStatus(AStatus_fromStatus(::android::UNKNOWN_ERROR)); + } + return ndk::ScopedAStatus::ok(); +} +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.h b/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.h new file mode 100644 index 0000000000..8345654fa7 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/NetlinkInterceptor.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2021 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 + +#include + +#include "InterceptorRelay.h" + +namespace android::nlinterceptor { + +class NetlinkInterceptor + : public ::aidl::android::hardware::net::nlinterceptor::BnInterceptor { + using ClientMap = + std::map<::android::nlinterceptor::InterceptedSocket, + std::unique_ptr<::android::nlinterceptor::InterceptorRelay>>; + + using AidlInterceptedSocket = + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket; + + public: + ndk::ScopedAStatus createSocket( + int32_t nlFamily, int32_t clientNlPid, const std::string& clientName, + AidlInterceptedSocket* interceptedSocket) override; + + ndk::ScopedAStatus closeSocket( + const AidlInterceptedSocket& interceptedSocket) override; + + ndk::ScopedAStatus subscribeGroup( + const AidlInterceptedSocket& interceptedSocket, + int32_t nlGroup) override; + + ndk::ScopedAStatus unsubscribeGroup( + const AidlInterceptedSocket& interceptedSocket, + int32_t nlGroup) override; + + private: + ClientMap mClientMap; +}; + +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/aidl/default/OWNERS b/wifi/netlinkinterceptor/aidl/default/OWNERS new file mode 100644 index 0000000000..b738dac662 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/OWNERS @@ -0,0 +1,2 @@ +chrisweir@google.com +twasilczyk@google.com diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc new file mode 100644 index 0000000000..353cb27cd0 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc @@ -0,0 +1,4 @@ +service nlinterceptor /vendor/bin/hw/android.hardware.net.nlinterceptor-service.default + class hal + user root + group system inet diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml new file mode 100644 index 0000000000..d7d257e000 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml @@ -0,0 +1,9 @@ + + + android.hardware.net.nlinterceptor + + IInterceptor + default + + + diff --git a/wifi/netlinkinterceptor/aidl/default/service.cpp b/wifi/netlinkinterceptor/aidl/default/service.cpp new file mode 100644 index 0000000000..2aec3a5780 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/service.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2021 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 "NetlinkInterceptor.h" + +namespace android::nlinterceptor { +using namespace std::string_literals; + +static void service() { + base::SetDefaultTag("nlinterceptor"); + base::SetMinimumLogSeverity(base::VERBOSE); + LOG(DEBUG) << "Netlink Interceptor service starting..."; + + // TODO(202549296): Sometimes this causes an Address Sanitizer error. + auto interceptor = ndk::SharedRefBase::make(); + const auto instance = NetlinkInterceptor::descriptor + "/default"s; + const auto status = AServiceManager_addService( + interceptor->asBinder().get(), instance.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + LOG(FATAL) << "Netlink Interceptor has stopped"; +} + +} // namespace android::nlinterceptor + +int main() { + ::android::nlinterceptor::service(); + return 0; +} diff --git a/wifi/netlinkinterceptor/aidl/default/util.cpp b/wifi/netlinkinterceptor/aidl/default/util.cpp new file mode 100644 index 0000000000..c7347472c9 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/util.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 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 "util.h" + +#include + +namespace android::nlinterceptor { + +bool isSocketReadable(const short revents) { return 0 != (revents & POLLIN); } + +bool isSocketBad(const short revents) { + return 0 != (revents & (POLLERR | POLLHUP | POLLNVAL)); +} + +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/aidl/default/util.h b/wifi/netlinkinterceptor/aidl/default/util.h new file mode 100644 index 0000000000..9b8ec63c1a --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/default/util.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2021 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::nlinterceptor { + +/** + * Handy-dandy helper to get the size of a statically initialized array. + * + * \param N the array to get the size of. + * \return the size of the array. + */ +template +size_t countof(T (&)[N]) { + return N; +} + +/** + * Helper to check if socket is readable (POLLIN is set). + * + * \param revents pollfd.revents value to check. + * \return true if socket is ready to read. + */ +bool isSocketReadable(short revents); + +/** + * Helper to check if socket is bad (POLLERR, POLLHUP or POLLNVAL is set). + * + * \param revents pollfd.revents value to check. + * \return true if socket is bad. + */ +bool isSocketBad(short revents); + +} // namespace android::nlinterceptor diff --git a/wifi/netlinkinterceptor/libnlinterceptor/Android.bp b/wifi/netlinkinterceptor/libnlinterceptor/Android.bp new file mode 100644 index 0000000000..a5e17668c3 --- /dev/null +++ b/wifi/netlinkinterceptor/libnlinterceptor/Android.bp @@ -0,0 +1,56 @@ +// +// Copyright (C) 2021 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. +// + +cc_defaults { + name: "nlinterceptor@defaults", + cpp_std: "experimental", + cflags: [ + "-Wall", + "-Wextra", + "-Wsuggest-override", + "-Werror", + ], + shared_libs: [ + "libbase", + "libutils", + ], + sanitize: { + address: true, + undefined: true, + all_undefined: true, + fuzzer: true, + cfi: true, + integer_overflow: true, + scs: true, + }, + strip: { + keep_symbols_and_debug_frame: true, + }, +} + +cc_library_static { + name: "libnlinterceptor", + defaults: ["nlinterceptor@defaults"], + vendor_available: true, + shared_libs: [ + "android.hardware.net.nlinterceptor-V1-ndk", + "libbinder_ndk", + ], + srcs: [ + "libnlinterceptor.cpp", + ], + export_include_dirs: ["include"], +} diff --git a/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h new file mode 100644 index 0000000000..ac8653ebe4 --- /dev/null +++ b/wifi/netlinkinterceptor/libnlinterceptor/include/libnlinterceptor/libnlinterceptor.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2021 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 + +#ifdef __cplusplus + +#include +#include +#include + +#include +#include + +namespace android::nlinterceptor { + +/** + * Wrapper structure to uniquely identifies a socket that Netlink Interceptor + * has allocated for us. + */ +struct InterceptedSocket { + uint32_t nlFamily; + uint32_t portId; + + InterceptedSocket( + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket sock); + InterceptedSocket(uint32_t nlFamily, uint32_t portId); + + bool operator<(const InterceptedSocket& other) const; + operator sockaddr_nl() const; + operator ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket() + const; +}; + +/** + * Output stream operator for InterceptedSocket + */ +std::ostream& operator<<(std::ostream& os, const InterceptedSocket& sock); + +/** + * Checks if an instance Netlink Interceptor exists. + * + * \return true if supported, false if not. + */ +bool isEnabled(); + +/** + * Asks Netlink Interceptor to allocate a socket to which we can send Netlink + * traffic. + * + * \param clientSocket - File descriptor for the client's Netlink socket. + * \param clientName - Human readable name of the client application. + * \return Identifier for the socket created by Netlink Interceptor, nullopt on + * error. + */ +std::optional createSocket(base::borrowed_fd clientSocket, + const std::string& clientName); + +/** + * Asks Netlink Interceptor to close a socket that it created for us previously, + * if it exists. + * + * \param sock - Identifier for the socket created by Netlink Interceptor. + */ +void closeSocket(const InterceptedSocket& sock); + +/** + * Asks Netlink Interceptor to subscribe a socket that it created for us + * previously to a specified multicast group. + * + * \param sock - Identifier for the socket created by Netlink Interceptor. + * \param group - A single Netlink multicast group for which we would like to + * receive events. + * \return true for success, false if something went wrong. + */ +bool subscribe(const InterceptedSocket& sock, uint32_t group); + +/** + * Asks Netlink Interceptor to unsubscribe a socket that it created for us + * previously from a specified multicast group. + * + * \param sock - Identifier for the socket created by Netlink Interceptor. + * \param group - A single Netlink multicast group for which we no longer wish + * to receive events. + * \return true for success, false if something went wrong. + */ +bool unsubscribe(const InterceptedSocket& sock, uint32_t group); +} // namespace android::nlinterceptor +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// C wrappers for libnlinterceptor +struct android_nlinterceptor_InterceptedSocket { + uint32_t nlFamily; + uint32_t portId; +}; + +bool android_nlinterceptor_isEnabled(); + +bool android_nlinterceptor_createSocket( + int clientSocketFd, const char* clientName, + struct android_nlinterceptor_InterceptedSocket* interceptedSocket); + +void android_nlinterceptor_closeSocket( + const struct android_nlinterceptor_InterceptedSocket* sock); + +bool android_nlinterceptor_subscribe( + const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group); + +bool android_nlinterceptor_unsubscribe( + const struct android_nlinterceptor_InterceptedSocket* sock, uint32_t group); + +#ifdef __cplusplus +} +#endif diff --git a/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp new file mode 100644 index 0000000000..575f90031a --- /dev/null +++ b/wifi/netlinkinterceptor/libnlinterceptor/libnlinterceptor.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2021 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 +#include +#include + +#include + +namespace android::nlinterceptor { +using namespace std::string_literals; +using namespace ::aidl::android::hardware::net::nlinterceptor; +using base::borrowed_fd; +using AidlInterceptedSocket = + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket; + +static const auto kServiceName = IInterceptor::descriptor + "/default"s; + +InterceptedSocket::InterceptedSocket( + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket sock) + : nlFamily(sock.nlFamily), portId(sock.portId) {} + +InterceptedSocket::InterceptedSocket(uint32_t nlFamily, uint32_t portId) + : nlFamily(nlFamily), portId(portId) {} + +std::ostream& operator<<(std::ostream& os, const InterceptedSocket& sock) { + return os << "family: " << sock.nlFamily << ", portId: " << sock.portId; +} + +bool InterceptedSocket::operator<(const InterceptedSocket& other) const { + if (nlFamily != other.nlFamily) { + return nlFamily < other.nlFamily; + } + return portId < other.portId; +} + +InterceptedSocket::operator sockaddr_nl() const { + return { + .nl_family = AF_NETLINK, + .nl_pad = 0, + .nl_pid = portId, + .nl_groups = 0, + }; +} + +InterceptedSocket::operator AidlInterceptedSocket() const { + return { + .nlFamily = static_cast(nlFamily), + .portId = static_cast(portId), + }; +} + +bool isEnabled() { + static std::mutex supportedMutex; + static std::optional interceptorSupported; + // Avoid querying service manager when we can cache the result. + if (interceptorSupported.has_value()) return *interceptorSupported; + std::lock_guard lock(supportedMutex); + if (interceptorSupported.has_value()) return *interceptorSupported; + + if (!AServiceManager_isDeclared(kServiceName.c_str())) { + interceptorSupported = false; + return false; + } + interceptorSupported = true; + return true; +} + +static IInterceptor& getInstance() { + static std::mutex instanceMutex; + static std::shared_ptr interceptorInstance; + CHECK(isEnabled()) << "Can't getInstance! Interceptor not supported!"; + // Don't overwrite the pointer once we've acquired it. + if (interceptorInstance != nullptr) return *interceptorInstance; + std::lock_guard lock(instanceMutex); + if (interceptorInstance != nullptr) return *interceptorInstance; + interceptorInstance = IInterceptor::fromBinder( + ndk::SpAIBinder(AServiceManager_waitForService(kServiceName.c_str()))); + CHECK(interceptorInstance != nullptr) + << "Failed to get Netlink Interceptor service!"; + return *interceptorInstance; +} + +std::optional createSocket(borrowed_fd clientSocket, + const std::string& clientName) { + sockaddr_nl nladdr = {}; + socklen_t nlsize = sizeof(nladdr); + if (getsockname(clientSocket.get(), reinterpret_cast(&nladdr), + &nlsize) < 0) { + PLOG(ERROR) << "Failed to get pid of fd passed by " << clientName; + return std::nullopt; + } + + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket + interceptedSocket; + auto aidlStatus = getInstance().createSocket( + nladdr.nl_family, nladdr.nl_pid, clientName, &interceptedSocket); + if (!aidlStatus.isOk()) { + return std::nullopt; + } + + return InterceptedSocket{nladdr.nl_family, + uint32_t(interceptedSocket.portId)}; +} + +void closeSocket(const InterceptedSocket& sock) { + auto aidlStatus = getInstance().closeSocket(sock); + if (!aidlStatus.isOk()) { + LOG(ERROR) << "Failed to close socket with pid = " << sock.portId; + } +} + +bool subscribe(const InterceptedSocket& sock, uint32_t group) { + auto aidlStatus = getInstance().subscribeGroup(sock, group); + return aidlStatus.isOk(); +} + +bool unsubscribe(const InterceptedSocket& sock, uint32_t group) { + auto aidlStatus = getInstance().unsubscribeGroup(sock, group); + return aidlStatus.isOk(); +} + +extern "C" bool android_nlinterceptor_isEnabled() { return isEnabled(); } + +extern "C" bool android_nlinterceptor_createSocket( + int clientSocketFd, const char* clientName, + android_nlinterceptor_InterceptedSocket* interceptedSocket) { + if (!clientName || clientSocketFd <= 0) return false; + const auto maybeSocket = + createSocket(borrowed_fd(clientSocketFd), clientName); + if (!maybeSocket) return false; + *interceptedSocket = {.nlFamily = maybeSocket->nlFamily, + .portId = maybeSocket->portId}; + return true; +} + +extern "C" void android_nlinterceptor_closeSocket( + const android_nlinterceptor_InterceptedSocket* sock) { + if (!sock) { + LOG(ERROR) << "Can't close socket identified by a null pointer!"; + return; + } + closeSocket({sock->nlFamily, sock->portId}); +} + +extern "C" bool android_nlinterceptor_subscribe( + const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) { + if (!sock) return false; + return subscribe({sock->nlFamily, sock->portId}, group); +} + +extern "C" bool android_nlinterceptor_unsubscribe( + const android_nlinterceptor_InterceptedSocket* sock, uint32_t group) { + if (!sock) return false; + return unsubscribe({sock->nlFamily, sock->portId}, group); +} + +} // namespace android::nlinterceptor