【问题标题】:Calling getaddrinfo directly from Python: ai_addr is null pointer直接从 Python 调用 getaddrinfo:ai_addr 为空指针
【发布时间】:2018-12-01 21:13:16
【问题描述】:

我正在尝试在 Mac OS 上通过 ctypes / libc 从 Python 调用 getaddrinfo,以查找域的 IP 地址。

调用似乎成功:没有返回错误代码,ai_addrlen 设置为 28,据我所知,这是 IPv6 地址的适当长度。但是,ai_addr 似乎是一个空指针,我不知道如何开始调试它。

如何使用libc.getaddrinfo 找到域的 IP 地址?

from ctypes import (
    byref,
    c_char, c_char_p, c_int, c_size_t, c_void_p,
    CDLL,
    POINTER,
    pointer,
    Structure,
)

libc = CDLL(None)

class c_addrinfo(Structure):
    pass

c_addrinfo._fields_ = [
    ('ai_flags', c_int),
    ('ai_family', c_int),
    ('ai_socktype', c_int),
    ('ai_protocol', c_int),
    ('ai_addrlen', c_size_t),
    ('ai_addr', c_void_p),
    ('ai_canonname', c_char_p),
    ('ai_next', POINTER(c_addrinfo)),
]

c_addrinfo_p = POINTER(c_addrinfo)
result = c_addrinfo_p()
error = libc.getaddrinfo(
    c_char_p(b'www.google.com'),
    None,
    None,
    byref(result),
)

print(error)                          # 0
print(result.contents.ai_canonname)   # b'\x1c\x1e
print(result.contents.ai_addrlen)     # 28
print(bool(result.contents.ai_addr))  # False === null pointer

libc.freeaddrinfo(result)

【问题讨论】:

  • socket.getaddrinfo 对你不起作用有什么原因吗?
  • @zwol 在获取 IP 地址方面:据我所知没有。但是,我希望更加熟悉从 Python 进行系统调用,这似乎是一个合理的起点。
  • getaddrinfo 不是 C 程序员使用的术语意义上的“系统调用”。它是 C 库中更复杂的例程之一,我认为这不是从 ctypes 开始的好地方。不过,这并没有使这个问题成为一个严格按照自己的条件来回答的坏问题。
  • 按照自己的方式解决这个问题,当我运行这个程序时,我得到的输出是0None16True(四行)。所以我不知道为什么它不适合你。
  • @zwol Ah 明白这不是系统调用。你在什么操作系统上运行?

标签: python python-3.x ctypes libc getaddrinfo


【解决方案1】:

根据linux man page for getaddrinfo addrinfo 结构,其结果形式存储在getaddrinfo 定义为

struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};

根据FreeBSD man page for getaddrinfo(或类似的Apple's man pages for getaddrinfo之一),假设所有类型都匹配,它的addrinfo看起来是一样的。

struct addrinfo {
     int ai_flags;             /* input flags */
     int ai_family;            /* address family for socket */
     int ai_socktype;          /* socket type */
     int ai_protocol;          /* protocol for socket */
     socklen_t ai_addrlen;     /* length of socket-address */
     struct sockaddr *ai_addr; /* socket-address for socket */
     char *ai_canonname;       /* canonical name for service location */
     struct addrinfo *ai_next; /* pointer to next in list */
};

然而查看FreeBSD source(或类似的one of the open source Apple projects),我们看到了一个微妙不同的定义:

struct addrinfo {
    int ai_flags;             /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
    int ai_family;            /* AF_xxx */
    int ai_socktype;          /* SOCK_xxx */
    int ai_protocol;          /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
    socklen_t ai_addrlen;     /* length of ai_addr */
    char *ai_canonname;       /* canonical name for hostname */
    struct sockaddr *ai_addr; /* binary address */
    struct addrinfo *ai_next; /* next structure in linked list */
};

很容易错过,但 ai_canonnameai_addr 与它们的记录方式相反。这意味着 Mac(/similar) 的 Python ctypes 定义应该是

class c_addrinfo(Structure):
    pass

c_addrinfo._fields_ = [
    ('ai_flags', c_int),
    ('ai_family', c_int),
    ('ai_socktype', c_int),
    ('ai_protocol', c_int),
    ('ai_addrlen', c_size_t),
    ('ai_canonname', c_char_p),
    ('ai_addr', c_void_p),
    ('ai_next', POINTER(c_addrinfo)),
]

或者一个可以在 Mac 和 Linux 上运行的(并且在其他平台上没有评论)

import platform

c_addrinfo._fields_ = [
    ('ai_flags', c_int),
    ('ai_family', c_int),
    ('ai_socktype', c_int),
    ('ai_protocol', c_int),
    ('ai_addrlen', c_size_t),
] + ([
    ('ai_canonname', c_char_p),
    ('ai_addr', c_void_p),
] if platform.system() == 'Darwin' else [
    ('ai_addr', c_void_p),
    ('ai_canonname', c_char_p),
]) + [
    ('ai_next', POINTER(c_addrinfo)),
]

对于这些版本,在 Mac 上,指针 ai_addr 不再为空。你也可以看到early/experimental version that parses the addresses themselves that works in both Mac and Linux

编辑:它看起来像documentation issue has already been reported to FreeBSD

【讨论】:

  • 不错的收获!这巧妙地解释了为什么ai_addr 出现为NULL,以及为什么ai_canonname 似乎是垃圾——它将地址的前几个字节解释为字符串!我可以确认 NetBSD 使用与 Darwin 和 FreeBSD 相同的顺序,并且其 getaddrinfo(3) 联机帮助页具有相同的不一致。
猜你喜欢
  • 2018-10-18
  • 2013-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-31
相关资源
最近更新 更多