如何在Python中创建IPv6套接字?为什么会出现socket.error: (22, '无效参数')?

10 投票
2 回答
11174 浏览
提问于 2025-04-16 04:38

我想在Python中创建一个IPv6的套接字,我是这样做的:

#!/usr/bin/env python
import sys
import struct
import socket

host = 'fe80::225:b3ff:fe26:576'
sa = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
sa.bind((host , 50000))

但是失败了:

socket.error: (22, 'Invalid argument') ?

有没有人能帮我一下?谢谢!

我又尝试了这样做,但还是不行:

    >>>host = 'fe80::225:b3ff:fe26:576'
    >>>sa = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
    >>>res = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_DGRAM, 0, socket.AI_PASSIVE)
    >>>family, socktype, proto, canonname, sockaddr = res[0]
    >>>print sockaddr
('fe80::225:b3ff:fe26:576', 50001, 0, 0)
    >>>sa.bind(sockaddr)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<string>", line 1, in bind
socket.error: (22, 'Invalid argument')

2 个回答

3

上面提到的还有一个缺失的点,就是fe80::*是链接本地地址。简单来说,这种地址只能在特定的网络连接上使用,所以你在使用的时候必须加上一个范围标识符(Scope ID)。
如果你不太明白,“链接本地”的意思就是这个地址只能在某个特定的网络上用,而因为这个地址是特定于网络的,理论上你可以在多个网络上使用同样的地址。
通常情况下,链接本地地址是通过把一个独特的硬件地址(EUI-48)转换成EUI-64格式,然后打开一个特定的位(U/L位)来生成的(查看这里)。不过,这并不是生成链接本地地址的唯一方法,其他方式也可能导致地址重复使用(我觉得没有什么规定禁止这样做)。所以你现在知道为什么它不工作了。
接下来的问题是,如何解决这个问题,或者说说你该从哪里获取范围标识符(Scope ID)。 不幸的是,这个并不是很明显。如果你查阅讨论“范围”的IPv6标准文档,你会发现它的定义比较笼统。实际上,如果你在Linux上运行这段代码(可能在Windows或Mac上也可以),你可以使用接口索引来获取。

需要注意的是,目前在nss-mdns中有一个bug,导致.local的名称返回的范围标识符总是为零,所以在Linux上使用.local名称基本上是行不通的,除非你的代码已经找到了范围标识符并自己设置了它。

7

这个问题可以分成两个部分

第一个问题

你应该使用 sa.bind(sockaddr),其中 sockaddr 是通过 getaddrinfo 获取的

>>> HOST = 'localhost'
>>> PORT = 50007 
>>> res = socket.getaddrinfo(HOST, PORT, socket.AF_UNSPEC, socket.SOCK_DGRAM, 0, socket.AI_PASSIVE)
>>> family, socktype, proto, canonname, sockaddr = res[1]
>>> proto
17
>>> sockaddr
('fe80::1%lo0', 50007, 0, 1)

第二个问题

如果你查看 socket 文档中的示例,地址是

Socket 接受三个参数

socket( [family[, type[, proto]]])

根据文档的说明

Create a new socket using the given address family, 
socket type and protocol number. The address family 
should be AF_INET (the default), AF_INET6 or AF_UNIX. 
The socket type should be SOCK_STREAM (the default), 
SOCK_DGRAM or perhaps one of the other "SOCK_" constants. 
The protocol number is usually zero and may be omitted in that case.

如果你使用 getaddressinfo 来获取 proto 的值,那么这个值会和默认的 0 不一样

但是当我执行以下代码时,我得到的协议值是 17。你可能也想调查一下这个问题。

当然,对我来说,socket.has_ipv6 是 True。

撰写回答