猴子修补套接字库以使用特定的网络接口

时间:2018-10-29 22:57:46

标签: python python-3.x

我一直在尝试使用requests库但使用不同的网络接口来向网站提出请求。以下是我尝试使用但无法使用的答案列表。

answer描述了如何实现我想要的功能,但是它使用了pycurl。我可以使用pycurl,但已经了解了有关猴子修补的知识,并想尝试一下。

另外answer似乎一开始就起作用了,因为它不会引发任何错误。但是,我使用Wireshark监视了网络流量,并且数据包是从默认界面发送的。我试图在答案的作者定义的函数set_src_addr中打印消息,但消息未显示。因此,我认为它正在修补一个从未调用过的函数。我收到HTTP 200响应,因为我已经将套接字绑定到127.0.0.1,所以不会发生。

import socket

real_create_conn = socket.create_connection

def set_src_addr(*args):
    address, timeout = args[0], args[1]
    source_address = ('127.0.0.1', 0)
    return real_create_conn(address, timeout, source_address)

socket.create_connection = set_src_addr

import requests
r = requests.get('http://www.google.com')
r

<Response [200]>

我也尝试过此answer。使用这种方法,我会得到两种错误:

import socket                  
true_socket = socket.socket    
def bound_socket(*a, **k):     
    sock = true_socket(*a, **k)
    sock.bind(('127.0.0.1', 0))
    return sock                
socket.socket = bound_socket   
import requests

这将不允许我创建套接字并引发此error。我还尝试过修改此答案,看起来像这样:

import requests                           
import socket                             
true_socket = socket.socket               
def bound_socket(*a, **k):                
    sock = true_socket(*a, **k)           
    sock.bind(('192.168.0.10', 0))        
    print(sock)                           
    return sock                           
socket.socket = bound_socket              
r = requests.get('https://www.google.com')         

这也不起作用,并引发此error

我遇到以下问题:我希望每个进程都通过特定的网络接口发送请求。我认为,由于线程共享全局内存(包括库),因此我应该更改代码以使用进程。现在,我想在某个地方应用猴子补丁解决方案,以使每个进程可以使用不同的接口进行通信。我想念什么吗?这是解决此问题的最佳方法吗?

编辑: 我也想知道不同的程序是否可能具有相同库的不同版本。如果它们是共享的,我如何在Python中拥有不同版本的库(每个进程一个)?

3 个答案:

答案 0 :(得分:1)

这似乎适用于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')

它失败,但出现以下异常:

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",))

答案 1 :(得分:0)

我将记录找到的解决方案,并列出过程中遇到的一些问题。

salparadise正确。它与我发现的第一个answer非常相似。我假设requests模块导入urllib3,而后者具有自己的socket库版本。因此,requests模块很有可能永远不会直接调用socket库,但是其功能将由urllib3模块提供。

我没有首先注意到它,但是我在问题中遇到的第三个片段正在起作用。我拥有ConnectionError的问题是因为我试图通过无线物理接口使用macvlan虚拟接口(如果我理解正确,则在MAC地址不匹配的情况下会丢弃数据包)。因此,以下解决方案确实有效:

import requests                                
from socket import socket as backup            
import socket                                  
def socket_custom_src_ip(src_ip):              
    original_socket = backup                   
    def bound_socket(*args, **kwargs):         
        sock = original_socket(*args, **kwargs)
        sock.bind((src_ip, 0))                 
        print(sock)                            
        return sock                            
    return bound_socket                        

在我的问题中,我将需要多次更改套接字的IP地址。我遇到的问题之一是,如果不对套接字函数进行备份,则对其进行多次更改将导致错误RecursionError: maximum recursion depth exceeded。发生这种情况是因为在第二次更改中,socket.socket函数将不是原始函数。因此,我上面的解决方案创建了原始套接字功能的副本,以用作备份其他IP的进一步绑定。

最后,以下是如何使用不同的库实现多个过程的概念证明。有了这个想法,我可以在进程中导入和猴子修补每个套接字,使其具有不同的猴子修补版本。

import importlib                                     
import multiprocessing                               
class MyProcess(multiprocessing.Process):            
    def __init__(self, module):                      
        super().__init__()                           
        self.module = module                         
    def run(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)>                    

这也可以使用import语句和global关键字在以下所有函数内部提供透明访问

import multiprocessing                   
def fun(self):                           
    import os                            
    global os                            
    os.var = f'{repr(self)}'             
    fun2()                               
def fun2():                              
    print(os.system(f'echo "{os.var}"')) 
class MyProcess(multiprocessing.Process):
    def __init__(self):                  
        super().__init__()               
    def run(self):                       
        if 'os' in dir():                
            print('os already imported') 
        fun(self)                                            
p1 = MyProcess()                                                              
p2 = MyProcess()                                                              
p2.start()                                                                  
<MyProcess(MyProcess-2, started)>                                 
p1.start()                                        
<MyProcess(MyProcess-1, started)>        

答案 2 :(得分:0)

我遇到了类似的问题,我想让一些本地主机流量不是源自127.0.0.1(我正在通过本地主机测试https连接)

这就是我使用python核心库sslhttp.client(参见docs)的方法,因为它似乎比我在网上使用{{1} }库。

requests