HTTP/HTTP2/HTTP3/Socks4/Socks5/Shadowsocks/SSH/Redirect/Pf/QUIC/CFP TCP/UDP asynchronous tunnel proxy implemented in Python3 asyncio.
- Python 3.12 or newer.
- A Git checkout; this project is intended for private Git installation and is not published to PyPI.
Install the current checkout directly from Git:
$ python3 -m pip install "git+https://github.com/by275/python-proxy.git"The optional extras are accelerated (PyCryptodome and uvloop), h2
(HTTP/2), sshtunnel (SSH), quic (QUIC/HTTP/3), and daemon
(daemon mode). They can be combined when needed, for example:
$ python3 -m pip install "pproxy[h2,sshtunnel,quic] @ git+https://github.com/by275/python-proxy.git"$ python3 -m pip install "pproxy @ git+https://github.com/by275/python-proxy.git"
Successfully installed pproxy from Git
$ pproxy
Serving on :8080 by http,socks4,socks5
^C
$ pproxy -l ss://chacha20:abc@:8080
Serving on :8080 by ss (chacha20-py)Optional: (better performance with C ciphers)
$ python3 -m pip install "pproxy[accelerated] @ git+https://github.com/by275/python-proxy.git"
Successfully installed pycryptodome and uvloopApply OS system-wide proxy: (MacOS, Windows)
$ pproxy -r ss://chacha20:abc@server_ip:8080 --sys -vv
Serving on :8080 by http,socks4,socks5
System proxy setting -> socks5 localhost:8080
socks5 ::1:57345 -> ss server_ip:8080 -> slack.com:443
socks5 ::1:57345 -> ss server_ip:8080 -> www.google.com:443
..... (all local traffic log) ......Apply CLI proxy: (MacOS, Linux)
$ export http_proxy=http://localhost:8080
$ export https_proxy=http://localhost:8080The published image is available from GHCR:
docker run -it -p 8080:8080 ghcr.io/by275/pproxy:latest -l http://:8080 -vv
- Lightweight single-thread asynchronous IO.
- Pure python, no additional library required.
- Proxy client/server for TCP/UDP.
- Schedule (load balance) among remote servers.
- Incoming traffic auto-detect.
- Tunnel/jump/backward-jump support.
- Unix domain socket support.
- HTTP v2, HTTP v3 (QUIC)
- Authenticated worker WebSocket tunnel (CFP)
- User/password authentication support.
- Filter/block hostname by regex patterns.
- SSL/TLS client/server support.
- Shadowsocks OTA (One-Time-Auth), SSR plugins.
- Statistics by bandwidth and traffic.
- PAC support for javascript configuration.
- Iptables/Pf NAT redirect packet tunnel.
- System proxy auto-setting support.
- Client/Server API provided.
| Name | TCP server | TCP client | UDP server | UDP client | scheme |
|---|---|---|---|---|---|
| http (connect) | ✔ | ✔ | http:// | ||
| http (get,post,etc) | ✔ | httponly:// (as client) | |||
| http v2 (connect) | ✔ | ✔ | h2:// | ||
| http v3 (connect) | ✔ by UDP | ✔ by UDP | h3:// | ||
| https | ✔ | ✔ | http+ssl:// | ||
| socks4 | ✔ | ✔ | socks4:// | ||
| socks5 | ✔ | ✔ | ✔ udp-only | ✔ udp-only | socks5:// |
| socks5 over TLS | ✔ | ✔ | socks5+ssl:// | ||
| shadowsocks | ✔ | ✔ | ✔ | ✔ | ss:// |
| shadowsocks aead | ✔ | ✔ | ss:// | ||
| shadowsocksR | ✔ | ✔ | ssr:// | ||
| trojan | ✔ | ✔ | trojan:// | ||
| ssh tunnel | ✔ | ssh:// | |||
| quic | ✔ by UDP | ✔ by UDP | ✔ | ✔ | http+quic:// |
| iptables nat | ✔ | redir:// | |||
| pfctl nat (macos) | ✔ | pf:// | |||
| echo | ✔ | ✔ | echo:// | ||
| tunnel (raw socket) | ✔ | ✔ | ✔ | ✔ | tunnel:// tunnel{ip}:// |
| websocket (simple tunnel) | ✔ | ✔ | ws:// ws{dst_ip}:// | ||
| cfp worker tunnel | ✔ | cfp:// | |||
| xxx over TLS | ✔ | ✔ | xxx+ssl:// | ||
| AUTO DETECT | ✔ | ✔ | a+b+c+d:// |
The canonical registry schemes are direct, http, httponly,
httpadmin, ssh, socks4, socks5 (and the socks alias),
ss, ssr, redir, pf, tunnel, echo, ws, cfp,
trojan, h2, h3, and quic. ssl, secure, insecure,
and in are transport modifiers. H2, H3, QUIC, and SSH require their
corresponding optional extra.
| Name | TCP | UDP | Parameter | Default |
|---|---|---|---|---|
| first_available | ✔ | ✔ | -s fa | ✔ |
| round_robin | ✔ | ✔ | -s rr | |
| random_choice | ✔ | ✔ | -s rc | |
| least_connection | ✔ | -s lc |
pycryptodome is an optional library to enable faster (C version) cipher. pproxy has many built-in pure python ciphers. They are lightweight and stable, but slower than C ciphers. After speedup with PyPy, pure python ciphers can get similar performance as C version. If the performance is important and don't have PyPy, install pycryptodome instead.
asyncssh is an optional library to enable ssh tunnel client support.
h2 is an optional library to enable HTTP/2 support.
aioquic is an optional library to enable HTTP/3 and QUIC support.
python-daemon is an optional library for the --daemon mode.
These are some performance benchmarks between Python and C ciphers (dataset: 8M):
| chacha20-c | 0.64 secs |
| chacha20-py (pypy3) | 1.32 secs |
| chacha20-py | 48.86 secs |
PyPy3 Quickstart:
$ pypy3 -m ensurepip
$ pypy3 -m pip install "pproxy @ git+https://github.com/by275/python-proxy.git"$ pproxy -h
usage: pproxy [-h] [-l LISTEN] [-r RSERVER] [-ul ULISTEN] [-ur URSERVER]
[-b BLOCK] [-a ALIVED] [-s {fa,rr,rc,lc}] [-d] [-v]
[--ssl SSLFILE] [--pac PAC] [--get GETS] [--auth AUTHTIME]
[--sys] [--reuse] [--daemon] [--test TEST] [--version]
Proxy server that can tunnel among remote servers by regex rules. Supported
protocols: direct,http,httponly,httpadmin,ssh,socks5,socks4,socks,ss,ssr,redir,pf,tunnel,echo,ws,cfp,trojan,h2,h3,quic
options:
-h, --help show this help message and exit
-l LISTEN tcp server uri (default: http+socks4+socks5://127.0.0.1:8080/)
-r RSERVER tcp remote server uri (default: direct)
-ul ULISTEN udp server setting uri (default: none)
-ur URSERVER udp remote server uri (default: direct)
-b BLOCK block regex rules
-a ALIVED interval to check remote alive (default: no check)
-s {fa,rr,rc,lc} scheduling algorithm (default: first_available)
-d turn on debug to see tracebacks (default: no debug)
-v print verbose output
--ssl SSLFILE certfile[,keyfile] if server listen in ssl mode
--pac PAC http PAC path
--get GETS http custom {path,file}
--auth AUTHTIME re-auth time interval for same ip (default: 86400*30)
--sys change system proxy setting (mac, windows)
--reuse set SO_REUSEPORT (Linux only)
--daemon run as a daemon (Linux only)
--test TEST test this url for all remote proxies and exit
--version show program's version number and exit
Online help: <https://github.com/by275/python-proxy>The protocol line is generated from the runtime registry, so optional schemes remain visible in help even when their dependency is not installed. The command still fails with the existing missing-library message if such a scheme is used without its extra.
{scheme}://[{cipher}@]{netloc}/[@{localbind}][,{plugins}][?{rules}][#{auth}]scheme
Currently supported scheme: http, socks, ss, ssl, secure, cfp. You can use + to link multiple protocols together.
http
http protocol (CONNECT)
httponly
http protocol (GET/POST)
socks4
socks4 protocol
socks5
socks5 protocol
ss
shadowsocks protocol
ssr
shadowsocksr (SSR) protocol
trojan
trojan protocol
ssh
ssh client tunnel
redir
redirect (iptables nat)
pf
pfctl (macos pf nat)
ssl
unsecured ssl/tls (no cert)
secure
secured ssl/tls (cert)
tunnel
raw connection
ws
websocket connection
cfp
authenticated worker tunnel
echo
echo-back service
direct
direct connection
"http://" accepts GET/POST/CONNECT as server, sends CONNECT as client. "httponly://" sends "GET/POST" as client, works only on http traffic.
cfp://host[:port]/#[authorization]connects to an authenticated worker WebSocket tunnel. The default port is 443. Usecfp+insecure://only when certificate verification is intentionally disabled.- Valid schemes: http://, http+socks4+socks5://, http+ssl://, ss+secure://, http+socks5+ss://
- Invalid schemes: ssl://, secure://
cipher
Cipher's format: "cipher_name:cipher_key". Cipher can be base64-encoded. So cipher string with "YWVzLTEyOC1nY206dGVzdA==" is equal to "aes-128-gcm:test".
Full cipher support list:
pproxy ciphers have pure python implementations. Program will switch to C cipher if there is C implementation available within pycryptodome. Otherwise, use pure python cipher.
RC4, RC4-MD5, Blowfish-CFB, CAST5-CFB, and DES-CFB are retained for wire compatibility only. Selecting one emits a warning; new deployments should prefer an AEAD cipher such as chacha20-ietf-poly1305 or aes-256-gcm.
See
docs/SECURITY_POLICY.mdfor the current legacy-cipher policy and the criteria required before any future deprecation or removal.AEAD ciphers use additional payload after each packet. The underlying protocol is different. Specifications: AEAD.
Some pure python ciphers (aes-256-cfb1-py) is quite slow, and is not recommended to use without PyPy speedup. Try install pycryptodome and use C version cipher instead.
To enable OTA encryption with shadowsocks, add '!' immediately after cipher name.
netloc
- It can be "hostname:port" or "/unix_domain_socket". If the hostname is empty, server will listen on all interfaces.
- Valid netloc: localhost:8080, 0.0.0.0:8123, /tmp/domain_socket, :8123
localbind
- It can be "@in" or @ipv4_address or @ipv6_address
- Valid localbind: @in, @192.168.1.15, @::1
plugins
- It can be multiple plugins joined by ",". Supported plugins: plain, origin, http_simple, tls1.2_ticket_auth, verify_simple, verify_deflate
- Valid plugins: /,tls1.2_ticket_auth,verify_simple
rules
- The filename that contains regex rules
auth
- The username, colon ':', and the password
URIs can be joined by "__" to indicate tunneling by jump. For example, ss://1.2.3.4:1324__http://4.5.6.7:4321 make remote connection to the first shadowsocks proxy server, and then jump to the second http proxy server.
TCP Client API
import asyncio, pproxy async def test_tcp(proxy_uri): async with pproxy.Connection(proxy_uri) as conn: reader, writer = await conn.tcp_connect('google.com', 80) writer.write(b'GET / HTTP/1.1\r\nHost: google.com\r\n\r\n') data = await reader.read(1024*16) writer.close() print(data.decode()) asyncio.run(test_tcp('ss://aes-256-cfb:password@remote_host:remote_port'))UDP Client API
import asyncio, pproxy async def test_udp(proxy_uri): async with pproxy.Connection(proxy_uri) as conn: answer = asyncio.Future() await conn.udp_sendto('8.8.8.8', 53, b'hello the world', answer.set_result) await answer print(answer.result()) asyncio.run(test_udp('ss://chacha20:password@remote_host:remote_port'))
Server API example:
import asyncio import pproxy async def serve(): option = pproxy.Server('ss://127.0.0.1:1234') remote = pproxy.Connection('ss://1.2.3.4:5678') listener = await option.start_server({ 'rserver': [remote], 'verbose': print, }) try: await asyncio.Event().wait() finally: listener.close() await listener.wait_closed() await option.aclose() await remote.aclose() asyncio.run(serve())The listener returned by
start_server()and the proxy option are separate owners.close()remains available for synchronous callers; usewait_closed()after it when shutdown must be observed, or useaclose()and the async context manager for application-owned proxy objects. Seedocs/RUNTIME_API.mdfor the complete lifecycle and logging examples.
The existing -v option and verbose callback retain their console format
and statistics behavior. Applications may opt into JSON records for their own
logging events:
import logging
from pproxy.observability import configure_logging
logger = configure_logging(logging.INFO, structured=True)
logger.info('application event')configure_logging() does not convert or redirect the legacy verbose
callback automatically; applications which need proxy events in structured
logs should connect their own logging or callback integration.
Regex rule
Define regex file "rules" as follow:
#google domains (?:.+\.)?google.*\.com (?:.+\.)?gstatic\.com (?:.+\.)?gmail\.com (?:.+\.)?ntp\.org (?:.+\.)?glpals\.com (?:.+\.)?akamai.*\.net (?:.+\.)?ggpht\.com (?:.+\.)?android\.com (?:.+\.)?gvt1\.com (?:.+\.)?youtube.*\.com (?:.+\.)?ytimg\.com (?:.+\.)?goo\.gl (?:.+\.)?youtu\.be (?:.+\.)?google\..+
Then start pproxy
$ pproxy -r http://aa.bb.cc.dd:8080?rules -vv Serving on :8080 by http,socks4,socks5 http ::1:57768 -> http aa.bb.cc.dd:8080 -> www.googleapis.com:443 http ::1:57772 -> www.yahoo.com:80 socks4 ::1:57770 -> http aa.bb.cc.dd:8080 -> www.youtube.com:443
pproxy will serve incoming traffic by http/socks4/socks5 auto-detect protocol, redirect all google traffic to http proxy aa.bb.cc.dd:8080, and visit all other traffic directly from local.
Use cipher
Add cipher encryption to make sure data can't be intercepted. Run pproxy locally as:
$ pproxy -l ss://:8888 -r ss://chacha20:cipher_key@aa.bb.cc.dd:12345 -vv
Next, run pproxy.py remotely on server "aa.bb.cc.dd". The base64 encoded string of "chacha20:cipher_key" is also supported:
$ pproxy -l ss://chacha20:cipher_key@:12345
The same as:
$ pproxy -l ss://Y2hhY2hhMjA6Y2lwaGVyX2tleQ==@:12345
The traffic between local and aa.bb.cc.dd is encrypted by stream cipher Chacha20 with secret key "cipher_key".
Unix domain socket
A more complex example:
$ pproxy -l ss://salsa20!:complex_cipher_key@/tmp/pproxy_socket -r http+ssl://domain1.com:443#username:password
pproxy listen on the unix domain socket "/tmp/pproxy_socket" with cipher "salsa20" and key "complex_cipher_key". OTA packet protocol is enabled by adding ! after cipher name. The traffic is tunneled to remote https proxy with simple http authentication.
SSL/TLS server
If you want to listen in SSL/TLS, you must specify ssl certificate and private key files by parameter "--ssl":
$ pproxy -l http+ssl://0.0.0.0:443 -l http://0.0.0.0:80 --ssl server.crt,server.key --pac /autopac
pproxy listen on both 80 HTTP and 443 HTTPS ports, use the specified SSL/TLS certificate and private key files. The "--pac" enable PAC feature, so you can put "https://yourdomain.com/autopac" path in your device's auto-configure url.
Simple guide for generating self-signed ssl certificates:
$ openssl genrsa -des3 -out server.key 1024 $ openssl req -new -key server.key -out server.csr $ cp server.key server.key.org $ openssl rsa -in server.key.org -out server.key $ openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
SSR plugins
ShadowsocksR example with plugin "tls1.2_ticket_auth" to emulate common tls traffic:
$ pproxy -l ssr://chacha20:mypass@0.0.0.0:443/,tls1.2_ticket_auth,verify_simple
Local bind ip
If you want to route the traffic by different local bind, use the @localbind URI syntax. For example, server has three ip interfaces: 192.168.1.15, 111.0.0.1, 112.0.0.1. You want to route traffic matched by "rule1" to 111.0.0.2 and traffic matched by "rule2" to 222.0.0.2, and the remaining traffic directly:
$ pproxy -l ss://:8000/@in -r ss://111.0.0.2:8000/@111.0.0.1?rule1 -r ss://222.0.0.2:8000/@222.0.0.1?rule2
Redirect/Pf protocol
IPTable NAT redirect example (Ubuntu):
$ sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-ports 5555 $ pproxy -l redir://:5555 -r http://remote_http_server:3128 -vv
The above example illustrates how to redirect all local output tcp traffic with destination port 80 to localhost port 5555 listened by pproxy, and then tunnel the traffic to remote http proxy.
PF redirect example (MacOS):
$ sudo pfctl -ef /dev/stdin rdr pass on lo0 inet proto tcp from any to any port 80 -> 127.0.0.1 port 8080 pass out on en0 route-to lo0 inet proto tcp from any to any port 80 keep state ^D $ sudo pproxy -l pf://:8080 -r socks5://remote_socks5_server:1324 -vv
Make sure pproxy runs in root mode (sudo), otherwise it cannot redirect pf packet.
Multiple jumps example
$ pproxy -r http://server1__ss://server2__socks://server3
pproxy will connect to server1 first, tell server1 connect to server2, and tell server2 connect to server3, and make real traffic by server3.
Raw connection tunnel
TCP raw connection tunnel example:
$ pproxy -l tunnel{google.com}://:80 $ curl -H "Host: google.com" http://localhostUDP dns tunnel example:
$ pproxy -ul tunnel{8.8.8.8}://:53 $ nslookup google.com localhostUDP more complicated example
Run the shadowsocks udp proxy on remote machine:
$ pproxy -ul ss://remote_server:13245
Run the commands on local machine:
$ pproxy -ul tunnel{8.8.8.8}://:53 -ur ss://remote_server:13245 -vv UDP tunnel 127.0.0.1:60573 -> ss remote_server:13245 -> 8.8.8.8:53 UDP tunnel 127.0.0.1:60574 -> ss remote_server:13245 -> 8.8.8.8:53 ... $ nslookup google.com localhostLoad balance example
Specify multiple -r server, and a scheduling algorithm (rr = round_robin, rc = random_choice, lc = least_connection):
$ pproxy -r http://server1 -r ss://server2 -r socks5://server3 -s rr -vv http ::1:42356 -> http server1 -> google.com:443 http ::1:42357 -> ss server2 -> google.com:443 http ::1:42358 -> socks5 server3 -> google.com:443 http ::1:42359 -> http server1 -> google.com:443 ... $ pproxy -ul tunnel://:53 -ur tunnel://8.8.8.8:53 -ur tunnel://8.8.4.4:53 -s rc -vv UDP tunnel ::1:35378 -> tunnel 8.8.8.8:53 UDP tunnel ::1:35378 -> tunnel 8.8.4.4:53 ...
WebSocket example
WebSocket protocol is similar to Tunnel protocol. It is raw and doesn't support any proxy function. It can connect to other proxy like Tunnel protocol.
First run pproxy on remote machine:
$ pproxy -l ws://:80 -r tunnel:///tmp/myproxy -v $ pproxy -l ss://chacha20:abc@/tmp/myproxy -v
Run pproxy on local machine:
$ pproxy -l tunnel://:1234 -r ws://remote_ip:80 -vv
Then port :1234 on local machine is connected to the /tmp/myproxy on remote machine by WebSocket tunnel. You can specify any proxy protocol details on /tmp/myproxy.
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.
Backward proxy
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.
Run pproxy client as follows:
$ pproxy -l http://:8080 -r http+in://:8081 -v
Run pproxy server as follows:
$ pproxy -l http+in://client_ip:8081
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.
$ pproxy -l http+in://jumpserver__http://client_ip:8081
It is a complicated example. Server connects to client_ip:8081 by jump http://jumpserver. The backward proxy works through jumps.
SSH client tunnel
SSH client tunnel support is enabled by installing additional library asyncssh. After "pip3 install asyncssh", you can specify "ssh" as scheme to proxy via ssh client tunnel.
$ pproxy -l http://:8080 -r ssh://remote_server.com/#login:password
If a client private key is used to authenticate, put double colon "::" between login and private key path.
$ pproxy -l http://:8080 -r ssh://remote_server.com/#login::private_key_path
SSH server host-key verification uses AsyncSSH's known_hosts configuration by default. For controlled compatibility tests only, add the explicit
insecureURI modifier (for example,ssh+insecure://).SSH jump
SSH jump is supported by using "__" concatenation
$ pproxy -r ssh://server1__ssh://server2__ssh://server3
First connection to server1 is made. Second, ssh connection to server2 is made from server1. Finally, connect to server3, and use server3 for proxying traffic.
SSH remote forward
$ pproxy -l ssh://server__tunnel://0.0.0.0:1234 -r tunnel://127.0.0.1:1234
TCP :1234 on remote server is forwarded to 127.0.0.1:1234 on local server
$ pproxy -l ssh://server1__ssh://server2__ss://0.0.0.0:1234 -r ss://server3:1234
It is a complicated example. SSH server2 is jumped from SSH server1, and ss://0.0.0.0:1234 on server2 is listened. Traffic is forwarded to ss://server3:1234.
Trojan protocol example
Normally trojan:// should be used together with ssl://. You should specify the SSL crt/key file for ssl usage. A typical trojan server would be:
$ pproxy --ssl ssl.crt,ssl.key -l trojan+tunnel{localhost:80}+ssl://:443#yourpassword -vvIf trojan password doesn't match, the tunnal{localhost:80} will be switched to. It looks exactly the same as a common HTTPS website.
QUIC protocol example
QUIC is a UDP stream protocol used in HTTP/3. Library aioquic is required if you want to proxy via QUIC. QUIC is listened on UDP port, but can handle TCP or UDP traffic. If you want to handle TCP traffic, you should use "-l quic+http" instead of "-ul quic+http".
$ pip3 install aioquic $ pproxy --ssl ssl.crt,ssl.key -l quic+http://:1234
On the client:
$ pproxy -r quic+http://server:1234
QUIC protocol can transfer a lot of TCP streams on one single UDP stream. If the connection number is hugh, QUIC can benefit by reducing TCP handshake time.
VPN Server Example
You can run VPN server simply by installing pvpn (python vpn), a lightweight VPN server with pproxy tunnel feature.
$ pip3 install pvpn Successfully installed pvpn-0.2.1 $ pvpn -wg 9999 -r http://remote_server:remote_port Serving on UDP :500 :4500... Serving on UDP :9000 (WIREGUARD)... TCP xx.xx.xx.xx:xx -> HTTP xx.xx.xx.xx:xx -> xx.xx.xx.xx:xx
- python-vpn - VPN Server (IPSec,IKE,IKEv2,L2TP,WireGuard) in pure python
- shadowproxy - Awesome python proxy implementation by guyingbo
