Skip to content

Commit 9c9144b

Browse files
authored
Merge pull request #1 from qwj/master
pull from master
2 parents 24155d1 + 6c71230 commit 9c9144b

2 files changed

Lines changed: 38 additions & 9 deletions

File tree

README.md

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,29 @@ Features
1414
- This utility can automatically detect incoming protocol from HTTP/Socks5/PSocks.
1515
- Implemented basic authentication method for HTTP/Socks5/PSocks.
1616
- Regex pattern file support for redirecting/blocking the incoming hosts.
17+
- Stream cipher chacha20 support to communicate with remote server securely.
1718
- Basic statistics for bandwidth and total traffic by client/hostname
1819

1920
Usage
2021
-----------
2122

2223
$ python3.6 pproxy.py -h
23-
usage: pproxy.py [-h] [-p PORT] [-t TYPES] [-a AUTH] [-rs RSERVER] [-rt RTYPE]
24-
[-ra RAUTH] [-m MATCH] [-b BLOCK] [-v]
25-
24+
usage: pproxy.py [-h] [-p PORT] [-t TYPES] [-a AUTH] [-c CIPHER] [-rs RSERVER]
25+
[-rt RTYPE] [-ra RAUTH] [-rc RCIPHER] [-m MATCH] [-b BLOCK]
26+
[-v]
27+
2628
Proxy server that can tunnel by http,socks,psocks protocol.
27-
29+
2830
optional arguments:
2931
-h, --help show this help message and exit
3032
-p PORT listen port server bound to (default: 8080)
3133
-t TYPES proxy server protocols (default: socks,http)
3234
-a AUTH authentication requirement
35+
-c CIPHER cipher key (default: no cipher)
3336
-rs RSERVER remote server address (default: direct)
3437
-rt RTYPE remote server type (default: psocks)
3538
-ra RAUTH remote authorization code
39+
-rc RCIPHER remote cipher key (default: no cipher)
3640
-m MATCH match pattern file
3741
-b BLOCK block pattern file
3842
-v print verbose output
@@ -60,8 +64,17 @@ We can define file "rules" as follow:
6064

6165
Then start the pproxy.py
6266

63-
python3.6 pserver.py -rs aa.bb.cc.dd:8080 -rt http -m rules -v
67+
python3.6 pproxy.py -rs aa.bb.cc.dd:8080 -rt http -m rules -v
6468

6569
With these parameters, this utility will serve incoming traffic by either http/socks5 protocol, redirect all google traffic to http proxy aa.bb.cc.dd:8080, and visit all other traffic locally.
6670

71+
To bridge two servers, add cipher key to ensure data can't be intercepted. First, run pproxy.py locally
72+
73+
python3.6 pproxy.py -rs aa.bb.cc.dd:12345 -rt psocks -rc "This is a cipher key" -m rules -v
74+
75+
Next, run pproxy.py remotely on server "aa.bb.cc.dd"
76+
77+
python3.6 pproxy.py -p 12345 -t psocks -rc "This is a cipher key"
78+
79+
By doing this, the traffic between local and aa.bb.cc.dd is encrypted by stream cipher Chacha20 with key "This is a cipher key". If target hostname is not in "rules", traffic will go through locally. Otherwise, traffic will go through the remote server by encryption.
6780

pproxy.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
CLIENT_TIMEOUT = 10
44
CONNECTION_TIMEOUT = 90
5-
AUTH_TIME = 86400*7
5+
AUTH_TIME = 86400 * 30
66
HTTP_LINE = re.compile('([^ ]+) +(.+?) +([^ ]+)')
77

88
def all_stat(stats):
@@ -75,7 +75,14 @@ async def http_channel(reader, writer, stat_bytes):
7575
finally:
7676
writer.close()
7777

