Standard library · Network

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.

net-shadowsocks-guide-loopback.salam
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
end
Outputtrue true hello through shadowsocks server saw example.com:443

A 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.

net-shadowsocks-guide-relay.salam
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)
end
Outputthrough the relay

The 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.

MethodKeySaltPassword formNotes
aes-128-gcm16 bytes16 bytesany textClassic AEAD. Slow here, see below.
aes-256-gcm32 bytes32 bytesany textClassic AEAD. The default of Config. Slow here, see below.
chacha20-ietf-poly130532 bytes32 bytesany textClassic AEAD. The one to prefer.
2022-blake3-aes-128-gcm16 bytes16 bytesbase64 of exactly 16 bytesShadowsocks 2022, TCP and UDP.
2022-blake3-aes-256-gcm32 bytes32 bytesbase64 of exactly 32 bytesShadowsocks 2022, TCP and UDP.
2022-blake3-chacha20-poly130532 bytes32 bytesbase64 of exactly 32 bytesShadowsocks 2022, TCP and UDP.
net-shadowsocks-guide-methods.salam
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")
end
Outputaes-128-gcm true 16 16 false aes-256-gcm true 32 32 false chacha20-ietf-poly1305 true 32 32 false 2022-blake3-aes-128-gcm true 16 16 true 2022-blake3-aes-256-gcm true 32 32 true 2022-blake3-chacha20-poly1305 true 32 32 true false 0 0 false

For 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. PackUdp and UnpackUdp are for the classic methods and refuse the three 2022 methods; use PackUdp2022 and UnpackUdp2022, or UdpListen and UdpDial, 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-obfs and of the 2022 TCP stream. They are tested against this package itself and against separate Python reimplementations written from the specifications, not against ssserver, shadowsocks-rust or the real obfs-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-obfs exist, not the plugin v2ray-plugin or 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.

net-shadowsocks-guide-uri.salam
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
end
Outputtrue chacha20-ietf-poly1305 secret 203.0.113.5 8388 my server obfs-local;obfs=tls;obfs-host=cdn.example.com obfs-local tls true false 203.0.113.5 8388 chacha20-ietf-poly1305 tls cdn.example.com ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpzZWNyZXQ@203.0.113.5:8388/?plugin=obfs-local%3Bobfs%3Dtls%3Bobfs-host%3Dcdn.example.com#my%20server false

simple-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 a 101 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.

net-shadowsocks-guide-obfs.salam
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)
end
Outputinside an http disguise

The 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.

net-shadowsocks-guide-udp.salam
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)
end
Outputping 127.0.0.1 true

Several 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.

net-shadowsocks-guide-over-stream.salam
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)
end
Outputany stream will do true

The 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-poly1305 unless 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 DialOver and AcceptOver), or use simple-obfs with tls and 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 with AsStream where 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 Dial and 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
A replay filter: it remembers the salts of recent connections in two generations so memory stays bounded. It is thread safe.
NameTypeDescription
curHashMapThe current generation of seen salts.
oldHashMapThe previous generation, dropped when the current one fills.
capint = 0How many salts one generation holds.
locksync.MutexThe mutex that guards both generations.
net-shadowsocks-SaltFilter.salam
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)
end
Output1000 false true true false true 2 0

KeyChain

struct KeyChain
The keys of a 2022 password written as serverkey:userkey, split and decoded into one buffer of equal sized keys.
NameTypeDescription
keysBufAll keys one after another, key_len bytes each.
countint = 0How many keys there are.
key_lenint = 0The length of one key in bytes.
okbool = falseFalse when the method has no identity headers or a part of the password is not a valid key.
net-shadowsocks-Identity.salam
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)
end
Output16 shadowsocks 2022 identity subkey true 2 16 32 101112131415161718191a1b1c1d1e1f false 0 ea5ff194405ece4f55ae7a150c523884 e80f7e725b072607ae6b9ec3a0822bec true aa0134431e6e7835e63c404ab77bd547 true true false 0

Config

