繁体   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