Python TCP socket with IPv6 address failed

ipv6, linux, python, sockets, tcp

Solution

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.

Problem

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?

Original source