如果您想在 Linux 上执行此操作,您可以将 SO_BINDTODEVICE 标志用于 setsockopt(请查看 man 7 socket,了解更多详细信息)。事实上,它就是使用by curl,如果你在linux上使用--interfaceoption。但请记住,SO_BINDTODEVICE 需要 root 权限(CAP_NET_RAW,尽管有 some attempts 来更改此设置)并且如果SO_BINDTODEVICE 失败,curl 会退回到常规的bind 技巧。
这里是 curl strace 失败时的示例:
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 +++
另外,我想说的是,使用普通的bind 并不总是保证流量会通过指定的接口(@987654325@ 使用普通的bind)。在 linux 上,只有SO_BINDTODEVICE 保证流量会通过指定的设备。
这是一个如何将SO_BINDTODEVICE 与requests 和requests-toolbelt 一起使用的示例(正如我所说,它需要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)
或者,如果您不想使用requests-toolbelt,您可以自己实现适配器类:
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)