struct Config
How to reach a Shadowsocks server: its address, the cipher, the password and the optional disguise. Pass it to Dial, DialOver or UdpDial.
NameTypeDescription
hoststr = ""The server host name or address. Not used by DialOver.
portint = 0The server port. Not used by DialOver.
methodstr = "aes-256-gcm"The cipher method name. The default is aes-256-gcm; prefer chacha20-ietf-poly1305.
passwordstr = ""The password; for a 2022 method the base64 of the key.
obfsstr = ""http, tls or empty for no simple-obfs.
obfs_hoststr = "www.bing.com"The host name the disguise shows. The default is www.bing.com.

ConnState

struct ConnState
Everything one connection needs: the socket or underlying stream, both ciphers, the buffers and the handshake progress. You only hold a pointer to it inside a Conn.
NameTypeDescription
socktcp.ConnThe TCP socket, when the connection does not run over a stream.
methodMethodThe cipher method.
keyBufThe master key.
encEncoderThe encoder for the data that is sent.
decDecoderThe decoder for the data that is received.
plainBufDecrypted bytes that were not read yet.
plain_posint = 0How much of plain was read.
outBufA scratch buffer for the bytes about to be sent.
rdvoid*The receive buffer.
pendingBufThe address header held back until the first write.
targetTargetThe destination of the connection: asked for by the client, or read by the server.
obfsObfsThe disguise layer.
updyn tunnel.StreamThe underlying stream, when use_up is true.
use_upbool = falseTrue when the connection runs over a stream and not a socket.
obufBufA scratch buffer for disguised output.
oinBufRaw bytes received before the disguise was removed.
rd2void*The raw receive buffer.
v2022bool = falseTrue for a 2022 method.
is_serverbool = falseTrue on the server side.
wphaseint = 0The write side handshake step.
rphaseint = 0The read side handshake step.
first_lenint = 0The length announced by the first 2022 header.
target_okbool = falseTrue once the target was received.
vtmpBufA scratch buffer for the 2022 header.
livebool = falseTrue while the connection is open.
eihBufThe identity keys of a serverkey:userkey password, all keys except the last one, one after another.
eih_nint = 0How many identity keys eih holds.
eih_donebool = falseOn a server, true once the identity header of the client was opened and its user key is in use.
userstr = ""On a server, the name of the user the identity header named; empty otherwise.

Conn

struct Conn
One Shadowsocks connection, as a client or on a server. It holds a pointer to its ConnState; with a null pointer the connection is not usable.
NameTypeDescription
stConnState*The state of the connection, or null when it could not be made.

UdpSession

struct UdpSession
The state of one Shadowsocks 2022 UDP session: the session ID, the packet counter and the replay window of the packets received.
NameTypeDescription
sidi64 = 0The random 64 bit session ID of this side.
pidi64 = 0The number of the next packet this side sends.
peeri64 = 0The session ID of the other side, learned from its first valid packet; 0 until then.
topi64 = 0The highest packet number accepted so far.
bitsu64 = 0The replay window: bit k is set when the packet numbered top minus k was seen, over the last 64 packets.
anybool = falseTrue once a packet was accepted, so that top and bits mean something.
lasti64 = 0The packet number of the datagram opened last.
net-shadowsocks-Udp2022.salam
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)
end
Outputtrue true 16 16 true false true true true true true 10.0.0.7 53 query true 0 true false true 5 true true true true true hello

Plugin

struct Plugin
A parsed plugin string: its name and its options in order.
NameTypeDescription
namestr = ""The plugin name, such as obfs-local.
keysVectorThe option names.
valuesVectorThe option values, in the same order as keys; empty for an option without a value.

Uri

struct Uri
A parsed ss:// link.
NameTypeDescription
methodstr = ""The cipher method name.
passwordstr = ""The password, or the base64 key for a 2022 method.
hoststr = ""The server host name or address (IPv6 without brackets).
portint = 0The server port.
pluginstr = ""The raw plugin text, empty when the link has none.
tagstr = ""The name after the #, decoded.
okbool = falseTrue when the link parsed and the method is one this package supports.

Obfs

