11

这是一个相关的问题,但我不知道如何将答案应用于 mechanize/urllib2: how to force python httplib library to use only A requests

基本上,给出这个简单的代码:

#!/usr/bin/python
import urllib2
print urllib2.urlopen('http://python.org/').read(100)

这导致wireshark说以下内容:

  0.000000  10.102.0.79 -> 8.8.8.8      DNS Standard query A python.org
  0.000023  10.102.0.79 -> 8.8.8.8      DNS Standard query AAAA python.org
  0.005369      8.8.8.8 -> 10.102.0.79  DNS Standard query response A 82.94.164.162
  5.004494  10.102.0.79 -> 8.8.8.8      DNS Standard query A python.org
  5.010540      8.8.8.8 -> 10.102.0.79  DNS Standard query response A 82.94.164.162
  5.010599  10.102.0.79 -> 8.8.8.8      DNS Standard query AAAA python.org
  5.015832      8.8.8.8 -> 10.102.0.79  DNS Standard query response AAAA 2001:888:2000:d::a2

那是5秒的延迟

我的系统中的任何地方都没有启用 IPv6(gentoo 用 编译USE=-ipv6)所以我认为 python 没有任何理由尝试 IPv6 查找。

上面引用的问题建议明确设置AF_INET听起来不错的套接字类型。我不知道如何强制 urllib 或 mechanize 使用我创建的任何套接字。

编辑:我知道 AAAA 查询是问题,因为其他应用程序也有延迟,一旦我在禁用 ipv6 的情况下重新编译,问题就消失了……除了在 python 中仍然执行 AAAA 请求。

4

4 回答 4

16

Suffering from the same problem, here is an ugly hack (use at your own risk..) based on the information given by J.J. .

This basically forces the family parameter of socket.getaddrinfo(..) to socket.AF_INET instead of using socket.AF_UNSPEC (zero, which is what seems to be used in socket.create_connection), not only for calls from urllib2 but should do it for all calls to socket.getaddrinfo(..):

#--------------------
# do this once at program startup
#--------------------
import socket
origGetAddrInfo = socket.getaddrinfo

def getAddrInfoWrapper(host, port, family=0, socktype=0, proto=0, flags=0):
    return origGetAddrInfo(host, port, socket.AF_INET, socktype, proto, flags)

# replace the original socket.getaddrinfo by our version
socket.getaddrinfo = getAddrInfoWrapper

#--------------------
import urllib2

print urllib2.urlopen("http://python.org/").read(100)

This works for me at least in this simple case.

于 2011-06-11T23:03:20.573 回答
4

没有答案,但有几个数据点。DNS 解析似乎来自(我的 python 2.5.4 stdlib 上的第 670 行httplib.pyHTTPConnection.connect()

代码流程大致是:

for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
    af, socktype, proto, canonname, sa = res
    self.sock = socket.socket(af, socktype, proto)
    try:
        self.sock.connect(sa)
    except socket.error, msg: 
        continue
    break

关于正在发生的事情的一些评论:

  • 第三个参数是socket.getaddrinfo()限制套接字系列——即 IPv4 与 IPv6。通过零返回所有家庭。零被硬编码到标准库中。

  • 将主机名传递给getaddrinfo()将导致名称解析——在我启用 IPv6 的 OS X 机器上,A 和 AAAA 记录都出去了,两个答案都回来了,两个都被返回了。

  • 连接循环的其余部分尝试每个返回的地址,直到一个成功

例如:

>>> socket.getaddrinfo("python.org", 80, 0, socket.SOCK_STREAM)
[
 (30, 1, 6, '', ('2001:888:2000:d::a2', 80, 0, 0)), 
 ( 2, 1, 6, '', ('82.94.164.162', 80))
]
>>> help(socket.getaddrinfo)
getaddrinfo(...)
    getaddrinfo(host, port [, family, socktype, proto, flags])
        -> list of (family, socktype, proto, canonname, sockaddr)

一些猜测:

  • 由于其中的套接字系列getaddrinfo()被硬编码为零,因此您将无法通过 urllib 中的某些受支持的 API 接口覆盖 A 与 AAAA 记录。除非机械化出于其他原因进行自己的名称解析,否则机械化也不能。从连接循环的构造来看,这是设计使然。

  • python 的 socket 模块是 POSIX 套接字 API 的一个薄包装器;我希望他们能够解决系统上所有可用和配置的家庭。仔细检查 Gentoo 的 IPv6 配置。

于 2010-01-10T01:19:32.280 回答
2

最可能的原因是出口防火墙损坏。例如,瞻博网络防火墙可能会导致这种情况,尽管它们有可用的解决方法

如果您无法让网络管理员修复防火墙,您可以尝试基于主机的解决方法。将此行添加到您的/etc/resolv.conf

options single-request-reopen

手册页很好地解释了它:

解析器对 A 和 AAAA 请求使用相同的套接字。一些硬件错误地只发回一个回复。当这种情况发生时,客户端系统将坐下来等待第二个回复。打开此选项会更改此行为,因此如果来自同一端口的两个请求未正确处理,它将关闭套接字并在发送第二个请求之前打开一个新请求。

于 2012-12-03T06:50:46.990 回答
2

当被问及 python.org 的 AAAA 时,DNS 服务器 8.8.8.8 (Google DNS) 立即回复。因此,我们在您发布的跟踪中没有看到此回复的事实可能表明此数据包没有返回(UDP 发生这种情况)。如果这个损失是随机的,那是正常的。如果是系统性的,则意味着您的网络设置存在问题,可能是防火墙损坏,阻止了第一个 AAAA 回复回来。

5 秒的延迟来自您的存根解析器。在这种情况下,如果是随机的,可能是运气不好,但与 IPv6 无关,对 A 记录的回复也可能失败。

禁用 IPv6 似乎是一个非常奇怪的举动,距离最后一个 IPv4 地址分发只有两年时间!

% dig @8.8.8.8  AAAA python.org

; <<>> DiG 9.5.1-P3 <<>> @8.8.8.8 AAAA python.org
; (1 server found)
;; global options:  printcmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50323
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;python.org.                    IN      AAAA

;; ANSWER SECTION:
python.org.             69917   IN      AAAA    2001:888:2000:d::a2

;; Query time: 36 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sat Jan  9 21:51:14 2010
;; MSG SIZE  rcvd: 67
于 2010-01-09T20:56:08.870 回答