Sans-io socks 4/5 client/server library/framework.
- No one-shot socks servers
- Sans-io
- asyncio-ready
twunnel3
is dead aiosocks
do not mimicasyncio.open_connection
arguments (maybe dead too)- Fun
- Only tcp connect (no tcp bind or udp associate)
- Both client and server
- Socks versions: 4, 4a, 5
- Socks5 auth: no auth, username/password
- Couple io backends: asyncio, trio, socketserver
- One-shot socks server (
python -m siosocks
)
siosocks
is offered under MIT license.
- python 3.11+
Framework | Client | Server |
---|---|---|
asyncio | + | + |
trio | + | + |
socket | + |
Feel free to make it bigger 😉
End user implementations mimic «parent» library api.
- asyncio:
open_connection
- trio:
open_tcp_stream
Extra keyword-only arguments:
socks_host
: stringsocks_port
: integersocks_version
: integer (4 or 5)username
: optional string (default:None
)password
: optional string (default:None
)encoding
: optional string (default:"utf-8"
)socks4_extras
: optional dictionarysocks5_extras
: optional dictionary
Extras:
- socks4
user_id
: string (default:""
)
- socks5
- None at this moment, added for uniform api
End user implementations mimic «parent» library server request handlers.
- asyncio:
start_server
- trio:
serve_tcp
- socketserver:
ThreadingTCPServer
You should use partial
to bind socks specific arguments:
allowed_versions
: set of integers (default:{4, 5}
)username
: optional string (default:None
)password
: optional string (default:None
)strict_security_policy
: boolean, ifTrue
exception will be raised if authentication required and 4 is in allowed versions set (default:True
)encoding
: optional string (default:"utf-8"
)
Nothing to say more. Typical usage can be found at __main__.py
This section will use asyncio
as backend, since it is main target/reason for siosocks
import asyncio
from siosocks.io.asyncio import open_connection
HOST = "api.ipify.org"
REQ = """GET /?format=json HTTP/1.1
Host: api.ipify.org
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en,en-US;q=0.7,ru;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0
"""
async def main():
# assume we have tor started
r, w = await open_connection(HOST, 80, socks_host="localhost", socks_port=9050, socks_version=5)
w.write(REQ.replace("\n", "\r\n").encode())
await w.drain()
print(await r.read(8192))
w.close()
asyncio.run(main())
import socket
import asyncio
import contextlib
from siosocks.io.asyncio import socks_server_handler
loop = asyncio.get_event_loop()
server = loop.run_until_complete(asyncio.start_server(socks_server_handler, port=1080))
addresses = []
for sock in server.sockets:
if sock.family in (socket.AF_INET, socket.AF_INET6):
host, port, *_ = sock.getsockname()
addresses.append(f"{host}:{port}")
print(f"Socks4/5 proxy serving on {', '.join(addresses)}")
with contextlib.suppress(KeyboardInterrupt):
loop.run_forever()
But if you just want one-shot socks server then try:
python -m siosocks
This will start socks 4, 5 server on all interfaces on 1080 port. For more information try --help
python -m siosocks --help
usage: siosocks [-h] [--backend {asyncio,socketserver,trio}] [--host HOST]
[--port PORT] [--family {ipv4,ipv6,auto}] [--socks SOCKS]
[--username USERNAME] [--password PASSWORD]
[--encoding ENCODING] [--no-strict] [-v]
Socks proxy server
optional arguments:
-h, --help show this help message and exit
--backend {asyncio,socketserver,trio}
Socks server backend [default: asyncio]
--host HOST Socks server host [default: None]
--port PORT Socks server port [default: 1080]
--family {ipv4,ipv6,auto}
Socket family [default: auto]
--socks SOCKS Socks protocol version [default: []]
--username USERNAME Socks auth username [default: None]
--password PASSWORD Socks auth password [default: None]
--encoding ENCODING String encoding [default: utf-8]
--no-strict Allow multiversion socks server, when socks5 used with
username/password auth [default: False]
-v, --version Show siosocks version
siosocks
have unified exception for all types of socks-related errors:
import asyncio
from siosocks.exceptions import SocksException
from siosocks.io.asyncio import open_connection
async def main():
try:
r, w = await open_connection("127.0.0.1", 80, socks_host="localhost", socks_port=9050, socks_version=5)
except SocksException:
...
else:
# at this point all socks-related tasks done and returned reader and writer
# are just plain asyncio objects without any siosocks proxies
asyncio.run(main())
Shadowsocks-like client/server. Shadowsocks-like built on top of socks5 and encryption. It have «client», which is actually socks server and «server». So, precisely there are two servers: client side and server side. Purpose of shadowsocks is to encrypt data between «incoming» and «outgoing» servers. In common this looks like:
client (non-encrypted socks) «incoming» socks server (encrypted socks) «outgoing» socks server (non-socks connection) target server
Example above use Caesar cipher for simplicity (and security of course).
- add more backends (average)
- speed up
passthrough
implementation (seems hard) - full protocol implementation (#4)