来源于产品开发需求,需要在linux系统下实现网络状类型查询及网络类型变更通知,比如从Ethernet变为Wifi,从Wifi变为Ethernet等。
实现步骤:
网络类型 | |
---|---|
None | 无在线网卡 |
Ethernet | 在线网卡类型一致,均为Ethernet |
Wifi | 在线网卡类型一致,均为Wifi |
Unknown | 在线网卡类型不一致,比如同时存在Ethernet和Wifi |
#pragma once
#include
#include
#include
#include
#include
enum NetworkType : int32_t {
kNetworkUnknown = 0, // A connection exists, but its type is unknown. Also used as a default value.
kNetworkEthernet = 1,
kNetworkWifi = 2,
kNetwork2G = 3,
kNetwork3G = 4,
kNetwork4G = 5,
kNetwork5G = 6,
kNetworkWWAN = 7,
kNetworkBluetooth = 8,
kNetworkNone = 9,
};
enum Result : int32_t {
kNoError = 0, /** 没有错误 */
kFatalError = 1, /** 错误*/
};
namespace internal {
class AddressTrackerLinux;
}
class INetworkMonitorEventSink;
class NetworkMonitorLinux {
public:
NetworkMonitorLinux(INetworkMonitorEventSink* sink);
virtual ~NetworkMonitorLinux();
public:
Result Start();
Result Stop();
NetworkType GetCurrentNetworkType();
private:
bool has_started = false;
std::unique_ptr<internal::AddressTrackerLinux> address_tracker_linux_;
};
class INetworkMonitorEventSink {
public:
virtual ~INetworkMonitorEventSink() = default;
virtual void OnDeviceNetworkChanged(NetworkType type) = 0;
};
namespace internal {
class AddressTrackerLinux
{
public:
AddressTrackerLinux();
AddressTrackerLinux(INetworkMonitorEventSink* sink);
virtual ~AddressTrackerLinux();
std::set<int> GetOnlineLinks();
NetworkType GetCurrentConnectionType();
void StartTracking();
void StopTracking();
private:
bool Init();
void DeInit();
void Close();
void UpdateCurrentConnectionType();
void ReadMessages();
void HandleMessage(char* buffer,
size_t length,
bool* address_changed,
bool* link_changed,
bool* tunnel_changed);
void WakeUpThread();
INetworkMonitorEventSink* sink_;
std::thread thread_;
std::atomic_bool thread_stopped_;
int netlink_fd_ = -1;
std::mutex online_links_lock_;
std::set<int> online_links_;
std::mutex current_connection_type_lock_;
NetworkType current_connection_type_ = kNetworkUnknown;
static const int MAX_EVENT_NUMBER = 1024;
struct epoll_event events_[MAX_EVENT_NUMBER];
int epoll_fd_ = -1;
int event_fd_ = -1;
};
class SocketGuard
{
public:
SocketGuard(int sockfd);
virtual ~SocketGuard();
private:
int sockfd_ = -1;
};
} // namespace internal
#include "network_monitor.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
NetworkMonitorLinux::NetworkMonitorLinux(INetworkMonitorEventSink* sink)
{
address_tracker_linux_ = std::make_unique<internal::AddressTrackerLinux>(sink);
}
NetworkMonitorLinux::~NetworkMonitorLinux() {
Stop();
}
Result NetworkMonitorLinux::Start() {
if (has_started) {
return kFatalError;
}
has_started = true;
if (address_tracker_linux_) {
address_tracker_linux_->StartTracking();
}
return kNoError;
}
Result NetworkMonitorLinux::Stop() {
if (!has_started) {
return kFatalError;
}
has_started = false;
if (address_tracker_linux_) {
address_tracker_linux_->StopTracking();
}
return kNoError;
}
NetworkType NetworkMonitorLinux::GetCurrentNetworkType() {
if (address_tracker_linux_) {
return address_tracker_linux_->GetCurrentConnectionType();
} else {
return kNetworkUnknown;
}
}
namespace internal {
static
char* GetInterfaceName(int interface_index, char* buf) {
memset(buf, 0, IFNAMSIZ);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
SocketGuard socket_guard(sockfd);
if (sockfd < 0) {
return buf;
}
struct ifreq ifr = {};
ifr.ifr_ifindex = interface_index;
if (ioctl(sockfd, SIOCGIFNAME, &ifr) == 0)
strncpy(buf, ifr.ifr_name, IFNAMSIZ - 1);
return buf;
}
static
bool IsTunnelInterface(const char *name) {
// Linux kernel drivers/net/tun.c uses "tun" name prefix.
return strncmp(name, "tun", 3) == 0;
}
static
bool IsVirtualNetworkInterface(const char *name) {
// Remove VMware network interfaces as they're internal and should not be
// used to determine the network connection type.
std::string interfce_str(name);
std::transform(interfce_str.begin(), interfce_str.end(), interfce_str.begin(), ::tolower);
return interfce_str.find("vmnet") != std::string::npos;
}
static
bool IsDockerNetworkInterface(const char *name) {
// Remove docker network interfaces as they're internal and should not be
// used to determine the network connection type.
std::string interfce_str(name);
std::transform(interfce_str.begin(), interfce_str.end(), interfce_str.begin(), ::tolower);
return interfce_str.find("docker") != std::string::npos;
}
static
NetworkType GetInterfaceConnectionType(
const std::string& ifname) {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
SocketGuard socket_guard(sockfd);
if (sockfd < 0) {
return kNetworkUnknown;
}
// Test wireless extensions for CONNECTION_WIFI
struct iwreq pwrq = {};
strncpy(pwrq.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCGIWNAME, &pwrq) != -1)
return kNetworkWifi;
#if !defined(OS_ANDROID)
// Test ethtool for CONNECTION_ETHERNET
struct ethtool_cmd ecmd = {};
ecmd.cmd = ETHTOOL_GSET;
struct ifreq ifr = {};
ifr.ifr_data = &ecmd;
strncpy(ifr.ifr_name, ifname.c_str(), IFNAMSIZ - 1);
if (ioctl(sockfd, SIOCETHTOOL, &ifr) != -1)
return kNetworkEthernet;
#endif // !defined(OS_ANDROID)
return kNetworkUnknown;
}
static
NetworkType ConnectionTypeFromInterfaceList(const std::map<int, NetworkType> &online_links_type) {
bool first = true;
NetworkType result = kNetworkNone;
for (auto s : online_links_type) {
if (first) {
first = false;
result = s.second;
} else if (result != s.second) {
return kNetworkUnknown;
}
}
return result;
}
AddressTrackerLinux::AddressTrackerLinux()
: sink_(nullptr)
, thread_stopped_(true) {
}
AddressTrackerLinux::AddressTrackerLinux(INetworkMonitorEventSink* sink)
: sink_(sink)
, thread_stopped_(true) {
}
AddressTrackerLinux::~AddressTrackerLinux() {
StopTracking();
}
void AddressTrackerLinux::DeInit() {
{
std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
current_connection_type_ = kNetworkUnknown;
}
{
std::lock_guard<std::mutex> auto_lock(online_links_lock_);
online_links_.clear();
}
}
bool AddressTrackerLinux::Init() {
// domain - AF_NETLINK: Kernel user interface device
// type - SOCK_RAW: Provides raw network protocol access.
// protocol - NETLINK_ROUTE:
netlink_fd_ = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (netlink_fd_ < 0) {
printf("Could not create NETLINK socket\n");
return false;
}
// Request notifications.
struct sockaddr_nl addr = {};
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
// TODO(szym): Track RTMGRP_LINK as well for ifi_type,
// http://crbug.com/113993
addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_NOTIFY | RTMGRP_LINK;
int rv = bind(
netlink_fd_, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
if (rv < 0) {
printf("Could not bind NETLINK socket\n");
Close();
return false;
}
struct sockaddr_nl peer = {};
peer.nl_family = AF_NETLINK;
struct {
struct nlmsghdr header;
struct rtgenmsg msg;
} request = {};
request.header.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
request.header.nlmsg_type = RTM_GETLINK;
request.header.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; // must set NLM_F_DUMP, or will receive message error -22
request.header.nlmsg_pid = getpid();
request.msg.rtgen_family = AF_UNSPEC;
rv = sendto(netlink_fd_, &request, request.header.nlmsg_len,
0, reinterpret_cast<struct sockaddr*>(&peer),
sizeof(peer));
if (rv < 0) {
printf("Could not send NETLINK request\n");
Close();
return false;
}
ReadMessages();
epoll_fd_ = epoll_create(5);
if (epoll_fd_ < 0) {
printf("Could not create epoll\n");
return false;
}
struct epoll_event event;
event.events = EPOLLIN | EPOLLET; // must read all data once in et mode
event.data.fd = netlink_fd_;
epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, netlink_fd_, &event);
event_fd_ = eventfd(0, 0);
if (event_fd_ < 0) {
printf("Could not create eventfd\n");
return false;
}
event.data.fd = event_fd_;
epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, event_fd_, &event);
return true;
}
void AddressTrackerLinux::Close() {
if (event_fd_ >= 0 && close(event_fd_) < 0) {
printf("Could not close event fd\n");
}
event_fd_ = -1;
if (epoll_fd_ >= 0 && close(epoll_fd_) < 0) {
printf("Could not close epoll fd\n");
}
epoll_fd_ = -1;
if (netlink_fd_ >= 0 && close(netlink_fd_) < 0) {
printf("Could not close NETLINK socket\n");
}
netlink_fd_ = -1;
}
void AddressTrackerLinux::UpdateCurrentConnectionType() {
std::set<int> online_links = GetOnlineLinks();
std::map<int, NetworkType> online_links_type;
// Strip out tunnel | virtual | docker interfaces from online_links
for (auto it = online_links.begin(); it != online_links.end(); ) {
char buf[IFNAMSIZ] = {0};
GetInterfaceName(*it, buf);
if (IsTunnelInterface(buf) ||
IsVirtualNetworkInterface(buf) ||
IsDockerNetworkInterface(buf)) {
it = online_links.erase(it);
} else {
online_links_type[*it] = GetInterfaceConnectionType(std::string(buf));
++it;
}
}
NetworkType type = ConnectionTypeFromInterfaceList(online_links_type);
std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
current_connection_type_ = type;
if (sink_) {
sink_->OnDeviceNetworkChanged(current_connection_type_);
}
}
NetworkType
AddressTrackerLinux::GetCurrentConnectionType() {
std::lock_guard<std::mutex> auto_lock(current_connection_type_lock_);
return current_connection_type_;
}
std::set<int> AddressTrackerLinux::GetOnlineLinks() {
std::lock_guard<std::mutex> auto_lock(online_links_lock_);
return online_links_;
}
void AddressTrackerLinux::ReadMessages() {
bool address_changed = false;
bool link_changed = false;
bool tunnel_changed = false;
char buffer[4096];
bool first_loop = true;
for (;;) {
int rv = recv(netlink_fd_,
buffer,
sizeof(buffer),
// Block the first time through loop.
first_loop ? 0 : MSG_DONTWAIT);
first_loop = false;
if (rv == 0) {
printf("Unexpected shutdown of NETLINK socket\n");
return;
}
if (rv < 0) {
if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
break;
printf("Failed to recv from netlink socket\n");
return;
}
HandleMessage(buffer, rv, &address_changed, &link_changed, &tunnel_changed);
}
if (link_changed || address_changed) {
UpdateCurrentConnectionType();
}
}
void AddressTrackerLinux::HandleMessage(char* buffer,
size_t length,
bool* address_changed,
bool* link_changed,
bool* tunnel_changed) {
if (!buffer || length <= 0) {
return;
}
for (struct nlmsghdr* header = reinterpret_cast<struct nlmsghdr*>(buffer);
NLMSG_OK(header, length);
header = NLMSG_NEXT(header, length)) {
switch (header->nlmsg_type) {
case NLMSG_DONE:
return;
case NLMSG_ERROR: {
const struct nlmsgerr* msg =
reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(header));
printf("Unexpected netlink error: %d\n", msg->error);
} return;
case RTM_NEWADDR: {
// todo
} break;
case RTM_DELADDR: {
// todo
} break;
case RTM_NEWLINK: {
const struct ifinfomsg* msg =
reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
if (!(msg->ifi_flags & IFF_LOOPBACK) && (msg->ifi_flags & IFF_UP) &&
(msg->ifi_flags & IFF_LOWER_UP) && (msg->ifi_flags & IFF_RUNNING)) {
std::lock_guard<std::mutex> auto_lock(online_links_lock_);
if (online_links_.insert(msg->ifi_index).second) {
*link_changed = true;
}
} else {
std::lock_guard<std::mutex> auto_lock(online_links_lock_);
if (online_links_.erase(msg->ifi_index)) {
*link_changed = true;
}
}
} break;
case RTM_DELLINK: {
const struct ifinfomsg* msg =
reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(header));
std::lock_guard<std::mutex> auto_lock(online_links_lock_);
if (online_links_.erase(msg->ifi_index)) {
*link_changed = true;
}
} break;
default:
break;
}
}
}
void AddressTrackerLinux::StartTracking() {
if (!thread_stopped_) {
return;
}
thread_stopped_ = false;
if (!Init()) {
printf("Init failed\n");
return;
}
thread_ = std::thread([this]() {
while (!thread_stopped_) {
int ret = epoll_wait(epoll_fd_, events_, MAX_EVENT_NUMBER, -1);
if (ret < 0) {
printf("epoll wait failure\n");
break;
}
auto IsEventFdReady = [] (int event_fd, int event_numbers, struct epoll_event *events) {
for (int i = 0; i < event_numbers; i++) {
if (event_fd == events[i].data.fd) {
return true;
}
}
return false;
};
if (IsEventFdReady(event_fd_, ret, events_)) {
// used to wake up thread
} else {
ReadMessages();
}
}
});
pthread_setname_np(thread_.native_handle(), "network_monitor");
}
void AddressTrackerLinux::StopTracking() {
if (thread_stopped_) {
return;
}
thread_stopped_ = true;
WakeUpThread();
if (thread_.joinable()) {
thread_.join();
}
Close();
DeInit();
}
void AddressTrackerLinux::WakeUpThread() {
eventfd_write(event_fd_, 1);
}
SocketGuard::SocketGuard(int sockfd)
: sockfd_(sockfd) {
}
SocketGuard::~SocketGuard() {
if (sockfd_ >= 0) {
close(sockfd_);
} else {
printf("Failed to create socket\n");
}
sockfd_ = -1;
}
} // namespace internal
#include "network_monitor.h"
#include
#include
class NetworkMonitorEventSink : public INetworkMonitorEventSink {
public:
virtual ~NetworkMonitorEventSink() = default;
void OnDeviceNetworkChanged(NetworkType type) override {
std::cout << "[sink] Device Network Changed to type " << type << std::endl;;
}
};
int main(void)
{
NetworkMonitorEventSink sink;
NetworkMonitorLinux networkMonitor(&sink);
networkMonitor.Start();
std::cout << "Device Network type is " << networkMonitor.GetCurrentNetworkType() << std::endl;
std::cout << "输入回车结束" << std::endl;
getchar();
networkMonitor.Stop();
return 0;
}