78-
async def proxy_handler(reader, writer, types, auth, rserver, rtype, rauth, match, block, verbose, stats, auth_tables, **kwargs):
78+
async def apply_cipher(reader, writer, cipher, read):
79+
writer_cipher = cipher[0].new(key=cipher[1])
80+
writer.write(writer_cipher.nonce)
81+
writer.write = lambda s, o=writer.write, p=writer_cipher.encrypt: o(p(s))
82+
reader_cipher = cipher[0].new(key=cipher[1], nonce=await read(8))
83+
reader.feed_data = lambda s, o=reader.feed_data, p=reader_cipher.decrypt: o(p(s))
84+
85+
async def proxy_handler(reader, writer, types, auth, rserver, rtype, rauth, match, block, verbose, stats, auth_tables, cipher, rcipher, **kwargs):
7986
try:
8087
initbuf = b''
8188
pack2 = lambda s: struct.pack('>H', s)
@@ -84,6 +91,8 @@ async def proxy_handler(reader, writer, types, auth, rserver, rtype, rauth, matc
8491
read = lambda n: asyncio.wait_for(reader.readexactly(n), timeout=CLIENT_TIMEOUT)
8592
writer.get_extra_info('socket').setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
8693
remote_ip = writer.get_extra_info('peername')[0]
94+
if cipher:
95+
await apply_cipher(reader, writer, cipher, read)
8796
header = await read(1)
8897
if 'socks' in types and header == b'\x05':
8998
methods = await read((await read(1))[0])
@@ -151,7 +160,7 @@ async def proxy_handler(reader, writer, types, auth, rserver, rtype, rauth, matc
151160
raise Exception('Unsupported protocol')
152161
if block and block(host_name):
153162
raise Exception('BLOCKED ' + host_name)
154-
host_name_2 = '.'.join(host_name.split('.')[-2:]) if host_name.split('.')[-1].isalpha() else host_name
163+
host_name_2 = '.'.join(host_name.split('.')[-3 if host_name.endswith('.com.cn') else -2:]) if host_name.split('.')[-1].isalpha() else host_name
155164
tostat = (stats[0], stats.setdefault(remote_ip, {}).setdefault(host_name_2, [0]*6))
156165
modstat = lambda i: lambda s: [st.__setitem__(i, st[i] + s) for st in tostat]
157166
viaproxy = bool(rserver and (not match or match(host_name)))
@@ -175,10 +184,12 @@ async def proxy_handler(reader, writer, types, auth, rserver, rtype, rauth, matc
175184
raise Exception(f'Connection timeout {rserver}')
176185
try:
177186
writer_remote.get_extra_info('socket').setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
187+
readr = lambda n: asyncio.wait_for(reader_remote.readexactly(n), timeout=CLIENT_TIMEOUT)
188+
if viaproxy and rcipher:
189+
await apply_cipher(reader_remote, writer_remote, rcipher, readr)
178190
writer_remote.write(initbuf)
179191
if viaproxy and rtype == 'socks':
180192
await asyncio.wait_for(reader_remote.readuntil(b'\x00\x05\x00\x00'), timeout=CLIENT_TIMEOUT)
181-
readr = lambda n: asyncio.wait_for(reader_remote.readexactly(n), timeout=CLIENT_TIMEOUT)
182193
await readr(6 if (await readr(1))[0] == 1 else (await readr(1))[0] + 2)
183194
elif viaproxy and rtype == 'http':
184195
await asyncio.wait_for(reader_remote.readuntil(b'\r\n\r\n'), timeout=CLIENT_TIMEOUT)
@@ -200,13 +211,18 @@ def main():
200211
def addr_compile(s):
201212
ip, _, port = s.partition(':')
202213
return (ip, int(port) if port else 18436)
214+
def cipher_compile(key): #pip install pycryptodome
215+
from Crypto.Cipher import ChaCha20
216+
return (ChaCha20, key.encode().rjust(32, b'\x55'))
203217
parser = argparse.ArgumentParser(description='Proxy server that can tunnel by http,socks,psocks protocol.')
204218
parser.add_argument('-p', dest='port', type=int, default=8080, help='listen port server bound to (default: 8080)')
205219
parser.add_argument('-t', dest='types', type=lambda s: s.split(','), default=['socks','http'], help='proxy server protocols (default: socks,http)')
206220
parser.add_argument('-a', dest='auth', type=lambda s: s.encode(), help='authentication requirement')
221+
parser.add_argument('-c', dest='cipher', type=cipher_compile, help='cipher key (default: no cipher)')
207222
parser.add_argument('-rs', dest='rserver', type=addr_compile, help='remote server address (default: direct)')
208223
parser.add_argument('-rt', dest='rtype', default='psocks', help='remote server type (default: psocks)')
209224
parser.add_argument('-ra', dest='rauth', default=b'', type=lambda s: s.encode(), help='remote authorization code')
225+
parser.add_argument('-rc', dest='rcipher', type=cipher_compile, help='remote cipher key (default: no cipher)')
210226
parser.add_argument('-m', dest='match', type=pattern_compile, help='match pattern file')
211227
parser.add_argument('-b', dest='block', type=pattern_compile, help='block pattern file')
212228
parser.add_argument('-v', dest='v', action='store_true', help='print verbose output')

0 commit comments

Comments
 (0)