基于netlink的Linux Network Monitor实现

一、背景

来源于产品开发需求,需要在linux系统下实现网络状类型查询及网络类型变更通知,比如从Ethernet变为Wifi,从Wifi变为Ethernet等。

二、设计方案

  • Linux系统提供了Netlink套接字用以实现用户进程与内核进程通信的一种特殊的进程间通信,本文实现的获取客户端网络类型便采用Netlink进行实现。
  • 在Linux桌面端系统下网络类型一般有以下两种:
    • 有线网:Ethernet
    • 无线网:Wifi
  • 模块间通信框图如下:
  • 在这里插入图片描述

三、实现流程图

实现步骤:

  1. 获取处于在线状态(running)的网卡Link列表
  2. 排除以下内部或特殊网卡:
    • 本地环回网络(loopback,127.0.0.1)
    • 虚拟机网络(vmnet)
    • tunnel网络(tun*)
    • docker网络(docker
  3. 针对最终的目标Link列表遍历对应网卡的网络类型(使用ioctl系统调用),如果一致则变更网络类型(Ethernet或Wifi),否则变更网络类型(Unknown)
  4. 若不存在在线网卡,则变更网络类型(None)
网络类型
None无在线网卡
Ethernet在线网卡类型一致,均为Ethernet
Wifi在线网卡类型一致,均为Wifi
Unknown在线网卡类型不一致,比如同时存在Ethernet和Wifi

在这里插入图片描述

四、后续优化计划

  1. 采用netlink方案可以实现ipv4和ipv6变更的通知;
  2. Linux系统发行版比较多和场景碎片化,目前主要考虑桌面端和服务器系统下的表现,针对IOT设备暂时未详细验证,后续针对IOT设备可进一步验证和优化。

五、技术栈

  • netlink
    • 用于获取在线网卡网络类型
  • epoll
    • 用于实现多路复用IO
  • eventfd
    • 用于实现线程(event loop)唤醒

六、源码参考

  • network_monitor.h
#pragma once

#include <thread>
#include <atomic>
#include <mutex>
#include <set>
#include <sys/epoll.h>

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

  • network_monitor.cpp
#include "network_monitor.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/rtnetlink.h>
#include <linux/if.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <linux/wireless.h>
#include <sys/ioctl.h>
#include <algorithm>
#include <unistd.h>
#include <string.h>
#include <map>
#include <sys/eventfd.h>

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 <iostream>
#include <unistd.h>

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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值