Skip to content

Commit 561a5a4

Browse files
committed
backward proxy feature
1 parent 05c3d27 commit 561a5a4

4 files changed

Lines changed: 48 additions & 35 deletions

File tree

README.rst

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,27 +60,22 @@ Run With Docker
6060
Features
6161
--------
6262

63-
- Single-thread asynchronous IO with high availability and scalability.
64-
- Lightweight (~500 lines) and powerful by leveraging python builtin *asyncio* library.
65-
- No additional library is required. All codes are in pure Python.
66-
- Auto-detect incoming traffic.
67-
- Tunnel by remote proxy servers.
68-
- Tunnel and relay with several layers.
69-
- Unix domain socket.
70-
- Basic authentication for all protocols.
71-
- Regex pattern file to route/block by hostname.
63+
- Lightweight single-thread asynchronous IO.
64+
- Pure python, no additional library required.
65+
- Proxy client/server for TCP/UDP.
66+
- Schedule (load balance) among remote servers.
67+
- Incoming traffic auto-detect.
68+
- Tunnel/relay/backward-relay support.
69+
- Unix domain socket support.
70+
- User/password authentication support.
71+
- Filter/block hostname by regex patterns.
7272
- SSL/TLS client/server support.
73-
- Built-in encryption ciphers. (chacha20, aes-256-cfb, etc)
74-
- Shadowsocks OTA (One-Time-Auth_).
75-
- SSR plugins. (http_simple, verify_simple, tls1.2_ticket_auth, etc)
73+
- Shadowsocks OTA (One-Time-Auth_), SSR plugins.
7674
- Statistics by bandwidth and traffic.
7775
- PAC support for javascript configuration.
78-
- Iptables NAT redirect packet tunnel.
79-
- PyPy3 support with JIT speedup.
76+
- Iptables/Pf NAT redirect packet tunnel.
8077
- System proxy auto-setting support.
81-
- UDP proxy client/server support.
82-
- Schedule (load balance) among remote servers.
83-
- Client/Server API support.
78+
- Client/Server API provided.
8479

8580
.. _One-Time-Auth: https://shadowsocks.org/en/spec/one-time-auth.html
8681

@@ -625,3 +620,21 @@ Examples
625620

626621
It is a good practice to use some CDN in the middle of local/remote machines. CDN with WebSocket support can hide remote machine's real IP from public.
627622

623+
- Backward proxy
624+
625+
Sometimes, the proxy server hides behind an NAT router and doesn't have a public ip. the client side has a public ip "client_ip". Backward proxy feature enables the server to connect backward to client and wait for proxy requests.
626+
627+
Run **pproxy** client as follows:
628+
629+
.. code:: rst
630+
631+
$ pproxy -l http://:8080 -r http+in://:8081 -v
632+
633+
Run **pproxy** server as follows:
634+
635+
.. code:: rst
636+
637+
$ pproxy -l http+in://client_ip:8081
638+
639+
Server connects to client_ip:8081 and waits for client proxy requests. The protocol http specified is just an example. It can be any protocol and cipher **pproxy** supports. The scheme **in** should exist in URI to inform **pproxy** that it is a backward proxy.
640+

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.4"
2+
__version__ = "2.0.5"
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/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
Connection = server.ProxyURI.compile_relay
44
DIRECT = server.ProxyURI.DIRECT
55
Server = server.ProxyURI.compile
6-
Rule = server.pattern_compile
6+
Rule = server.ProxyURI.compile_rule

pproxy/server.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,30 +185,22 @@ async def check_server_alive(interval, rserver, verbose):
185185
except Exception:
186186
pass
187187

