//
// Copyright (C) 2012 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 "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/sockios.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <map>
#include <string>
#include <vector>
#include <base/bind.h>
#include "shill/adaptor_interfaces.h"
#include "shill/control_interface.h"
#include "shill/device.h"
#include "shill/device_info.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/pppoe/pppoe_service.h"
#include "shill/profile.h"
#include "shill/property_accessor.h"
#include "shill/refptr_types.h"
#include "shill/store_interface.h"
#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_process_proxy_interface.h"
#include "shill/supplicant/wpa_supplicant.h"
#endif // DISABLE_WIRED_8021X
using std::map;
using std::string;
using std::vector;
namespace shill {
namespace Logging {
static auto kModuleLogScope = ScopeLogger::kEthernet;
static string ObjectID(Ethernet* e) { return e->GetRpcIdentifier(); }
}
Ethernet::Ethernet(ControlInterface* control_interface,
EventDispatcher* dispatcher,
Metrics* metrics,
Manager* manager,
const string& link_name,
const string& address,
int interface_index)
: Device(control_interface,
dispatcher,
metrics,
manager,
link_name,
address,
interface_index,
Technology::kEthernet),
control_interface_(control_interface),
link_up_(false),
#if !defined(DISABLE_WIRED_8021X)
is_eap_authenticated_(false),
is_eap_detected_(false),
eap_listener_(new EapListener(dispatcher, interface_index)),
supplicant_process_proxy_(
control_interface_->CreateSupplicantProcessProxy(
base::Closure(), base::Closure())),
#endif // DISABLE_WIRED_8021X
sockets_(new Sockets()),
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->RegisterDerivedBool(kPPPoEProperty, BoolAccessor(
new CustomAccessor<Ethernet, bool>(this,
&Ethernet::GetPPPoEMode,
&Ethernet::ConfigurePPPoEMode,
&Ethernet::ClearPPPoEMode)));
#if !defined(DISABLE_WIRED_8021X)
eap_listener_->set_request_received_callback(
base::Bind(&Ethernet::OnEapDetected, weak_ptr_factory_.GetWeakPtr()));
#endif // DISABLE_WIRED_8021X
service_ = CreateEthernetService();
SLOG(this, 2) << "Ethernet device " << link_name << " initialized.";
}
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 (!manager()->HasService(service_)) {
manager()->RegisterService(service_);
}
if (error)
error->Reset(); // indicate immediate completion
}
void Ethernet::Stop(Error* error,
const EnabledStateChangedCallback& /*callback*/) {
manager()->DeregisterService(service_);
#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.
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_);
DestroyIPConfig();
SelectService(nullptr);
manager()->UpdateService(service_);
service_->OnVisibilityChanged();
#if !defined(DISABLE_WIRED_8021X)
is_eap_detected_ = false;
GetEapProvider()->ClearCredentialChangeCallback(this);
SetIsEapAuthenticated(false);
StopSupplicant();
eap_listener_->Stop();
#endif // DISABLE_WIRED_8021X
}
}
bool Ethernet::Load(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 true;
}
void Ethernet::ConnectTo(EthernetService* service) {
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();
}
}
void Ethernet::DisconnectFrom(EthernetService* service) {
CHECK(service == service_.get()) << "Ethernet was asked to disconnect the "
<< "wrong service?";
DropConnection();
}
#if !defined(DISABLE_WIRED_8021X)
void Ethernet::TryEapAuthentication() {
try_eap_authentication_callback_.Reset(
Bind(&Ethernet::TryEapAuthenticationTask,
weak_ptr_factory_.GetWeakPtr()));
dispatcher()->PostTask(try_eap_authentication_callback_.callback());
}
void Ethernet::BSSAdded(const string& path, const KeyValueStore& properties) {
NOTREACHED() << __func__ << " is not implemented for Ethernet";
}
void Ethernet::BSSRemoved(const string& 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(Bind(&Ethernet::CertificationTask,
weak_ptr_factory_.GetWeakPtr(),
subject, depth));
}
}
void Ethernet::EAPEvent(const string& status, const string& parameter) {
dispatcher()->PostTask(Bind(&Ethernet::EAPEventTask,
weak_ptr_factory_.GetWeakPtr(),
status,
parameter));
}
void Ethernet::PropertiesChanged(const KeyValueStore& properties) {
if (!properties.ContainsString(WPASupplicant::kInterfacePropertyState)) {
return;
}
dispatcher()->PostTask(
Bind(&Ethernet::SupplicantStateChangedTask,
weak_ptr_factory_.GetWeakPtr(),
properties.GetString(WPASupplicant::kInterfacePropertyState)));
}
void Ethernet::ScanDone(const bool& /*success*/) {
NOTREACHED() << __func__ << " is not implented for Ethernet";
}
void Ethernet::TDLSDiscoverResponse(const std::string& peer_address) {
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;
eap_listener_->Stop();
GetEapProvider()->SetCredentialChangeCallback(
this,
base::Bind(&Ethernet::TryEapAuthentication,
weak_ptr_factory_.GetWeakPtr()));
TryEapAuthentication();
}
bool Ethernet::StartSupplicant() {
if (supplicant_interface_proxy_.get()) {
return true;
}
string interface_path;
KeyValueStore create_interface_args;
create_interface_args.SetString(WPASupplicant::kInterfacePropertyName,
link_name());
create_interface_args.SetString(WPASupplicant::kInterfacePropertyDriver,
WPASupplicant::kDriverWired);
create_interface_args.SetString(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_.reset(
control_interface_->CreateSupplicantInterfaceProxy(this, interface_path));
supplicant_interface_path_ = interface_path;
return true;
}
bool Ethernet::StartEapAuthentication() {
KeyValueStore params;
GetEapService()->eap()->PopulateSupplicantProperties(
&certificate_file_, ¶ms);
params.SetString(WPASupplicant::kNetworkPropertyEapKeyManagement,
WPASupplicant::kKeyManagementIeee8021X);
params.SetUint(WPASupplicant::kNetworkPropertyEapolFlags, 0);
params.SetUint(WPASupplicant::kNetworkPropertyScanSSID, 0);
service_->ClearEAPCertification();
eap_state_handler_.Reset();
if (!supplicant_network_path_.empty()) {
if (!supplicant_interface_proxy_->RemoveNetwork(supplicant_network_path_)) {
LOG(ERROR) << "Failed to remove network: " << supplicant_network_path_;
return false;
}
}
if (!supplicant_interface_proxy_->AddNetwork(params,
&supplicant_network_path_)) {
LOG(ERROR) << "Failed to add network";
return false;
}
CHECK(!supplicant_network_path_.empty());
supplicant_interface_proxy_->SelectNetwork(supplicant_network_path_);
supplicant_interface_proxy_->EAPLogon();
return true;
}
void Ethernet::StopSupplicant() {
if (supplicant_interface_proxy_.get()) {
supplicant_interface_proxy_->EAPLogoff();
}
supplicant_interface_proxy_.reset();
if (!supplicant_interface_path_.empty()) {
if (!supplicant_process_proxy_->RemoveInterface(
supplicant_interface_path_)) {
LOG(ERROR) << __func__ << ": Failed to remove interface from supplicant.";
}
}
supplicant_network_path_ = "";
supplicant_interface_path_ = "";
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.
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::kFailureUnknown;
if (eap_state_handler_.ParseStatus(status, parameter, &failure)) {
LOG(INFO) << "EAP authentication succeeded!";
SetIsEapAuthenticated(true);
} else if (failure != Service::Service::kFailureUnknown) {
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();
}
#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, 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
CHECK(service_);
EthernetServiceRefPtr service = nullptr;
if (enable && service_->technology() != Technology::kPPPoE) {
service = CreatePPPoEService();
} else if (!enable && service_->technology() == Technology::kPPPoE) {
service = CreateEthernetService();
} else {
return false;
}
CHECK(service);
service_->Disconnect(error, nullptr);
manager()->DeregisterService(service_);
service_ = service;
manager()->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);
}
EthernetServiceRefPtr Ethernet::CreateEthernetService() {
return new EthernetService(control_interface_,
dispatcher(),
metrics(),
manager(),
weak_ptr_factory_.GetWeakPtr());
}
EthernetServiceRefPtr Ethernet::CreatePPPoEService() {
return new PPPoEService(control_interface_,
dispatcher(),
metrics(),
manager(),
weak_ptr_factory_.GetWeakPtr());
}
} // namespace shill