Monkey Patching Sockets Library To Use A Specifc Network Interface
Solution 1:
This appears to work for python3:
In [1]: import urllib3
In [2]: real_create_conn = urllib3.util.connection.create_connection
In [3]: def set_src_addr(address, timeout, *args, **kw):
...: source_address = ('127.0.0.1', 0)
...: return real_create_conn(address, timeout=timeout, source_address=source_address)
...:
...: urllib3.util.connection.create_connection = set_src_addr
...:
...: import requests
...: r = requests.get('http://httpbin.org')
It fails with the following exception:
ConnectionError: HTTPConnectionPool(host='httpbin.org', port=80): Max retries exceeded with url: / (Caused by NewConnectionError("<urllib3.connection.HTTPConnection object at 0x10c4b89b0>: Failed to establish a new connection: [Errno 49] Can't assign requested address",))
Solution 2:
I will document the solution I have found and list some problems I had in the process.
salparadise had it right. It is very similar to the first answer I have found. I am assuming that the requests
module import the urllib3
and the latter has its own version of the socket
library. Therefore, it is very likely that the requests
module will never directly call the socket
library, but will have its functionality provided by the urllib3
module.
I have not noticed it first, but the third snippet I had in my question was working. The problem why I had a ConnectionError
is because I was trying to use a macvlan virtual interface over a wireless physical interface (which, if I understood correctly, drops packets if the MAC addresses do not match). Therefore, the following solution does work:
import requests
from socket import socket as backup
import socket
defsocket_custom_src_ip(src_ip):
original_socket = backup
defbound_socket(*args, **kwargs):
sock = original_socket(*args, **kwargs)
sock.bind((src_ip, 0))
print(sock)
return sock
return bound_socket
In my problem, I will need to change the IP address of a socket several times. One of the problems I had was that if no backup of the socket function is made, changing it several times would cause an error RecursionError: maximum recursion depth exceeded
. This occurs since on the second change, the socket.socket
function would not be the original one. Therefore, my solution above creates a copy of the original socket function to use as a backup for further bindings of different IPs.
Lastly, following is a proof of concept of how to achieve multiple processes using different libraries. With this idea, I can import and monkey-patch each socket inside my processes, having different monkey-patched versions of them.
import importlib
import multiprocessing
classMyProcess(multiprocessing.Process):
def__init__(self, module):
super().__init__()
self.module = module
defrun(self):
i = importlib.import_module(f'{self.module}')
print(f'{i}')
p1 = MyProcess('os')
p2 = MyProcess('sys')
p1.start()
<module 'os'from'/usr/lib/python3.7/os.py'>
p2.start()
<module 'sys' (built-in)>
This also works using the import
statement and global
keyword to provide transparent access inside all functions as the following
import multiprocessing
deffun(self):
import os
global os
os.var = f'{repr(self)}'
fun2()
deffun2():
print(os.system(f'echo "{os.var}"'))
classMyProcess(multiprocessing.Process):
def__init__(self):
super().__init__()
defrun(self):
if'os'indir():
print('os already imported')
fun(self)
p1 = MyProcess()
p2 = MyProcess()
p2.start()
<MyProcess(MyProcess-2, started)>
p1.start()
<MyProcess(MyProcess-1, started)>
Solution 3:
I faced a similar issue where I wanted to have some localhost traffic originating not from 127.0.0.1 ( I was testing a https connection over localhost)
This is how I did it using the python core libraries ssl
and http.client
(cf docs), as it seemed cleaner than the solutions I found online using the requests
library.
import http.client as http
import ssl
dst= 'sever.infsec.local'# dns record was added to OS
src = ('127.0.0.2',0) # 0 -> select available port
context = ssl.SSLContext()
context.load_default_certs() # loads OS certifcate context
request = http.HTTPSConnection(dst, 443, context=context,
source_address=src)
request.connect()
request.request("GET", '/', json.dumps(request_data))
response = request.getresponse()
Post a Comment for "Monkey Patching Sockets Library To Use A Specifc Network Interface"