什么在sendto for ipv6中作为“地址”准确传递

时间:2013-11-11 08:57:49

标签: python sockets ipv6 icmp sendto

我试图以root身份运行 icmpv6 ping数据包(Linux上的python 2.7)

我知道 sendto 在ipv4的情况下使用两个元组结构(并且它可以工作)并且知道ipv6使用4元组结构。我仍然无法让它发挥作用。

导致“无效参数”“socket.gaierror:[Errno -2]名称或服务未知”

以下是一个显示我正在尝试的最基本的示例。如果我能在ipv6 ie :: 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')

我实际上尝试了addr列表中的每个元组,但结果是一样的。

更新

还试过替换sendto的参数,但是无论我使用本地主机还是google ipv6地址都会导致无效的参数

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

更新2:

作为参考,遵循工作的ipv4代码(如评论中所述)

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

更新3:

当我运行带有dest [4]作为唯一参数的v6版本(没有字符串,只有元组和NO端口)时,在我的机器(Mint 15)上输出以下内容,其中包括打印接口

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

我不确定为什么它仍会产生无效的参数

2 个答案:

答案 0 :(得分:2)

您最初的问题是像2元组这样奇怪的东西,其第一个成员是4元组地址的Python字符串表示,甚至不能接近指定地址的有效方法。

你可以通过使用dest[4]本身 - 也就是你作为getaddrinfo的sockaddr部分得到的元组 - 作为地址来解决这个问题。 (正如Sander Steffann的回答所解释的那样,你并没有完全按照这种方式做到这一点。但在你的情况下,至少对于'::1''localhost'以及你指定的其他值,你会得到返回要使用的正确值。)您还应该使用addrs[0]而不是addrs[2]

无论如何,在您的更新3 中,您似乎已经完成了这一步,并且您获得了socket.error: [Errno 22] Invalid argument。但是sendto两个参数,而其他的参数是无效的:''不是有效的ICMP6数据包,因为它没有ICMP6报头中。

您可以通过connect dest[4] send来轻松测试,这将成功,然后执行简单的EINVAL,这将失败并出现相同的错误。

出于某种原因,在Fedora 10(古老的Linux)上,无论如何这个电话似乎都成功了。我不知道电线上发生了什么(如果有的话)。但是在Ubuntu 13.10(当前的linux)上,它与ENOBUFS失败了,完全一样。在OS X 10.7.5和10.9.0上,它以sendto失败,这很奇怪。在所有这三种情况下,如果我将connect分为sendsend,那么'\x80\0\0\0\0\0\0\0'就会失败。

ENETUNREACH是有效的ICMP6数据包(没有数据的Echo服务请求标头)。如果我使用它而不是空字符串,它现在适用于所有四台机器。

(当我尝试在互联网上点击某些东西时,我仍然会得到EHOSTUNREACH或{{1}},因为我没有IPv6可路由的连接。)

答案 1 :(得分:1)

您正在寻找的所有答案几乎都在manual

首先,端口号是getaddrinfo返回的信息的一部分。这样称呼:

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

现在正在运行main('ipv6.google.com'),您可以使用tcpdump看到ping和回复:

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