// Copyright 2023 Google LLC // // 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 // // https://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 "internal/platform/implementation/linux/wifi_direct.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "absl/time/clock.h" #include "absl/time/time.h" #include "internal/flags/nearby_flags.h" #include "internal/platform/flags/nearby_platform_feature_flags.h" #include "internal/platform/implementation/linux/dbus.h" #include "internal/platform/implementation/linux/generated/dbus/networkmanager/wifi_p2p_peer_client.h" #include "internal/platform/implementation/linux/network_manager_active_connection.h" #include "internal/platform/implementation/linux/tcp_server_socket.h" #include "internal/platform/implementation/linux/utils.h" #include "internal/platform/implementation/linux/wifi_direct_socket.h" #include "internal/platform/implementation/linux/wifi_direct_utils.h" #include "internal/platform/logging.h" namespace nearby::linux { namespace { constexpr absl::Duration kPeerDiscoveryTimeout = absl::Seconds(30); constexpr absl::Duration kActivationTimeout = absl::Seconds(30); constexpr absl::Duration kPeerUpdatePollInterval = absl::Milliseconds(500); constexpr int kCancellationPollMillis = 100; class NetworkManagerWifiP2PPeer : public sdbus::ProxyInterfaces< org::freedesktop::NetworkManager::WifiP2PPeer_proxy> { public: NetworkManagerWifiP2PPeer( const std::shared_ptr& system_bus, const sdbus::ObjectPath& object_path) : ProxyInterfaces(*system_bus, sdbus::ServiceName("org.freedesktop.NetworkManager"), object_path) { registerProxy(); } ~NetworkManagerWifiP2PPeer() { unregisterProxy(); } }; bool WaitForTcpConnect(int fd, absl::Duration timeout, CancellationFlag* cancellation_flag) { const absl::Time deadline = absl::Now() + timeout; while (absl::Now() < deadline) { if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { return false; } const int remaining_ms = static_cast(std::max( 1, absl::ToInt64Milliseconds(deadline - absl::Now()))); pollfd descriptor{.fd = fd, .events = POLLOUT, .revents = 0}; int result; do { result = poll(&descriptor, 1, std::min(kCancellationPollMillis, remaining_ms)); } while (result < 0 && errno == EINTR); if (result < 0) { return false; } if (result == 0) { continue; } int socket_error = 0; socklen_t socket_error_length = sizeof(socket_error); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &socket_error, &socket_error_length) != 0) { return false; } if (socket_error == 0) { return true; } errno = socket_error; return false; } return false; } std::optional ConnectTcp(const std::string& ip_address, int port, absl::Duration timeout, CancellationFlag* cancellation_flag) { sockaddr_in address{}; address.sin_family = AF_INET; address.sin_port = htons(port); if (inet_pton(AF_INET, ip_address.c_str(), &address.sin_addr) != 1) { LOG(ERROR) << __func__ << ": Invalid IPv4 address " << ip_address; return std::nullopt; } int fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0); if (fd < 0) { LOG(ERROR) << __func__ << ": socket failed: " << std::strerror(errno); return std::nullopt; } const int flags = fcntl(fd, F_GETFL, 0); if (flags < 0 || fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) { LOG(ERROR) << __func__ << ": fcntl failed: " << std::strerror(errno); close(fd); return std::nullopt; } int result = connect(fd, reinterpret_cast(&address), sizeof(address)); if (result != 0 && errno != EINPROGRESS) { close(fd); return std::nullopt; } if (result != 0 && !WaitForTcpConnect(fd, timeout, cancellation_flag)) { close(fd); return std::nullopt; } if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { close(fd); return std::nullopt; } return TCPSocket(fd); } void SleepWithCancellation(absl::Duration duration, CancellationFlag* cancellation_flag) { const absl::Time deadline = absl::Now() + duration; while (absl::Now() < deadline) { if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { return; } absl::SleepFor(std::min(absl::Milliseconds(kCancellationPollMillis), deadline - absl::Now())); } } } // namespace NetworkManagerWifiDirectMedium::~NetworkManagerWifiDirectMedium() { DisconnectWifiDirect(); unregisterProxy(); } void NetworkManagerWifiDirectMedium::onPeerAdded(const sdbus::ObjectPath&) { absl::MutexLock lock(state_mutex_); peer_changed_.SignalAll(); } void NetworkManagerWifiDirectMedium::onPeerRemoved(const sdbus::ObjectPath&) { absl::MutexLock lock(state_mutex_); peer_changed_.SignalAll(); } std::optional NetworkManagerWifiDirectMedium::FindPeerByName(absl::string_view device_name) { std::vector paths; try { paths = Peers(); } catch (const sdbus::Error& error) { DBUS_LOG_PROPERTY_GET_ERROR(this, "Peers", error); return std::nullopt; } std::vector peers; peers.reserve(paths.size()); for (const sdbus::ObjectPath& path : paths) { NetworkManagerWifiP2PPeer peer(system_bus_, path); try { peers.push_back(Peer{.path = path, .name = peer.Name(), .hardware_address = peer.HwAddress(), .strength = peer.Strength(), .last_seen = peer.LastSeen()}); } catch (const sdbus::Error& error) { LOG(WARNING) << __func__ << ": Failed to inspect peer " << path << ": " << error.getName() << ": " << error.getMessage(); } } return wifi_direct_internal::SelectPeerByName(peers, device_name); } void NetworkManagerWifiDirectMedium::StopDiscovery() { bool should_stop = false; { absl::MutexLock lock(state_mutex_); should_stop = discovering_; discovering_ = false; peer_changed_.SignalAll(); } if (!should_stop) { return; } try { StopFind(); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(this, "StopFind", error); } } bool NetworkManagerWifiDirectMedium::ActivatePeer(const Peer& peer) { auto uuid = NewUuidStr(); if (!uuid.has_value()) { LOG(ERROR) << __func__ << ": Could not generate connection UUID"; return false; } wifi_direct_internal::ConnectionSettings settings = wifi_direct_internal::BuildGcConnectionSettings(*uuid, peer.hardware_address); sdbus::ObjectPath active_path; try { auto [connection_path, returned_active_path, result] = network_manager_->AddAndActivateConnection2( settings, wifi_p2p_device_path_, peer.path, {{"persist", sdbus::Variant(std::string("volatile"))}, {"bind-activation", sdbus::Variant(std::string("dbus-client"))}}); active_path = std::move(returned_active_path); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "AddAndActivateConnection2", error); return false; } networkmanager::ActiveConnection active_connection(system_bus_, active_path); auto [reason, timed_out] = active_connection.WaitForConnection(kActivationTimeout); if (timed_out || reason.has_value()) { LOG(ERROR) << __func__ << ": Wi-Fi Direct activation failed for " << active_path; try { network_manager_->DeactivateConnection(active_path); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", error); } return false; } std::vector addresses = active_connection.GetIP4Addresses(); if (addresses.empty()) { LOG(ERROR) << __func__ << ": Activated Wi-Fi Direct connection has no IPv4 address"; try { network_manager_->DeactivateConnection(active_path); } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", error); } return false; } std::string gateway = active_connection.GetIP4Gateway(); absl::MutexLock lock(state_mutex_); active_connection_path_ = active_path; local_ip_address_ = addresses.front(); remote_ip_address_ = std::move(gateway); connected_ = true; LOG(INFO) << __func__ << ": Wi-Fi Direct GC connected; local IPv4=" << local_ip_address_ << ", GO IPv4=" << remote_ip_address_; return true; } bool NetworkManagerWifiDirectMedium::ConnectWifiDirect( const WifiDirectCredentials& wifi_direct_credentials) { if (wifi_direct_credentials.GetDeviceName().empty()) { LOG(ERROR) << __func__ << ": Device name is empty"; return false; } DisconnectWifiDirect(); { absl::MutexLock lock(state_mutex_); discovering_ = true; } try { StartFind({{"timeout", sdbus::Variant(std::int32_t(30))}}); } catch (const sdbus::Error& error) { { absl::MutexLock lock(state_mutex_); discovering_ = false; } DBUS_LOG_METHOD_CALL_ERROR(this, "StartFind", error); return false; } const absl::Time deadline = absl::Now() + kPeerDiscoveryTimeout; std::optional peer; while (absl::Now() < deadline) { peer = FindPeerByName(wifi_direct_credentials.GetDeviceName()); if (peer.has_value()) { break; } absl::MutexLock lock(state_mutex_); if (!discovering_) { break; } peer_changed_.WaitWithTimeout( &state_mutex_, std::min(kPeerUpdatePollInterval, deadline - absl::Now())); } StopDiscovery(); if (!peer.has_value()) { LOG(WARNING) << __func__ << ": Timed out finding Wi-Fi Direct peer " << wifi_direct_credentials.GetDeviceName(); return false; } return ActivatePeer(*peer); } bool NetworkManagerWifiDirectMedium::DisconnectWifiDirect() { StopDiscovery(); std::string active_path; { absl::MutexLock lock(state_mutex_); active_path = std::move(active_connection_path_); connected_ = false; local_ip_address_.clear(); remote_ip_address_.clear(); } if (active_path.empty()) { return true; } try { network_manager_->DeactivateConnection(sdbus::ObjectPath(active_path)); return true; } catch (const sdbus::Error& error) { DBUS_LOG_METHOD_CALL_ERROR(network_manager_, "DeactivateConnection", error); return false; } } std::unique_ptr NetworkManagerWifiDirectMedium::ConnectToService( absl::string_view ip_address, int port, CancellationFlag* cancellation_flag) { std::string remote_ip; { absl::MutexLock lock(state_mutex_); if (!connected_) { LOG(ERROR) << __func__ << ": Wi-Fi Direct GC is not connected"; return nullptr; } remote_ip = remote_ip_address_; } if (remote_ip.empty()) { remote_ip = std::string(ip_address); } if (remote_ip.empty() || port <= 0 || port > 65535) { LOG(ERROR) << __func__ << ": Invalid service address or port"; return nullptr; } const std::int64_t retries = std::max( 1, NearbyFlags::GetInstance().GetInt64Flag( platform::config_package_nearby::nearby_platform_feature:: kWifiHotspotConnectionMaxRetries)); const absl::Duration retry_interval = absl::Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( platform::config_package_nearby::nearby_platform_feature:: kWifiHotspotConnectionIntervalMillis)); const absl::Duration connect_timeout = absl::Milliseconds(NearbyFlags::GetInstance().GetInt64Flag( platform::config_package_nearby::nearby_platform_feature:: kWifiHotspotConnectionTimeoutMillis)); for (std::int64_t attempt = 0; attempt < retries; ++attempt) { if (cancellation_flag != nullptr && cancellation_flag->Cancelled()) { return nullptr; } auto socket = ConnectTcp(remote_ip, port, connect_timeout, cancellation_flag); if (socket.has_value()) { return std::make_unique(std::move(*socket)); } if (attempt + 1 < retries) { SleepWithCancellation(retry_interval, cancellation_flag); } } LOG(ERROR) << __func__ << ": Failed to connect to Wi-Fi Direct service " << remote_ip << ":" << port; return nullptr; } std::unique_ptr NetworkManagerWifiDirectMedium::ListenForService(int port) { LOG(WARNING) << __func__ << ": Linux Wi-Fi Direct is GC-only; listening is unsupported"; return nullptr; } bool NetworkManagerWifiDirectMedium::StartWifiDirect( WifiDirectCredentials* wifi_direct_credentials) { LOG(WARNING) << __func__ << ": Linux Wi-Fi Direct autonomous GO is unsupported"; return false; } bool NetworkManagerWifiDirectMedium::StopWifiDirect() { return true; } } // namespace nearby::linux