强制python mechanize / urllib2只使用A请求?

时间:2010-01-06 16:43:32

标签: python mechanize ipv6 urllib

这是一个相关问题,但我无法弄清楚如何将答案应用于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(使用USE=-ipv6编译的gentoo)所以我不认为python有任何理由甚至尝试IPv6查找。

上面提到的问题建议明确将套接字类型设置为AF_INET,这听起来很棒。我不知道如何强制urllib或机械化使用我创建的任何套接字。

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

4 个答案:

答案 0 :(得分:15)

遇到同样的问题,根据J.J.给出的信息,这是一个丑陋的黑客(使用风险自负......) 。

这基本上强制family的{​​{1}}参数为socket.getaddrinfo(..),而不是使用socket.AF_INET(零,这似乎是socket.AF_UNSPEC中使用的) ,不仅适用于来自socket.create_connection的来电,还应针对urllib2的所有来电:

socket.getaddrinfo(..)

至少在这个简单的情况下,这对我有用。

答案 1 :(得分:4)

没有答案,但有一些数据点。 DNS解析似乎源自httplib.py中的HTTPConnection.connect()(我的python 2.5.4 stdlib上的第670行)

代码流程大致如下:

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。传递零返回所有家庭。零被硬编码到stdlib中。

  • 将主机名传递给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记录。除非机械化由于某些其他原因而自行解析名称,否则机械化也不能。从连接循环的构造,这是By Design。

  • python的套接字模块是一个围绕POSIX套接字API的瘦包装器;我期待他们正在解决每个家庭的问题。在系统上配置。仔细检查Gentoo的IPv6配置。

答案 2 :(得分:2)

DNS服务器8.8.8.8(Google DNS)在被问及python.org的AAAA时立即回复。因此,我们在您发布的跟踪中没有看到此回复的事实可能表明该数据包没有返回(这发生在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

答案 3 :(得分:2)

最可能的原因是broken egress firewall。例如,Juniper防火墙可能会导致此问题,尽管它们有workaround可用。

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

options single-request-reopen

手册页解释得很好:

  

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