Frage

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

War es hilfreich?

Lösung

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

Andere Tipps

All the answers you are looking for are pretty much in the manual.

First, the port number is part of the info that getaddrinfo returns. Call it like this:

def main(dest_name):
    # A minimal ICMP6-echo message (thanks to abarnert)
    data = '\x80\0\0\0\0\0\0\0'

    # Parameters for getaddrinfo
    req_port = 0
    req_family = socket.AF_INET6
    req_socktype = socket.SOCK_RAW
    req_proto = socket.getprotobyname('ipv6-icmp')

    # Resolve the name and get the addrinfo
    addrs = socket.getaddrinfo(dest_name, req_port, req_family, req_socktype, req_proto)

    # This gives me: [(30, 3, 58, '', ('2a00:1450:4013:c01::63', 0, 0, 0))]
    # Which is what you use in your calls to `socket` and `sendto`, like:
    success = False
    for addr in addrs:
        try:
            (family, socktype, proto, canonname, sockaddr) = addr
            send_socket = socket.socket(family, socktype, proto)
            sent = send_socket.sendto(data, sockaddr)
            send_socket.close()
        except socket.error:
            # Try the next address
            continue

        # Stop if it worked
        if sent == len(data):
            success = True
            break

    return success

When now running main('ipv6.google.com') you can see the ping and the reply using i.e. tcpdump:

01:14:46.763160 IP6 2a00:8640::5ce4 > 2a00:1450:4013:c01::63: ICMP6, echo request, seq 0, length 8
01:14:46.785060 IP6 2a00:1450:4013:c01::63 > 2a00:8640::5ce4: ICMP6, echo reply, seq 0, length 8
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top