struct Obfs
The state of the simple-obfs layer of one connection.
NameTypeDescription
modeint = 0One of the OBFS_ constants.
serverbool = falseTrue on the server side.
hoststr = ""The host name shown in the fake request or hello.
sent_firstbool = falseTrue once the first packet was sent.
got_firstbool = falseTrue once the first packet was received and checked.
seen_hellobool = falseTrue once the fake TLS hello record was read, so that a later handshake record is taken as the start of the data.
rxBufBytes received that do not form a whole record yet.
frame_leftint = 0How many bytes of the current TLS record remain.
ws_keystr = ""The WebSocket key of the fake HTTP upgrade.

Target

struct Target
A destination: a host (a name or an address) and a port.
NameTypeDescription
hoststr = ""The host name or the IP address in text.
portint = 0The port.
net-shadowsocks-Codec.salam
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)
end
Outputwire bytes 77 salt 32 overhead 34 true hello codec true 15 030b6578616d706c652e636f6d20fb 15 example.com 8443 true 60 true 10.0.0.7 53 query

Buf

struct Buf
A growable byte buffer with its own memory. You must free it with BufFree.
NameTypeDescription
pvoid*The start of the memory.
lenint = 0How many bytes are in use.
capint = 0How many bytes were allocated.
badbool = falseTrue 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
Seals a byte stream into Shadowsocks AEAD chunks. It keeps the session key, the nonce counter and whether the salt has gone out.
NameTypeDescription
methodMethodThe cipher method.
ctxvoid*The AEAD context holding the session key.
noncevoid*The 12 byte counter used as the nonce, increased for every sealed piece.
saltBufThe random salt of this direction.
salt_sentbool = falseTrue once the salt was emitted.
max_chunkint = 16383The 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
Reads a Shadowsocks AEAD stream, in pieces of any size, and returns the plaintext. It buffers incomplete chunks.
NameTypeDescription
methodMethodThe cipher method.
mastervoid*The master key the session key is derived from.
ctxvoid*The AEAD context, made when the salt has arrived.
noncevoid*The 12 byte counter used as the nonce.
saltBufThe salt that was read from the start of the stream.
have_saltbool = falseTrue once the salt was read.
inbBufThe bytes received and not yet used.
offint = 0The read offset in inb.
needint = -1How many bytes the next piece needs, or -1 when it is not known yet.
tmpBufA scratch buffer for the length field.
badbool = falseTrue after an authentication failure; the stream cannot be used any more.
max_chunkint = 16383The largest accepted chunk size.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

ServerState

struct ServerState
The state of a running server: the listening socket, the cipher and key, the replay filter and the worker pool. You get one inside a Server and only pass it around.
NameTypeDescription
lstcp.ServerThe listening TCP socket.
hoststr = ""The address the server listens on; empty means every IPv4 interface. Stop uses it to wake the accept loop.
methodMethodThe cipher the server uses.
keyBufThe master key, from the password.
filterSaltFilterThe salt cache that rejects a replayed connection.
workerspool.PoolThe pool of threads that run client handlers.
stopi64*A flag that tells Serve to stop; Stop sets it.
jobschan.ChanThe queue of accepted connections waiting for a worker.
obfs_modestr = ""The obfuscation mode, http, tls or empty for none.
usersHashMapThe keys of the users added with AddUser, by the hex of the hash that names them in an identity header.
user_namesHashMapThe names of those users, by the same hex.
user_nint = 0How many users were added.
net-shadowsocks-IPv6.salam
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)
end

Server

struct Server
A Shadowsocks server. It holds a pointer to its ServerState; the zero value (st null) means the server could not be started.
NameTypeDescription
stServerState*The state of the server, or null when Listen failed.

Method

struct Method
A cipher method: its name, key and salt sizes, and whether it is a 2022 method.
NameTypeDescription
namestr = ""The method name, with aliases resolved.
kindint = 0The AEAD kind, as net.tls names it.
key_lenint = 0The key length in bytes, 16 or 32.
salt_lenint = 0The salt length in bytes, 16 or 32.
v2022bool = falseTrue for the 2022 methods.
okbool = falseTrue when the name was a known method.

