3

我正在尝试以 root 身份发送一个icmpv6 ping 数据包(Linux 上的 python 2.7)

我明白sendto在 ipv4 的情况下使用两个元组结构(并且它有效)并且知道 ipv6 使用一个 4 元组结构。我仍然无法让它工作。

它要么导致“无效参数”“socket.gaierror:[Errno -2] Name or service not known”

以下是显示我正在尝试的最小示例。如果我可以让它在 ipv6 的情况下与本地主机一起工作,我什至可以,即 ::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 的参数替代,但无论我使用本地主机还是谷歌 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] 作为唯一参数(没有字符串,只有元组和 NO 端口)运行 v6 版本时,以下是我的机器(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

我不确定为什么它仍然会产生无效的论点

4

2 回答 2

2

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

你可以通过使用dest[4]它自己来解决这个问题——也就是说,你作为 sockaddr 部分返回的元组getaddrinfo——作为地址。(正如 Sander Steffann 的回答所解释的那样,您并没有完全干净地做到这一点。但在您的情况下,至少对于您指定的其他值'::1''localhost'使用您指定的其他值,您将获得要使用的正确值。)您应该也可能使用addrs[0]而不是addrs[2].

无论如何,在您的Update 3中,您似乎已经做到了这一点,并且您正在获得socket.error: [Errno 22] Invalid argument. 但是 有两个参数sendto另一个是无效的:''不是有效的 ICMP6 数据包,因为它没有 ICMP6 标头。

你可以很容易地测试这个,首先connecting to dest[4],这将成功,然后做一个 plain send,这将失败并出现同样的错误。

出于某种原因,在 Fedora 10(古代 linux)上,调用似乎还是成功了。我不知道电线上发生了什么(如果有的话)。EINVAL但是在 Ubuntu 13.10(当前的 linux)上,它完全按照它应该的方式失败了。在 OS X 10.7.5 和 10.9.0 上,它失败并显示ENOBUFS,这很奇怪。在所有三种情况下,如果我将sendtoa 分成 aconnect和 a send,那send就是失败了。

'\x80\0\0\0\0\0\0\0'是一个有效的 ICMP6 数据包(一个没有数据的 Echo 服务请求头)。如果我使用它而不是你的空字符串,它现在可以在所有四台机器上运行。

(当然ENETUNREACHEHOSTUNREACH当我尝试在 Internet 上点击某些东西时,我仍然会遇到,因为我没有 IPv6 可路由连接。)

于 2013-11-11T20:48:06.707 回答
1

您正在寻找的所有答案都在手册中。

首先,端口号是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')您可以使用 ie 看到 ping 和回复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
于 2013-11-11T13:15:36.637 回答