mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
438 lines
14 KiB
C++
438 lines
14 KiB
C++
// 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 <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
#include <sys/socket.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <sdbus-c++/Error.h>
|
|
#include <sdbus-c++/ProxyInterfaces.h>
|
|
#include <sdbus-c++/Types.h>
|
|
|
|
#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<sdbus::IConnection>& 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<int>(std::max<std::int64_t>(
|
|
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<TCPSocket> 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<const sockaddr*>(&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::Peer>
|
|
NetworkManagerWifiDirectMedium::FindPeerByName(absl::string_view device_name) {
|
|
std::vector<sdbus::ObjectPath> paths;
|
|
try {
|
|
paths = Peers();
|
|
} catch (const sdbus::Error& error) {
|
|
DBUS_LOG_PROPERTY_GET_ERROR(this, "Peers", error);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::vector<Peer> 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<std::string> 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> 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<api::WifiDirectSocket>
|
|
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<std::int64_t>(
|
|
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<WifiDirectSocket>(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<api::WifiDirectServerSocket>
|
|
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
|