UdpClient

struct UdpClient
A UDP client that talks to a Shadowsocks UDP relay.
NameTypeDescription
sockudp.SocketThe local UDP socket.
hoststr = ""The relay host.
portint = 0The relay port.
methodMethodThe cipher method.
keyBufThe master key.
rbufvoid*The receive buffer.
okbool = falseTrue when the client could be set up.
sessUdpSession*The 2022 session of this client; null for a classic method.
eihBufThe identity keys of a serverkey:userkey password, all keys except the last one, one after another.
eih_nint = 0How many identity keys eih holds.

UdpServer

struct UdpServer
A Shadowsocks UDP relay with one outgoing socket per client.
NameTypeDescription
sockudp.SocketThe UDP socket clients send to.
methodMethodThe cipher method.
keyBufThe master key.
rbufvoid*The receive buffer.
assocHashMapThe outgoing socket of each client, keyed by the client address.
seenHashMapWhen each client was last heard from, in milliseconds.
clientsHashMapThe address each client sends from, for the replies.
stopci64*The flag that stops UdpServe.
okbool = falseTrue when the relay started.
sessHashMapThe 2022 session of each client, keyed by the client address.
skeysHashMapThe user key each client signed in with, keyed by the client address.
usersHashMapThe keys of the users added with UdpAddUser, by the hex of the hash that names them in an identity header.
user_nint = 0How many users were added.
filterSaltFilterThe salt cache that rejects a replayed datagram.

Constants and variables

IDENTITY_CONTEXT

const IDENTITY_CONTEXT: str = "shadowsocks 2022 identity subkey"
The BLAKE3 key derivation context for the key of an identity header.

The example for this item is shown with the ones above: net-shadowsocks-Identity.salam

EIH_LEN

const EIH_LEN: i32 = 16
The length of one identity header block in bytes: 16.

The example for this item is shown with the ones above: net-shadowsocks-Identity.salam

Obfuscation modes OBFS_…

Which disguise simple-obfs uses.
NameValue
OBFS_NONE0
OBFS_HTTP1
OBFS_TLS2

Address types ATYP_…

The first byte of the address header: what kind of address follows.
NameValue
ATYP_IPV41
ATYP_DOMAIN3
ATYP_IPV64

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

MAX_CHUNK

const MAX_CHUNK: i32 = 16383
The largest payload of one chunk in a classic stream, 16383 bytes.

TAG_LEN

const TAG_LEN: i32 = 16
The length of the AEAD authentication tag, 16 bytes.

Functions

NewSaltFilter

func NewSaltFilter(cap: int): SaltFilter
Makes a filter whose generations hold up to cap 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): bool
Returns true when a salt is already in the filter, without recording it. A server that must authenticate the first chunk before it trusts a connection asks this first and calls FilterCheck 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): bool
Records a salt and returns true when it is new, false when it was seen before and the connection should be refused.

The example for this item is shown with the ones above: net-shadowsocks-SaltFilter.salam

MakeKey

func MakeKey(m: Method, password: str): Buf
Turns a password into the master key for a method: for a classic method the derived key, for a 2022 method the base64 decoded password. The returned Buf 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): KeyChain
Splits a 2022 password written as serverkey: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*
A pointer to key number 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)
Wipes and frees the keys of a chain.

The example for this item is shown with the ones above: net-shadowsocks-Identity.salam

IdentityHash

func IdentityHash(psk: void*, klen: int, out16: void*)
Writes the 16 byte BLAKE3 hash of a key, which names a user in an identity header.

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*)
Derives the key that seals an identity header: a BLAKE3 key derivation with 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*): bool
Builds one identity header block: the hash of the next key down the chain, encrypted with AES under the key from IdentityKey. 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*): bool
The reverse of EihSeal: 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): bool
Tells whether a connection was made. After Dial it only means the setup and the TCP connect worked, not that the server accepted the password.

TargetOf

func TargetOf(c: Conn): Target
The destination of a connection: on a server, the host and port the client asked for.

