来自ctypes的垃圾uint64值

0 投票
1 回答
510 浏览
提问于 2025-04-17 23:02

我正在尝试从结构体 if_data64 中读取 ifi_ibytes 和 ifi_obytes 这两个字段,具体可以参考这个头文件。我已经让 ctypes 工作了,并且可以进行 sysctl 调用,得到了看起来可以用的信息。然而,我从非零整数中得到的结果似乎完全是垃圾。例如,我有一个接口接收了 0 字节,传输了大约 850 万字节。但是,我的 Python 代码却显示接收了 0 字节,传输了 3,590,592,659,456 字节。

我正在复制这个已知有效的程序的方法,但我从 Python 得到的字节计数与上面 MenuMeter 的数字完全不符。我使用的是一个稍微不同的 sysctl(NET_RT_IFLIST2),它返回的是 64 位整数,我在代码中考虑到了这一点。

这是我的代码:

from ctypes import *
from ctypes.util import find_library
from ctypes import sizeof as c_sizeof
import errno

libc = CDLL(find_library('c'), use_errno=True)

# hardcoded macosx constants!
CTL_NET = 4
PF_ROUTE = 17
NET_RT_IFLIST2 = 6

RTM_IFINFO2 = 0x12
IFF_LOOPBACK = 0x8
AF_LINK = 18

class if_data64(Structure):
   _pack_ = 4
   _fields_ = [('ifi_type', c_ubyte),
               ('ifi_typelen', c_ubyte),
               ('ifi_physical', c_ubyte),
               ('ifi_addrlen', c_ubyte),
               ('ifi_hdrlen', c_ubyte),
               ('ifi_recvquota', c_ubyte),
               ('ifi_xmitquota', c_ubyte),
               ('ifi_unused1', c_ubyte),
               ('ifi_mtu', c_uint32),
               ('ifi_metric', c_uint32),
               ('ifi_baudrate', c_uint32),
               ('ifi_ipackets', c_uint64),
               ('ifi_ierrors', c_uint64),
               ('ifi_opackets', c_uint64),
               ('ifi_oerrors', c_uint64),
               ('ifi_collisions', c_uint64),
               ('ifi_ibytes', c_uint64),
               ('ifi_obytes', c_uint64),
               ('ifi_imcasts', c_uint64),
               ('ifi_omcasts', c_uint64),
               ('ifi_iqdrops', c_uint64),
               ('ifi_noproto', c_uint64),
               ('ifi_recvtiming', c_uint32),
               ('ifi_xmittiming', c_uint32)]

class if_msghdr2(Structure):
   _fields_ = [('ifm_msglen', c_ushort),
               ('ifm_version', c_ubyte),
               ('ifm_type', c_ubyte),
               ('ifm_addrs', c_int),
               ('ifm_flags', c_int),
               ('ifm_index', c_ushort),
               ('ifm_snd_len', c_int),
               ('ifm_snd_maxlen', c_int),
               ('ifm_snd_drops', c_int),
               ('ifm_timer', c_int),
               ('ifm_data', if_data64)]

class sockaddr_dl(Structure):
   _fields_ = [('sdl_len', c_ubyte),
               ('sdl_family', c_ubyte),
               ('sdl_index', c_ushort),
               ('sdl_type', c_ubyte),
               ('sdl_nlen', c_ubyte),
               ('sdl_alen', c_ubyte),
               ('sdl_slen', c_ubyte),
               ('sdl_data', c_char * 12)] # for now

MIB_TYPE = c_int * 6
mib = MIB_TYPE(CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST2, 0)

sysctl_buf_len = c_uint(0)

rval = libc.sysctl(mib, 6, None, byref(sysctl_buf_len), None, 0)
if rval != 0:
   raise Exception(errno.errorcode[get_errno()])

sysctl_buf = create_string_buffer(sysctl_buf_len.value)
rval = libc.sysctl(mib, 6, sysctl_buf, byref(sysctl_buf_len), None, 0)
if rval != 0:
   raise Exception(errno.errorcode[get_errno()])

