简体   繁体   English

强制请求使用 IPv4 / IPv6

[英]Force requests to use IPv4 / IPv6

How to force the requests library to use a specific internet protocol version for a get request?如何强制requests库对获取请求使用特定的 Internet 协议版本? Or can this be achieved better with another method in Python?或者这可以用 Python 中的另一种方法更好地实现吗? I could but I do not want to use curl我可以,但我不想使用curl ...

Example to clarify purpose:阐明目的的示例:

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

I've found a minimalistic solution to force urrlib3 to use either ipv4 or ipv6.我找到了一个简约的解决方案来强制 urrlib3 使用 ipv4 或 ipv6。 This method is used by urrlib3 for creating new connection both for Http and Https. urrlib3 使用此方法为 Http 和 Https 创建新连接。 You can specify in it any AF_FAMILY you want to use.您可以在其中指定要使用的任何 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

This is a hack, but you can monkey-patch getaddrinfo to filter to only IPv4 addresses:这是一个 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

I've written a runtime patch for requests + urllib3 + socket that allows passing the required address family optionally and on a per-request basis.我已经为requests + urllib3 + socket编写了一个运行时补丁,它允许可选地并在每个请求的基础上传递所需的地址系列。

Unlike other solutions there is no monkeypatching involved, rather you replace your imports of requests with the patched file and it present a request -compatible interface with all exposed classes subclassed and patched and all “simple API” function reimplemented.与其他解决方案不同的是,它不涉及猴子补丁,而是用补丁文件替换requests的导入,它提供了一个与request兼容的接口,所有暴露的类都被子类化和补丁,所有“简单 API”功能都重新实现。 The only noticeable difference should be the fact that there is an extra family parameter exposed that you can use to restrict the address family used during name resolution to socket.AF_INET or socket.AF_INET6 .唯一明显的区别应该是有一个额外的family参数公开,您可以使用它来将名称解析期间使用的地址族限制为socket.AF_INETsocket.AF_INET6 A somewhat complicated (but mostly just LoC intensive) series of strategic method overrides is then used to pass this value all the way down to the bottom layers of urllib3 where it will be used in an alternate implementation of the socket.create_connection function call.然后使用一系列复杂的(但主要是 LoC 密集型)策略方法覆盖将这个值一直传递到urllib3的底层,在那里它将用于socket.create_connection函数调用的替代实现。

TL;DR usage looks like this: 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)

Full link to the patch library (~350 LoC): https://gitlab.com/snippets/1900824补丁库的完整链接(~350 LoC): https ://gitlab.com/snippets/1900824

I took a similar approach to https://stackoverflow.com/a/33046939/5059062 , but instead patched out the part in socket that makes DNS requests so it only does IPv6 or IPv4, for every request, which means this can be used in urllib just as effectively as in requests .我对https://stackoverflow.com/a/33046939/5059062采取了类似的方法,但是修补了socket中发出 DNS 请求的部分,因此它只针对每个请求执行 IPv6 或 IPv4,这意味着可以使用在urllib中与在requests中一样有效。

This might be bad if your program also uses unix pipes and other such things, so I urge caution with monkeypatching.如果您的程序还使用 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))

and without requests :并且没有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))

Tested in 3.5.2在 3.5.2 中测试

You can use this hack to force requests to use IPv4:您可以使用此 hack 来强制requests使用 IPv4:

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

(This was submitted as a comment by Nulano and I thought it deserved to be a proper answer). (这是 Nulano 作为评论提交的,我认为它应该是一个正确的答案)。

This is totally untested and will probably require some tweaks, but combining answers from Using Python “requests” with existing socket connection and how to force python httplib library to use only A requests , it looks like you should be able to create an IPv6 only socket and then have requests use that for its connection pool with something like:这完全未经测试,可能需要一些调整,但是结合使用 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

After reading the previous answer, I had to modify the code to force IPv4 instead of IPv6.在阅读了上一个答案后,我不得不修改代码以强制使用 IPv4 而不是 IPv6。 Notice that I used socket.AF_INET instead of socket.AF_INET6, and self.sock.connect() has 2-item tuple argument.请注意,我使用了 socket.AF_INET 而不是 socket.AF_INET6,并且 self.sock.connect() 有 2 项元组参数。

I also needed to override the HTTP S Connection which is much different than HTTPConnection since requests wraps the httplib.HTTPSConnection to verify the certificate if the ssl module is available.我还需要覆盖与 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