【问题标题】:what exactly is passed as "address" in sendto for ipv6在 ipv6 的 sendto 中究竟作为“地址”传递了什么
【发布时间】:2013-11-11 08:57:48
【问题描述】:

我正在尝试发送 icmpv6 ping 数据包,以 root 身份运行(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 代码如下(在 cmets 中询问)

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

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

【问题讨论】:

  • 你能展示你的工作 IPv4 代码吗?我不知道为什么你期望str(dest[4]) 可以与 IPv6 的 4 元组一起工作,而它不能与 IPv4 的 2 元组一起工作。无论哪种方式,它都会创建一个 Python 元组的字符串表示,它看起来不像 sendto 想要的。
  • 同时,请显示您得到的实际异常。当我使用dest[4](首先使用或不使用dest[4][1] = port)尝试此代码时,它会成功获取本地主机地址,并为Google 返回ENETUNREACH 错误(这是有道理的,因为我没有IPv6 互联网连接) .
  • 最后,您可以与 Google 建立 IPv6 TCP 连接吗?你能从 shell 中 traceroute6ping6 他们吗?
  • 感谢@abarnert,添加了ipv4代码
  • 我只将 dest[4] 转换为字符串,当我第一次放入它时,错误是,'函数需要字符串或缓冲区,而不是元组'。我后来才意识到这是因为我不必要地附加了另一个参数“端口”。

标签: python sockets ipv6 icmp sendto


【解决方案1】:

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

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

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

你可以很容易地测试这个,首先 connecting 到 dest[4],这将成功,然后执行一个普通的 send,这将失败并出现相同的错误。

出于某种原因,在 Fedora 10(古代 linux)上,调用似乎还是成功了。我不知道电线上发生了什么(如果有的话)。但是在 Ubuntu 13.10(当前的 linux)上,它以 EINVAL 失败,完全符合它的要求。在 OS X 10.7.5 和 10.9.0 上,它以 ENOBUFS 失败,这很奇怪。在这三种情况下,如果我将sendto 拆分为connectsend,则失败的是send

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

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

【讨论】:

  • 谢谢,我要试试这个并确认我在输入元组时犯了任何错误,然后回复你。
  • 它有效,太好了 - 谢谢。我从不关心有效载荷本身,因为我认为它适用于 ipv4,即不会出错并尝试吐出数据包,所以它也必须对 ipv6 做同样的事情。我基本上是在缩小问题的范围。所以这意味着实际问题在于我在 cmets.xml 中提到的手动标题结构本身。非常感谢你们俩的帮助。
  • @fayyazkl:IPv4 ICMP 也不应该与空数据包一起工作,但它似乎在比 IPv6 ICMP 更多的平台上这样做。据我所知,linux 2.6 允许空的 ICMP4 和 6,linux 3.10 允许 4 但不允许 6,OS X 10.7 和 10.9 都不允许。我不知道更远的链会发生什么——内核会默默地丢弃数据包吗?如果不是,路由器如何处理没有 ICMP 类型的 ICMP 数据包?玩起来可能会很有趣。
【解决方案2】:

您正在寻找的所有答案都在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') 时,您可以看到 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

【讨论】:

  • 没错,但是如果您查看他的代码,如果他只是将sendto 行修复为使用send_socket.sendto('', dest[4]),那么这些值与他最终得到的值完全相同,正如我在评论,正如他所说的那样(请参阅他的问题中的 UpdateUpdate3 )。所以,这不可能解决他的问题。
  • 谢谢,你是对的。只是想教人们如何正确地编写网络代码 :-) 我完全忘记在我的示例中指定data,对于一个最小的工作示例,它确实可能是'\x80\0\0\0\0\0\0\0'。固定!
  • 数据部分在这里并不重要。十多年来,我一直在为网络处理器和许多其他网络堆栈编写代码,恭敬地,我并不完全明白这里要教什么。确实,我大部分时间都是用 c 编码的,而且我是 python 新手。仍然使用上面给出的示例,我实际上使用了 data[4],它正是 sock_addr 结构,没有任何改进。在我的实际代码中,我也在构建 ip 标头,它代替数据传递,但与上述问题无关,因此我将其删除
  • (以及自己构建标题的 setsock 选项)。不过感谢您的帮助。欣赏。
  • @fayyazkl:但它与这里的问题有关。而且,您显然不知道 EINVAL 可能意味着您发送了无效的 ICMP 数据包,而不是发送到无效的 IPv6 地址,这一事实证明您并不知道一切。 (这并不奇怪,因为发现它的人——我——当然不知道几乎所有事情。)
猜你喜欢
  • 2011-06-18
  • 2020-04-03
  • 2012-07-23
  • 2016-09-10
  • 2023-03-15
  • 2012-10-17
  • 2021-06-04
  • 1970-01-01
  • 2018-07-30
相关资源
最近更新 更多