使用 netlink 获取进程 inode

get process inode using netlink

我想尝试将 IP 数据包(使用 libpcap)关联到进程。我使用相关的 /proc/net/ 文件取得了一些有限的成功,但发现在我使用的某些机器上,这个文件可能有数千行并且解析它效率不高(缓存缓解了一些性能问题).

我读到,使用 sock_diag netlink 子系统可以通过直接向内核查询我感兴趣的套接字来提供帮助。我的尝试取得了有限的成功,但在精神上遇到了问题.

对于初始查询,我有:

if (query_fd_) {
    struct {
      nlmsghdr nlh;
      inet_diag_req_v2 id_req;
    } req = {
      .nlh = {
         .nlmsg_len = sizeof(req),
         .nlmsg_type = SOCK_DIAG_BY_FAMILY,
         .nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP
      },
      .id_req = {
         .sdiag_family = packet.l3_protocol,
         .sdiag_protocol = packet.l4_protocol,
         .idiag_ext = 0,
         .pad = 0,
         .idiag_states = -1,
         .id = {
           .idiag_sport = packet.src_port,
           .idiag_dport = packet.dst_port
         }
      }
    };
    //packet ips are just binary data stored as strings!
    memcpy(req.id_req.id.idiag_src, packet.src_ip.c_str(), 4);
    memcpy(req.id_req.id.idiag_dst, packet.dst_ip.c_str(), 4);
    struct sockaddr_nl nladdr = {
      .nl_family = AF_NETLINK
    };
    struct iovec iov = {
      .iov_base = &req,
      .iov_len = sizeof(req)
    };
    struct msghdr msg = {
      .msg_name = (void *) &nladdr,
      .msg_namelen = sizeof(nladdr),
      .msg_iov = &iov,
      .msg_iovlen = 1
    };

    // Send message to kernel
    for (;;) {
      if (sendmsg(query_fd_, &msg, 0) < 0) {
        if (errno == EINTR)
          continue;

          perror("sendmsg");
          return false;
      }
      return true;
    }
  }
  return false;

接收码我有:

long buffer[8192];
  struct sockaddr_nl nladdr = {
    .nl_family = AF_NETLINK
  };
  struct iovec iov = {
    .iov_base = buffer,
    .iov_len = sizeof(buffer)
  };
  struct msghdr msg = {
    .msg_name = (void *) &nladdr,
    .msg_namelen = sizeof(nladdr),
    .msg_iov = &iov,
    .msg_iovlen = 1
  };
  int flags = 0;

  for (;;) {
    ssize_t rv = recvmsg(query_fd_, &msg, flags);

    // error handling
    if (rv < 0) {
      if (errno == EINTR)
        continue;
      if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
        break;
      perror("Failed to recv from netlink socket");
      return 0;
    }
    if (rv == 0) {
      printf("Unexpected shutdown of NETLINK socket");
      return 0;
    }

    for (const struct nlmsghdr* header = reinterpret_cast<const struct nlmsghdr*>(buffer);
          rv >= 0 && NLMSG_OK(header, static_cast<uint32_t>(rv));
          header = NLMSG_NEXT(header, rv)) {

      // The end of multipart message
      if (header->nlmsg_type == NLMSG_DONE)
        return 0;

      if (header->nlmsg_type == NLMSG_ERROR) {
        const struct nlmsgerr *err = reinterpret_cast<nlmsgerr*>(NLMSG_DATA(header));
        if (err == NULL)
          return 100;
        errno = -err->error;
        perror("NLMSG_ERROR");
        return 0;
      }

      if (header->nlmsg_type != SOCK_DIAG_BY_FAMILY) {
        printf("unexpected nlmsg_type %u\n", (unsigned)header->nlmsg_type);
        continue;
      }

      // Get the details....
      const struct inet_diag_msg* diag = reinterpret_cast<inet_diag_msg*>(NLMSG_DATA(header));
      if (header->nlmsg_len < NLMSG_LENGTH(sizeof(*diag))) {
        printf("Message too short %d vs %d\n", header->nlmsg_len, NLMSG_LENGTH(sizeof(*diag)));
        return 0;
      }

      if (diag->idiag_family != PF_INET) {
         printf("unexpected family %u\n", diag->idiag_family);
         return 1;
      }

      return diag->idiag_inode;

问题:

diag->udiag_inode 值与我在 netstat 输出或 /proc/net/ 文件中看到的值不匹配。也是应该的吗?如果没有,是否可以使用这种方法检索进程的 inode 编号,以便我可以查询 /proc 以获取相应的 PID?

我不太明白的另一件事是在检查header中的nlmsg_type时的NLMSG_DONE。我看到的是:

1 - TCP 10.0.9.15:51002 -> 192.168.64.11:3128 [15047]
2 - TCP 192.168.64.11:3128 -> 10.0.9.15:51002 [0]
3 - TCP 10.0.9.15:51002 -> 192.168.64.11:3128 [0]
4 - TCP 192.168.64.11:3128 -> 10.0.9.15:51002 [15047]
5 - TCP 10.0.9.15:51002 -> 192.168.64.11:3128 [0]
6 - TCP 192.168.64.11:3128 -> 10.0.9.15:51002 [0]
7 - TCP 10.0.9.15:51002 -> 192.168.64.11:3128 [15047]

所以我在第一次查询时得到了一个 inode 编号,然后是一些 NLMSG_DONE returns(单步执行代码确认这是路径)。为什么第 1 行和第 3 行的结果不同?

感谢任何帮助或建议。

找到答案并张贴以防万一有人偶然发现它:

  1. 我有一个 uint16_t 作为 recv 代码的 return 类型,而实际上它应该是 ino_t 或 uint32_t。当我注意到一些 inode 在重新启动后正确匹配然后在一段时间后停止匹配并且没有更改代码(inode 计数明显增加)时,我发现了这一点。在函数 return 中使用正确的类型解决了问题(所以我发布的代码实际上是正确的!)

  2. 我收到了多部分消息。我应该在标志中设置 NLM_F_MULTI 时循环,然后在收到 NLMSG_DONE.

  3. 时离开循环