在 Python 中查询现有套接字的地址族

Posted

技术标签:

【中文标题】在 Python 中查询现有套接字的地址族【英文标题】:Query an existing socket's address family in Python 【发布时间】:2018-08-11 09:18:52 【问题描述】:

我使用 UNIX 域套接字将套接字文件描述符传递给 Python 3 程序。我想查询套接字并确定它的地址族。

在这个answer 中,getsockname 系统调用用于在 C 中实现相同的结果。但是,Python 的 socket.getsockname 函数不返回地址族,而是假设调用者已经知道它。此外,它仅适用于套接字,不适用于文件描述符,并且从文件描述符创建套接字对象也需要传递地址族。

有没有办法从 Python 中查询地址族?

编辑:我想强调我正在寻找一个已经打开的套接字的地址族,我有文件描述符。

【问题讨论】:

【参考方案1】:

您只需将文件描述符传递给socket.socket,将其作为fileno= 关键字属性。套接字类计算出与文件描述符对应的套接字类型。然后您可以按照@klaus 的建议找到家人:s.family

import socket

s = socket.socket()
print("original socket", s)
desc = s.fileno()
print("file descriptor", type(desc), desc)
new_s = socket.socket(fileno=desc)
print("new socket", new_s.family, int(new_s.family))

这里我在 python 中创建了一个套接字,然后从套接字对象中提取文件描述符(生成一个包含描述符编号的普通int),以便演示将其放回一个新的套接字对象中。运行上述产生:

original socket <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>
file descriptor <class 'int'> 3
new socket AddressFamily.AF_INET 2

编辑 嗯...上述方法似乎不适用于AF_INET6套接字,至少在我的python 3.5.2版本中。

s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
new_s = socket.socket(fileno=s.fileno())
print(new_s)
<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('::%1', 0, 0, 1)>

这对我来说似乎是一个明显的错误。文档说:

如果指定了 fileno,则从指定的文件描述符中自动检测 family、type 和 proto 的值。

您可能需要检查new_s.laddr 并启发式地确定族。 (对于AF_INET6laddr 将是一个 4 元组,而 AF_INET 使用一个 2 元组)。如果您期望 AF_UNIX 或其他家庭,您可能需要扩展它。

【讨论】:

谢谢。我完全错过了文档的这一部分。我看到有一张关于这个的公开票:bugs.python.org/issue28134,尽管我检查了 Linux 上的 Python 3.7 并且它在那里工作。【参考方案2】:

您可以在 python 中检查 .family 属性来查询套接字的地址族。 sock 对象中还保存了其他属性

import socket
import sys
# Create a TCP/IP socket
sock = socket.create_connection(('www.***.com', 80))
print(sock)
print(sock.family)

输出是

<socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('x.x.x.x', y), raddr=('x.x.x.x', 80)>
AddressFamily.AF_INET

【讨论】:

谢谢,但是当我写 palivek 时,我想找到一个现有套接字的地址族,我有一个打开的文件描述符,而不是转换地址以找到选定的地址族。 【参考方案3】:

如果您只想了解有关套接字的信息,我建议您使用socket.getaddrinfo

Documentation:

该函数返回具有以下结构的 5 元组列表:

(family, type, proto, canonname, sockaddr)

>>> socket.getaddrinfo("example.org", 80, proto=socket.IPPROTO_TCP)
[(<AddressFamily.AF_INET6: 10>, <SocketType.SOCK_STREAM: 1>,
 6, '', ('2606:2800:220:1:248:1893:25c8:1946', 80, 0, 0)),
 (<AddressFamily.AF_INET: 2>, <SocketType.SOCK_STREAM: 1>,
 6, '', ('93.184.216.34', 80))]

【讨论】:

谢谢,但我想找到现有套接字的地址族,我有一个打开的文件描述符,而不是转换地址。【参考方案4】:

这是一个基于我编写的 ctypes 的解决方案,因此我可以使用旧版本的 Python:

import os
import socket
from ctypes import *

libc = CDLL('libc.so.6', use_errno=True)
libc.getsockname.argtypes = [c_int, c_void_p, POINTER(c_int32)]
libc.getsockopt.argtypes = [c_int, c_int, c_int, c_void_p, POINTER(c_int32)]

def get_sock_family(fd):
    family = c_ushort(0)
    length = c_int32(sizeof(family))
    ret = libc.getsockname(fd, byref(family), length)
    if ret:
        errno = get_errno()
        raise OSError(errno, os.strerror(errno))

    return socket.AddressFamily(family.value)

def get_sock_type(fd):
    socket_type = c_int()
    length = c_int32(sizeof(socket_type))
    ret = libc.getsockopt(fd, socket.SOL_SOCKET, socket.SO_TYPE, byref(socket_type), length)
    if ret:
        errno = get_errno()
        raise OSError(errno, os.strerror(errno))

    return socket.SocketKind(socket_type.value)

【讨论】:

以上是关于在 Python 中查询现有套接字的地址族的主要内容,如果未能解决你的问题,请参考以下文章

python socket编程

epmd 错误打开流套接字:协议不支持地址族

套接字地址结构

套接字编程

C语言socket()函数解析(创建套接字)af地址族,ip地址类型(Address Family)INET(Inetnet)PF(Protocol Family)

套接字地址结构