Write

func Write(c: Conn, data: void*, n: int): int
Encrypts and sends bytes. Returns the number sent, or -1 on failure. On a new client connection the first write carries the target address too.

WriteStr

func WriteStr(c: Conn, s: str): int
Sends a string. Same as Write with its length.

Read

func Read(c: Conn, buf: void*, max: int): int
Waits for data and copies up to max 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): str
Reads up to max 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)
Closes the connection and frees its state. Using the Conn afterwards is an error.

Shutdown

func Shutdown(c: Conn)
Shuts the connection down without freeing it, which wakes a thread that is blocked reading.

Dial

func Dial(cfg: Config, target_host: str, target_port: int): Conn
Connects to a Shadowsocks server and asks it to reach a target host and port. The host may be a name; the server resolves it. Returns at once, with the target sent along with the first write.

DialOver

func DialOver(s: dyn tunnel.Stream, cfg: Config, target_host: str, target_port: int): Conn
Like Dial 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.Stream
Wraps a connection as a tunnel.Stream, so any code that takes a stream can use it.

NewUdpSession

func NewUdpSession(): UdpSession
Makes a session with a random 64 bit session ID.

The example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam

SessionAccept

func SessionAccept(s &: UdpSession, pid: i64): bool
Checks the packet number of a received datagram against the replay window of the session. Returns true and records the number when it is new and not more than 63 behind the highest; false for a repeat or a number that is too old.

The 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): bool
Builds one Shadowsocks 2022 datagram and appends it to the buffer: the encrypted header with the session ID and the packet number, the identity header blocks for a multi user password, then the sealed body with the time, the target and the payload. ukey 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): bool
Reads the session ID out of the header of a datagram without opening the body, so that a relay can find the session first. Works for the AES 2022 methods only; false for ChaCha or a packet that is too short.

The 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): bool
Opens a 2022 datagram: checks the header and the tag, the time (within 30 seconds) and the side, fills the target and appends the payload to the buffer. skip 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*): bool
Opens the identity header block of a datagram with the identity key of the server and writes the 16 byte hash of the user key it names. Works for the AES 2022 methods only; false for ChaCha or a packet that is too short.

The example for this item is shown with the ones above: net-shadowsocks-Udp2022.salam

ParseUri

func ParseUri(text: str): Uri
Parses an ss:// 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): str
Writes a Uri as an ss:// link, including the plugin and the tag when they are set.

ParsePlugin

func ParsePlugin(text: str): Plugin
Splits a plugin string such as obfs-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): str
The value of an option, or the fallback when the option is missing.

PluginHas

func PluginHas(p: Plugin, key: str): bool
Tells whether an option is present.

PluginFree

func PluginFree(p: Plugin)
Frees the vectors of a Plugin.

ToConfig

func ToConfig(u: Uri): Config
Builds a Config from a Uri, applying the simple-obfs options of its plugin.

ObfsWrap

func ObfsWrap(o &: Obfs, p: void*, n: int, out &: Buf)
Wraps outgoing bytes in the disguise (the fake request or hello for the first packet, TLS records after that) and appends the result to the buffer.

ObfsUnwrap

func ObfsUnwrap(o &: Obfs, p: void*, n: int, out &: Buf): int
Removes the disguise from incoming bytes and appends the payload to the buffer. Returns the number of bytes consumed, 0 when more bytes are needed, or a negative number when the data is not a valid disguise.

ObfsInit

func ObfsInit(mode: int, server: bool, host: str): Obfs
Makes the state for a mode, for the client or the server side, with the host name to show.

ObfsFree

func ObfsFree(o &: Obfs)
Frees an Obfs.

ObfsMode

func ObfsMode(name: str): int
Turns http or tls into the OBFS_ constant; anything else gives OBFS_NONE.

EncodeTarget

func EncodeTarget(host: str, port: int, out &: Buf): bool
Appends the address header for a host and port to the buffer. A host that looks like an IPv4 or IPv6 address is sent as such, anything else as a name of at most 255 bytes. Returns false for a bad port or name.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

ParseTarget

