Search code examples
pythonsocketsipv6icmpsendto

what exactly is passed as "address" in sendto for ipv6


I am trying to send an icmpv6 ping packet across running as root (python 2.7 on Linux)

I understand that sendto uses two tuple struct in case of ipv4 (and it works) and know that ipv6 uses a 4 tuple struct. Still i can't get it to work.

It either results in an "invalid argument" or "socket.gaierror: [Errno -2] Name or service not known"

Following is a bare minimum example showing what i am attempting. I am even fine if i can get it to work with local host in case of ipv6 i.e. ::1

import socket

def main(dest_name):
    #dest_addr = socket.gethostbyname(dest_name)
    addrs = socket.getaddrinfo(dest_name, 0, socket.AF_INET6, 0, socket.SOL_IP)

    print addrs
    dest = addrs[2]

    port = 33434 # just some random number because of icmp
    icmp = socket.getprotobyname('ipv6-icmp')
    #print icmp

    send_socket = socket.socket(socket.AF_INET6, socket.SOCK_RAW, icmp)
    print "sent to " + str(dest[4])
    send_socket.sendto('', (str(dest[4]), port))
    send_socket.close()

if __name__ == '__main__':
    main('ipv6.google.com')

I actually tried each tuple from the addr list, but the result is same.

Update:

Also tried alternates with sendto's params, but it results in invalid arguments whether i use local host or google ipv6 address

send_socket.sendto('', dest[4])

Update 2:

For reference, working ipv4 code follows (as asked in comments)

def main(dest_name):
    dest_addr = socket.gethostbyname(dest_name)
    icmp = socket.getprotobyname('icmp')

    send_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
    print "sent to " + dest_name#str(dest[4])
    send_socket.sendto('', (dest_addr, 0))
    send_socket.close()

if __name__ == '__main__':
    main('www.google.com')

Update 3:

When i run the v6 version with dest[4] as the only parameter (no string, just the tuple and NO port), following is output on my machine (Mint 15) which includes printing interfaces

sudo python test_v6.py 
[(10, 1, 6, '', ('::1', 0, 0, 0)), (10, 2, 17, '', ('::1', 0, 0, 0)), (10, 3, 0, '', ('::1', 0, 0, 0))]
sent to ('::1', 0, 0, 0)
Traceback (most recent call last):
  File "test_v6.py", line 18, in <module>
    main('::1')
  File "test_v6.py", line 14, in main
    send_socket.sendto('', dest[4])
socket.error: [Errno 22] Invalid argument

I am not sure why it still produces invalid argument


Solution

  • Your original problem was that bizarre things like a 2-tuple whose first member is a Python string representation of the 4-tuple address are not even close to valid ways to specify an address.

    You can fix that by just using dest[4] itself—that is, the tuple you got back as the sockaddr part of getaddrinfo—as the address. (As Sander Steffann's answer explains, you're not exactly doing this cleanly. But in your case, at least for '::1' or 'localhost' with the other values you've specified, you're going to get back the right values to use.) You should also probably use addrs[0] rather than addrs[2].

    Anyway, in your Update 3, you appear to have done exactly that, and you're getting socket.error: [Errno 22] Invalid argument. But there are two arguments to sendto, and it's the other one that's invalid: '' is not a valid ICMP6 packet because it has no ICMP6 header.

    You can test this pretty easily by first connecting to dest[4], which will succeed, and then doing a plain send, which will fail with the same error.

    For some reason, on Fedora 10 (ancient linux), the call seems to succeed anyway. I don't know what goes out over the wire (if anything). But on Ubuntu 13.10 (current linux), it fails with EINVAL, exactly as it should. On OS X 10.7.5 and 10.9.0, it fails with ENOBUFS, which is bizarre. In all three cases, if I split the sendto into a connect and a send, it's the send that fails.

    '\x80\0\0\0\0\0\0\0' is a valid ICMP6 packet (an Echo service request header with no data). If I use that instead of your empty string, it now works on all four machines.

    (Of course I still get ENETUNREACH or EHOSTUNREACH when I try to hit something on the Internet, because I don't have an IPv6-routable connection.)