簡體   English   中英

強制請求使用 IPv4 / IPv6

[英]Force requests to use IPv4 / IPv6

如何強制requests庫對獲取請求使用特定的 Internet 協議版本? 或者這可以用 Python 中的另一種方法更好地實現嗎? 我可以,但我不想使用curl ...

闡明目的的示例:

import requests
r = requests.get('https://my-dyn-dns-service.domain/?hostname=my.domain',
                 auth = ('myUserName', 'my-password'))

我找到了一個簡約的解決方案來強制 urrlib3 使用 ipv4 或 ipv6。 urrlib3 使用此方法為 Http 和 Https 創建新連接。 您可以在其中指定要使用的任何 AF_FAMILY。

import socket
import requests.packages.urllib3.util.connection as urllib3_cn
    
   
def allowed_gai_family():
    """
     https://github.com/shazow/urllib3/blob/master/urllib3/util/connection.py
    """
    family = socket.AF_INET
    if urllib3_cn.HAS_IPV6:
        family = socket.AF_INET6 # force ipv6 only if it is available
    return family

urllib3_cn.allowed_gai_family = allowed_gai_family

這是一個 hack,但您可以使用猴子補丁 getaddrinfo 過濾到僅 IPv4 地址:

# Monkey patch to force IPv4, since FB seems to hang on IPv6
import socket
old_getaddrinfo = socket.getaddrinfo
def new_getaddrinfo(*args, **kwargs):
    responses = old_getaddrinfo(*args, **kwargs)
    return [response
            for response in responses
            if response[0] == socket.AF_INET]
socket.getaddrinfo = new_getaddrinfo

我已經為requests + urllib3 + socket編寫了一個運行時補丁,它允許可選地並在每個請求的基礎上傳遞所需的地址系列。

與其他解決方案不同的是,它不涉及猴子補丁,而是用補丁文件替換requests的導入,它提供了一個與request兼容的接口,所有暴露的類都被子類化和補丁,所有“簡單 API”功能都重新實現。 唯一明顯的區別應該是有一個額外的family參數公開,您可以使用它來將名稱解析期間使用的地址族限制為socket.AF_INETsocket.AF_INET6 然后使用一系列復雜的(但主要是 LoC 密集型)策略方法覆蓋將這個值一直傳遞到urllib3的底層,在那里它將用於socket.create_connection函數調用的替代實現。

TL;DR用法如下所示:

import socket

from . import requests_wrapper as requests  # Use this load the patch


# This will work (if IPv6 connectivity is available) …
requests.get("http://ip6only.me/", family=socket.AF_INET6)
# … but this won't
requests.get("http://ip6only.me/", family=socket.AF_INET)

# This one will fail as well
requests.get("http://127.0.0.1/", family=socket.AF_INET6)

# This one will work if you have IPv4 available
requests.get("http://ip6.me/", family=socket.AF_INET)

# This one will work on both IPv4 and IPv6 (the default)
requests.get("http://ip6.me/", family=socket.AF_UNSPEC)

補丁庫的完整鏈接(~350 LoC): https ://gitlab.com/snippets/1900824

我對https://stackoverflow.com/a/33046939/5059062采取了類似的方法,但是修補了socket中發出 DNS 請求的部分,因此它只針對每個請求執行 IPv6 或 IPv4,這意味着可以使用在urllib中與在requests中一樣有效。

如果您的程序還使用 unix 管道和其他類似的東西,這可能會很糟糕,所以我敦促謹慎使用猴子補丁。

import requests
import socket
from unittest.mock import patch
import re

orig_getaddrinfo = socket.getaddrinfo
def getaddrinfoIPv6(host, port, family=0, type=0, proto=0, flags=0):
    return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET6, type=type, proto=proto, flags=flags)

def getaddrinfoIPv4(host, port, family=0, type=0, proto=0, flags=0):
    return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET, type=type, proto=proto, flags=flags)

with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv6):
    r = requests.get('http://ip6.me')
    print('ipv6: '+re.search(r'\+3>(.*?)</',r.content.decode('utf-8')).group(1))

with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv4):
    r = requests.get('http://ip6.me')
    print('ipv4: '+re.search(r'\+3>(.*?)</',r.content.decode('utf-8')).group(1))

並且沒有requests

import urllib.request
import socket
from unittest.mock import patch
import re

orig_getaddrinfo = socket.getaddrinfo
def getaddrinfoIPv6(host, port, family=0, type=0, proto=0, flags=0):
    return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET6, type=type, proto=proto, flags=flags)

def getaddrinfoIPv4(host, port, family=0, type=0, proto=0, flags=0):
    return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET, type=type, proto=proto, flags=flags)

with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv6):
    r = urllib.request.urlopen('http://ip6.me')
    print('ipv6: '+re.search(r'\+3>(.*?)</',r.read().decode('utf-8')).group(1))

with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv4):
    r = urllib.request.urlopen('http://ip6.me')
    print('ipv4: '+re.search(r'\+3>(.*?)</',r.read().decode('utf-8')).group(1))

在 3.5.2 中測試

您可以使用此 hack 來強制requests使用 IPv4:

requests.packages.urllib3.util.connection.HAS_IPV6 = False

(這是 Nulano 作為評論提交的,我認為它應該是一個正確的答案)。

這完全未經測試,可能需要一些調整,但是結合使用 Python“請求”的答案與現有的套接字連接以及如何強制 python httplib 庫只使用 A 請求,看起來你應該能夠創建一個僅 IPv6 的套接字然后讓請求將其用於其連接池,例如:

try:
    from http.client import HTTPConnection
except ImportError:
    from httplib import HTTPConnection

class MyHTTPConnection(HTTPConnection):
    def connect(self):
        print("This actually called called")
        self.sock = socket.socket(socket.AF_INET6)
        self.sock.connect((self.host, self.port,0,0))
        if self._tunnel_host:
            self._tunnel()

requests.packages.urllib3.connectionpool.HTTPConnection = MyHTTPConnection

在閱讀了上一個答案后,我不得不修改代碼以強制使用 IPv4 而不是 IPv6。 請注意,我使用了 socket.AF_INET 而不是 socket.AF_INET6,並且 self.sock.connect() 有 2 項元組參數。

我還需要覆蓋與 HTTPConnection 大不相同的 HTTP S連接,因為requests包裝了 httplib.HTTPSConnection 以驗證證書是否可用ssl模塊。

import socket
import ssl
try:
    from http.client import HTTPConnection
except ImportError:
    from httplib import HTTPConnection
from requests.packages.urllib3.connection import VerifiedHTTPSConnection

# HTTP
class MyHTTPConnection(HTTPConnection):
    def connect(self):
        self.sock = socket.socket(socket.AF_INET)
        self.sock.connect((self.host, self.port))
        if self._tunnel_host:
            self._tunnel()

requests.packages.urllib3.connectionpool.HTTPConnection = MyHTTPConnection
requests.packages.urllib3.connectionpool.HTTPConnectionPool.ConnectionCls = MyHTTPConnection

# HTTPS
class MyHTTPSConnection(VerifiedHTTPSConnection):
    def connect(self):
        self.sock = socket.socket(socket.AF_INET)
        self.sock.connect((self.host, self.port))
        if self._tunnel_host:
            self._tunnel()
        self.sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file)

requests.packages.urllib3.connectionpool.HTTPSConnection = MyHTTPSConnection
requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection = MyHTTPSConnection
requests.packages.urllib3.connectionpool.HTTPSConnectionPool.ConnectionCls = MyHTTPSConnection

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM