全局启用pysocks代理RDNS


通常,他们说重写socket.socket将使socks代理可在任何python库中使用:

1
2
3
4
import socks
import socket
socks.set_default_proxy(socks.PROXY_TYPE_SOCKS5, "host", port)
socket.socket = socks.socksocket

但是,如果要连接的计算机只能通过代理连接(即必须使用RDNS),则无法正常工作,这是因为socket.create_connection()始终在本地解析该计算机的IP地址:

(cf https://github.com/python/cpython/blob/2.7/Lib/socket.py#L557)

1
2
3
4
5
6
7
8
    host, port = address
    for res in getaddrinfo(host, port, 0, SOCK_STREAM):
        af, socktype, proto, canonname, sa = res ### sa is resolved locally!
        sock = None
        try:
            sock = socket(af, socktype, proto) ### this socket is overridden by socks.socksocket, but RDNS will not work well.
            sock.connect(sa)
            return sock

因此,如果我们需要RDNS,则create_connection也需要包装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
_done_tryPatchCreateConnections = False
def tryPatchCreateConnections():
    global _done_tryPatchCreateConnections
    if _done_tryPatchCreateConnections:
        return True
    try:
        import socks
        import socket
        socket.socket = socks.socksocket

        def patchCreateConnection(module):
            def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None, socket_options=None):
                #print('trying to override create_connection')
                if socks.socksocket.default_proxy is not None and socks.socksocket.default_proxy[3]:
                    return socks.create_connection(
                        address, timeout=timeout, source_address=source_address,
                        proxy_type=socks.socksocket.default_proxy[0],
                        proxy_addr=socks.socksocket.default_proxy[1],
                        proxy_port=socks.socksocket.default_proxy[2],
                        proxy_rdns=socks.socksocket.default_proxy[3],
                        proxy_username=socks.socksocket.default_proxy[4],
                        proxy_password=socks.socksocket.default_proxy[5],
                        socket_options=socket_options,
                    )
                elif module==socket:
                    return module._real_create_connection(address, timeout=timeout, source_address=source_address)
                else:
                    # likely urllib3
                    return module._real_create_connection(address, timeout=timeout, source_address=source_address, socket_options=socket_options)
            module._real_create_connection = module.create_connection
            module.create_connection = create_connection
            module.socket = socks.socksocket

        def patchCreateConnections():
            patchCreateConnection(socket)
            try:
                import urllib3
                patchCreateConnection(urllib3.util.connection)
            except ImportError:
                pass
            try:
                import requests
                import requests.packages.urllib3
                patchCreateConnection(requests.packages.urllib3.util.connection)
            except ImportError:
                pass

        patchCreateConnections()
        _done_tryPatchCreateConnections = True
        return True
    except ImportError:
        return False