blob: 0a880d28a0812dca5e9627f611a518390934d86b [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shill/ethernet/ethernet.h"
#include <linux/ethtool.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <linux/if.h> // NOLINT - Needs definitions from netinet/ether.h
#include <linux/netdevice.h>
#include <linux/sockios.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <base/bind.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/location.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/shill/dbus-constants.h>
#include "shill/adaptor_interfaces.h"
#include "shill/control_interface.h"
#include "shill/device.h"
#include "shill/device_id.h"
#include "shill/device_info.h"
#include "shill/ethernet/ethernet_provider.h"
#include "shill/ethernet/ethernet_service.h"
#include "shill/event_dispatcher.h"
#include "shill/logging.h"
#include "shill/manager.h"
#include "shill/net/rtnl_handler.h"
#include "shill/profile.h"
#include "shill/property_accessor.h"
#include "shill/refptr_types.h"
#include "shill/store_interface.h"
#if !defined(DISABLE_PPPOE)
#include "shill/pppoe/pppoe_service.h"
#endif // DISABLE_PPPOE
#if !defined(DISABLE_WIRED_8021X)
#include "shill/eap_credentials.h"
#include "shill/eap_listener.h"
#include "shill/ethernet/ethernet_eap_provider.h"
#include "shill/supplicant/supplicant_interface_proxy_interface.h"
#include "shill/supplicant/supplicant_manager.h"
#include "shill/supplicant/supplicant_process_proxy_interface.h"
#include "shill/supplicant/wpa_supplicant.h"
#endif // DISABLE_WIRED_8021X
using std::string;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kEthernet;
static string ObjectID(const Ethernet* e) {
return e->GetRpcIdentifier().value();
}
} // namespace Logging
namespace {
// Path to file with |ethernet_mac0| VPD field value.
constexpr char kVpdEthernetMacFilePath[] = "/sys/firmware/vpd/ro/ethernet_mac0";
// Path to file with |dock_mac| VPD field value.
constexpr char kVpdDockMacFilePath[] = "/sys/firmware/vpd/ro/dock_mac";
bool IsValidMac(const std::string& mac_address) {
if (mac_address.length() != 12) {
return false;
}
return base::ContainsOnlyChars(mac_address, "0123456789abcdef");
}
} // namespace
Ethernet::Ethernet(Manager* manager,
const string& link_name,
const string& mac_address,
int interface_index)
: Device(manager,
link_name,
mac_address,
interface_index,
Technology::kEthernet),
link_up_(false),
bus_type_(GetDeviceBusType()),
#if !defined(DISABLE_WIRED_8021X)
is_eap_authenticated_(false),
is_eap_detected_(false),
eap_listener_(new EapListener(interface_index)),
#endif // DISABLE_WIRED_8021X
sockets_(new Sockets()),
permanent_mac_address_(GetPermanentMacAddressFromKernel()),
weak_ptr_factory_(this) {
PropertyStore* store = this->mutable_store();
#if !defined(DISABLE_WIRED_8021X)
store->RegisterConstBool(kEapAuthenticationCompletedProperty,
&is_eap_authenticated_);
store->RegisterConstBool(kEapAuthenticatorDetectedProperty,
&is_eap_detected_);
#endif // DISABLE_WIRED_8021X
store->RegisterConstBool(kLinkUpProperty, &link_up_);
store->RegisterConstString(kDeviceBusTypeProperty, &bus_type_);
store->RegisterDerivedBool(
kPPPoEProperty,
BoolAccessor(new CustomAccessor<Ethernet, bool>(
this, &Ethernet::GetPPPoEMode, &Ethernet::ConfigurePPPoEMode,
&Ethernet::ClearPPPoEMode)));
store->RegisterDerivedString(
kUsbEthernetMacAddressSourceProperty,
StringAccessor(new CustomAccessor<Ethernet, std::string>(
this, &Ethernet::GetUsbEthernetMacAddressSource, nullptr)));
#if !defined(DISABLE_WIRED_8021X)
eap_listener_->set_request_received_callback(
base::Bind(&Ethernet::OnEapDetected, weak_ptr_factory_.GetWeakPtr()));
#endif // DISABLE_WIRED_8021X
SLOG(this, 2) << "Ethernet device " << link_name << " initialized.";
if (bus_type_ == kDeviceBusTypeUsb) {
// Force change MAC address to |permanent_mac_address_| if
// |mac_address_| != |permanent_mac_address_|.
SetUsbEthernetMacAddressSource(kUsbEthernetMacAddressSourceUsbAdapterMac,
nullptr, ResultCallback());
}
}
Ethernet::~Ethernet() {}
void Ethernet::Start(Error* error,
const EnabledStateChangedCallback& /*callback*/) {
rtnl_handler()->SetInterfaceFlags(interface_index(), IFF_UP, IFF_UP);
OnEnabledStateChanged(EnabledStateChangedCallback(), Error());
LOG(INFO) << "Registering " << link_name() << " with manager.";
if (!service_) {
service_ = CreateEthernetService();
}
RegisterService(service_);
if (error)
error->Reset(); // indicate immediate completion
}
void Ethernet::Stop(Error* error,
const EnabledStateChangedCallback& /*callback*/) {
DeregisterService(service_);
// EthernetProvider::DeregisterService will ResetEthernet() when the Service
// being deregistered is the only Service remaining (instead of releasing the
// Service entirely) so that the ethernet_any service continues to live. When
// this happens, disassociate the EthernetService here as well.
if (!service_->HasEthernet()) {
service_ = nullptr;
}
#if !defined(DISABLE_WIRED_8021X)
StopSupplicant();
#endif // DISABLE_WIRED_8021X
OnEnabledStateChanged(EnabledStateChangedCallback(), Error());
if (error)
error->Reset(); // indicate immediate completion
}
void Ethernet::LinkEvent(unsigned int flags, unsigned int change) {
Device::LinkEvent(flags, change);
if ((flags & IFF_LOWER_UP) != 0 && !link_up_) {
link_up_ = true;
adaptor()->EmitBoolChanged(kLinkUpProperty, link_up_);
// We SetupWakeOnLan() here, instead of in Start(), because with
// r8139, "ethtool -s eth0 wol g" fails when no cable is plugged
// in.
if (service_) {
manager()->UpdateService(service_);
service_->OnVisibilityChanged();
}
SetupWakeOnLan();
#if !defined(DISABLE_WIRED_8021X)
eap_listener_->Start();
#endif // DISABLE_WIRED_8021X
} else if ((flags & IFF_LOWER_UP) == 0 && link_up_) {
link_up_ = false;
adaptor()->EmitBoolChanged(kLinkUpProperty, link_up_);
DropConnection();
if (service_) {
manager()->UpdateService(service_);
service_->OnVisibilityChanged();
}
#if !defined(DISABLE_WIRED_8021X)
is_eap_detected_ = false;
adaptor()->EmitBoolChanged(kEapAuthenticatorDetectedProperty,
is_eap_detected_);
GetEapProvider()->ClearCredentialChangeCallback(this);
SetIsEapAuthenticated(false);
StopSupplicant();
eap_listener_->Stop();
#endif // DISABLE_WIRED_8021X
}
}
bool Ethernet::Load(const StoreInterface* storage) {
const string id = GetStorageIdentifier();
if (!storage->ContainsGroup(id)) {
SLOG(this, 2) << "Device is not available in the persistent store: " << id;
return false;
}
bool pppoe = false;
storage->GetBool(id, kPPPoEProperty, &pppoe);
Error error;
ConfigurePPPoEMode(pppoe, &error);
if (!error.IsSuccess()) {
LOG(WARNING) << "Error configuring PPPoE mode. Ignoring!";
}
return Device::Load(storage);
}
bool Ethernet::Save(StoreInterface* storage) {
const string id = GetStorageIdentifier();
storage->SetBool(id, kPPPoEProperty, GetPPPoEMode(nullptr));
return Device::Save(storage);
}
void Ethernet::ConnectTo(EthernetService* service) {
CHECK(service_) << "Service should not be null";
CHECK(service == service_.get()) << "Ethernet was asked to connect the "
<< "wrong service?";
CHECK(!GetPPPoEMode(nullptr)) << "We should never connect in PPPoE mode!";
if (!link_up_) {
return;
}
SelectService(service);
if (AcquireIPConfigWithLeaseName(service->GetStorageIdentifier())) {
SetServiceState(Service::kStateConfiguring);
} else {
LOG(ERROR) << "Unable to acquire DHCP config.";
SetServiceState(Service::kStateFailure);
DestroyIPConfig();
}
}
std::string Ethernet::GetStorageIdentifier() const {
if (!permanent_mac_address_.empty()) {
return "device_" + permanent_mac_address_;
}
return Device::GetStorageIdentifier();
}
void Ethernet::DisconnectFrom(EthernetService* service) {
CHECK(service_) << "Service should not be null";
CHECK(service == service_.get()) << "Ethernet was asked to disconnect the "
<< "wrong service?";
DropConnection();
}
EthernetProvider* Ethernet::GetProvider() {
EthernetProvider* provider = manager()->ethernet_provider();
CHECK(provider);
return provider;
}
#if !defined(DISABLE_WIRED_8021X)
void Ethernet::TryEapAuthentication() {
try_eap_authentication_callback_.Reset(Bind(
&Ethernet::TryEapAuthenticationTask, weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostTask(FROM_HERE,
try_eap_authentication_callback_.callback());
}
void Ethernet::BSSAdded(const RpcIdentifier& path,
const KeyValueStore& properties) {
NOTREACHED() << __func__ << " is not implemented for Ethernet";
}
void Ethernet::BSSRemoved(const RpcIdentifier& path) {
NOTREACHED() << __func__ << " is not implemented for Ethernet";
}
void Ethernet::Certification(const KeyValueStore& properties) {
string subject;
uint32_t depth;
if (WPASupplicant::ExtractRemoteCertification(properties, &subject, &depth)) {
dispatcher()->PostTask(
FROM_HERE, Bind(&Ethernet::CertificationTask,
weak_ptr_factory_.GetWeakPtr(), subject, depth));
}
}
void Ethernet::EAPEvent(const string& status, const string& parameter) {
dispatcher()->PostTask(
FROM_HERE, Bind(&Ethernet::EAPEventTask, weak_ptr_factory_.GetWeakPtr(),
status, parameter));
}
void Ethernet::PropertiesChanged(const KeyValueStore& properties) {
if (!properties.Contains<string>(WPASupplicant::kInterfacePropertyState)) {
return;
}
dispatcher()->PostTask(
FROM_HERE,
Bind(&Ethernet::SupplicantStateChangedTask,
weak_ptr_factory_.GetWeakPtr(),
properties.Get<string>(WPASupplicant::kInterfacePropertyState)));
}
void Ethernet::ScanDone(const bool& /*success*/) {
NOTREACHED() << __func__ << " is not implented for Ethernet";
}
EthernetEapProvider* Ethernet::GetEapProvider() {
EthernetEapProvider* eap_provider = manager()->ethernet_eap_provider();
CHECK(eap_provider);
return eap_provider;
}
ServiceConstRefPtr Ethernet::GetEapService() {
ServiceConstRefPtr eap_service = GetEapProvider()->service();
CHECK(eap_service);
return eap_service;
}
void Ethernet::OnEapDetected() {
is_eap_detected_ = true;
adaptor()->EmitBoolChanged(kEapAuthenticatorDetectedProperty,
is_eap_detected_);
eap_listener_->Stop();
GetEapProvider()->SetCredentialChangeCallback(
this, base::Bind(&Ethernet::TryEapAuthentication,
weak_ptr_factory_.GetWeakPtr()));
TryEapAuthentication();
}
bool Ethernet::StartSupplicant() {
if (supplicant_interface_proxy_) {
return true;
}
RpcIdentifier interface_path;
KeyValueStore create_interface_args;
create_interface_args.Set<string>(WPASupplicant::kInterfacePropertyName,
link_name());
create_interface_args.Set<string>(WPASupplicant::kInterfacePropertyDriver,
WPASupplicant::kDriverWired);
create_interface_args.Set<string>(WPASupplicant::kInterfacePropertyConfigFile,
WPASupplicant::kSupplicantConfPath);
if (!supplicant_process_proxy()->CreateInterface(create_interface_args,
&interface_path)) {
// Interface might've already been created, try to retrieve it.
if (!supplicant_process_proxy()->GetInterface(link_name(),
&interface_path)) {
LOG(ERROR) << __func__ << ": Failed to create interface with supplicant.";
StopSupplicant();
return false;
}
}
supplicant_interface_proxy_ =
control_interface()->CreateSupplicantInterfaceProxy(this, interface_path);
supplicant_interface_path_ = interface_path;
return true;
}
bool Ethernet::StartEapAuthentication() {
KeyValueStore params;
GetEapService()->eap()->PopulateSupplicantProperties(&certificate_file_,
&params);
params.Set<string>(WPASupplicant::kNetworkPropertyEapKeyManagement,
WPASupplicant::kKeyManagementIeee8021X);
params.Set<uint32_t>(WPASupplicant::kNetworkPropertyEapolFlags, 0);
params.Set<uint32_t>(WPASupplicant::kNetworkPropertyScanSSID, 0);
if (service_) {
service_->ClearEAPCertification();
}
eap_state_handler_.Reset();
if (!supplicant_network_path_.value().empty()) {
if (!supplicant_interface_proxy_->RemoveNetwork(supplicant_network_path_)) {
LOG(ERROR) << "Failed to remove network: "
<< supplicant_network_path_.value();
return false;
}
}
if (!supplicant_interface_proxy_->AddNetwork(params,
&supplicant_network_path_)) {
LOG(ERROR) << "Failed to add network";
return false;
}
CHECK(!supplicant_network_path_.value().empty());
supplicant_interface_proxy_->SelectNetwork(supplicant_network_path_);
supplicant_interface_proxy_->EAPLogon();
return true;
}
void Ethernet::StopSupplicant() {
if (supplicant_interface_proxy_) {
supplicant_interface_proxy_->EAPLogoff();
}
supplicant_interface_proxy_.reset();
if (!supplicant_interface_path_.value().empty()) {
if (!supplicant_process_proxy()->RemoveInterface(
supplicant_interface_path_)) {
LOG(ERROR) << __func__ << ": Failed to remove interface from supplicant.";
}
}
supplicant_network_path_ = RpcIdentifier("");
supplicant_interface_path_ = RpcIdentifier("");
SetIsEapAuthenticated(false);
}
void Ethernet::SetIsEapAuthenticated(bool is_eap_authenticated) {
if (is_eap_authenticated == is_eap_authenticated_) {
return;
}
// If our EAP authentication state changes, we have now joined a different
// network. Restart the DHCP process and any other connection state.
if (service_) {
DisconnectFrom(service_.get());
ConnectTo(service_.get());
}
is_eap_authenticated_ = is_eap_authenticated;
adaptor()->EmitBoolChanged(kEapAuthenticationCompletedProperty,
is_eap_authenticated_);
}
void Ethernet::CertificationTask(const string& subject, uint32_t depth) {
CHECK(service_) << "Ethernet " << link_name() << " " << __func__
<< " with no service.";
service_->AddEAPCertification(subject, depth);
}
void Ethernet::EAPEventTask(const string& status, const string& parameter) {
LOG(INFO) << "In " << __func__ << " with status " << status << ", parameter "
<< parameter;
Service::ConnectFailure failure = Service::kFailureNone;
if (eap_state_handler_.ParseStatus(status, parameter, &failure)) {
LOG(INFO) << "EAP authentication succeeded!";
SetIsEapAuthenticated(true);
} else if (failure != Service::Service::kFailureNone) {
LOG(INFO) << "EAP authentication failed!";
SetIsEapAuthenticated(false);
}
}
void Ethernet::SupplicantStateChangedTask(const string& state) {
LOG(INFO) << "Supplicant state changed to " << state;
}
void Ethernet::TryEapAuthenticationTask() {
if (!GetEapService()->Is8021xConnectable()) {
if (is_eap_authenticated_) {
LOG(INFO) << "EAP Service lost 802.1X credentials; "
<< "terminating EAP authentication.";
} else {
LOG(INFO) << "EAP Service lacks 802.1X credentials; "
<< "not doing EAP authentication.";
}
StopSupplicant();
return;
}
if (!is_eap_detected_) {
LOG(WARNING) << "EAP authenticator not detected; "
<< "not doing EAP authentication.";
return;
}
if (!StartSupplicant()) {
LOG(ERROR) << "Failed to start supplicant.";
return;
}
StartEapAuthentication();
}
SupplicantProcessProxyInterface* Ethernet::supplicant_process_proxy() const {
return manager()->supplicant_manager()->proxy();
}
#endif // DISABLE_WIRED_8021X
void Ethernet::SetupWakeOnLan() {
int sock;
struct ifreq interface_command;
struct ethtool_wolinfo wake_on_lan_command;
if (link_name().length() >= sizeof(interface_command.ifr_name)) {
LOG(WARNING) << "Interface name " << link_name()
<< " too long: " << link_name().size()
<< " >= " << sizeof(interface_command.ifr_name);
return;
}
sock = sockets_->Socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_IP);
if (sock < 0) {
LOG(WARNING) << "Failed to allocate socket: " << sockets_->ErrorString()
<< ".";
return;
}
ScopedSocketCloser socket_closer(sockets_.get(), sock);
memset(&interface_command, 0, sizeof(interface_command));
memset(&wake_on_lan_command, 0, sizeof(wake_on_lan_command));
wake_on_lan_command.cmd = ETHTOOL_SWOL;
if (manager()->IsWakeOnLanEnabled()) {
wake_on_lan_command.wolopts = WAKE_MAGIC;
}
interface_command.ifr_data = &wake_on_lan_command;
memcpy(interface_command.ifr_name, link_name().data(), link_name().length());
int res = sockets_->Ioctl(sock, SIOCETHTOOL, &interface_command);
if (res < 0) {
LOG(WARNING) << "Failed to enable wake-on-lan: " << sockets_->ErrorString()
<< ".";
return;
}
}
bool Ethernet::ConfigurePPPoEMode(const bool& enable, Error* error) {
#if defined(DISABLE_PPPOE)
if (enable) {
LOG(WARNING) << "PPPoE support is not implemented. Ignoring attempt "
<< "to configure " << link_name();
error->Populate(Error::kNotSupported);
}
return false;
#else
if (!service_) {
// If |service_| is null, we haven't started this Device yet.
if (enable) {
// Create a PPPoEService but let Start() register it.
service_ = CreatePPPoEService();
} else {
// Reset |service_| and let Start() create and register a standard
// EthernetService.
service_ = nullptr;
}
return true;
}
EthernetServiceRefPtr service = nullptr;
if (enable && service_->technology() != Technology::kPPPoE) {
service = CreatePPPoEService();
if (!manager()->HasService(service_)) {
// |service_| is unregistered, which means the Device is not started.
// Create a PPPoEService, but let Start() register it.
service_ = service;
return true;
}
} else if (!enable && service_->technology() == Technology::kPPPoE) {
if (!manager()->HasService(service_)) {
// |service_| is unregistered, which means ths Device is not started. Let
// Start() create and register a standard EthernetService.
service_ = nullptr;
return true;
}
service = CreateEthernetService();
} else {
return false;
}
CHECK(service);
// If |service_| has not begun to connect (i.e. this method is called prior to
// Manager::SortServicesTask being executed and triggering an autoconnect),
// Disconnect would return an error. We can get away with ignoring any error
// here because DisconnectFrom does not have any failure scenarios.
//
// TODO(crbug.com/1003958) If/when PPPoE is redesigned, this hack will be
// unnecessary to begin with.
Error unused_error;
service_->Disconnect(&unused_error, __func__);
DeregisterService(service_);
service_ = service;
RegisterService(service_);
return true;
#endif // DISABLE_PPPOE
}
bool Ethernet::GetPPPoEMode(Error* error) {
if (service_ == nullptr) {
return false;
}
return service_->technology() == Technology::kPPPoE;
}
void Ethernet::ClearPPPoEMode(Error* error) {
ConfigurePPPoEMode(false, error);
}
std::string Ethernet::GetUsbEthernetMacAddressSource(Error* error) {
return usb_ethernet_mac_address_source_;
}
EthernetServiceRefPtr Ethernet::CreateEthernetService() {
return GetProvider()->CreateService(weak_ptr_factory_.GetWeakPtr());
}
EthernetServiceRefPtr Ethernet::CreatePPPoEService() {
#if defined(DISABLE_PPPOE)
NOTREACHED() << __func__ << " should not be called when PPPoE is disabled";
return nullptr;
#else
return new PPPoEService(manager(), weak_ptr_factory_.GetWeakPtr());
#endif // DISABLE_PPPOE
}
void Ethernet::RegisterService(EthernetServiceRefPtr service) {
if (!service) {
return;
}
if (service->technology() == Technology::kPPPoE) {
manager()->RegisterService(service);
} else {
GetProvider()->RegisterService(service);
}
}
void Ethernet::DeregisterService(EthernetServiceRefPtr service) {
if (!service) {
return;
}
if (service->technology() == Technology::kPPPoE) {
manager()->DeregisterService(service);
} else {
GetProvider()->DeregisterService(service);
}
}
void Ethernet::SetUsbEthernetMacAddressSource(const std::string& source,
Error* error,
const ResultCallback& callback) {
SLOG(this, 2) << __func__ << " " << source;
if (bus_type_ != kDeviceBusTypeUsb) {
Error::PopulateAndLog(FROM_HERE, error, Error::kNotSupported,
"Not supported for non-USB devices: " + bus_type_);
return;
}
std::string new_mac_address;
if (source == kUsbEthernetMacAddressSourceDesignatedDockMac) {
new_mac_address =
ReadMacAddressFromFile(base::FilePath(kVpdDockMacFilePath));
} else if (source == kUsbEthernetMacAddressSourceBuiltinAdapterMac) {
new_mac_address =
ReadMacAddressFromFile(base::FilePath(kVpdEthernetMacFilePath));
} else if (source == kUsbEthernetMacAddressSourceUsbAdapterMac) {
new_mac_address = permanent_mac_address_;
} else {
Error::PopulateAndLog(FROM_HERE, error, Error::kInvalidArguments,
"Unknown source: " + source);
return;
}
if (new_mac_address.empty()) {
Error::PopulateAndLog(
FROM_HERE, error, Error::kNotSupported,
"Failed to find out new MAC address for source: " + source);
return;
}
if (new_mac_address == mac_address()) {
SLOG(this, 4) << __func__ << " new MAC address is equal to the old one";
if (usb_ethernet_mac_address_source_ != source) {
usb_ethernet_mac_address_source_ = source;
adaptor()->EmitStringChanged(kUsbEthernetMacAddressSourceProperty,
usb_ethernet_mac_address_source_);
}
if (error) {
error->Populate(Error::kSuccess);
}
return;
}
SLOG(this, 2) << "Send netlink request to change MAC address for "
<< link_name() << " device from " << mac_address() << " to "
<< new_mac_address;
rtnl_handler()->SetInterfaceMac(
interface_index(), ByteString::CreateFromHexString(new_mac_address),
base::BindOnce(&Ethernet::OnSetInterfaceMacResponse,
weak_ptr_factory_.GetWeakPtr(), source, new_mac_address,
callback));
}
std::string Ethernet::ReadMacAddressFromFile(const base::FilePath& file_path) {
std::string mac_address;
if (!base::ReadFileToString(file_path, &mac_address)) {
PLOG(ERROR) << "Unable to read MAC address from file: "
<< file_path.value();
return std::string();
}
base::RemoveChars(base::ToLowerASCII(mac_address), ":", &mac_address);
if (!IsValidMac(mac_address)) {
LOG(ERROR) << "MAC address from file " << file_path.value()
<< " is invalid: " << mac_address;
return std::string();
}
return mac_address;
}
void Ethernet::OnSetInterfaceMacResponse(const std::string& mac_address_source,
const std::string& new_mac_address,
const ResultCallback& callback,
int32_t error) {
if (error) {
LOG(ERROR) << __func__ << " received response with error "
<< strerror(error);
if (!callback.is_null()) {
callback.Run(Error(Error::kNotSupported));
}
return;
}
SLOG(this, 2) << __func__ << " received successful response";
usb_ethernet_mac_address_source_ = mac_address_source;
adaptor()->EmitStringChanged(kUsbEthernetMacAddressSourceProperty,
usb_ethernet_mac_address_source_);
set_mac_address(new_mac_address);
if (!callback.is_null()) {
callback.Run(Error(Error::kSuccess));
}
}
void Ethernet::set_mac_address(const std::string& new_mac_address) {
SLOG(this, 2) << __func__ << " " << new_mac_address;
ProfileRefPtr profile;
if (service_) {
profile = service_->profile();
}
// Abandon and adopt service if service storage identifier will change after
// changing ethernet MAC address.
if (permanent_mac_address_.empty() && profile &&
!service_->HasStorageIdentifier()) {
profile->AbandonService(service_);
Device::set_mac_address(new_mac_address);
profile->AdoptService(service_);
} else {
Device::set_mac_address(new_mac_address);
}
if (service_) {
DisconnectFrom(service_.get());
ConnectTo(service_.get());
}
}
std::string Ethernet::GetPermanentMacAddressFromKernel() {
struct ifreq ifr;
if (link_name().length() >= sizeof(ifr.ifr_name)) {
LOG(WARNING) << "Interface name " << link_name()
<< " too long: " << link_name().size()
<< " >= " << sizeof(ifr.ifr_name);
return std::string();
}
memset(&ifr, 0, sizeof(ifr));
memcpy(ifr.ifr_name, link_name().data(), link_name().length());
constexpr int kPermAddrBufferSize =
sizeof(struct ethtool_perm_addr) + MAX_ADDR_LEN;
char perm_addr_buffer[kPermAddrBufferSize];
memset(perm_addr_buffer, 0, kPermAddrBufferSize);
struct ethtool_perm_addr* perm_addr = static_cast<struct ethtool_perm_addr*>(
static_cast<void*>(perm_addr_buffer));
perm_addr->cmd = ETHTOOL_GPERMADDR;
perm_addr->size = MAX_ADDR_LEN;
ifr.ifr_data = perm_addr;
const int fd = sockets_->Socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
PLOG(WARNING) << "Failed to allocate socket";
return std::string();
}
ScopedSocketCloser socket_closer(sockets_.get(), fd);
int err = sockets_->Ioctl(fd, SIOCETHTOOL, &ifr);
if (err < 0) {
PLOG(WARNING) << "Failed to read permanent MAC address";
return std::string();
}
if (perm_addr->size != ETH_ALEN) {
LOG(WARNING) << "Invalid permanent MAC address size: " << perm_addr->size;
return std::string();
}
std::string mac_address =
base::ToLowerASCII(ByteString(perm_addr->data, ETH_ALEN).HexEncode());
if (!IsValidMac(mac_address)) {
LOG(ERROR) << "Invalid permanent MAC address: " << mac_address;
return std::string();
}
return mac_address;
}
std::string Ethernet::GetDeviceBusType() const {
auto device_id = DeviceId::CreateFromSysfs(base::FilePath(
base::StringPrintf("/sys/class/net/%s/device", link_name().c_str())));
constexpr DeviceId kPciDevicePattern{DeviceId::BusType::kPci};
if (device_id && device_id->Match(kPciDevicePattern)) {
return kDeviceBusTypePci;
}
constexpr DeviceId kUsbDevicePattern{DeviceId::BusType::kUsb};
if (device_id && device_id->Match(kUsbDevicePattern)) {
return kDeviceBusTypeUsb;
}
return std::string();
}
} // namespace shill