func ParseTarget(p: void*, n: int, t &: Target): int
Reads an address header from bytes into a Target. 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*
A pointer to the byte at an offset in the buffer.

PtrAt

func PtrAt(p: void*, off: int): void*
A pointer a number of bytes after another pointer.

NewBuf

func NewBuf(cap0: int): Buf
Allocates an empty buffer with room for at least that many bytes (never less than 64).

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

BufFree

func BufFree(b &: Buf)
Releases the memory of a buffer.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

BufWipe

func BufWipe(b &: Buf)
Overwrites the whole allocation with zeros and sets the length to 0. Use it before freeing keys.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

BufReserve

func BufReserve(b &: Buf, extra: int)
Makes room for that many more bytes, growing the buffer if needed.

BufAppend

func BufAppend(b &: Buf, src: void*, n: int)
Appends bytes from memory.

BufAppendByte

func BufAppendByte(b &: Buf, v: int)
Appends one byte (the low 8 bits of the number).

BufAppendStr

func BufAppendStr(b &: Buf, s: str)
Appends the bytes of a string.

BufConsume

func BufConsume(b &: Buf, n: int)
Removes bytes from the front of the buffer and moves the rest down.

BufToStr

func BufToStr(b: Buf): str
Copies the buffer into a new string. The result stops at a zero byte, so use it for text only.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

BufToHex

func BufToHex(b: Buf): str
The buffer as lowercase hexadecimal text.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

HexOf

func HexOf(p: void*, n: int): str
Lowercase hexadecimal text of bytes in memory.

BufFromHex

func BufFromHex(hex: str): Buf
Reads hexadecimal text into a new buffer; bad is set when the text is not valid hex.

NewEncoder

func NewEncoder(m: Method, master: void*, salt: void*): Encoder
Makes an encoder with a given salt.

NewEncoderRandom

func NewEncoderRandom(m: Method, master: void*): Encoder
Makes an encoder with a fresh random salt.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

EncoderFree

func EncoderFree(e &: Encoder)
Frees an encoder and its key material.

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)
Seals one piece of data with the AEAD and appends the ciphertext and the tag to the buffer.

EmitSalt

func EmitSalt(e &: Encoder, out &: Buf)
Appends the salt to the buffer, once, at the start of a connection.

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)
Appends data as length prefixed encrypted chunks, split at 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*): Decoder
Makes a decoder for a method and a master key.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

DecoderFree

func DecoderFree(d &: Decoder)
Frees a 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)
Gives the decoder bytes that arrived from the network.

TakeSalt

func TakeSalt(d &: Decoder): bool
Reads the salt from the bytes fed so far and sets up the key. Returns true when it is ready.

TakeFrame

func TakeFrame(d &: Decoder, n: int, out &: Buf): int
Reads and opens one piece of a given size. Returns its length, or a negative number when more bytes are needed or authentication failed.

NextChunk

func NextChunk(d &: Decoder, out &: Buf): int
Appends the next complete decrypted chunk to the buffer and returns its length, 0 when more bytes are needed, or a negative number on error.

DecodeAll

func DecodeAll(d &: Decoder, data: void*, n: int, out &: Buf): bool
Decodes everything in a block of bytes that holds a whole stream, appending the plaintext. Returns false on error.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

Listen

func Listen(port: int, method: str, password: str): Server
Starts a server on a TCP port (0 picks a free one) with a method and a password. For a 2022 method the password is base64 of the key. Returns a Server 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): Server
Like ListenObfs, 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.
net-shadowsocks-MultiUser.salam
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)
end
Outputtrue true true true false 2 false 2 false true true true false 1 1 0

ListenObfs

func ListenObfs(port: int, method: str, password: str, obfs: str): Server
Like Listen 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): bool
Adds a user to a 2022 AES server: a name that shows up in logs and the base64 user key. Clients then connect with the password serverkey: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): bool
Tells whether the server started.

ServerPort

func ServerPort(s: Server): int
The port the server listens on; useful after Listen(0, ...).

Accept

