net.shadowsocks
A Shadowsocks client and server: AEAD and Shadowsocks 2022 ciphers, ss:// links, simple-obfs, a UDP relay, and a stream that runs over any tunnel.Stream.
import net.shadowsocks
Shadowsocks is a small encrypted proxy protocol. A client connects to a Shadowsocks server and says which host and port it wants to reach; the server connects there and carries bytes both ways. Everything after the first byte looks like random data, so there is no handshake to recognise. net.shadowsocks is a client and a server for it written in Salam, with no native library. It is one of the proxy packages built on net.tunnel and can be plugged into the same routers and chains as the others.
The main calls are few. Dial opens a Conn through a server to a target such as example.com:443; Read, Write and Close then work like on any socket. Listen starts a server and Serve or ServeRelay answers clients. Config holds the server address, the cipher method, the password and the optional obfs disguise. Everything else (Buf, Encoder, Decoder, the BLAKE3 and key functions) is the lower level the protocol is built from, public so you can test it or build your own framing.
Quick start
This program starts a server on a free port, connects a client through it to example.com:443 and sends one message. The handler never connects to example.com; it only reads the target the client asked for with TargetOf and echoes the bytes back, so the whole run stays on the loopback interface. Stop wakes the accept loop so that Serve returns, and Destroy frees the server.
import mem
import net.shadowsocks
const PASSWORD := "correct horse"
mut srv := shadowsocks.Server {}
mut seen := ""
func answer(c: shadowsocks.Conn):
t := shadowsocks.TargetOf(c)
seen = t.host + ":" + t.port
buf := mem.Allocate(4096 as u64)
n := shadowsocks.Read(c, buf, 4096)
shadowsocks.Write(c, buf, n)
mem.Free(buf)
shadowsocks.Close(c)
end
func serve():
shadowsocks.Serve(srv, (c: shadowsocks.Conn): answer(c) end)
end
func main:
srv = shadowsocks.Listen(0, "chacha20-ietf-poly1305", PASSWORD)
println shadowsocks.ServerOk(srv)
t := spawn(serve)
cfg := shadowsocks.Config { host = "127.0.0.1", port = shadowsocks.ServerPort(srv), method = "chacha20-ietf-poly1305", password = PASSWORD }
c := shadowsocks.Dial(cfg, "example.com", 443)
println shadowsocks.Ok(c)
shadowsocks.WriteStr(c, "hello through shadowsocks")
println shadowsocks.ReadStr(c, 4096)
shadowsocks.Close(c)
shadowsocks.Stop(srv)
join(t)
shadowsocks.Destroy(srv)
println "server saw", seen
endA real server uses ServeRelay, which does the forwarding for you: for each client it connects to the requested target and copies bytes in both directions until either side closes. The next example runs a relay in front of a small TCP echo server and sends a message through it.
import mem
import net.tcp
import net.shadowsocks
mut target := tcp.Server {}
mut srv := shadowsocks.Server {}
func echo():
c := tcp.Accept(target)
buf := mem.Allocate(4096 as u64)
n := tcp.ReadBytes(c, buf, 4096)
tcp.WriteBytes(c, buf, n)
mem.Free(buf)
tcp.Close(c)
end
func serve():
shadowsocks.ServeRelay(srv)
end
func main:
target = tcp.Bind(0)
te := spawn(echo)
srv = shadowsocks.Listen(0, "aes-256-gcm", "relay password")
ts := spawn(serve)
cfg := shadowsocks.Config { host = "127.0.0.1", port = shadowsocks.ServerPort(srv), method = "aes-256-gcm", password = "relay password" }
c := shadowsocks.Dial(cfg, "127.0.0.1", tcp.Port(target))
shadowsocks.WriteStr(c, "through the relay")
println shadowsocks.ReadStr(c, 4096)
shadowsocks.Close(c)
join(te)
shadowsocks.Stop(srv)
join(ts)
shadowsocks.Destroy(srv)
tcp.CloseServer(target)
endThe client side is just Dial with a Config. Dial returns at once without waiting for the server: the target address travels in the first encrypted packet together with your first Write. So a wrong password is not reported by Dial; the server drops the connection and your first Read returns 0 or a negative number. Check Ok for a connection that could not even start (an unknown method, or the server not reachable).
Ciphers
The method is a name from this table. LookupMethod turns a name into a Method and returns one with ok false for a name it does not know. The name chacha20-poly1305 is accepted too, as an alias of chacha20-ietf-poly1305. Old stream ciphers such as rc4-md5 and aes-256-cfb are not supported and never will be: they are broken.
| Method | Key | Salt | Password form | Notes |
|---|---|---|---|---|
aes-128-gcm | 16 bytes | 16 bytes | any text | Classic AEAD. Slow here, see below. |
aes-256-gcm | 32 bytes | 32 bytes | any text | Classic AEAD. The default of Config. Slow here, see below. |
chacha20-ietf-poly1305 | 32 bytes | 32 bytes | any text | Classic AEAD. The one to prefer. |
2022-blake3-aes-128-gcm | 16 bytes | 16 bytes | base64 of exactly 16 bytes | Shadowsocks 2022, TCP and UDP. |
2022-blake3-aes-256-gcm | 32 bytes | 32 bytes | base64 of exactly 32 bytes | Shadowsocks 2022, TCP and UDP. |
2022-blake3-chacha20-poly1305 | 32 bytes | 32 bytes | base64 of exactly 32 bytes | Shadowsocks 2022, TCP and UDP. |
import net.shadowsocks
func show(name: str):
m := shadowsocks.LookupMethod(name)
println m.name, m.ok, m.key_len, m.salt_len, m.v2022
end
func main:
show("aes-128-gcm")
show("aes-256-gcm")
show("chacha20-ietf-poly1305")
show("2022-blake3-aes-128-gcm")
show("2022-blake3-aes-256-gcm")
show("2022-blake3-chacha20-poly1305")
show("rc4-md5")
endFor the first three, the password is any text and the key is derived from it the classic way (BytesToKey, MD5 based), so use a long random password. For the 2022 ciphers the password is the key: it must be the base64 of exactly the key length, for example the output of openssl rand -base64 32. A password of the wrong length makes MakeKey return a Buf with bad set, and Listen or Dial then fail. The 2022 ciphers add replay protection with a timestamp (a request more than 30 seconds off is refused) and a salt cache, and the connection header is authenticated.
What is not there
- Shadowsocks 2022 UDP has its own functions.
PackUdpandUnpackUdpare for the classic methods and refuse the three 2022 methods; usePackUdp2022andUnpackUdp2022, orUdpListenandUdpDial, which pick the right one. - Several users on one port (the 2022 extensible identity headers) work only with the two AES 2022 methods. The ChaCha 2022 method takes a single key.
- No real server was used to test the interoperability of
simple-obfsand of the 2022 TCP stream. They are tested against this package itself and against separate Python reimplementations written from the specifications, not againstssserver,shadowsocks-rustor the realobfs-server. The classic AEAD ciphers are checked against published test vectors. Treat the 2022 methods and the obfuscation as young and test against your own server before relying on them. - AES is done in software (bitsliced, to avoid timing leaks) and is slow: in the tests here about four to five times slower than ChaCha20. Prefer
chacha20-ietf-poly1305. - The relay has no half close: when one side closes, the whole connection is shut down. A protocol that depends on a client finishing its request with a close and still reading the answer will break.
- No anti probing behaviour: a server that receives a bad first packet closes the connection at once instead of draining it as a real web server would, so a prober can tell it is not one.
- UDP works over IPv4 and IPv6 (client and relay sockets accept both;
UdpListenOn("::", port)serves both), and the UDP relay has no replay filter (TCP has one, a salt cache of 100000 entries). - Only the HTTP and TLS shapes of
simple-obfsexist, not the pluginv2ray-pluginor other plugins.
ss:// links and plugin options
Shadowsocks servers are shared as ss:// links. ParseUri reads the SIP002 form ss://base64(method:password)@host:port/?plugin=...#tag, the form with the user info in plain percent encoded text (used by the 2022 ciphers), and the old form where the whole method:password@host:port is base64. It returns a Uri whose ok is true only when the link is well formed and the method is known. FormatUri writes the link back (a 2022 link is written with the method and password in text, the others as base64). ToConfig turns a Uri into a Config that Dial accepts, including the obfuscation options of the plugin.
The plugin text is a plugin name followed by key=value options separated by semicolons, with a backslash to escape a semicolon or equals sign. ParsePlugin splits it into a Plugin and PluginGet and PluginHas read the options. Free it with PluginFree. ToConfig understands the plugin names obfs-local and simple-obfs with the options obfs (http or tls) and obfs-host; any other plugin is ignored, so check u.plugin yourself if you need to refuse it.
import net.shadowsocks
func main:
u := shadowsocks.ParseUri("ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpzZWNyZXQ@203.0.113.5:8388/?plugin=obfs-local%3Bobfs%3Dtls%3Bobfs-host%3Dcdn.example.com#my%20server")
println u.ok, u.method, u.password, u.host, u.port, u.tag
println u.plugin
p := shadowsocks.ParsePlugin(u.plugin)
println p.name, shadowsocks.PluginGet(p, "obfs", ""), shadowsocks.PluginHas(p, "obfs-host"), shadowsocks.PluginHas(p, "missing")
shadowsocks.PluginFree(p)
cfg := shadowsocks.ToConfig(u)
println cfg.host, cfg.port, cfg.method, cfg.obfs, cfg.obfs_host
println shadowsocks.FormatUri(u)
bad := shadowsocks.ParseUri("ss://not-a-link")
println bad.ok
endsimple-obfs
simple-obfs hides the first bytes of a Shadowsocks connection inside something that looks like ordinary web traffic, which helps against filters that classify connections by their first packet. Set obfs in the client Config to "http" or "tls", set obfs_host to the host name to show (the Host header or the TLS server name, default www.bing.com), and start the server with ListenObfs and the same mode. Both sides must agree: a server without obfs does not understand an obfs client and the other way round.
http: the first client packet is sent as an HTTP request with a WebSocket upgrade header, the server answers with a101 Switching Protocols, and after that the data flows as is.tls: the first client packet is a fake TLS ClientHello carrying the data in a session ticket extension and the server name you chose, the server answers with a fake ServerHello, and the rest is wrapped in TLS application data records.
This is camouflage, not encryption: the Shadowsocks cipher underneath still protects the data. The fake handshake is simple and a determined censor can fingerprint it; it also does not make the connection look like a real site to an active prober. ObfsMode turns the names into the constants OBFS_NONE, OBFS_HTTP and OBFS_TLS, and ObfsInit, ObfsWrap, ObfsUnwrap and ObfsFree expose the framing for your own use.
import mem
import net.shadowsocks
mut srv := shadowsocks.Server {}
func answer(c: shadowsocks.Conn):
buf := mem.Allocate(4096 as u64)
n := shadowsocks.Read(c, buf, 4096)
shadowsocks.Write(c, buf, n)
mem.Free(buf)
shadowsocks.Close(c)
end
func serve():
shadowsocks.Serve(srv, (c: shadowsocks.Conn): answer(c) end)
end
func main:
srv = shadowsocks.ListenObfs(0, "chacha20-ietf-poly1305", "pw", "http")
t := spawn(serve)
cfg := shadowsocks.Config { host = "127.0.0.1", port = shadowsocks.ServerPort(srv), method = "chacha20-ietf-poly1305", password = "pw", obfs = "http", obfs_host = "cdn.example.com" }
c := shadowsocks.Dial(cfg, "example.com", 80)
shadowsocks.WriteStr(c, "inside an http disguise")
println shadowsocks.ReadStr(c, 4096)
shadowsocks.Close(c)
shadowsocks.Stop(srv)
join(t)
shadowsocks.Destroy(srv)
endThe UDP relay
Shadowsocks carries UDP too, for DNS and for games, one encrypted datagram at a time: the packet holds a random salt, the target address and the payload, sealed as a whole. UdpListen starts a relay on a UDP port; UdpServe loops until UdpStop is called, and UdpStep handles one packet and returns 1 when it did, so you can drive it yourself. The relay keeps one outgoing socket per client and forgets a client after 60 seconds of silence. On the client, UdpDial makes a UdpClient, UdpSend sends a payload to a target through the relay and UdpRecv returns the next reply and who it came from; UdpSetTimeout bounds the wait. UDP also works with the three 2022 ciphers (SIP022): the client picks a random session ID, numbers its packets, and the first 16 bytes of each datagram are that header encrypted on its own (AES-ECB for the AES methods, an XChaCha20-Poly1305 nonce for the ChaCha one). Packets carry a timestamp that must be within 30 seconds, and the relay drops any packet ID it has already seen in a 64 packet window. The classic methods get a salt filter on the relay, so a recorded datagram is answered once. UDP targets must be IPv4 or domain names.
import mem
import str
import net.udp
import net.shadowsocks
mut echo_sock := udp.Socket {}
mut relay := shadowsocks.UdpServer {}
func echo():
buf := mem.Allocate(2000 as u64)
mut host := ""
mut port := 0
n := udp.ReceiveBytes(echo_sock, buf, 2000, host, port)
udp.SendBytes(echo_sock, host, port, buf, n)
mem.Free(buf)
end
func serve():
shadowsocks.UdpServe(relay)
end
func main:
echo_sock = udp.Bind(0)
relay = shadowsocks.UdpListen(0, "chacha20-ietf-poly1305", "udp password")
te := spawn(echo)
ts := spawn(serve)
cfg := shadowsocks.Config { host = "127.0.0.1", port = shadowsocks.UdpServerPort(relay), method = "chacha20-ietf-poly1305", password = "udp password" }
c := shadowsocks.UdpDial(cfg)
shadowsocks.UdpSetTimeout(c, 3000)
shadowsocks.UdpSend(c, "127.0.0.1", udp.Port(echo_sock), "ping" as void*, 4)
buf := mem.AllocateZeroed(2000 as u64)
mut from := shadowsocks.Target {}
n := shadowsocks.UdpRecv(c, buf, 1999, from)
println str.Substr(buf as str, 0, n), from.host, from.port == udp.Port(echo_sock)
mem.Free(buf)
join(te)
shadowsocks.UdpStop(relay)
join(ts)
shadowsocks.UdpClose(c)
shadowsocks.UdpDestroy(relay)
udp.Close(echo_sock)
endSeveral users on one 2022 server
An AES 2022 server can serve many users, each with their own key. Give the server its own key as the password, then call AddUser (UdpAddUser for UDP) once per user key. A client connects with the password serverkey:userkey: it sends an identity header that names its user without revealing the key, and the server picks that user's key for the session. ChainOf, EihSeal and EihOpen are the pieces underneath. The ChaCha 2022 method has no identity headers, so it takes a single key. ListenOn and UdpListenOn bind to one address instead of every interface, for example 127.0.0.1. When a handshake fails the server keeps the socket open for a random 1.5 to 4.5 seconds and reads what is sent before closing, so a probe cannot tell a refusal from a slow server. Relay passes a half close in both directions: when one side stops sending, the other side is told and can still answer.
Running over a tunnel.Stream
The client normally dials its server with TCP, but DialOver takes any tunnel.Stream instead: a WebSocket, a TLS connection, the output of another proxy hop, or a stream you made. The server side has AcceptOver. In both cases the Shadowsocks layer treats the stream as the wire, so you can put Shadowsocks inside a transport from net.transport, or behind a proxy chain from net.tunnel. AsStream does the opposite: it wraps a Conn as a tunnel.Stream, so a Shadowsocks connection can be handed to anything that takes a stream, such as a router outbound or another protocol layer.
import mem
import sync
import net.tcp
import net.tunnel
import net.shadowsocks
mut port := 0
mut ready := sync.NewWaitGroup()
func server():
carrier := tcp.Bind(0)
port = tcp.Port(carrier)
srv := shadowsocks.Listen(0, "aes-128-gcm", "inner")
sync.Done(ready)
raw := tcp.Accept(carrier)
c := shadowsocks.AcceptOver(srv, tunnel.TcpStream { conn = raw })
if shadowsocks.Ok(c):
buf := mem.Allocate(4096 as u64)
n := shadowsocks.Read(c, buf, 4096)
shadowsocks.Write(c, buf, n)
mem.Free(buf)
shadowsocks.Close(c)
end
shadowsocks.Destroy(srv)
tcp.CloseServer(carrier)
end
func main:
sync.Add(ready, 1 as i64)
t := spawn(server)
sync.Wait(ready)
link := tunnel.TcpStream { conn = tcp.Dial("127.0.0.1", port) }
cfg := shadowsocks.Config { method = "aes-128-gcm", password = "inner" }
c := shadowsocks.DialOver(link, cfg, "example.com", 443)
shadowsocks.WriteStr(c, "any stream will do")
println shadowsocks.ReadStr(c, 4096)
s := shadowsocks.AsStream(c)
println s.Ok()
shadowsocks.Close(c)
join(t)
endThe framing layer
The bottom of the package is public. An Encoder seals a byte stream into length prefixed AEAD chunks (EmitSalt first, then EncodeStream); a Decoder takes in the bytes as they arrive with Feed and returns the plaintext with NextChunk, or all at once with DecodeAll. EncodeTarget and ParseTarget write and read the address header (ATYP_IPV4, ATYP_DOMAIN, ATYP_IPV6), and PackUdp and UnpackUdp do the same for one datagram. Buf is the growable byte buffer all of them use: you own it, free it with BufFree and wipe key material with BufWipe first. This is how the protocol is tested against known vectors, and how you can write a custom front end.
The example under Buf below runs a whole round trip: it seals a stream and opens it again, writes and reads an address header, and packs and unpacks one UDP datagram.
Practical notes for Iran
These are tips for people building tools for networks with heavy filtering. They come from how the protocol works, not from tests against any particular ISP, and what works changes from month to month.
- Pick ChaCha20. Use
chacha20-ietf-poly1305unless the other end only offers something else. It is the fastest cipher here by a wide margin, and the security is the same as AES-GCM. Choose a 2022 method only when your server also speaks it and you can test it end to end; it has the best design (replay protection, authenticated header) but the least interoperability testing. - Use a long random password for the classic methods, at least 20 characters from a password generator. Short passwords can be guessed offline from a captured connection.
- Do not rely on plain Shadowsocks alone. Pure random looking traffic on an unusual port is itself a signal, and the lack of an anti probing reply makes it easier to confirm. Put it on port 443, behind a transport that looks like a web site (a WebSocket over TLS, with
DialOverandAcceptOver), or usesimple-obfswithtlsand a host name that is allowed and common on that network. - Use the tunnel router. net.tunnel has routing rules with an Iran preset: domestic sites go direct and the rest goes through the proxy, so the local network is not slowed down and domestic sites do not see a foreign address. Make a Shadowsocks outbound from a
Config, wrap it as a stream withAsStreamwhere a dialer wants a stream, and let the router choose. For a local SOCKS or HTTP entry point see net.socks and net.httpproxy. - Resolve names remotely. Pass the host name to
Dialand let the server resolve it; do not resolve it locally first, or the lookup leaks. When a local lookup is unavoidable, net.doh sends it over HTTPS. - Keep sharing safe. A link carries the password in clear text apart from base64: send it over a private channel, and do not publish servers you want to keep alive.
Types
SaltFilter
struct SaltFilter| Name | Type | Description |
|---|---|---|
cur | HashMap | The current generation of seen salts. |
old | HashMap | The previous generation, dropped when the current one fills. |
cap | int = 0 | How many salts one generation holds. |
lock | sync.Mutex | The mutex that guards both generations. |
import net.shadowsocks
func main:
mut f := shadowsocks.NewSaltFilter(1000)
a := "salt number one, 32 bytes long.."
b := "salt number two, 32 bytes long.."
println f.cap, shadowsocks.FilterSeen(f, a as void*, 32)
println shadowsocks.FilterCheck(f, a as void*, 32), shadowsocks.FilterSeen(f, a as void*, 32)
println shadowsocks.FilterCheck(f, a as void*, 32), shadowsocks.FilterCheck(f, b as void*, 32)
println f.cur.size(), f.old.size()
shadowsocks.FilterFree(f)
endKeyChain
struct KeyChainserverkey:userkey, split and decoded into one buffer of equal sized keys.| Name | Type | Description |
|---|---|---|
keys | Buf | All keys one after another, key_len bytes each. |
count | int = 0 | How many keys there are. |
key_len | int = 0 | The length of one key in bytes. |
ok | bool = false | False when the method has no identity headers or a part of the password is not a valid key. |
import mem
import net.shadowsocks
func main:
println shadowsocks.EIH_LEN, shadowsocks.IDENTITY_CONTEXT
m := shadowsocks.LookupMethod("2022-blake3-aes-128-gcm")
mut chain := shadowsocks.ChainOf(m, "AAECAwQFBgcICQoLDA0ODw==:EBESExQVFhcYGRobHB0eHw==")
println chain.ok, chain.count, chain.key_len, chain.keys.len
println shadowsocks.BufToHex(shadowsocks.Buf { p = shadowsocks.ChainKey(chain, 1), len = 16, cap = 16 })
bad := shadowsocks.ChainOf(m, "not a key")
println bad.ok, bad.count
ipsk := shadowsocks.ChainKey(chain, 0)
upsk := shadowsocks.ChainKey(chain, 1)
h := mem.AllocateZeroed(16 as u64)
shadowsocks.IdentityHash(upsk, m.key_len, h)
println shadowsocks.HexOf(h, 16)
salt := mem.AllocateZeroed(16 as u64)
ik := mem.AllocateZeroed(16 as u64)
shadowsocks.IdentityKey(ipsk, m.key_len, salt, m.salt_len, ik)
println shadowsocks.HexOf(ik, 16)
block := mem.AllocateZeroed(16 as u64)
println shadowsocks.EihSeal(ipsk, m.key_len, salt, m.salt_len, upsk, block)
println shadowsocks.HexOf(block, 16)
opened := mem.AllocateZeroed(16 as u64)
println shadowsocks.EihOpen(ipsk, m.key_len, salt, m.salt_len, block, opened)
println shadowsocks.HexOf(opened, 16) == shadowsocks.HexOf(h, 16)
shadowsocks.ChainFree(chain)
println chain.ok, chain.count
mem.Free(h)
mem.Free(salt)
mem.Free(ik)
mem.Free(block)
mem.Free(opened)
endConfig
struct ConfigDial, DialOver or UdpDial.| Name | Type | Description |
|---|---|---|
host | str = "" | The server host name or address. Not used by DialOver. |
port | int = 0 | The server port. Not used by DialOver. |
method | str = "aes-256-gcm" | The cipher method name. The default is aes-256-gcm; prefer chacha20-ietf-poly1305. |
password | str = "" | The password; for a 2022 method the base64 of the key. |
obfs | str = "" | http, tls or empty for no simple-obfs. |
obfs_host | str = "www.bing.com" | The host name the disguise shows. The default is www.bing.com. |
ConnState
struct ConnStateConn.| Name | Type | Description |
|---|---|---|
sock | tcp.Conn | The TCP socket, when the connection does not run over a stream. |
method | Method | The cipher method. |
key | Buf | The master key. |
enc | Encoder | The encoder for the data that is sent. |
dec | Decoder | The decoder for the data that is received. |
plain | Buf | Decrypted bytes that were not read yet. |
plain_pos | int = 0 | How much of plain was read. |
out | Buf | A scratch buffer for the bytes about to be sent. |
rd | void* | The receive buffer. |
pending | Buf | The address header held back until the first write. |
target | Target | The destination of the connection: asked for by the client, or read by the server. |
obfs | Obfs | The disguise layer. |
up | dyn tunnel.Stream | The underlying stream, when use_up is true. |
use_up | bool = false | True when the connection runs over a stream and not a socket. |
obuf | Buf | A scratch buffer for disguised output. |
oin | Buf | Raw bytes received before the disguise was removed. |
rd2 | void* | The raw receive buffer. |
v2022 | bool = false | True for a 2022 method. |
is_server | bool = false | True on the server side. |
wphase | int = 0 | The write side handshake step. |
rphase | int = 0 | The read side handshake step. |
first_len | int = 0 | The length announced by the first 2022 header. |
target_ok | bool = false | True once the target was received. |
vtmp | Buf | A scratch buffer for the 2022 header. |
live | bool = false | True while the connection is open. |
eih | Buf | The identity keys of a serverkey:userkey password, all keys except the last one, one after another. |
eih_n | int = 0 | How many identity keys eih holds. |
eih_done | bool = false | On a server, true once the identity header of the client was opened and its user key is in use. |
user | str = "" | On a server, the name of the user the identity header named; empty otherwise. |
Conn
struct ConnConnState; with a null pointer the connection is not usable.| Name | Type | Description |
|---|---|---|
st | ConnState* | The state of the connection, or null when it could not be made. |
UdpSession
struct UdpSession| Name | Type | Description |
|---|---|---|
sid | i64 = 0 | The random 64 bit session ID of this side. |
pid | i64 = 0 | The number of the next packet this side sends. |
peer | i64 = 0 | The session ID of the other side, learned from its first valid packet; 0 until then. |
top | i64 = 0 | The highest packet number accepted so far. |
bits | u64 = 0 | The replay window: bit k is set when the packet numbered top minus k was seen, over the last 64 packets. |
any | bool = false | True once a packet was accepted, so that top and bits mean something. |
last | i64 = 0 | The packet number of the datagram opened last. |
import net.shadowsocks
func main:
m := shadowsocks.LookupMethod("2022-blake3-aes-128-gcm")
println m.ok, m.v2022, m.key_len
mut key := shadowsocks.MakeKey(m, "AAECAwQFBgcICQoLDA0ODw==")
println key.len
mut client := shadowsocks.NewUdpSession()
mut server := shadowsocks.UdpSession {}
println client.sid != 0, server.any
mut packet := shadowsocks.NewBuf(256)
println shadowsocks.PackUdp2022(m, key.p, key.p, shadowsocks.Buf {}, 0, client, true, "10.0.0.7", 53, "query" as void*, 5, packet), packet.len > 60
mut sid := 0 as i64
println shadowsocks.Udp2022Peek(m, key.p, packet.p, packet.len, sid), sid == client.sid
mut target := shadowsocks.Target {}
mut body := shadowsocks.NewBuf(64)
println shadowsocks.UnpackUdp2022(m, key.p, key.p, 0, false, packet.p, packet.len, server, target, body), target.host, target.port, shadowsocks.BufToStr(body)
println server.peer == client.sid, server.last
println shadowsocks.SessionAccept(server, 5), shadowsocks.SessionAccept(server, 5), shadowsocks.SessionAccept(server, 3), server.top, server.any
mut chain := shadowsocks.ChainOf(m, "AAECAwQFBgcICQoLDA0ODw==:EBESExQVFhcYGRobHB0eHw==")
ipsk := shadowsocks.ChainKey(chain, 0)
upsk := shadowsocks.ChainKey(chain, 1)
mut multi := shadowsocks.UdpSession { sid = 7 as i64 }
mut wire := shadowsocks.NewBuf(256)
println shadowsocks.PackUdp2022(m, upsk, ipsk, chain.keys, 1, multi, true, "10.0.0.8", 443, "hello" as void*, 5, wire)
mut who := shadowsocks.NewBuf(16)
who.len = 16
println shadowsocks.Udp2022Identity(m, ipsk, wire.p, wire.len, who.p)
mut expected := shadowsocks.NewBuf(16)
expected.len = 16
shadowsocks.IdentityHash(upsk, m.key_len, expected.p)
println shadowsocks.BufToHex(who) == shadowsocks.BufToHex(expected)
mut seen := shadowsocks.UdpSession {}
mut inner := shadowsocks.NewBuf(64)
println shadowsocks.UnpackUdp2022(m, upsk, ipsk, shadowsocks.EIH_LEN, false, wire.p, wire.len, seen, target, inner), shadowsocks.BufToStr(inner)
shadowsocks.BufFree(wire)
shadowsocks.BufFree(who)
shadowsocks.BufFree(expected)
shadowsocks.BufFree(inner)
shadowsocks.ChainFree(chain)
shadowsocks.BufFree(packet)
shadowsocks.BufFree(body)
shadowsocks.BufWipe(key)
shadowsocks.BufFree(key)
endPlugin
struct Plugin| Name | Type | Description |
|---|---|---|
name | str = "" | The plugin name, such as obfs-local. |
keys | Vector | The option names. |
values | Vector | The option values, in the same order as keys; empty for an option without a value. |
Uri
struct Uriss:// link.| Name | Type | Description |
|---|---|---|
method | str = "" | The cipher method name. |
password | str = "" | The password, or the base64 key for a 2022 method. |
host | str = "" | The server host name or address (IPv6 without brackets). |
port | int = 0 | The server port. |
plugin | str = "" | The raw plugin text, empty when the link has none. |
tag | str = "" | The name after the #, decoded. |
ok | bool = false | True when the link parsed and the method is one this package supports. |
Obfs
struct Obfssimple-obfs layer of one connection.| Name | Type | Description |
|---|---|---|
mode | int = 0 | One of the OBFS_ constants. |
server | bool = false | True on the server side. |
host | str = "" | The host name shown in the fake request or hello. |
sent_first | bool = false | True once the first packet was sent. |
got_first | bool = false | True once the first packet was received and checked. |
seen_hello | bool = false | True once the fake TLS hello record was read, so that a later handshake record is taken as the start of the data. |
rx | Buf | Bytes received that do not form a whole record yet. |
frame_left | int = 0 | How many bytes of the current TLS record remain. |
ws_key | str = "" | The WebSocket key of the fake HTTP upgrade. |
Target
struct Target| Name | Type | Description |
|---|---|---|
host | str = "" | The host name or the IP address in text. |
port | int = 0 | The port. |
import net.shadowsocks
func main:
m := shadowsocks.LookupMethod("chacha20-ietf-poly1305")
mut key := shadowsocks.MakeKey(m, "codec password")
mut enc := shadowsocks.NewEncoderRandom(m, key.p)
mut wire := shadowsocks.NewBuf(256)
shadowsocks.EmitSalt(enc, wire)
shadowsocks.EncodeStream(enc, "hello codec" as void*, 11, wire)
println "wire bytes", wire.len, "salt", m.salt_len, "overhead", wire.len - 11 - m.salt_len
mut dec := shadowsocks.NewDecoder(m, key.p)
mut plain := shadowsocks.NewBuf(64)
println shadowsocks.DecodeAll(dec, wire.p, wire.len, plain), shadowsocks.BufToStr(plain)
mut head := shadowsocks.NewBuf(64)
println shadowsocks.EncodeTarget("example.com", 8443, head), head.len, shadowsocks.BufToHex(head)
mut t := shadowsocks.Target {}
used := shadowsocks.ParseTarget(head.p, head.len, t)
println used, t.host, t.port
mut packet := shadowsocks.NewBuf(128)
println shadowsocks.PackUdp(m, key.p, "10.0.0.7", 53, "query" as void*, 5, packet), packet.len
mut got := shadowsocks.Target {}
mut body := shadowsocks.NewBuf(64)
println shadowsocks.UnpackUdp(m, key.p, packet.p, packet.len, got, body), got.host, got.port, shadowsocks.BufToStr(body)
shadowsocks.EncoderFree(enc)
shadowsocks.DecoderFree(dec)
shadowsocks.BufFree(wire)
shadowsocks.BufFree(plain)
shadowsocks.BufFree(head)
shadowsocks.BufFree(packet)
shadowsocks.BufFree(body)
shadowsocks.BufWipe(key)
shadowsocks.BufFree(key)
endBuf
struct BufBufFree.| Name | Type | Description |
|---|---|---|
p | void* | The start of the memory. |
len | int = 0 | How many bytes are in use. |
cap | int = 0 | How many bytes were allocated. |
bad | bool = false | True after an allocation failed or a parse error; the buffer must not be trusted then. |
The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
Encoder
struct Encoder| Name | Type | Description |
|---|---|---|
method | Method | The cipher method. |
ctx | void* | The AEAD context holding the session key. |
nonce | void* | The 12 byte counter used as the nonce, increased for every sealed piece. |
salt | Buf | The random salt of this direction. |
salt_sent | bool = false | True once the salt was emitted. |
max_chunk | int = 16383 | The largest payload of one chunk: 16383 bytes, or 65535 for the 2022 methods. |
The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
Decoder
struct Decoder| Name | Type | Description |
|---|---|---|
method | Method | The cipher method. |
master | void* | The master key the session key is derived from. |
ctx | void* | The AEAD context, made when the salt has arrived. |
nonce | void* | The 12 byte counter used as the nonce. |
salt | Buf | The salt that was read from the start of the stream. |
have_salt | bool = false | True once the salt was read. |
inb | Buf | The bytes received and not yet used. |
off | int = 0 | The read offset in inb. |
need | int = -1 | How many bytes the next piece needs, or -1 when it is not known yet. |
tmp | Buf | A scratch buffer for the length field. |
bad | bool = false | True after an authentication failure; the stream cannot be used any more. |
max_chunk | int = 16383 | The largest accepted chunk size. |
The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
ServerState
struct ServerStateServer and only pass it around.| Name | Type | Description |
|---|---|---|
ls | tcp.Server | The listening TCP socket. |
host | str = "" | The address the server listens on; empty means every IPv4 interface. Stop uses it to wake the accept loop. |
method | Method | The cipher the server uses. |
key | Buf | The master key, from the password. |
filter | SaltFilter | The salt cache that rejects a replayed connection. |
workers | pool.Pool | The pool of threads that run client handlers. |
stop | i64* | A flag that tells Serve to stop; Stop sets it. |
jobs | chan.Chan | The queue of accepted connections waiting for a worker. |
obfs_mode | str = "" | The obfuscation mode, http, tls or empty for none. |
users | HashMap | The keys of the users added with AddUser, by the hex of the hash that names them in an identity header. |
user_names | HashMap | The names of those users, by the same hex. |
user_n | int = 0 | How many users were added. |
import net.shadowsocks
func main:
server := shadowsocks.ListenOn("::1", 0, "aes-256-gcm", "secret", "")
println shadowsocks.ServerOk(server), server.st.host
shadowsocks.Stop(server)
shadowsocks.Destroy(server)
endServer
struct ServerServerState; the zero value (st null) means the server could not be started.| Name | Type | Description |
|---|---|---|
st | ServerState* | The state of the server, or null when Listen failed. |
Method
struct Method| Name | Type | Description |
|---|---|---|
name | str = "" | The method name, with aliases resolved. |
kind | int = 0 | The AEAD kind, as net.tls names it. |
key_len | int = 0 | The key length in bytes, 16 or 32. |
salt_len | int = 0 | The salt length in bytes, 16 or 32. |
v2022 | bool = false | True for the 2022 methods. |
ok | bool = false | True when the name was a known method. |
UdpClient
struct UdpClient| Name | Type | Description |
|---|---|---|
sock | udp.Socket | The local UDP socket. |
host | str = "" | The relay host. |
port | int = 0 | The relay port. |
method | Method | The cipher method. |
key | Buf | The master key. |
rbuf | void* | The receive buffer. |
ok | bool = false | True when the client could be set up. |
sess | UdpSession* | The 2022 session of this client; null for a classic method. |
eih | Buf | The identity keys of a serverkey:userkey password, all keys except the last one, one after another. |
eih_n | int = 0 | How many identity keys eih holds. |
UdpServer
struct UdpServer| Name | Type | Description |
|---|---|---|
sock | udp.Socket | The UDP socket clients send to. |
method | Method | The cipher method. |
key | Buf | The master key. |
rbuf | void* | The receive buffer. |
assoc | HashMap | The outgoing socket of each client, keyed by the client address. |
seen | HashMap | When each client was last heard from, in milliseconds. |
clients | HashMap | The address each client sends from, for the replies. |
stopc | i64* | The flag that stops UdpServe. |
ok | bool = false | True when the relay started. |
sess | HashMap | The 2022 session of each client, keyed by the client address. |
skeys | HashMap | The user key each client signed in with, keyed by the client address. |
users | HashMap | The keys of the users added with UdpAddUser, by the hex of the hash that names them in an identity header. |
user_n | int = 0 | How many users were added. |
filter | SaltFilter | The salt cache that rejects a replayed datagram. |
Constants and variables
IDENTITY_CONTEXT
const IDENTITY_CONTEXT: str = "shadowsocks 2022 identity subkey"The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
EIH_LEN
const EIH_LEN: i32 = 16The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
Obfuscation modes OBFS_…
simple-obfs uses.| Name | Value |
|---|---|
OBFS_NONE | 0 |
OBFS_HTTP | 1 |
OBFS_TLS | 2 |
Address types ATYP_…
| Name | Value |
|---|---|
ATYP_IPV4 | 1 |
ATYP_DOMAIN | 3 |
ATYP_IPV6 | 4 |
The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
MAX_CHUNK
const MAX_CHUNK: i32 = 16383TAG_LEN
const TAG_LEN: i32 = 16Functions
NewSaltFilter
func NewSaltFilter(cap: int): SaltFiltercap salts each (at least 16).The example for this item is shown with the ones above: net-shadowsocks-SaltFilter.salam
FilterSeen
func FilterSeen(f &: SaltFilter, salt: void*, n: int): boolFilterCheck only once the chunk is good, so a forged packet cannot fill the filter.The example for this item is shown with the ones above: net-shadowsocks-SaltFilter.salam
FilterCheck
func FilterCheck(f &: SaltFilter, salt: void*, n: int): boolThe example for this item is shown with the ones above: net-shadowsocks-SaltFilter.salam
FilterFree
func FilterFree(f &: SaltFilter)The example for this item is shown with the ones above: net-shadowsocks-SaltFilter.salam
MakeKey
func MakeKey(m: Method, password: str): BufBuf has bad set when a 2022 password has the wrong length.The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
ChainOf
func ChainOf(m: Method, password: str): KeyChainserverkey:userkey (up to 8 base64 keys) into a KeyChain. ok is false for a classic method, more than 8 parts or a part that is not a key of the right length.The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
ChainKey
func ChainKey(c: KeyChain, i: int): void*i of the chain, counting from 0. The last key is the one of the user.The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
ChainFree
func ChainFree(c &: KeyChain)The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
IdentityHash
func IdentityHash(psk: void*, klen: int, out16: void*)The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
IdentityKey
func IdentityKey(ipsk: void*, klen: int, salt: void*, slen: int, out: void*)IDENTITY_CONTEXT over the identity key followed by the salt. Writes klen bytes.The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
EihSeal
func EihSeal(ipsk: void*, klen: int, salt: void*, slen: int, next_psk: void*, out16: void*): boolIdentityKey. Writes 16 bytes. Returns false for a bad key length.The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
EihOpen
func EihOpen(ipsk: void*, klen: int, salt: void*, slen: int, block16: void*, out_hash16: void*): boolEihSeal: decrypts a block and writes the 16 byte hash of the next key. Returns false for a bad key length.The example for this item is shown with the ones above: net-shadowsocks-Identity.salam
Ok
func Ok(c: Conn): boolDial it only means the setup and the TCP connect worked, not that the server accepted the password.TargetOf
func TargetOf(c: Conn): TargetWrite
func Write(c: Conn, data: void*, n: int): intWriteStr
func WriteStr(c: Conn, s: str): intWrite with its length.Read
func Read(c: Conn, buf: void*, max: int): intmax bytes to the buffer. Returns the length, 0 when the other side closed, or a negative number on error or a failed authentication.ReadStr
func ReadStr(c: Conn, max: int): strmax bytes as a string, empty at the end or on error. Binary data can contain zero bytes that cut a string short, so use Read for that.Close
func Close(c: Conn)Conn afterwards is an error.Shutdown
func Shutdown(c: Conn)Dial
func Dial(cfg: Config, target_host: str, target_port: int): ConnDialOver
func DialOver(s: dyn tunnel.Stream, cfg: Config, target_host: str, target_port: int): ConnDial but runs over a stream you give it instead of opening a TCP connection. host and port of the config are ignored.AsStream
func AsStream(c: Conn): dyn tunnel.Streamtunnel.Stream, so any code that takes a stream can use it.NewUdpSession
func NewUdpSession(): UdpSessionThe example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
SessionAccept
func SessionAccept(s &: UdpSession, pid: i64): boolThe example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
PackUdp2022
func PackUdp2022(m: Method, ukey: void*, hdr_key: void*, eih: Buf, eih_n: int, s &: UdpSession, client: bool, host: str, port: int, data: void*, n: int, out &: Buf): boolukey is the user key and hdr_key the key of the header block; for one key both are that key, and with identity keys it is the first of them. client says which side is sending. Returns false for a classic method or a bad target.The example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
Udp2022Peek
func Udp2022Peek(m: Method, hdr_key: void*, p: void*, n: int, sid &: i64): boolThe example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
UnpackUdp2022
func UnpackUdp2022(m: Method, ukey: void*, hdr_key: void*, skip: int, client: bool, p: void*, n: int, s &: UdpSession, t &: Target, out &: Buf): boolskip is the number of identity header bytes between the header and the body. On the client side it also rejects replayed packets with the session. Returns false when anything is wrong.The example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
Udp2022Identity
func Udp2022Identity(m: Method, ipsk: void*, p: void*, n: int, out_hash16: void*): boolThe example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam
ParseUri
func ParseUri(text: str): Uriss:// link in the SIP002 form, the plain text user info form or the old whole base64 form. On bad input ok of the result is false.FormatUri
func FormatUri(u: Uri): strUri as an ss:// link, including the plugin and the tag when they are set.ParsePlugin
func ParsePlugin(text: str): Pluginobfs-local;obfs=tls;obfs-host=a.com into a name and options. Free the result with PluginFree.PluginGet
func PluginGet(p: Plugin, key: str, fallback: str): strPluginHas
func PluginHas(p: Plugin, key: str): boolPluginFree
func PluginFree(p: Plugin)Plugin.ToConfig
func ToConfig(u: Uri): ConfigConfig from a Uri, applying the simple-obfs options of its plugin.ObfsWrap
func ObfsWrap(o &: Obfs, p: void*, n: int, out &: Buf)ObfsUnwrap
func ObfsUnwrap(o &: Obfs, p: void*, n: int, out &: Buf): intObfsInit
func ObfsInit(mode: int, server: bool, host: str): ObfsObfsFree
func ObfsFree(o &: Obfs)Obfs.ObfsMode
func ObfsMode(name: str): inthttp or tls into the OBFS_ constant; anything else gives OBFS_NONE.EncodeTarget
func EncodeTarget(host: str, port: int, out &: Buf): boolThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
ParseTarget
func ParseTarget(p: void*, n: int, t &: Target): intTarget. Returns how many bytes the header took, or 0 when the bytes are short or invalid.The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
BufAt
func BufAt(b: Buf, off: int): void*PtrAt
func PtrAt(p: void*, off: int): void*NewBuf
func NewBuf(cap0: int): BufThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
BufFree
func BufFree(b &: Buf)The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
BufWipe
func BufWipe(b &: Buf)The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
BufReserve
func BufReserve(b &: Buf, extra: int)BufAppend
func BufAppend(b &: Buf, src: void*, n: int)BufAppendByte
func BufAppendByte(b &: Buf, v: int)BufAppendStr
func BufAppendStr(b &: Buf, s: str)BufConsume
func BufConsume(b &: Buf, n: int)BufToStr
func BufToStr(b: Buf): strThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
BufToHex
func BufToHex(b: Buf): strThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
HexOf
func HexOf(p: void*, n: int): strBufFromHex
func BufFromHex(hex: str): Bufbad is set when the text is not valid hex.NewEncoder
func NewEncoder(m: Method, master: void*, salt: void*): EncoderNewEncoderRandom
func NewEncoderRandom(m: Method, master: void*): EncoderThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
EncoderFree
func EncoderFree(e &: Encoder)The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
SealFrame
func SealFrame(e &: Encoder, pt: void*, n: int, out &: Buf)EmitSalt
func EmitSalt(e &: Encoder, out &: Buf)The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
EncodeStream
func EncodeStream(e &: Encoder, data: void*, n: int, out &: Buf)max_chunk. Emit the salt first.The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
NewDecoder
func NewDecoder(m: Method, master: void*): DecoderThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
DecoderFree
func DecoderFree(d &: Decoder)The example for this item is shown with the ones above: net-shadowsocks-Codec.salam
Feed
func Feed(d &: Decoder, data: void*, n: int)TakeSalt
func TakeSalt(d &: Decoder): boolTakeFrame
func TakeFrame(d &: Decoder, n: int, out &: Buf): intNextChunk
func NextChunk(d &: Decoder, out &: Buf): intDecodeAll
func DecodeAll(d &: Decoder, data: void*, n: int, out &: Buf): boolThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
Listen
func Listen(port: int, method: str, password: str): ServerServer that is not ServerOk when the method, the password or the port is bad.ListenOn
func ListenOn(host: str, port: int, method: str, password: str, obfs: str): ServerListenObfs, but listens only on the given address, such as 127.0.0.1, instead of every interface. The last argument is the obfuscation, http, tls or empty for none.import net.shadowsocks
func main:
method := "2022-blake3-aes-128-gcm"
ipsk := "AAECAwQFBgcICQoLDA0ODw=="
server := shadowsocks.ListenOn("127.0.0.1", 0, method, ipsk, "")
println shadowsocks.ServerOk(server), shadowsocks.ServerPort(server) > 0
println shadowsocks.AddUser(server, "alice", "EBESExQVFhcYGRobHB0eHw==")
println shadowsocks.AddUser(server, "bob", "ICEiIyQlJicoKSorLC0uLw==")
println shadowsocks.AddUser(server, "bad", "short")
println server.st.user_n, server.st.users.has("x"), server.st.user_names.size()
shadowsocks.Destroy(server)
classic := shadowsocks.ListenOn("127.0.0.1", 0, "chacha20-ietf-poly1305", "password", "")
println shadowsocks.AddUser(classic, "carol", "password")
shadowsocks.Destroy(classic)
mut relay := shadowsocks.UdpListenOn("127.0.0.1", 0, method, ipsk)
println relay.ok, shadowsocks.UdpServerPort(relay) > 0
println shadowsocks.UdpAddUser(relay, "EBESExQVFhcYGRobHB0eHw==")
println shadowsocks.UdpAddUser(relay, "short")
println relay.user_n, relay.users.size(), relay.sess.size()
shadowsocks.UdpDestroy(relay)
endListenObfs
func ListenObfs(port: int, method: str, password: str, obfs: str): ServerListen but also expects clients to use the simple-obfs mode http or tls; an empty string means none.AddUser
func AddUser(s: Server, name: str, password: str): boolserverkey:userkey. Returns false for a classic method, for ChaCha 2022 or for a bad key.The example for this item is shown with the ones above: net-shadowsocks-MultiUser.salam
ServerOk
func ServerOk(s: Server): boolServerPort
func ServerPort(s: Server): intListen(0, ...).Accept
func Accept(s: Server): ConnConn. A connection that fails the handshake (a wrong password, a replay, a bad timestamp) is closed without a reply and the wait continues. TargetOf tells where the client wants to go.AcceptOver
func AcceptOver(s: Server, io: dyn tunnel.Stream): ConnAccept but for one client that arrives over a stream you already have, such as a WebSocket. Returns a Conn that is not Ok when the handshake fails.Relay
func Relay(c: Conn)Serve
func Serve(s: Server, handler: func(Conn))Stop is called and runs the handler for each on a worker thread. The handler owns the Conn and must close it.ServeRelay
func ServeRelay(s: Server)Relay. This is the whole server of a plain Shadowsocks proxy.Stop
func Stop(s: Server)Serve to return. Call it from another thread, then wait for the serving thread and call Destroy.Destroy
func Destroy(s: Server)Serve has returned.LookupMethod
func LookupMethod(name: str): Methodok false for an unknown name.BytesToKey
func BytesToKey(password: str, klen: int, out: void*)klen bytes to out.HkdfSha1
func HkdfSha1(key: void*, klen: int, salt: void*, slen: int, info: str, out: void*, olen: int)olen bytes from a key, a salt and an info string. The classic session key uses it.SessionSubkey
func SessionSubkey(m: Method, master: void*, salt: void*, out: void*)ss-subkey).RandomSalt
func RandomSalt(m: Method, out: void*)out with a random salt of the length of the method.PasswordKey
func PasswordKey(m: Method, password: str): BufBuf.PackUdp
func PackUdp(m: Method, key: void*, host: str, port: int, data: void*, n: int, out &: Buf): boolThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
UnpackUdp
func UnpackUdp(m: Method, key: void*, p: void*, n: int, t &: Target, out &: Buf): boolThe example for this item is shown with the ones above: net-shadowsocks-Codec.salam
UdpDial
func UdpDial(cfg: Config): UdpClientConfig. For a 2022 method the password may be serverkey:userkey. ok is false for an unknown method or a bad password.UdpSend
func UdpSend(c: UdpClient, host: str, port: int, data: void*, n: int): boolUdpRecv
func UdpRecv(c: UdpClient, buf: void*, max: int, from &: Target): intmax bytes), filling from with the sender. Returns the length, or a negative number on timeout or on a packet that does not authenticate.UdpSetTimeout
func UdpSetTimeout(c: UdpClient, millis: int)UdpRecv waits at most.UdpClose
func UdpClose(c: UdpClient)UdpListen
func UdpListen(port: int, method: str, password: str): UdpServerok is false for an unknown method, a bad password or a port that is taken.UdpListenOn
func UdpListenOn(host: str, port: int, method: str, password: str): UdpServerUdpListen, but listens only on the given address, such as 127.0.0.1.The example for this item is shown with the ones above: net-shadowsocks-MultiUser.salam
UdpAddUser
func UdpAddUser(s &: UdpServer, password: str): boolThe example for this item is shown with the ones above: net-shadowsocks-MultiUser.salam
UdpServerPort
func UdpServerPort(s: UdpServer): intUdpStep
func UdpStep(s &: UdpServer): intUdpServe
func UdpServe(s &: UdpServer)UdpStep in a loop until UdpStop. Run it on its own thread.UdpStop
func UdpStop(s: UdpServer)UdpServe to return.UdpDestroy
func UdpDestroy(s &: UdpServer)Blake3Hash
func Blake3Hash(p: void*, n: int, out: void*, outlen: int)outlen bytes to out (32 is the usual length).Blake3KeyedHash
func Blake3KeyedHash(key32: void*, p: void*, n: int, out: void*, outlen: int)Blake3DeriveKey
func Blake3DeriveKey(context: str, p: void*, n: int, out: void*, outlen: int)Blake3SessionKey
func Blake3SessionKey(psk: void*, klen: int, salt: void*, slen: int, out: void*)