Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force requests to use IPv4 / IPv6

How to force the requests library to use a specific internet protocol version for a get request? Or can this be achieved better with another method in Python? I could but I do not want to use curl

Example to clarify purpose:

import requests
r = requests.get('https://my-dyn-dns-service.domain/?hostname=my.domain',
                 auth = ('myUserName', 'my-password'))
like image 891
ominug Avatar asked Oct 09 '15 20:10

ominug


4 Answers

I've found a minimalistic solution to force urrlib3 to use either ipv4 or ipv6. This method is used by urrlib3 for creating new connection both for Http and Https. You can specify in it any AF_FAMILY you want to use.

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
like image 192
Yuri Fedoseev Avatar answered Oct 20 '22 23:10

Yuri Fedoseev


This is a hack, but you can monkey-patch getaddrinfo to filter to only IPv4 addresses:

# 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
like image 25
Jeff Kaufman Avatar answered Oct 20 '22 22:10

Jeff Kaufman


You can use this hack to force requests to use 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).

like image 8
Kerrick Staley Avatar answered Oct 20 '22 23:10

Kerrick Staley


I've written a runtime patch for requests+urllib3+socket that allows passing the required address family optionally and on a per-request basis.

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. 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. 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.

TL;DR usage looks like this:

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

like image 4
ntninja Avatar answered Oct 21 '22 00:10

ntninja