188-
def pattern_compile(filename):
189-
with open(filename) as f:
190-
return re.compile('(:?'+''.join('|'.join(i.strip() for i in f if i.strip() and not i.startswith('#')))+')$').match
191-
192-
class Backward(object):
193-
MAX_CONN = 1
188+
class BackwardConnection(object):
194189
def __init__(self, uri):
195190
self.uri = uri
196191
self.closed = False
197192
self.conn = asyncio.Queue()
198193
self.open_connection = self.conn.get
199-
self.writer = None
200194
def close(self):
201195
self.closed = True
202196
try:
203197
self.writer.close()
204198
except Exception:
205199
pass
206200
async def start_server(self, handler):
207-
self.handler = handler
208-
for _ in range(self.MAX_CONN):
209-
asyncio.ensure_future(self.server_run())
201+
asyncio.ensure_future(self.server_run(handler))
210202
return self
211-
async def server_run(self):
203+
async def server_run(self, handler):
212204
errwait = 0
213205
while not self.closed:
214206
if self.uri.unix:
@@ -221,15 +213,19 @@ async def server_run(self):
221213
data = await reader.read_()
222214
if data:
223215
reader._buffer[0:0] = data
224-
asyncio.ensure_future(self.handler(reader, writer))
216+
asyncio.ensure_future(handler(reader, writer))
225217
errwait = 0
226218
except Exception as ex:
219+
try:
220+
writer.close()
221+
except Exception:
222+
pass
227223
if not self.closed:
228224
await asyncio.sleep(errwait)
229225
errwait = errwait*1.3 + 0.1
230226
def client_run(self):
231227
async def handler(reader, writer):
232-
while self.conn.qsize() >= self.MAX_CONN:
228+
while not self.conn.empty():
233229
r, w = await self.conn.get()
234230
try: w.close()
235231
except Exception: pass
@@ -247,7 +243,7 @@ def __init__(self, **kw):
247243
self.handler = None
248244
self.streams = None
249245
if self.backward:
250-
self.backward = Backward(self)
246+
self.backward = BackwardConnection(self)
251247
def logtext(self, host, port):
252248
if self.direct:
253249
return f' -> {host}:{port}'
@@ -381,6 +377,10 @@ async def udp_sendto(self, host, port, data, answer_cb, local_addr=None):
381377
data = self.prepare_udp_connection(host, port, data)
382378
await self.open_udp_connection(host, port, data, local_addr, answer_cb)
383379
@classmethod
380+
def compile_rule(cls, filename):
381+
with open(filename) as f:
382+
return re.compile('(:?'+''.join('|'.join(i.strip() for i in f if i.strip() and not i.startswith('#')))+')$').match
383+
@classmethod
384384
def compile_relay(cls, uri):
385385
tail = cls.DIRECT
386386
for urip in reversed(uri.split('__')):
@@ -430,7 +430,7 @@ def compile(cls, uri, relay=None):
430430
if err_str:
431431
raise argparse.ArgumentTypeError(err_str)
432432
cipher.plugins.append(plugin)
433-
match = pattern_compile(url.query) if url.query else None
433+
match = cls.compile_rule(url.query) if url.query else None
434434
if loc:
435435
host_name, _, port = loc.partition(':')
436436
port = int(port) if port else 8080
@@ -479,7 +479,7 @@ def main():
479479
parser.add_argument('-r', dest='rserver', default=[], action='append', type=ProxyURI.compile_relay, help='tcp remote server uri (default: direct)')
480480
parser.add_argument('-ul', dest='ulisten', default=[], action='append', type=ProxyURI.compile, help='udp server setting uri (default: none)')
481481
parser.add_argument('-ur', dest='urserver', default=[], action='append', type=ProxyURI.compile_relay, help='udp remote server uri (default: direct)')
482-
parser.add_argument('-b', dest='block', type=pattern_compile, help='block regex rules')
482+
parser.add_argument('-b', dest='block', type=ProxyURI.compile_rule, help='block regex rules')
483483
parser.add_argument('-a', dest='alived', default=0, type=int, help='interval to check remote alive (default: no check)')
484484
parser.add_argument('-s', dest='salgorithm', default='fa', choices=('fa', 'rr', 'rc', 'lc'), help='scheduling algorithm (default: first_available)')
485485
parser.add_argument('-v', dest='v', action='count', help='print verbose output')

0 commit comments

Comments
 (0)