# walk the structure.  you need to know the length from ifm_msglen
idx = addressof(sysctl_buf)
end = idx + sysctl_buf_len.value
while idx < end:
   batch_off = idx - addressof(sysctl_buf)
   ifmsg = cast(c_void_p(idx), POINTER(if_msghdr2))
   if ifmsg.contents.ifm_type != RTM_IFINFO2:
      idx += ifmsg.contents.ifm_msglen
      continue
   if ifmsg.contents.ifm_flags & IFF_LOOPBACK:
      idx += ifmsg.contents.ifm_msglen
      continue
   # 12 bytes to compensate for 32 bit alignment
   sdl = cast(c_void_p(idx + c_sizeof(if_msghdr2) + 12), POINTER(sockaddr_dl))
   if sdl.contents.sdl_family != AF_LINK:
      idx += ifmsg.contents.ifm_msglen
      continue

   print sdl.contents.sdl_data[0:sdl.contents.sdl_nlen]
   print ifmsg.contents.ifm_data.ifi_ibytes, ifmsg.contents.ifm_data.ifi_obytes
   idx += ifmsg.contents.ifm_msglen

1 个回答

2

if_data64 中,ifi_baudrate 这个字段应该是 c_uint64 类型,而不是 c_uint32。这样修正后,ifi_bytesifo_bytes 的位置就会对齐到正确的偏移量。此外,最后一个字段 ifi_lastchange 也缺失了。这个字段是一个占用8个字节的 timeval32 结构体。综合来看,这就解释了为什么 sdl 需要额外的12个字节的偏移量。

class timeval32(Structure):
    _fields_ = [('tv_sec', c_int32), 
                ('tv_usec', c_int32)]

class if_data64(Structure):
    _pack_ = 4
    _fields_ = [('ifi_type', c_ubyte),
                ('ifi_typelen', c_ubyte),
                ('ifi_physical', c_ubyte),
                ('ifi_addrlen', c_ubyte),
                ('ifi_hdrlen', c_ubyte),
                ('ifi_recvquota', c_ubyte),
                ('ifi_xmitquota', c_ubyte),
                ('ifi_unused1', c_ubyte),
                ('ifi_mtu', c_uint32),
                ('ifi_metric', c_uint32),
                ('ifi_baudrate', c_uint64),    # was c_uint32
                ('ifi_ipackets', c_uint64),
                ('ifi_ierrors', c_uint64),
                ('ifi_opackets', c_uint64),
                ('ifi_oerrors', c_uint64),
                ('ifi_collisions', c_uint64),
                ('ifi_ibytes', c_uint64),
                ('ifi_obytes', c_uint64),
                ('ifi_imcasts', c_uint64),
                ('ifi_omcasts', c_uint64),
                ('ifi_iqdrops', c_uint64),
                ('ifi_noproto', c_uint64),
                ('ifi_recvtiming', c_uint32),
                ('ifi_xmittiming', c_uint32),
                ('ifi_lastchange', timeval32)] # was missing

与其使用指针的 cast,我会用 from_buffer 方法(适用于 Python 2.6 及以上版本)和偏移量来写这个循环。

buf_offset = 0
while buf_offset < sysctl_buf_len.value:
   msg_offset = buf_offset
   ifmsg = if_msghdr2.from_buffer(sysctl_buf, msg_offset)
   buf_offset += ifmsg.ifm_msglen
   if ifmsg.ifm_type != RTM_IFINFO2 or ifmsg.ifm_flags & IFF_LOOPBACK:
      continue
   sdl_offset = msg_offset + c_sizeof(if_msghdr2)
   sdl = sockaddr_dl.from_buffer(sysctl_buf, sdl_offset)
   if sdl.sdl_family != AF_LINK:
      continue
   print sdl.sdl_data[:sdl.sdl_nlen]
   print ifmsg.ifm_data.ifi_ibytes, ifmsg.ifm_data.ifi_obytes

撰写回答