chromium/chrome/browser/ash/network_change_manager/network_change_manager_client.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/network_change_manager/network_change_manager_client.h"

#include "base/functional/bind.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/crosapi/mojom/network_change.mojom.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/network_service_util.h"
#include "net/base/network_change_notifier_passive.h"
#include "services/network/public/mojom/network_service.mojom.h"

namespace ash {

namespace {
NetworkChangeManagerClient* g_network_change_manager_client = nullptr;
}

NetworkChangeManagerClient::NetworkChangeManagerClient(
    net::NetworkChangeNotifierPassive* network_change_notifier)
    : connection_type_(net::NetworkChangeNotifier::GetConnectionType()),
      connection_subtype_(net::NetworkChangeNotifier::GetConnectionSubtype()),
      network_change_notifier_(network_change_notifier) {
  DCHECK(!g_network_change_manager_client);
  g_network_change_manager_client = this;

  chromeos::PowerManagerClient::Get()->AddObserver(this);

  network_state_handler_observer_.Observe(
      NetworkHandler::Get()->network_state_handler());

  if (content::IsOutOfProcessNetworkService())
    ConnectToNetworkChangeManager();

  // Update initial connection state.
  DefaultNetworkChanged(
      NetworkHandler::Get()->network_state_handler()->DefaultNetwork());
}

NetworkChangeManagerClient::~NetworkChangeManagerClient() {
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
  DCHECK_EQ(g_network_change_manager_client, this);
  g_network_change_manager_client = nullptr;
}

// static
NetworkChangeManagerClient* NetworkChangeManagerClient::GetInstance() {
  return g_network_change_manager_client;
}

void NetworkChangeManagerClient::SuspendDone(base::TimeDelta sleep_duration) {
  // Set `ip_address_changed` to true to force invalidation of network resources
  // on resume.
  NotifyObservers(
      /*dns_changed=*/false, /*ip_address_changed=*/true,
      /*connection_type_changed=*/false,
      net::NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
      /*connection_subtype_changed=*/false,
      net::NetworkChangeNotifier::ConnectionSubtype::SUBTYPE_NONE);
}

void NetworkChangeManagerClient::DefaultNetworkChanged(
    const NetworkState* default_network) {
  bool connection_type_changed = false;
  bool connection_subtype_changed = false;
  bool ip_address_changed = false;
  bool dns_changed = false;

  UpdateState(default_network, &dns_changed, &ip_address_changed,
              &connection_type_changed, &connection_subtype_changed);

  NotifyObservers(dns_changed, ip_address_changed, connection_type_changed,
                  connection_type_, connection_subtype_changed,
                  connection_subtype_);
}

void NetworkChangeManagerClient::AddLacrosNetworkChangeObserver(
    mojo::PendingRemote<crosapi::mojom::NetworkChangeObserver> observer) {
  mojo::Remote<crosapi::mojom::NetworkChangeObserver> remote(
      std::move(observer));

  // Tell the observer what the current connection type is.
  remote->OnNetworkChanged(
      /*dns_changed=*/false, /*ip_address_changed=*/false,
      /*connection_type_changed=*/true,
      crosapi::mojom::ConnectionType(connection_type_),
      /*connection_subtype_changed=*/true,
      crosapi::mojom::ConnectionSubtype(connection_subtype_));

  lacros_network_change_observers_.Add(std::move(remote));
}

void NetworkChangeManagerClient::ConnectToNetworkChangeManager() {
  if (network_change_manager_.is_bound())
    network_change_manager_.reset();

  content::GetNetworkService()->GetNetworkChangeManager(
      network_change_manager_.BindNewPipeAndPassReceiver());
  network_change_manager_.set_disconnect_handler(base::BindOnce(
      &NetworkChangeManagerClient::ReconnectToNetworkChangeManager,
      base::Unretained(this)));
}

void NetworkChangeManagerClient::ReconnectToNetworkChangeManager() {
  ConnectToNetworkChangeManager();

  // Tell the restarted network service what the current connection type is.
  network_change_manager_->OnNetworkChanged(
      /*dns_changed=*/false, /*ip_address_changed=*/false,
      /*connection_type_changed=*/true,
      network::mojom::ConnectionType(connection_type_),
      /*connection_subtype_changed=*/true,
      network::mojom::ConnectionSubtype(connection_subtype_));
}

void NetworkChangeManagerClient::UpdateState(
    const NetworkState* default_network,
    bool* dns_changed,
    bool* ip_address_changed,
    bool* connection_type_changed,
    bool* connection_subtype_changed) {
  *connection_type_changed = false;
  *connection_subtype_changed = false;
  *ip_address_changed = false;
  *dns_changed = false;

  if (!default_network || !default_network->IsConnectedState()) {
    // If we lost a default network, we must update our state and notify
    // observers, otherwise we have nothing to do.
    if (connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE) {
      NET_LOG(EVENT) << "NCN DefaultNetwork lost"
                     << NetworkPathId(service_path_);
      *ip_address_changed = true;
      *dns_changed = true;
      *connection_type_changed = true;
      *connection_subtype_changed = true;
      connection_type_ = net::NetworkChangeNotifier::CONNECTION_NONE;
      connection_subtype_ = net::NetworkChangeNotifier::SUBTYPE_NONE;
      service_path_.clear();
      ip_address_.clear();
      dns_servers_.clear();
    }
    return;
  }

  // We do have a default network and it is connected.
  net::NetworkChangeNotifier::ConnectionType new_connection_type =
      ConnectionTypeFromShill(default_network->type(),
                              default_network->network_technology());
  if (new_connection_type != connection_type_) {
    NET_LOG(EVENT) << "NCN Default connection type changed: "
                   << net::NetworkChangeNotifier::ConnectionTypeToString(
                          connection_type_)
                   << " -> "
                   << net::NetworkChangeNotifier::ConnectionTypeToString(
                          new_connection_type);
    *connection_type_changed = true;
  }
  if (default_network->path() != service_path_) {
    NET_LOG(EVENT) << "NCN Default network changed: "
                   << NetworkPathId(service_path_) << " -> "
                   << NetworkId(default_network);

    // If we had a default network service change, network resources
    // must always be invalidated.
    *ip_address_changed = true;
    *dns_changed = true;
  }

  std::string new_ip_address = default_network->GetIpAddress();
  if (new_ip_address != ip_address_) {
    // Is this a state update with an online->online transition?
    bool stayed_online =
        (!*connection_type_changed &&
         connection_type_ != net::NetworkChangeNotifier::CONNECTION_NONE);

    bool is_suppressed = true;
    // Suppress IP address change signalling on online->online transitions
    // when getting an IP address update for the first time.
    if (!(stayed_online && ip_address_.empty())) {
      is_suppressed = false;
      *ip_address_changed = true;
    }
    NET_LOG(EVENT) << "NCN Default IPAddress changed"
                   << (is_suppressed ? " (Suppressed)" : "") << ip_address_
                   << " -> " << new_ip_address;
  }
  std::string new_dns_servers = default_network->GetDnsServersAsString();
  if (new_dns_servers != dns_servers_) {
    NET_LOG(EVENT) << "NCN Default DNS server changed" << dns_servers_ << " -> "
                   << new_dns_servers;
    *dns_changed = true;
  }

  connection_type_ = new_connection_type;
  service_path_ = default_network->path();
  ip_address_ = new_ip_address;
  dns_servers_ = new_dns_servers;
  net::NetworkChangeNotifier::ConnectionSubtype new_subtype =
      GetConnectionSubtype(default_network->type(),
                           default_network->network_technology());
  if (new_subtype != connection_subtype_) {
    connection_subtype_ = new_subtype;
    *connection_subtype_changed = true;
  }
}

void NetworkChangeManagerClient::NotifyObservers(
    bool dns_changed,
    bool ip_address_changed,
    bool connection_type_changed,
    net::NetworkChangeNotifier::ConnectionType connection_type,
    bool connection_subtype_changed,
    net::NetworkChangeNotifier::ConnectionSubtype connection_subtype) {
  // If `test_notifications_only_` is set, skip notifying.
  if (network_change_notifier_->IsTestNotificationsOnly())
    return;

  // Notify NetworkChangeNotifier.
  if (ip_address_changed)
    network_change_notifier_->OnIPAddressChanged();
  if (dns_changed)
    network_change_notifier_->OnDNSChanged();
  if (connection_type_changed)
    network_change_notifier_->OnConnectionChanged(connection_type);
  if (connection_subtype_changed || connection_type_changed)
    network_change_notifier_->OnConnectionSubtypeChanged(connection_type,
                                                         connection_subtype);

  // Notify NetworkChangeManager if exists.
  if (network_change_manager_) {
    network_change_manager_->OnNetworkChanged(
        dns_changed, ip_address_changed, connection_type_changed,
        network::mojom::ConnectionType(connection_type),
        connection_subtype_changed,
        network::mojom::ConnectionSubtype(connection_subtype));
  }

  // Notify NetworkChangeObserver in Lacros if exists.
  for (auto& observer : lacros_network_change_observers_) {
    observer->OnNetworkChanged(
        dns_changed, ip_address_changed, connection_type_changed,
        crosapi::mojom::ConnectionType(connection_type),
        connection_subtype_changed,
        crosapi::mojom::ConnectionSubtype(connection_subtype));
  }
}

// static
net::NetworkChangeNotifier::ConnectionType
NetworkChangeManagerClient::ConnectionTypeFromShill(
    const std::string& type,
    const std::string& technology) {
  if (NetworkTypePattern::Ethernet().MatchesType(type))
    return net::NetworkChangeNotifier::CONNECTION_ETHERNET;
  if (type == shill::kTypeWifi)
    return net::NetworkChangeNotifier::CONNECTION_WIFI;

  if (type != shill::kTypeCellular)
    return net::NetworkChangeNotifier::CONNECTION_UNKNOWN;

  // For cellular types, mapping depends on the technology.
  if (technology == shill::kNetworkTechnologyEvdo ||
      technology == shill::kNetworkTechnologyGsm ||
      technology == shill::kNetworkTechnologyUmts ||
      technology == shill::kNetworkTechnologyHspa) {
    return net::NetworkChangeNotifier::CONNECTION_3G;
  }
  if (technology == shill::kNetworkTechnologyHspaPlus ||
      technology == shill::kNetworkTechnologyLte ||
      technology == shill::kNetworkTechnologyLteAdvanced) {
    return net::NetworkChangeNotifier::CONNECTION_4G;
  }
  if (technology == shill::kNetworkTechnology5gNr)
    return net::NetworkChangeNotifier::CONNECTION_5G;

  // Default cellular type is 2G.
  return net::NetworkChangeNotifier::CONNECTION_2G;
}

// static
net::NetworkChangeNotifier::ConnectionSubtype
NetworkChangeManagerClient::GetConnectionSubtype(
    const std::string& type,
    const std::string& technology) {
  if (type != shill::kTypeCellular)
    return net::NetworkChangeNotifier::SUBTYPE_UNKNOWN;

  if (technology == shill::kNetworkTechnology1Xrtt)
    return net::NetworkChangeNotifier::SUBTYPE_1XRTT;
  if (technology == shill::kNetworkTechnologyEvdo)
    return net::NetworkChangeNotifier::SUBTYPE_EVDO_REV_0;
  if (technology == shill::kNetworkTechnologyGsm)
    return net::NetworkChangeNotifier::SUBTYPE_GSM;
  if (technology == shill::kNetworkTechnologyGprs)
    return net::NetworkChangeNotifier::SUBTYPE_GPRS;
  if (technology == shill::kNetworkTechnologyEdge)
    return net::NetworkChangeNotifier::SUBTYPE_EDGE;
  if (technology == shill::kNetworkTechnologyUmts)
    return net::NetworkChangeNotifier::SUBTYPE_UMTS;
  if (technology == shill::kNetworkTechnologyHspa)
    return net::NetworkChangeNotifier::SUBTYPE_HSPA;
  if (technology == shill::kNetworkTechnologyHspaPlus)
    return net::NetworkChangeNotifier::SUBTYPE_HSPAP;
  if (technology == shill::kNetworkTechnologyLte)
    return net::NetworkChangeNotifier::SUBTYPE_LTE;
  if (technology == shill::kNetworkTechnologyLteAdvanced)
    return net::NetworkChangeNotifier::SUBTYPE_LTE_ADVANCED;

  return net::NetworkChangeNotifier::SUBTYPE_UNKNOWN;
}

}  // namespace ash