nethogs的局限性

之前想找一个可以监视系统进程网络流量的工具,网上都推荐使用nethogs,我在自己的linux系统上安装了nethogs,感觉还挺好用的,但是当我在公司的服务器上运行的时候就会出现各种问题,有时候会卡死,有时候虽然不卡死,显示的信息也不太对,奇奇怪怪,但是又很想用这个功能,于是阅读了下nethogs的源码,发现了一些问题,应该是该工具存在一些局限性。

我所谓的局限性主要体现在 当服务器上有大量的网络连接,也就是短连接比较多的情况,nethogs就无法正常工作。而我使用的服务器恰恰有比较多的短连接,导致该工具无法使用,下面说下产生这种情况的原因。

该工具大体的思路是通过libpcap库,抓取网络包,并处理抓取的包信息。

首先判断该包是否为已存在连接的包:

  while (current != NULL) {
    /* the reference packet is always *outgoing* */
    if (packet->match(current->getVal()->refpacket)) {
      return current->getVal();
    }
    current = current->getNext();
  }



bool Packet::match(Packet *other) {
  return sa_family == other->sa_family && (sport == other->sport) &&
         (dport == other->dport) &&
         (sa_family == AF_INET
              ? (sameinaddr(sip, other->sip)) && (sameinaddr(dip, other->dip))
              : (samein6addr(sip6, other->sip6)) &&
                    (samein6addr(dip6, other->dip6)));
}

上面的while循环就是查找所有已经存在的连接,方法是比较协议族和四元组。如果找不到,还有一种情况,查找源地址是否存在,通过比较端口号和IP地址。

  while (current != NULL) {
    /* the reference packet is always outgoing */
    if (packet->matchSource(current->getVal()->refpacket)) {
      return current->getVal();
    }

bool Packet::matchSource(Packet *other) {
  return (sport == other->sport) && (sameinaddr(sip, other->sip));
}

如果通过上面的查找找到了,万事大吉,直接把这个新的包添加到该连接即可。如果connection不等于NULL,表示找到了,将新的包添加到该连接。如果找不到,就比较麻烦,说明是一个新的连接,由于nethogs是用来监视进程流量的,所以它必须知道该连接是属于哪个进程。所以要通过getProcess获取进程信息,但是该信息的获取可能会比较花费时间。

  Connection *connection = findConnection(packet, IPPROTO_TCP);

  if (connection != NULL) {
    /* add packet to the connection */
    connection->add(packet);
  } else {
    /* else: unknown connection, create new */
    connection = new Connection(packet);
    getProcess(connection, args->device);
  }

因为getProcess比较重要,分开说明。第一步先判断该连接是否已经存在,方法是查找该连接的inode值,gethashstring即获取四元组组成的一个字符串,每个对应一个inode,获取方法一会再说。

  unsigned long inode = conninode[connection->refpacket->gethashstring()];

gethashstring:

  hashstring = (char *)malloc(HASHKEYSIZE * sizeof(char));

  char *local_string = (char *)malloc(50);
  char *remote_string = (char *)malloc(50);
  if (sa_family == AF_INET) {
    inet_ntop(sa_family, &sip, local_string, 49);
    inet_ntop(sa_family, &dip, remote_string, 49);
  } else {
    inet_ntop(sa_family, &sip6, local_string, 49);
    inet_ntop(sa_family, &dip6, remote_string, 49);
  }
  if (Outgoing()) {
    snprintf(hashstring, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d",
             local_string, sport, remote_string, dport);
  } else {
    snprintf(hashstring, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d",
             remote_string, dport, local_string, sport);
  }

因为刚才判断新抓到的包是属于新的连接的,所以这里的inode应该不存在,也就是inode等于0。如果inode为0,要先获取inode值,获取方法为:

void reread_mapping() {
  DIR *proc = opendir("/proc");

  if (proc == 0) {
    std::cerr << "Error reading /proc, needed to get inode-to-pid-maping\n";
    exit(1);
  }

  dirent *entry;

  while ((entry = readdir(proc))) {
    if (entry->d_type != DT_DIR)
      continue;

    if (!is_number(entry->d_name))
      continue;

    get_info_for_pid(entry->d_name);
  }
  closedir(proc);
}

即遍历整个/proc目录,查找所有进程的inode值,即查找每个进程的fd目录,套接字类型的描述符,该值就是下图中socket:[]中的那串数字。这样就获取了进程和inode的对照关系。

linux0@ubuntu:/proc/5544/fd$ ls -ltr
total 0
lrwx------ 1 linux0 linux0 64 Dec  5 09:56 3 -> 'socket:[89478]'
lrwx------ 1 linux0 linux0 64 Dec  5 09:56 2 -> /dev/pts/1
lrwx------ 1 linux0 linux0 64 Dec  5 09:56 1 -> /dev/pts/1
lrwx------ 1 linux0 linux0 64 Dec  5 09:56 0 -> /dev/pts/1

知道了进程和inode的对照关系,还要知道inode和连接的 关系,所以refreshconninode获取当前所有的连接,方法是读取/proc/net/tcp和/proc/net/tcp6,获取四元组信息和inode。

  if (!addprocinfo("/proc/net/tcp")) {
    std::cout << "Error: couldn't open /proc/net/tcp\n";
    exit(0);
  }
  addprocinfo("/proc/net/tcp6");
  int matches = sscanf(buffer,
                       "%*d: %64[0-9A-Fa-f]:%X %64[0-9A-Fa-f]:%X %*X "
                       "%*X:%*X %*X:%*X %*X %*d %*d %ld %*512s\n",
                       local_addr, &local_port, rem_addr, &rem_port, &inode);


  char *hashkey = (char *)malloc(HASHKEYSIZE * sizeof(char));
  char *local_string = (char *)malloc(50);
  char *remote_string = (char *)malloc(50);
  inet_ntop(sa_family, &result_addr_local, local_string, 49);
  inet_ntop(sa_family, &result_addr_remote, remote_string, 49);

  snprintf(hashkey, HASHKEYSIZE * sizeof(char), "%s:%d-%s:%d", local_string,
           local_port, remote_string, rem_port);
  free(local_string);

  conninode[hashkey] = inode;

buffer就是/proc/net/tcp和/proc/net/tcp6中的一行数据。将conninode更新为最新的四元组和inode对照表,包含当前的所有inode信息。这样就可以通过inode,间接找到进程和连接四元组的关系。知道新的包是属于哪个进程的。

通过上面的一系列操作,可以发现,假设服务器以长连接为主,那么整个服务器的进程与包的关系是比较容易判断的,因为连接比较固定,nethogs也就可以正常发挥作用。但是如果服务器存在比较多的短连接,那么每次这些新加入的短连接与进程的对应关系都是不明确的,都得经过上面分析的查找过程,特别是当/proc/net/tcp文件比较大的时候,该查找是非常缓慢的,导致的结果就是nethogs把绝大多数的时间都花在了查找连接与进程的关系上,而不是处理流量大小上,这种情况下, nethogs无法正常工作。

后面因为感觉这种进程流量监视的方式太过麻烦,最关键的是nethogs无法对udp做出有效处理,最终我选择自己根据nethogs写一个只监视一个进程流量的工具,可以处理tcp和udp,虽然可能太过简单存在很多问题,但是在我一般的测试中,感觉基本可用,主要也基本符合使用需求。可以同时检测tcp和udp。

 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值