Python ‘requests’ module: How to force use of IPv4 or IPv6

Updated: January 2, 2024 By: Guest Contributor Post a comment

Introduction

Understanding how to control the IP version when making HTTP requests in Python can be crucial when dealing with servers that are accessible only via IPv4 or IPv6.

The requests module in Python is a powerful, user-friendly tool for sending HTTP requests. By default, it automatically determines whether to use IPv4 or IPv6 based on the network stack’s configuration. This article guides you on how to explicitly force IPv4 or IPv6 when using the requests module, with practical code examples.

Installing ‘requests’ Module

# Use pip to install the 'requests' module
pip install requests

Basic Request Example

import requests

response = requests.get('https://example.com')
print(response.text)

Understanding Network Stacks

Before forcing a specific IP version, it’s important to know that modern operating systems support a dual network stack that allows the use of both IPv4 and IPv6. However, forcing a specific version can be necessary for certain network conditions or server configurations.

Forcing IPv4

To force the use of IPv4, one can modify the default socket connection behavior. This requires a little more control over the connection process. Here’s a basic example:

import socket
import requests
from requests.packages.urllib3.util.connection import allowed_gai_family

# Override the default function in the urllib3 utility module

original_allowed_gai_family = allowed_gai_family

def forced_ipv4_gai_family():
    return socket.AF_INET

allowed_gai_family = forced_ipv4_gai_family

# Now, all requests via the 'requests' module will use IPv4 only
response = requests.get('https://example.com')
print(response.text)

# Restore the original function after making IPv4 requests
allowed_gai_family = original_allowed_gai_family

Forcing IPv6

Forcing IPv6 is similar to forcing IPv4, but you direct the socket to use the IPv6 family:

import socket
import requests
from requests.packages.urllib3.util.connection import allowed_gai_family

def forced_ipv6_gai_family():
    return socket.AF_INET6

allowed_gai_family = forced_ipv6_gai_family

# Attempts will be made to connect using IPv6
response = requests.get('https://example.com')
print(response.text)

# Don't forget to reset the original function
allowed_gai_family = original_allowed_gai_family

Handling Exceptions

When forcing a specific IP version, it’s possible to encounter exceptions, such as ConnectionError if the requested domain does not support the forced IP version. Make sure to handle these:

import requests
from requests.exceptions import ConnectionError

try:
    response = requests.get('https://ipv4onlysite.com')
    print(response.text)
except ConnectionError as e:
    print('Error connecting to the site:', e)

Advanced Use Case: Custom Adapter

In advanced scenarios, you may want to create a custom adapter to control the IP version for specific domains or request sessions:

import requests
from requests.packages.urllib3.util.connection import allowed_gai_family

# Custom adapter that forces IPv4

class IPv4Adapter(requests.adapters.HTTPAdapter):
    def init_poolmanager(self, *args, **kwargs):
        self.poolmanager = requests.adapters.HTTPAdapter(pool_connections=kwargs.get('connections', self.poolmanager.pool_connections),
                                                        pool_maxsize=kwargs.get('maxsize', self.poolmanager.pool_maxsize),
                                                        max_retries=kwargs.get('retries', self.poolmanager.max_retries),
                                                        pool_block=kwargs.get('block', self.poolmanager.pool_block))
        self.poolmanager.pool_classes_by_scheme[IPv4Adapter.scheme] = requests.packages.urllib3.connectionpool.HTTPConnectionPool
        self.poolmanager.connection_from_host = lambda host, port=None, scheme=None, pool_kwargs=None: self.poolmanager.pool_classes_by_scheme[IPv4Adapter.scheme](host, port, strict=True, timeout=IPv4Adapter.poolmanager.ConnectionTimeout, **(pool_kwargs or {}))

# Add the custom adapter to a session

session = requests.Session()
session.mount('https://example.com', IPv4Adapter())

# The following request to https://example.com will use IPv4 due to the custom adapter

response = session.get('https://example.com')
print(response.text)

Conclusion

The Python requests module is versatile and supports both IPv4 and IPv6 usage. Forcing a specific IP protocol can be critical for compatibility or performance reasons and can be achieved through the techniques described. Always handle exceptions gracefully and reset any global state modifications to avoid side effects in other parts of your application.