From 55711a7c2f39c92b150398da229f3473972ce23c Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 14 Apr 2026 15:35:41 +0800 Subject: [PATCH 1/3] Convert ASCII diagrams to inline SVG and restore hero color Replace ASCII box-drawing diagrams in what-is-shadowsocks, aead, sip003, sip022, and sip023 with inline SVG using currentColor so they adapt to the VitePress light/dark theme toggle. Pin --vp-home-hero-name-color to the pre-upgrade emerald green (#10b981), restoring the homepage title color that shifted to indigo when VitePress upgraded from 1.0.0-alpha.75 to 1.6.4. Co-Authored-By: Claude Opus 4.6 --- docs/.vitepress/theme/custom.css | 3 + docs/.vitepress/theme/index.ts | 1 + docs/doc/aead.md | 32 ++- docs/doc/sip003.md | 38 ++- docs/doc/sip022.md | 426 ++++++++++++++++++++++++------- docs/doc/sip023.md | 53 +++- docs/doc/what-is-shadowsocks.md | 83 ++++-- 7 files changed, 492 insertions(+), 144 deletions(-) create mode 100644 docs/.vitepress/theme/custom.css diff --git a/docs/.vitepress/theme/custom.css b/docs/.vitepress/theme/custom.css new file mode 100644 index 00000000..8818dd12 --- /dev/null +++ b/docs/.vitepress/theme/custom.css @@ -0,0 +1,3 @@ +:root { + --vp-home-hero-name-color: #10b981; +} diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 5facf0c2..7e2de46c 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,5 +1,6 @@ import DefaultTheme from 'vitepress/theme' import SIP002Generator from './components/SIP002Generator.vue' +import './custom.css' export default { ...DefaultTheme, diff --git a/docs/doc/aead.md b/docs/doc/aead.md index 42f758e5..cf032d8c 100644 --- a/docs/doc/aead.md +++ b/docs/doc/aead.md @@ -51,9 +51,20 @@ AE_decrypt(key, nonce, ciphertext, tag) => message An AEAD encrypted TCP stream starts with a randomly generated salt to derive the per-session subkey, followed by any number of encrypted chunks. Each chunk has the following structure: -``` -[encrypted payload length][length tag][encrypted payload][payload tag] -``` + + + + + + + + + encrypted payload length + length tag + encrypted payload + payload tag + + Payload length is a 2-byte big-endian unsigned integer capped at 0x3FFF. The higher two bits are reserved and must be set to zero. Payload is therefore limited to 16*1024 - 1 bytes. @@ -64,9 +75,18 @@ The first AEAD encrypt/decrypt operation uses a counting nonce starting from 0. An AEAD encrypted UDP packet has the following structure -``` -[salt][encrypted payload][tag] -``` + + + + + + + + salt + encrypted payload + tag + + The salt is used to derive the per-session subkey and must be generated randomly to ensure uniqueness. Each UDP packet is encrypted/decrypted independently, using the derived subkey and a nonce with all zero bytes. diff --git a/docs/doc/sip003.md b/docs/doc/sip003.md index 360d377c..1e80cc20 100644 --- a/docs/doc/sip003.md +++ b/docs/doc/sip003.md @@ -4,17 +4,33 @@ The plugin of shadowsocks is very similar to the [Pluggable Transport](https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt) plugins from Tor project. Unlike the SOCKS5 proxy design in PT, every SIP003 plugin works as a tunnel (or called local port forwarding). This design aims to avoid per-connection arguments in PT, leading to much easier implementation. -``` -+------------+ +---------------------------+ -| SS Client +-- Local Loopback --+ Plugin Client (Tunnel) +--+ -+------------+ +---------------------------+ | - | - Public Internet (Obfuscated/Transformed traffic) ==> | - | -+------------+ +---------------------------+ | -| SS Server +-- Local Loopback --+ Plugin Server (Tunnel) +--+ -+------------+ +---------------------------+ -``` + + + + + + + + + + + + + + + + + + SS Client + Plugin Client (Tunnel) + SS Server + Plugin Server (Tunnel) + Local Loopback + Local Loopback + Public Internet + (Obfuscated / Transformed traffic) + + ## Life cycle of a plugin diff --git a/docs/doc/sip022.md b/docs/doc/sip022.md index 172eb583..f488d5e3 100644 --- a/docs/doc/sip022.md +++ b/docs/doc/sip022.md @@ -84,58 +84,149 @@ A length chunk is a 16-bit big-endian unsigned integer that describes the payloa A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The 0x3FFF (16383) length cap in Shadowsocks AEAD does not apply to this edition. -``` -+----------------+ -| length chunk | -+----------------+ -| u16 big-endian | -+----------------+ - -+---------------+ -| payload chunk | -+---------------+ -| variable | -+---------------+ - -Request stream: -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ -| salt | encrypted header chunk | encrypted header chunk | encrypted length chunk | encrypted payload chunk |...| -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ -| 16/32B | 11B + 16B tag | variable length + 16B tag | 2B length + 16B tag | variable length + 16B tag |...| -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ - -Response stream: -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ -| salt | encrypted header chunk | encrypted payload chunk | encrypted length chunk | encrypted payload chunk |...| -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ -| 16/32B | 27/43B + 16B tag | variable length + 16B tag | 2B length + 16B tag | variable length + 16B tag |...| -+--------+------------------------+---------------------------+------------------------+---------------------------+---+ -``` + + + + + + + + + length chunk + u16 big-endian + payload chunk + variable + + + +**Request stream:** + + + + + + + + + + + + + salt + 16/32B + encrypted header chunk + 11B + 16B tag + encrypted header chunk + variable length + 16B tag + encrypted length chunk + 2B length + 16B tag + encrypted payload chunk + variable length + 16B tag + ... + ... + + + +**Response stream:** + + + + + + + + + + + + + salt + 16/32B + encrypted header chunk + 27/43B + 16B tag + encrypted payload chunk + variable length + 16B tag + encrypted length chunk + 2B length + 16B tag + encrypted payload chunk + variable length + 16B tag + ... + ... + + #### 3.1.3. Header -``` -Request fixed-length header: -+------+------------------+--------+ -| type | timestamp | length | -+------+------------------+--------+ -| 1B | u64be unix epoch | u16be | -+------+------------------+--------+ - -Request variable-length header: -+------+----------+-------+----------------+----------+-----------------+ -| ATYP | address | port | padding length | padding | initial payload | -+------+----------+-------+----------------+----------+-----------------+ -| 1B | variable | u16be | u16be | variable | variable | -+------+----------+-------+----------------+----------+-----------------+ - -Response fixed-length header: -+------+------------------+----------------+--------+ -| type | timestamp | request salt | length | -+------+------------------+----------------+--------+ -| 1B | u64be unix epoch | 16/32B | u16be | -+------+------------------+----------------+--------+ +Request fixed-length header: + + + + + + + + + + type + 1B + timestamp + u64be unix epoch + length + u16be + + + +Request variable-length header: + + + + + + + + + + + + + ATYP + 1B + address + variable + port + u16be + padding length + u16be + padding + variable + initial payload + variable + + + +Response fixed-length header: + + + + + + + + + + + type + 1B + timestamp + u64be unix epoch + request salt + 16/32B + length + u16be + + +``` HeaderTypeClientStream = 0 HeaderTypeServerStream = 1 MinPaddingLength = 0 @@ -206,21 +297,37 @@ decrypted_body := session_aead_cipher.open(nonce: separate_header[4..16], body) A UDP packet consists of a separate header and an AEAD-encrypted body. The separate header consists of an 8-byte session ID and an 8-byte big-endian unsigned integer as packet ID. The body is made up of the main header and payload. -``` -Packet: -+---------------------------+---------------------------+ -| encrypted separate header | encrypted body | -+---------------------------+---------------------------+ -| 16B | variable length + 16B tag | -+---------------------------+---------------------------+ - -Separate header: -+------------+-----------+ -| session ID | packet ID | -+------------+-----------+ -| 8B | u64be | -+------------+-----------+ -``` +Packet: + + + + + + + + + encrypted separate header + 16B + encrypted body + variable length + 16B tag + + + +Separate header: + + + + + + + + + session ID + 8B + packet ID + u64be + + UDP sessions are initiated by clients. To start a UDP session, the client generates a new random session ID and maintains a counter starting at zero as packet ID. These are used in client-to-server messages and are usually referred to as client session ID and client packet ID. @@ -230,21 +337,72 @@ Servers use client session IDs to identify UDP sessions. For server-to-client me The main header, or message header, is the header at the start of the body. The client-to-server message header consists of type, timestamp, padding and SOCKS address. The server-to-client message header has an additional client session ID field, which maps the server session to a client session. -``` -Client-to-server message header: -+------+------------------+----------------+----------+------+----------+-------+ -| type | timestamp | padding length | padding | ATYP | address | port | -+------+------------------+----------------+----------+------+----------+-------+ -| 1B | u64be unix epoch | u16be | variable | 1B | variable | u16be | -+------+------------------+----------------+----------+------+----------+-------+ - -Server-to-client message header: -+------+------------------+-------------------+----------------+----------+------+----------+-------+ -| type | timestamp | client session ID | padding length | padding | ATYP | address | port | -+------+------------------+-------------------+----------------+----------+------+----------+-------+ -| 1B | u64be unix epoch | 8B | u16be | variable | 1B | variable | u16be | -+------+------------------+-------------------+----------------+----------+------+----------+-------+ +Client-to-server message header: + + + + + + + + + + + + + + type + 1B + timestamp + u64be unix epoch + padding length + u16be + padding + variable + ATYP + 1B + address + variable + port + u16be + + + +Server-to-client message header: + + + + + + + + + + + + + + + type + 1B + timestamp + u64be unix epoch + client session ID + 8B + padding length + u16be + padding + variable + ATYP + 1B + address + variable + port + u16be + + +``` HeaderTypeClientPacket = 0 HeaderTypeServerPacket = 1 ``` @@ -279,28 +437,98 @@ A UDP packet starts with the random nonce, followed by an encrypted body. The se The same sliding window filter is used for replay protection. It is not necessary to check for repeated nonce. -``` -Packet: -+-------+---------------------------+ -| nonce | encrypted body | -+-------+---------------------------+ -| 24B | variable length + 16B tag | -+-------+---------------------------+ - -Client-to-server message header: -+-------------------+------------------+------+------------------+----------------+----------+------+----------+-------+ -| client session ID | client packet ID | type | timestamp | padding length | padding | ATYP | address | port | -+-------------------+------------------+------+------------------+----------------+----------+------+----------+-------+ -| 8B | u64be | 1B | u64be unix epoch | u16be | variable | 1B | variable | u16be | -+-------------------+------------------+------+------------------+----------------+----------+------+----------+-------+ - -Server-to-client message header: -+-------------------+------------------+------+------------------+-------------------+----------------+----------+------+----------+-------+ -| server session ID | server packet ID | type | timestamp | client session ID | padding length | padding | ATYP | address | port | -+-------------------+------------------+------+------------------+-------------------+----------------+----------+------+----------+-------+ -| 8B | u64be | 1B | u64be unix epoch | 8B | u16be | variable | 1B | variable | u16be | -+-------------------+------------------+------+------------------+-------------------+----------------+----------+------+----------+-------+ -``` +Packet: + + + + + + + + + nonce + 24B + encrypted body + variable length + 16B tag + + + +Client-to-server message header: + + + + + + + + + + + + + + + + client session ID + 8B + client packet ID + u64be + type + 1B + timestamp + u64be unix epoch + padding length + u16be + padding + variable + ATYP + 1B + address + variable + port + u16be + + + +Server-to-client message header: + + + + + + + + + + + + + + + + + server session ID + 8B + server packet ID + u64be + type + 1B + timestamp + u64be unix epoch + client session ID + 8B + padding length + u16be + padding + variable + ATYP + 1B + address + variable + port + u16be + + ## Acknowledgement diff --git a/docs/doc/sip023.md b/docs/doc/sip023.md index b278404b..0248f30a 100644 --- a/docs/doc/sip023.md +++ b/docs/doc/sip023.md @@ -30,19 +30,46 @@ When iPSKs are used, the separate header MUST be encrypted with the first iPSK. ## Scenarios -``` - client0 >---+ -(iPSK0:iPSK1:uPSK0) \ - \ - client1 >------\ +---> server0 [iPSK1] -(iPSK0:iPSK1:uPSK1) \ / [uPSK0, uPSK1, uPSK2] - >-> relay0 [iPSK0] >-< - client2 / [iPSK1, uPSK3] \ -(iPSK0:iPSK1:uPSK2) >------/ +---> server1 [uPSK3] - / - client3 / - (iPSK0:uPSK3) >---+ -``` + + + + + + + + + + + + + + + + + + + + + + + client0 + (iPSK0:iPSK1:uPSK0) + client1 + (iPSK0:iPSK1:uPSK1) + client2 + (iPSK0:iPSK1:uPSK2) + client3 + (iPSK0:uPSK3) + relay0 + [iPSK0] + [iPSK1, uPSK3] + server0 + [iPSK1] + [uPSK0, uPSK1, uPSK2] + server1 + [uPSK3] + + A set of PSKs, delimited by `:`, are assigned to each client. To send a request, a client MUST generate one identity header for each iPSK. diff --git a/docs/doc/what-is-shadowsocks.md b/docs/doc/what-is-shadowsocks.md index 14382286..1b04cf1f 100644 --- a/docs/doc/what-is-shadowsocks.md +++ b/docs/doc/what-is-shadowsocks.md @@ -2,9 +2,29 @@ Shadowsocks is a secure split proxy loosely based on [SOCKS5](https://tools.ietf.org/html/rfc1928). -``` -client <---> ss-local <--[encrypted]--> ss-remote <---> target -``` + + + + + + + + + + + + + + + + + client + ss-local + ss-remote + target + encrypted + + The Shadowsocks local component (ss-local) acts like a traditional SOCKS5 server and provides proxy service to clients. It encrypts and forwards data streams and packets from the client to the Shadowsocks remote component (ss-remote), which decrypts and forwards to the target. Replies from target are similarly encrypted and relayed by ss-remote back to ss-local, which decrypts and eventually returns to the original client. @@ -13,9 +33,21 @@ The Shadowsocks local component (ss-local) acts like a traditional SOCKS5 server Addresses used in Shadowsocks follow the [SOCKS5 address format](https://tools.ietf.org/html/rfc1928#section-5): -``` -[1-byte type][variable-length host][2-byte port] -``` + + + + + + + + type + 1B + host + variable + port + 2B + + The following address types are defined: @@ -30,9 +62,16 @@ The port number is a 2-byte big-endian unsigned integer. ss-local initiates a TCP connection to ss-remote by sending an encrypted data stream starting with the target address followed by payload data. The exact encryption scheme differs depending on the cipher used. -``` -[target address][payload] -``` + + + + + + + target address + payload + + ss-remote receives the encrypted data stream, decrypts and parses the leading target address. It then establishes a new TCP connection to the target and forwards payload data to it. ss-remote receives reply from the target, encrypts and forwards it back to the ss-local, until ss-local disconnects. @@ -42,14 +81,28 @@ For better obfuscation purposes, both local and remote SHOULD send the handshake ss-local sends an encrypted data packet containing the target address and payload to ss-remote. -``` -[target address][payload] -``` + + + + + + + target address + payload + + Upon receiving the encrypted packet, ss-remote decrypts and parses the target address. It then sends a new data packet containing only the payload to the target. ss-remote receives data packets back from target and prepends the target address to the payload in each packet, then sends encrypted copies back to ss-local. -``` -[target address][payload] -``` + + + + + + + target address + payload + + Essentially, ss-remote is performing Network Address Translation for ss-local. \ No newline at end of file From 521ca045171b3cd56f0dcbe352dbaeb953921403 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 14 Apr 2026 16:16:58 +0800 Subject: [PATCH 2/3] Fix SVG double-stroked dividers and edge clipping Replace adjacent elements with a single outer plus internal dividers to avoid rendering shared borders twice, and inset the length/payload chunk rects by 0.5 so strokes sit fully inside the viewBox. Co-Authored-By: Claude Opus 4.6 --- docs/doc/aead.md | 14 +++++++------- docs/doc/sip022.md | 8 ++++---- docs/doc/what-is-shadowsocks.md | 18 +++++++++--------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/doc/aead.md b/docs/doc/aead.md index cf032d8c..ced8ea1a 100644 --- a/docs/doc/aead.md +++ b/docs/doc/aead.md @@ -53,10 +53,10 @@ An AEAD encrypted TCP stream starts with a randomly generated salt to derive the - - - - + + + + encrypted payload length @@ -77,9 +77,9 @@ An AEAD encrypted UDP packet has the following structure - - - + + + salt diff --git a/docs/doc/sip022.md b/docs/doc/sip022.md index f488d5e3..dcd3cb99 100644 --- a/docs/doc/sip022.md +++ b/docs/doc/sip022.md @@ -84,12 +84,12 @@ A length chunk is a 16-bit big-endian unsigned integer that describes the payloa A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The 0x3FFF (16383) length cap in Shadowsocks AEAD does not apply to this edition. - + - + - - + + length chunk diff --git a/docs/doc/what-is-shadowsocks.md b/docs/doc/what-is-shadowsocks.md index 1b04cf1f..60f61c6c 100644 --- a/docs/doc/what-is-shadowsocks.md +++ b/docs/doc/what-is-shadowsocks.md @@ -35,9 +35,9 @@ Addresses used in Shadowsocks follow the [SOCKS5 address format](https://tools.i - - - + + + type @@ -64,8 +64,8 @@ ss-local initiates a TCP connection to ss-remote by sending an encrypted data st - - + + target address @@ -83,8 +83,8 @@ ss-local sends an encrypted data packet containing the target address and payloa - - + + target address @@ -96,8 +96,8 @@ Upon receiving the encrypted packet, ss-remote decrypts and parses the target ad - - + + target address From 95a6aabc5fa8f5b9d640f1528b0ec3d1e88764f4 Mon Sep 17 00:00:00 2001 From: Max Lv Date: Tue, 14 Apr 2026 16:36:28 +0800 Subject: [PATCH 3/3] Make SVG diagrams responsive Add width:100% and height:auto so SVGs scale to the container instead of defaulting to the ~300px intrinsic size. Cap each at its viewBox width so narrow diagrams don't stretch past their natural size on wide viewports. Co-Authored-By: Claude Opus 4.6 --- docs/doc/aead.md | 4 ++-- docs/doc/sip003.md | 2 +- docs/doc/sip022.md | 26 +++++++++++++------------- docs/doc/sip023.md | 2 +- docs/doc/what-is-shadowsocks.md | 10 +++++----- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/doc/aead.md b/docs/doc/aead.md index ced8ea1a..37656e79 100644 --- a/docs/doc/aead.md +++ b/docs/doc/aead.md @@ -51,7 +51,7 @@ AE_decrypt(key, nonce, ciphertext, tag) => message An AEAD encrypted TCP stream starts with a randomly generated salt to derive the per-session subkey, followed by any number of encrypted chunks. Each chunk has the following structure: - + @@ -75,7 +75,7 @@ The first AEAD encrypt/decrypt operation uses a counting nonce starting from 0. An AEAD encrypted UDP packet has the following structure - + diff --git a/docs/doc/sip003.md b/docs/doc/sip003.md index 1e80cc20..dd00b6e8 100644 --- a/docs/doc/sip003.md +++ b/docs/doc/sip003.md @@ -4,7 +4,7 @@ The plugin of shadowsocks is very similar to the [Pluggable Transport](https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt) plugins from Tor project. Unlike the SOCKS5 proxy design in PT, every SIP003 plugin works as a tunnel (or called local port forwarding). This design aims to avoid per-connection arguments in PT, leading to much easier implementation. - + diff --git a/docs/doc/sip022.md b/docs/doc/sip022.md index dcd3cb99..f6b203fa 100644 --- a/docs/doc/sip022.md +++ b/docs/doc/sip022.md @@ -84,7 +84,7 @@ A length chunk is a 16-bit big-endian unsigned integer that describes the payloa A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The 0x3FFF (16383) length cap in Shadowsocks AEAD does not apply to this edition. - + @@ -101,7 +101,7 @@ A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The **Request stream:** - + @@ -129,7 +129,7 @@ A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The **Response stream:** - + @@ -159,7 +159,7 @@ A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The Request fixed-length header: - + @@ -178,7 +178,7 @@ A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The Request variable-length header: - + @@ -206,7 +206,7 @@ A payload chunk can have up to 0xFFFF (65535) bytes of unencrypted payload. The Response fixed-length header: - + @@ -299,7 +299,7 @@ A UDP packet consists of a separate header and an AEAD-encrypted body. The separ Packet: - + @@ -315,7 +315,7 @@ A UDP packet consists of a separate header and an AEAD-encrypted body. The separ Separate header: - + @@ -339,7 +339,7 @@ The main header, or message header, is the header at the start of the body. The Client-to-server message header: - + @@ -370,7 +370,7 @@ The main header, or message header, is the header at the start of the body. The Server-to-client message header: - + @@ -439,7 +439,7 @@ The same sliding window filter is used for replay protection. It is not necessar Packet: - + @@ -455,7 +455,7 @@ The same sliding window filter is used for replay protection. It is not necessar Client-to-server message header: - + @@ -492,7 +492,7 @@ The same sliding window filter is used for replay protection. It is not necessar Server-to-client message header: - + diff --git a/docs/doc/sip023.md b/docs/doc/sip023.md index 0248f30a..98ba512b 100644 --- a/docs/doc/sip023.md +++ b/docs/doc/sip023.md @@ -30,7 +30,7 @@ When iPSKs are used, the separate header MUST be encrypted with the first iPSK. ## Scenarios - + diff --git a/docs/doc/what-is-shadowsocks.md b/docs/doc/what-is-shadowsocks.md index 60f61c6c..489256be 100644 --- a/docs/doc/what-is-shadowsocks.md +++ b/docs/doc/what-is-shadowsocks.md @@ -2,7 +2,7 @@ Shadowsocks is a secure split proxy loosely based on [SOCKS5](https://tools.ietf.org/html/rfc1928). - + @@ -33,7 +33,7 @@ The Shadowsocks local component (ss-local) acts like a traditional SOCKS5 server Addresses used in Shadowsocks follow the [SOCKS5 address format](https://tools.ietf.org/html/rfc1928#section-5): - + @@ -62,7 +62,7 @@ The port number is a 2-byte big-endian unsigned integer. ss-local initiates a TCP connection to ss-remote by sending an encrypted data stream starting with the target address followed by payload data. The exact encryption scheme differs depending on the cipher used. - + @@ -81,7 +81,7 @@ For better obfuscation purposes, both local and remote SHOULD send the handshake ss-local sends an encrypted data packet containing the target address and payload to ss-remote. - + @@ -94,7 +94,7 @@ ss-local sends an encrypted data packet containing the target address and payloa Upon receiving the encrypted packet, ss-remote decrypts and parses the target address. It then sends a new data packet containing only the payload to the target. ss-remote receives data packets back from target and prepends the target address to the payload in each packet, then sends encrypted copies back to ss-local. - +