Python使用IPv6地址的TCP套接字失败。

5

我尝试使用IPv6地址绑定Python TCP套接字。

self.__addr = ('fe80::224:d7ff:fe9d:9800', 5050)
self.__type = socket.AF_INTE6
self.__sock = socket.socket(self.__type, socket.SOCK_STREAM)
for family, _, _, _, sockaddr in socket.getaddrinfo( self.__addr[0], self.__addr[1], 0, 0, socket.SOL_TCP ):
    if family == self.__type:
        self.__addr = sockaddr
            break

self.__sock.bind( self.__addr )
self.__sock.listen(1)

我使用了其他解决方案中提到的socket.getaddrinfo()的结果,但总是遇到这个错误:

self.__sock.bind( self.__addr )
  File "/usr/lib/python2.7/socket.py", line 224, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 22] Invalid argument

以下是网络接口的ifconfig结果:
wlan0     Link encap:Ethernet  Hardware Adresse 00:24:d7:9d:98:00  
          inet Adresse:192.168.0.103  Bcast:192.168.0.255  Maske:255.255.255.0
          inet6-Adresse: fe80::224:d7ff:fe9d:9800/64

任何想法为什么会出现这个错误?
2个回答

3
通常网络前缀不会重叠,每个IP地址只在单个接口上使用。然而,对于链路本地地址(link-local),相同的前缀(fe80::/10)会在启用IPv6的每个网络接口上使用。这意味着仅基于链路本地地址,系统无法确定要监听哪个网络接口。您需要自己指定接口。
在字符串表示法中编写地址时,您可以通过将接口ID附加到IPv6地址后面的%来完成。就像glglgl所展示的那样,这可能是fe80::224:d7ff:fe9d:9800%wlan0。接口名称取决于您所在的系统。我的OS X机器有接口en0en1,我的Linux框架有eth0,在Windows框架上,接口名称将是一个整数。
在套接字函数中,使用范围ID指定接口。这是地址元组的一部分。在您的示例中,您使用('fe80::224:d7ff:fe9d:9800', 5050)。这是可以的,因为对于IPv6,最后两个元素可以省略。完整的地址元组是例如('fe80::224:d7ff:fe9d:9800', 5050, 0, 4),其中4是范围ID。
您可以使用类似以下的小脚本进行测试:
import socket

addrs = [('fe80::cafe:1%en0', 5050),            
         ('fe80::cafe:1%en1', 5050)]    
for addr in addrs:
  print('Original addr: {}'.format(addr))
  for res in socket.getaddrinfo(addr[0], addr[1], socket.AF_INET6,
                                socket.SOCK_STREAM, socket.SOL_TCP):
      af, socktype, proto, canonname, sa = res
      print('Full addr:     {}'.format(sa))
  print('')

在我的系统上运行此脚本显示:

Original addr: ('fe80::cafe:1%en0', 5050)
Full addr:     ('fe80::cafe:1%en0', 5050, 0, 4)

Original addr: ('fe80::cafe:1%en1', 5050)
Full addr:     ('fe80::cafe:1%en1', 5050, 0, 5)
%后面的部分不再被套接字绑定使用,而是依赖于作用域 ID。
这意味着以下的bind调用会产生相同的结果:
s = socket.socket(af, socktype, proto)
s.bind(('fe80::cafe:1', 5050, 0, 4))
s.bind(('fe80::cafe:1%en0', 5050, 0, 4))
s.bind(('fe80::cafe:1%bla', 5050, 0, 4))

最好使用从getaddrinfo返回的原始值。依赖于这种未定义的行为并不是一个好主意。像这样的简单循环应该是最好的:
addr = ('fe80::224:d7ff:fe9d:9800%wlan0', 5050)
s = None
for res in socket.getaddrinfo(addr[0], addr[1], socket.AF_INET6,
                              socket.SOCK_STREAM, socket.SOL_TCP):
    af, socktype, proto, canonname, sa = res
    try:
        s = socket.socket(af, socktype, proto)
        s.bind(sa)
        s.listen(1)
    except socket.error:
        if s is not None:
            s.close()
        s = None

if s is None:
    print('Socket opening/binding failed')

etc.

谢谢你的回答。那就是问题所在。添加接口解决了它。 - hanneseilers

0

fe80:地址是本地链路地址,因此始终需要指定要使用的链路。

('fe80::224:d7ff:fe9d:9800%wlan0', 5050)可能是您所需的内容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接