qwj / python-proxy

HTTP/HTTP2/HTTP3/Socks4/Socks5/Shadowsocks/ShadowsocksR/SSH/Redirect/Pf TCP/UDP asynchronous tunnel proxy implemented in Python 3 asyncio.
MIT License
1.98k stars 332 forks source link

Rules seem to be ignored with 'direct' #177

Open MartinusR opened 8 months ago

MartinusR commented 8 months ago

I want to exclude local addresses from going through the proxy.

When I try to use direct with some rules, it seems that the rules are ignored and everything goes through direct:

pproxy -r direct://?rules -r socks5://server

With rules containing:

localhost

Then for instance wget google.com goes through direct:

http ::1:50386 -> google.com:80

If I create another local socks5 (or http) proxy that has no remote (direct only), this works: pproxy -r socks5://direct_proxy?rules -r socks5://server

When i do wget google.com, this is now working:

http ::1:50538 -> socks5 server -> google.com:80
gordon-quad commented 6 months ago

It works with older (2.2.0) version, but not with recent one. Unfortunately in 2.2.0 HTTP2 is broken (or not implemented). It would be really nice if this could be fixed.

gordon-quad commented 5 months ago

Simple but ugly workaround

diff --git a/pproxy/server.py b/pproxy/server.py
index dc3e4e5..26963ce 100644
--- a/pproxy/server.py
+++ b/pproxy/server.py
@@ -153,11 +153,12 @@ async def check_server_alive(interval, rserver, verbose):
                 pass

 class ProxyDirect(object):
-    def __init__(self, lbind=None):
+    def __init__(self, rule=None, lbind=None):
         self.bind = 'DIRECT'
         self.lbind = lbind
         self.unix = False
         self.alive = True
+        self.rule = compile_rule(rule) if rule else None
         self.connections = 0
         self.udpmap = {}
     @property
@@ -166,7 +167,7 @@ class ProxyDirect(object):
     def logtext(self, host, port):
         return '' if host == 'tunnel' else f' -> {host}:{port}'
     def match_rule(self, host, port):
-        return True
+        return (self.rule is None) or self.rule(host) or self.rule(str(port))
     def connection_change(self, delta):
         self.connections += delta
     def udp_packet_unpack(self, data):
@@ -827,7 +828,7 @@ def proxy_by_uri(uri, jump):
         auth = url.fragment.encode()
     users = [i.rstrip() for i in auth.split(b'\n')] if auth else None
     if 'direct' in protonames:
-        return ProxyDirect(lbind=lbind)
+        return ProxyDirect(lbind=lbind, rule=url.query)
     else:
         params = dict(jump=jump, protos=protos, cipher=cipher, users=users, rule=url.query, bind=loc or urlpath,
                       host_name=host_name, port=port, unix=not loc, lbind=lbind, sslclient=sslclient, sslserver=sslserver)
Jonney commented 5 months ago

This is my solution, no need to modify any code. Just write your own code.

import asyncio
import uvloop
import signal
from pproxy import Server,Connection
from pproxy.server import ProxyDirect

HOSTS=(
    'google.com',
    'x.com',
    'facebook.com',
)
LOCAL='http+socks5://127.0.0.1:8080'
REMOTE='ss://cipher:key@x.x.x.x:port'

def rule(host,port):
    for item in HOSTS:
        if host.endswith(item):
            return True

async def main():
    direct=ProxyDirect()
    direct.match_rule=rule
    server=Server(LOCAL)
    handler=await server.start_server({
        'rserver':(direct,Connection(REMOTE)),
        'verbose':print,
        'ruport':True,
    })
    stop=asyncio.Event()
    signal.signal(signal.SIGINT,lambda sig,frame:stop.set())
    await stop.wait()
    handler.close()
    if hasattr(handler,'wait_closed'):
        await handler.wait_closed()
    await asyncio.get_event_loop().shutdown_asyncgens()

if __name__=='__main__':
    uvloop.install()
    asyncio.run(main())