2010-01-06 24 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