Skip to content

Commit 05c3d27

Browse files
committed
backward proxy
1 parent 87745cc commit 05c3d27

3 files changed

Lines changed: 69 additions & 4 deletions

File tree

pproxy/__doc__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__title__ = "pproxy"
2-
__version__ = "2.0.3"
2+
__version__ = "2.0.4"
33
__license__ = "MIT"
44
__description__ = "Proxy server that can tunnel among remote servers by regex rules."
55
__keywords__ = "proxy socks http shadowsocks shadowsocksr ssr redirect pf tunnel cipher ssl udp"

pproxy/proto.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,7 @@ def udp_parse(protos, data, **kw):
560560
raise Exception(f'Unsupported protocol {data[:10]}')
561561

562562
MAPPINGS = dict(direct=Direct, http=HTTP, httponly=HTTPOnly, socks5=Socks5, socks4=Socks4, socks=Socks5, ss=SS, ssr=SSR, redir=Redir, pf=Pf, tunnel=Tunnel, echo=Echo, pack=Pack, ws=WS, ssl='', secure='')
563+
MAPPINGS['in'] = ''
563564

564565
def get_protos(rawprotos):
565566
protos = []

pproxy/server.py

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,65 @@ def pattern_compile(filename):
189189
with open(filename) as f:
190190
return re.compile('(:?'+''.join('|'.join(i.strip() for i in f if i.strip() and not i.startswith('#')))+')$').match
191191

192+
class Backward(object):
193+
MAX_CONN = 1
194+
def __init__(self, uri):
195+
self.uri = uri
196+
self.closed = False
197+
self.conn = asyncio.Queue()
198+
self.open_connection = self.conn.get
199+
self.writer = None
200+
def close(self):
201+
self.closed = True
202+
try:
203+
self.writer.close()
204+
except Exception:
205+
pass
206+
async def start_server(self, handler):
207+
self.handler = handler
208+
for _ in range(self.MAX_CONN):
209+
asyncio.ensure_future(self.server_run())
210+
return self
211+
async def server_run(self):
212+
errwait = 0
213+
while not self.closed:
214+
if self.uri.unix:
215+
wait = asyncio.open_unix_connection(path=self.uri.bind, ssl=self.uri.sslclient, server_hostname='' if self.uri.sslclient else None)
216+
else:
217+
wait = asyncio.open_connection(host=self.uri.host_name, port=self.uri.port, ssl=self.uri.sslclient, local_addr=(self.uri.lbind, 0) if self.uri.lbind else None)
218+
try:
219+
reader, writer = await asyncio.wait_for(wait, timeout=SOCKET_TIMEOUT)
220+
self.writer = writer
221+
data = await reader.read_()
222+
if data:
223+
reader._buffer[0:0] = data
224+
asyncio.ensure_future(self.handler(reader, writer))
225+
errwait = 0
226+
except Exception as ex:
227+
if not self.closed:
228+
await asyncio.sleep(errwait)
229+
errwait = errwait*1.3 + 0.1
230+
def client_run(self):
231+
async def handler(reader, writer):
232+
while self.conn.qsize() >= self.MAX_CONN:
233+
r, w = await self.conn.get()
234+
try: w.close()
235+
except Exception: pass
236+
await self.conn.put((reader, writer))
237+
if self.uri.unix:
238+
return asyncio.start_unix_server(handler, path=self.uri.bind, ssl=self.uri.sslserver)
239+
else:
240+
return asyncio.start_server(handler, host=self.uri.host_name, port=self.uri.port, ssl=self.uri.sslserver)
241+
192242
class ProxyURI(object):
193243
def __init__(self, **kw):
194244
self.__dict__.update(kw)
195245
self.total = 0
196246
self.udpmap = {}
197247
self.handler = None
198248
self.streams = None
249+
if self.backward:
250+
self.backward = Backward(self)
199251
def logtext(self, host, port):
200252
if self.direct:
201253
return f' -> {host}:{port}'
@@ -274,6 +326,8 @@ async def open_connection(self, host, port, local_addr, lbind):
274326
raise Exception('Unknown tunnel endpoint')
275327
local_addr = local_addr if lbind == 'in' else (lbind, 0) if lbind else None
276328
wait = asyncio.open_connection(host=host, port=port, local_addr=local_addr)
329+
elif self.backward:
330+
wait = self.backward.open_connection()
277331
elif self.unix:
278332
wait = asyncio.open_unix_connection(path=self.bind, ssl=self.sslclient, server_hostname='' if self.sslclient else None)
279333
else:
@@ -307,7 +361,9 @@ async def prepare_ciphers_and_headers(self, reader_remote, writer_remote, host,
307361
return reader_remote, writer_remote
308362
def start_server(self, args):
309363
handler = functools.partial(reuse_stream_handler if self.reuse else stream_handler, **vars(self), **args)
310-
if self.unix:
364+
if self.backward:
365+
return self.backward.start_server(handler)
366+
elif self.unix:
311367
return asyncio.start_unix_server(handler, path=self.bind, ssl=self.sslserver)
312368
else:
313369
return asyncio.start_server(handler, host=self.host_name, port=self.port, ssl=self.sslserver, reuse_port=args.get('ruport'))
@@ -384,8 +440,8 @@ def compile(cls, uri, relay=None):
384440
match=match, bind=loc or urlpath, host_name=host_name, port=port, \
385441
unix=not loc, lbind=lbind, sslclient=sslclient, sslserver=sslserver, \
386442
alive=True, direct='direct' in protonames, tunnel='tunnel' in protonames, \
387-
reuse='pack' in protonames or relay and relay.reuse, relay=relay)
388-
ProxyURI.DIRECT = ProxyURI(direct=True, tunnel=False, reuse=False, relay=None, alive=True, match=None, cipher=None)
443+
reuse='pack' in protonames or relay and relay.reuse, backward='in' in rawprotos, relay=relay)
444+
ProxyURI.DIRECT = ProxyURI(direct=True, tunnel=False, reuse=False, relay=None, alive=True, match=None, cipher=None, backward=None)
389445

390446
async def test_url(url, rserver):
391447
url = urllib.parse.urlparse(url)
@@ -480,6 +536,14 @@ def main():
480536
servers.append(server)
481537
except Exception as ex:
482538
print('Start server failed.\n\t==>', ex)
539+
for option in args.rserver:
540+
if option.backward:
541+
print('Serving on', option.bind, 'backward by', ",".join(i.name for i in option.protos) + ('(SSL)' if option.sslclient else ''), '({}{})'.format(option.cipher.name, ' '+','.join(i.name() for i in option.cipher.plugins) if option.cipher and option.cipher.plugins else '') if option.cipher else '')
542+
try:
543+
server = loop.run_until_complete(option.backward.client_run())
544+
servers.append(server)
545+
except Exception as ex:
546+
print('Start server failed.\n\t==>', ex)
483547
if servers:
484548
if args.sys:
485549
from . import sysproxy

0 commit comments

Comments
 (0)