If you want to do this on Linux you could use SO_BINDTODEVICE
flag for setsockopt
(check man 7 socket, for more details). In fact, it's what used by curl, if you use --interface
option on linux. But keep in mind that SO_BINDTODEVICE
requires root permissions (CAP_NET_RAW
, although there were some attempts to change this) and curl
fallbacks to a regular bind
trick if SO_BINDTODEVICE
fails.
Here's sample curl
strace when it fails:
strace -f -e setsockopt,bind curl --interface eth2 https://ifconfig.me/
strace: Process 18208 attached
[pid 18208] +++ exited with 0 +++
setsockopt(3, SOL_TCP, TCP_NODELAY, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
setsockopt(3, SOL_TCP, TCP_KEEPIDLE, [60], 4) = 0
setsockopt(3, SOL_TCP, TCP_KEEPINTVL, [60], 4) = 0
setsockopt(3, SOL_SOCKET, SO_BINDTODEVICE, "eth2\0", 5) = -1 EPERM (Operation not permitted)
bind(3, {sa_family=AF_INET, sin_port=htons(0), sin_addr=inet_addr("192.168.8.1")}, 16) = 0 # curl fallbacks to regular bind
127.0.0.1+++ exited with 0 +++
Also, wanted to say that using regular bind
does not always guarantee that traffic would go through specified interface (@MarSoft answer uses plain bind
). On linux, only SO_BINDTODEVICE
guarantees that traffic would go through the specified device.
Here's an example how to use SO_BINDTODEVICE
with requests
and requests-toolbelt (as I said, it requires CAP_NET_RAW
permissions).
import socket
import requests
from requests_toolbelt.adapters.socket_options import SocketOptionsAdapter
session = requests.Session()
# set interface here
options = [(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, b"eth0")]
for prefix in ('http://', 'https://'):
session.mount(prefix, SocketOptionsAdapter(socket_options=options))
print(session.get("https://ifconfig.me/").text)
Alternatively, if you don't want to use requests-toolbelt
you can implement adapter class yourself:
import socket
import requests
from requests import adapters
from urllib3.poolmanager import PoolManager
class InterfaceAdapter(adapters.HTTPAdapter):
def __init__(self, **kwargs):
self.iface = kwargs.pop('iface', None)
super(InterfaceAdapter, self).__init__(**kwargs)
def _socket_options(self):
if self.iface is None:
return []
else:
return [(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, self.iface)]
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
socket_options=self._socket_options()
)
session = requests.Session()
for prefix in ('http://', 'https://'):
session.mount(prefix, InterfaceAdapter(iface=b'eth0'))
print(session.get("https://ifconfig.me/").text)