Implementation of Linux Network Monitor based on netlink

1. Background

Derived from product development requirements, it is necessary to implement network type query and network type change notification under the Linux system, such as changing from Ethernet to Wifi, from Wifi to Ethernet, etc.

2. Design plan

  • The Linux system provides Netlink sockets to implement a special inter-process communication between user processes and kernel processes. Netlink is used to obtain the client network type implemented in this article.
  • There are generally two types of networks under Linux desktop systems:
    • Wired network: Ethernet
    • Wireless network: Wifi
  • The communication block diagram between modules is as follows:
  • Insert image description here

3. Implementation flow chart

Implementation steps:

  1. Get the list of network card links that are online (running)
  2. Exclude the following internal or special network cards:
    • Local loopback network (loopback, 127.0.0.1)
    • Virtual machine network ( vmnet )
    • tunnel network (tun*)
    • docker network ( docker )
  3. Traverse the network type of the corresponding network card (using ioctl system call) for the final target Link list. If they are consistent, change the network type (Ethernet or Wifi), otherwise change the network type (Unknown)
  4. If there is no online network card, change the network type (None)
Network Type
None No online network card
Ethernet The online network card types are the same, both are Ethernet
Wifi The online network card types are the same, both are Wifi
Unknown The online network card type is inconsistent, for example, Ethernet and Wifi exist at the same time.

Insert image description here

4. Follow-up optimization plan

  1. Notification of changes to ipv4 and ipv6 can be achieved using the netlink solution;
  2. There are many Linux system distributions and the scenarios are fragmented. At present, the performance on desktop and server systems is mainly considered. It has not yet been verified in detail for IOT devices. It can be further verified and optimized for IOT devices in the future.

5. Technology stack

  • netlink
    • Used to obtain the online network card network type
  • epoll
    • Used to implement multiplexed IO
  • eventfd
    • Used to implement thread (event loop) wake-up

6. Source code reference

  • 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
  • test code
#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;
}

Guess you like

Origin blog.csdn.net/weixin_36623563/article/details/129439576