2010-01-06 50 views
11

这里有一个相关的问题,但我无法弄清楚如何应用答案机械化/ urllib2的:how to force python httplib library to use only A requests强制python mechanize/urllib2只使用A请求?

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

#!/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或机械化使用我创建的任何套接字。

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

+0

同样在这里,在不同的机器连接到differend提供商。我使用了libwww-perl,它是GET命令 - 它可以在所有机器上立即运行。 – 2011-01-21 21:07:37

回答

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 
+0

很好,我很乐意使用IPv6 ...一旦停止加5秒延迟到我的DNS查询:-P。不幸的是,这不是“运气不好”,而是每一个查询。 – 2010-01-11 11:04:52

4

没有答案,但有几个数据点。 DNS解析似乎是从httplib.pyHTTPConnection.connect()始发(线670对我的Python 2.5.4 STDLIB)

代码流程大致是:

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()会造成域名解析 - 我的OS X框支持IPv6,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()是硬编码到零,你将无法覆盖A与AAAA通过urllib中支持的某个API接口进行记录。除非机械化因其他原因而自行解决名称问题,否则机械化也不行。从连接循环的结构来看,这是By Design。

  • python的插座模块是围绕POSIX API的插座的薄包装纸;我期待他们解决现有&每个家庭在系统上配置。仔细检查Gentoo的IPv6配置。

+0

在我看来,python不应该将'0'传递给'socket.getaddrinfo',如果它是没有ipv6支持的。也许这在某些方面可能被认为是一个小错误。 – 2010-01-11 11:07:09

15

来自同一个问题的痛苦,这里是一个丑陋的黑客攻击(使用您自己的风险..)基于由J·J给出的信息。

这基本上强制的socket.getaddrinfo(..)socket.AF_INET而不是使用socket.AF_UNSPEC(零,这是什么,似乎在socket.create_connection使用)的family参数,不仅从urllib2电话,但应以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) 

这至少在这个简单的例子对我的作品。

+0

刚刚测试,仍然完美地在python 3.5.2中运行。 – Rich 2017-10-15 08:00:38

2

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

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

options single-request-reopen 

手册页解释得好:

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

+0

谢谢你修复了我在Python中遇到的ipv6名称解析段错误问题。 – jan 2014-12-24 19:02:46