func Accept(s: Server): Conn
Waits for the next client whose handshake succeeds and returns its Conn. 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): Conn
Like Accept 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)
Serves one accepted connection as a proxy: connects to the target the client asked for and copies bytes in both directions until one side ends, then closes everything. There is no half close.

Serve

func Serve(s: Server, handler: func(Conn))
Accepts clients until 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)
Serves every client with Relay. This is the whole server of a plain Shadowsocks proxy.

Stop

func Stop(s: Server)
Asks Serve to return. Call it from another thread, then wait for the serving thread and call Destroy.

Destroy

func Destroy(s: Server)
Frees a server and wipes its key. Call it once, after Serve has returned.

LookupMethod

func LookupMethod(name: str): Method
Finds a method by name. The result has ok false for an unknown name.

BytesToKey

func BytesToKey(password: str, klen: int, out: void*)
The classic password to key function (repeated MD5), writing klen bytes to out.

HkdfSha1

func HkdfSha1(key: void*, klen: int, salt: void*, slen: int, info: str, out: void*, olen: int)
HKDF with SHA-1: derives 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*)
The classic session key for a master key and a salt (HKDF with the info ss-subkey).

RandomSalt

func RandomSalt(m: Method, out: void*)
Fills out with a random salt of the length of the method.

PasswordKey

func PasswordKey(m: Method, password: str): Buf
The master key of a classic method from a password, as a new Buf.

PackUdp

func PackUdp(m: Method, key: void*, host: str, port: int, data: void*, n: int, out &: Buf): bool
Builds one UDP datagram: a random salt, then the target and the payload sealed together. Appends it to the buffer. Returns false for a 2022 method or a bad target.

The 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): bool
Opens a datagram, fills the target and appends the payload to the buffer. Returns false when authentication fails, the packet is short or the method is a 2022 method.

The example for this item is shown with the ones above: net-shadowsocks-Codec.salam

UdpDial

func UdpDial(cfg: Config): UdpClient
Makes a UDP client for the server and the method in the Config. 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): bool
Sends a payload to a target through the relay. Returns true when the datagram was sent.

UdpRecv

func UdpRecv(c: UdpClient, buf: void*, max: int, from &: Target): int
Waits for one reply, opens it and copies the payload to the buffer (up to max 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)
How many milliseconds UdpRecv waits at most.

UdpClose

func UdpClose(c: UdpClient)
Closes the client and frees its buffers.

UdpListen

func UdpListen(port: int, method: str, password: str): UdpServer
Starts a UDP relay on a port with a method and a password. For a 2022 method the password is the base64 key. ok 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): UdpServer
Like UdpListen, 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): bool
Adds a user key to a 2022 AES UDP relay. 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

UdpServerPort

func UdpServerPort(s: UdpServer): int
The port the relay listens on.

UdpStep

func UdpStep(s &: UdpServer): int
Handles at most one waiting datagram, forwards it, hands pending replies back to their clients and drops idle clients. Returns 1 when a datagram was handled, else 0. It waits about 20 ms for a packet.

UdpServe

func UdpServe(s &: UdpServer)
Calls UdpStep in a loop until UdpStop. Run it on its own thread.

UdpStop

func UdpStop(s: UdpServer)
Asks UdpServe to return.

UdpDestroy

func UdpDestroy(s &: UdpServer)
Closes the relay, its sockets and its key.

Blake3Hash

func Blake3Hash(p: void*, n: int, out: void*, outlen: int)
The BLAKE3 hash of bytes, written as outlen bytes to out (32 is the usual length).

Blake3KeyedHash

func Blake3KeyedHash(key32: void*, p: void*, n: int, out: void*, outlen: int)
The keyed BLAKE3 hash, for a 32 byte key.

Blake3DeriveKey

func Blake3DeriveKey(context: str, p: void*, n: int, out: void*, outlen: int)
BLAKE3 key derivation with a context string.

Blake3SessionKey

func Blake3SessionKey(psk: void*, klen: int, salt: void*, slen: int, out: void*)
The Shadowsocks 2022 session key: derived from the pre shared key and the salt.