diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml index a781b35238..9dc6fae1bd 100644 --- a/compatibility_matrices/compatibility_matrix.current.xml +++ b/compatibility_matrices/compatibility_matrix.current.xml @@ -331,6 +331,13 @@ default + + android.hardware.net.nlinterceptor + + IInterceptor + default + + android.hardware.oemlock 1 diff --git a/wifi/netlinkinterceptor/aidl/Android.bp b/wifi/netlinkinterceptor/aidl/Android.bp new file mode 100644 index 0000000000..924edee384 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/Android.bp @@ -0,0 +1,36 @@ +// +// 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +aidl_interface { + name: "android.hardware.net.nlinterceptor", + vendor_available: true, + srcs: ["android/hardware/net/nlinterceptor/*.aidl"], + stability: "vintf", + backend: { + java: { + enabled: false, + }, + }, +} diff --git a/wifi/netlinkinterceptor/aidl/aidl_api/NetlinkInterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl b/wifi/netlinkinterceptor/aidl/aidl_api/NetlinkInterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl new file mode 100644 index 0000000000..249b343e8f --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/aidl_api/NetlinkInterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl @@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.net.nlinterceptor; +@VintfStability +interface IInterceptor { + int createSocket(in int nlFamily, in int clientNlPid, in String clientName); + void closeSocket(in int nlFamily, in int interceptorNlPid); + void subscribeGroup(in int nlFamily, in int interceptorNlPid, in int nlGroup); + void unsubscribeGroup(in int nlFamily, in int interceptorNlPid, in int nlGroup); +} diff --git a/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl b/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl new file mode 100644 index 0000000000..3d0f955371 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/IInterceptor.aidl @@ -0,0 +1,41 @@ +/* + * 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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.net.nlinterceptor; +@VintfStability +interface IInterceptor { + android.hardware.net.nlinterceptor.InterceptedSocket createSocket(in int nlFamily, in int clientNlPid, in String clientName); + void closeSocket(in android.hardware.net.nlinterceptor.InterceptedSocket handle); + void subscribeGroup(in android.hardware.net.nlinterceptor.InterceptedSocket handle, in int nlGroup); + void unsubscribeGroup(in android.hardware.net.nlinterceptor.InterceptedSocket handle, in int nlGroup); +} diff --git a/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/InterceptedSocket.aidl b/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/InterceptedSocket.aidl new file mode 100644 index 0000000000..b679be506a --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/aidl_api/android.hardware.net.nlinterceptor/current/android/hardware/net/nlinterceptor/InterceptedSocket.aidl @@ -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. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.net.nlinterceptor; +@VintfStability +parcelable InterceptedSocket { + int nlFamily; + int portId; +} diff --git a/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/IInterceptor.aidl b/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/IInterceptor.aidl new file mode 100644 index 0000000000..c222a1efc8 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/IInterceptor.aidl @@ -0,0 +1,78 @@ +/* + * 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. + */ + +package android.hardware.net.nlinterceptor; + +import android.hardware.net.nlinterceptor.InterceptedSocket; + +/** + * Netlink Interceptor + * + * This HAL provides a way for Android services to route their Netlink traffic to a location other + * than the Kernel. One might want to do this for a variety of reasons: + * -> Route Netlink traffic to a different host. + * -> Route Netlink traffic to a different VM. + * -> Convert Netlink commands into proprietary vendor hardware commands. + * + * Some important notes regarding Netlink Interceptor. + * -> All int values are treated as unsigned. + * -> Users of Netlink Interceptor must close their sockets with closeSocket manually. + * -> PID != process ID. In this case, it is "port ID", a unique number assigned by the kernel to a + * given Netlink socket. + * -> Netlink PIDs are only unique per family. This means that for all NETLINK_GENERIC sockets, + * there can only be one socket with PID "1234". HOWEVER, there can ALSO be a Netlink socket + * using NETLINK_ROUTE which has a PID of "1234". Hence, in order to uniquely identify a Netlink + * socket, both the PID and Netlink Family are required. + */ +@VintfStability +interface IInterceptor { + /** + * Creates a Netlink socket on both the HU and TCU, and a bi-directional gRPC stream to carry + * data between them. This must be closed by the caller with closeSocket(). + * + * @param nlFamily - Netlink Family. Support for families other than NETLINK_GENERIC is still + * experimental. + * @param clientNlPid - Port ID of the caller's Netlink socket. + * @param clientName - Human readable name of the caller. Used for debugging. + * + * @return InterceptedSocket identifying the socket on the HU allocated for the caller. + */ + InterceptedSocket createSocket(in int nlFamily, in int clientNlPid, in String clientName); + + /** + * Closes a socket and gRPC stream given the socket's identifier. This must be invoked manually + * by the caller of createSocket(). + * + * @param handle - unique identifier for a socket returned by createSocket. + */ + void closeSocket(in InterceptedSocket handle); + + /** + * Subscribes a socket on the TCU to a Netlink multicast group. + * + * @param handle - unique identifier for a socket returned by createSocket. + * @param nlGroup - A single Netlink multicast group that the caller wants to subscribe to. + */ + void subscribeGroup(in InterceptedSocket handle, in int nlGroup); + + /** + * Unsubscribes a socket on the TCU from a Netlink multicast group. + * + * @param handle - unique identifier for a socket returned by createSocket. + * @param nlGroup - A single Netlink multicast group that the caller wants to unsubscribe from. + */ + void unsubscribeGroup(in InterceptedSocket handle, in int nlGroup); +} diff --git a/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/InterceptedSocket.aidl b/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/InterceptedSocket.aidl new file mode 100644 index 0000000000..d74a556cf1 --- /dev/null +++ b/wifi/netlinkinterceptor/aidl/android/hardware/net/nlinterceptor/InterceptedSocket.aidl @@ -0,0 +1,33 @@ +/* + * 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. + */ + +package android.hardware.net.nlinterceptor; + +/** + * Unique identifier for a Netlink socket. + */ +@VintfStability +parcelable InterceptedSocket { + /** + * Netlink family of the identified socket + */ + int nlFamily; + + /** + * Netlink port ID of the identified socket. + */ + int portId; +} 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 diff --git a/wifi/netlinkinterceptor/vts/OWNERS b/wifi/netlinkinterceptor/vts/OWNERS new file mode 100644 index 0000000000..b738dac662 --- /dev/null +++ b/wifi/netlinkinterceptor/vts/OWNERS @@ -0,0 +1,2 @@ +chrisweir@google.com +twasilczyk@google.com diff --git a/wifi/netlinkinterceptor/vts/functional/Android.bp b/wifi/netlinkinterceptor/vts/functional/Android.bp new file mode 100644 index 0000000000..33284e8e89 --- /dev/null +++ b/wifi/netlinkinterceptor/vts/functional/Android.bp @@ -0,0 +1,51 @@ +// +// 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. +// + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "hardware_interfaces_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["hardware_interfaces_license"], +} + +cc_test { + name: "VtsHalNetlinkInterceptorV1_0Test", + defaults: [ + "VtsHalTargetTestDefaults", + "use_libaidlvintf_gtest_helper_static", + ], + cpp_std: "experimental", + srcs: [ + "interceptor_aidl_test.cpp", + ], + shared_libs: [ + "android.hardware.net.nlinterceptor-V1-ndk", + "libbase", + "libbinder_ndk", + ], + static_libs: [ + "libgmock", + "android.hardware.automotive.can@libnetdevice", + "libnl++", + ], + test_suites: [ + "general-tests", + "vts", + ], + disable_framework: true, +} diff --git a/wifi/netlinkinterceptor/vts/functional/interceptor_aidl_test.cpp b/wifi/netlinkinterceptor/vts/functional/interceptor_aidl_test.cpp new file mode 100644 index 0000000000..b26d8ec3e7 --- /dev/null +++ b/wifi/netlinkinterceptor/vts/functional/interceptor_aidl_test.cpp @@ -0,0 +1,195 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include + +using aidl::android::hardware::net::nlinterceptor::IInterceptor; +using AidlInterceptedSocket = + ::aidl::android::hardware::net::nlinterceptor::InterceptedSocket; +using namespace std::chrono_literals; +using namespace std::string_literals; + +class InterceptorAidlTest : public ::testing::TestWithParam { + public: + virtual void SetUp() override { + android::base::SetDefaultTag("InterceptorAidlTest"); + android::base::SetMinimumLogSeverity(android::base::VERBOSE); + const auto instance = IInterceptor::descriptor + "/default"s; + mNlInterceptorService = IInterceptor::fromBinder( + ndk::SpAIBinder(AServiceManager_getService(instance.c_str()))); + + ASSERT_NE(mNlInterceptorService, nullptr); + mSocket = std::make_unique(NETLINK_ROUTE); + ASSERT_TRUE(mSocket->getPid().has_value()); + + // If the test broke last run, clean up our mess, don't worry about "no + // such device". + if (android::netdevice::del(mTestIfaceName)) { + LOG(WARNING) << "Test interface wasn't cleaned up on previous run!"; + } + } + + void multicastReceiver(); + + std::shared_ptr mNlInterceptorService; + std::unique_ptr mSocket; + bool mRunning; + bool mGotMulticast; + const std::string mTestIfaceName = "interceptorvts0"; +}; + +TEST_P(InterceptorAidlTest, createSocketTest) { + // Ask IInterceptor for a socket. + AidlInterceptedSocket interceptedSocket; + auto aidlStatus = mNlInterceptorService->createSocket( + NETLINK_ROUTE, *(mSocket->getPid()), "createSocketTest", + &interceptedSocket); + ASSERT_TRUE(aidlStatus.isOk()); + ASSERT_NE(interceptedSocket.portId, 0); + uint32_t interceptorPid = interceptedSocket.portId; + + // Ask the kernel to tell us what interfaces are available. + android::nl::MessageFactory req(RTM_GETLINK, + NLM_F_REQUEST | NLM_F_DUMP); + req->rtgen_family = AF_PACKET; + sockaddr_nl sa = {.nl_family = AF_NETLINK, + .nl_pad = 0, + .nl_pid = interceptorPid, + .nl_groups = 0}; + EXPECT_TRUE(mSocket->send(req, sa)); + + // We'll likely get back several messages, as indicated by the MULTI flag. + unsigned received = 0; + for (const auto msg : *mSocket) { + ASSERT_NE(msg->nlmsg_type, NLMSG_ERROR); + ++received; + break; + if (msg->nlmsg_type == NLMSG_DONE) { + // TODO(202548749): NLMSG_DONE on NETLINK_ROUTE doesn't work? + break; + } + } + ASSERT_GE(received, 1); + + // Close the socket and make sure it's stopped working. + aidlStatus = mNlInterceptorService->closeSocket(interceptedSocket); + EXPECT_TRUE(aidlStatus.isOk()); + EXPECT_FALSE(mSocket->send(req, sa)); +} + +static bool isSocketReadable(const short revents) { + return 0 != (revents & POLLIN); +} + +static bool isSocketBad(const short revents) { + return 0 != (revents & (POLLERR | POLLHUP | POLLNVAL)); +} + +void InterceptorAidlTest::multicastReceiver() { + pollfd fds[] = { + mSocket->preparePoll(POLLIN), + }; + while (mRunning) { + if (poll(fds, 1, 300) < 0) { + PLOG(FATAL) << "poll failed"; + return; + } + const auto nlsockEvents = fds[0].revents; + ASSERT_FALSE(isSocketBad(nlsockEvents)); + if (!isSocketReadable(nlsockEvents)) continue; + + const auto [msgMaybe, sa] = mSocket->receiveFrom(); + ASSERT_TRUE(msgMaybe.has_value()); + auto msg = *msgMaybe; + + // Multicast messages have 0 for their pid and sequence number. + if (msg->nlmsg_pid == 0 && msg->nlmsg_seq == 0) { + mGotMulticast = true; + } + } +} + +TEST_P(InterceptorAidlTest, subscribeGroupTest) { + // Ask IInterceptor for a socket. + AidlInterceptedSocket interceptedSocket; + auto aidlStatus = mNlInterceptorService->createSocket( + NETLINK_ROUTE, *(mSocket->getPid()), "subscribeGroupTest", + &interceptedSocket); + ASSERT_TRUE(aidlStatus.isOk()); + ASSERT_TRUE(interceptedSocket.portId != 0); + + // Listen for interface up/down events. + aidlStatus = + mNlInterceptorService->subscribeGroup(interceptedSocket, RTNLGRP_LINK); + ASSERT_TRUE(aidlStatus.isOk()); + + // Start a thread to receive a multicast + mRunning = true; + mGotMulticast = false; + std::thread successfulReceiver(&InterceptorAidlTest::multicastReceiver, + this); + + // TODO(201695162): use futures with wait_for instead of a sleep_for(). + std::this_thread::sleep_for(50ms); + // create a network interface and bring it up to trigger a multicast event. + ASSERT_TRUE(android::netdevice::add(mTestIfaceName, /*type=*/"dummy")); + ASSERT_TRUE(android::netdevice::up(mTestIfaceName)); + std::this_thread::sleep_for(50ms); + EXPECT_TRUE(mGotMulticast); + mRunning = false; + successfulReceiver.join(); + + // Stop listening to interface up/down events. + aidlStatus = mNlInterceptorService->unsubscribeGroup(interceptedSocket, + RTNLGRP_LINK); + ASSERT_TRUE(aidlStatus.isOk()); + + // This time, we should hear nothing. + mGotMulticast = false; + mRunning = true; + std::thread unsuccessfulReceiver(&InterceptorAidlTest::multicastReceiver, + this); + std::this_thread::sleep_for(50ms); + ASSERT_TRUE(android::netdevice::down(mTestIfaceName)); + ASSERT_TRUE(android::netdevice::del(mTestIfaceName)); + std::this_thread::sleep_for(50ms); + EXPECT_FALSE(mGotMulticast); + mRunning = false; + unsuccessfulReceiver.join(); + + aidlStatus = mNlInterceptorService->closeSocket(interceptedSocket); + EXPECT_TRUE(aidlStatus.isOk()); +} + +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InterceptorAidlTest); +INSTANTIATE_TEST_SUITE_P(PerInstance, InterceptorAidlTest, + testing::ValuesIn(android::getAidlHalInstanceNames( + IInterceptor::descriptor)), + android::PrintInstanceNameToString);