Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python TCP socket with IPv6 address failed

I tried to bind a python tcp socket using an IPv6 address.

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)

I used the result from socket.getaddrinfo() as mentioned in other solutions but always get this error:

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

Here's my ifconfig result for the network interface

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

Any idea why this error occurs?

like image 690
hanneseilers Avatar asked Oct 21 '22 05:10

hanneseilers


1 Answers

Usually network prefixes don't overlap and every IP address is used only on a single interface. However for link-local the (same) prefix (fe80::/10) is used on every network interface with IPv6 enabled. That means that based on just the link-local address the system cannot determine on which network interface to listen. You will have to specify the interface yourself.

When writing an address in string notation you can do that by appending % followed by the id of the interface to the IPv6 address. As glglgl has shown, this could be e.g. fe80::224:d7ff:fe9d:9800%wlan0. The interface name depends on the system that you are on. My OS X machine has interfaces en0 and en1, my Linux box has eth0 and on a Windows box the interface name would be an integer.

In the socket function the interface is specified using the scope-id. This is part of the address tuple. In your example you use ('fe80::224:d7ff:fe9d:9800', 5050). This ok because for IPv6 the last two elements may be omitted. The full address tuple is e.g. ('fe80::224:d7ff:fe9d:9800', 5050, 0, 4) where 4 is the scope-id.

You can test this with a small script like:

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('')

On my system running this script shows:

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)

The part after the % isn't used anymore by the socket binding, it relies on the scope-id.


This does mean that the following bind calls produce identical results:

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))

Best to use the raw values that you get back fron getaddrinfo though. Depending on such undefined behaviour is not a good idea. A simple loop like this should work best:

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.
like image 133
Sander Steffann Avatar answered Oct 24 '22 11:10

Sander Steffann