Skip to content

feat: add Snell v3–v6 client outbound#33

Merged
dyhkwong merged 15 commits into
ExclaveNetwork:mainfrom
Minis233:feature/snell-support
Jul 11, 2026
Merged

feat: add Snell v3–v6 client outbound#33
dyhkwong merged 15 commits into
ExclaveNetwork:mainfrom
Minis233:feature/snell-support

Conversation

@Minis233

Copy link
Copy Markdown

Summary

Add Snell client outbound to exclave-core, covering v3–v6.

  • Protocol stack adapted from SagerNet/sing-snell (same as sing-box testing branch), inlined under proxy/snell/internal/singsnell so the module stays self-contained.
  • JSON protocol name: "snell"
  • Features: TCP CONNECT, UDP-over-TCP, obfs none|http|tls, connection reuse, Snell v6 modes default|unshaped|unsafe-raw
  • Conf loader: infra/conf/v4/snell.go
  • Registered in main/distro/all and outboundConfigLoader

Settings example

{
  "protocol": "snell",
  "settings": {
    "address": "1.2.3.4",
    "port": 44046,
    "psk": "secret",
    "obfs": "tls",
    "obfsHost": "www.bing.com",
    "version": 6,
    "reuse": true,
    "mode": "default"
  }
}

Notes

  • Snell carries its own AEAD; do not enable streamSettings TLS. Use obfs: "tls" for the fake TLS handshake.
  • Inlined against exclave-core’s current sing v0.8.11 (vectorised/packet-batch creator APIs from newer sing are stubbed; wire protocol is intact).
  • Companion Android UI PR will follow against ExclaveNetwork/Exclave.

Test plan

  • Built via forked Exclave debug workflow with this core branch
  • Real-device connect verified by reporter (Snell server reachable)
  • Review: TCP + UDP with snell-server v4/v5
  • Review: TCP + UDP with official Snell v6 (default mode)
  • Review: obfs none / http / tls

Minis233 added 13 commits July 10, 2026 10:49
Add an unofficial Snell v4/v5 outbound for exclave-core, covering TCP
CONNECT, UDP-over-TCP, optional http/tls obfs, and connection reuse.

Protocol code is adapted from missuo/opensnell (GPL-3.0) and vendored
under proxy/snell/internal so the module stays self-contained.

Snell v6 is intentionally not supported (deployment-unique framing).
Replace the OpenSnell-based v4/v5-only stack with sagernet/sing-snell,
which implements Snell v6 (ChaCha20-Poly1305 AEAD, deployment-unique
framing, default/unshaped/unsafe-raw modes) as well as v3–v5.

Config adds obfsHost and mode fields for v6.
Remote sagernet/sing-snell pulled sing v0.8.12 which conflicts with
exclave-core's utls (HandshakeTimeout). Vendor sing-snell under
proxy/snell/sing-snell and require sing v0.8.11 for a consistent build.
Import SagerNet/sing-snell sources into proxy/snell/internal/singsnell
(same module, no nested go.mod) and rewrite the outbound to match the
current ClientOptions / DialPacketConn APIs used by sing-box testing.

Supports Snell v4 (and 3/5 via v4 client) and official Snell v6.
exclave-core is on sing v0.8.11. Drop VectorisedWriteCreator /
PacketBatch* paths and server-only reuse types from inlined sing-snell
so the client builds cleanly while keeping Snell v4/v6 wire support.
Clean re-import of SagerNet/sing-snell client sources plus minimal
compat stubs so CreateVectorisedWriter/PacketBatch* APIs degrade
gracefully on sing v0.8.11. Server-only paths removed.
Restore RecordWriter.CreateVectorisedWriterFor, stub unsupported
vectorised creators for sing v0.8.11, and replace NewBufferSize with
buf.NewSize.
@dyhkwong

Copy link
Copy Markdown
Collaborator
  • Import sing-snell directly. It is okay to update sing to an unstable version.
  • Why did you add Snell v3? I don't think it is supported. Please correct it.
  • Option field paring should be as strict as possible. For example, version should accept 4 and 6 only, obfs doesn't need to be case-sensitive, and don't treat "off" as the alias of "none". Don't allow non-empty v4-only fields in v6 and vise versa.
  • Rename obfs to obfsMode.
  • The reader, ok := link.Reader.(buf.TimeoutReader) logic is for server-speaks-first protocols. Please check if server-speaks-first protocols work without that logic. Don't add that logic unless it is necessary.
  • userPSK is sing-box's private extension and only compatible with sing-box server. Please add it for now. Let's defer considering how to deal with it.
  • Does Snell server accept empty PSK?
  • For UDP, Snell protocol only supports replying with IP address. For UDP domain address destination. if sing-snell does not handle this, we may need to handle this ourselves.
  • Snell v6 is still in beta. Should we wait for the stable version?

- Import github.com/sagernet/sing-snell directly; bump sing to the
  unstable version required by sing-snell (add HandshakeTimeout to
  singbridge TLS wrapper).
- Support versions 4 and 6 only (drop v3/v5).
- Rename JSON field obfs → obfsMode; strict exact-match parsing
  (none|http|tls; no "off" alias; case-sensitive).
- Reject non-empty v4-only fields on v6 and vice versa.
- Add userPSK (sing-box private extension; optional).
- Allow empty PSK (servers may accept it).
- Remove server-speaks-first first-payload logic (Snell is client-first).
- Resolve UDP domain destinations to IP before write (Snell UDP has no
  domain ATYP).

Snell v6 remains beta upstream; exposed with that understanding.
@Minis233

Copy link
Copy Markdown
Author

Thanks for the review — addressed in the latest commit:

  1. Import sing-snell directly and bump github.com/sagernet/sing to the unstable revision required by sing-snell. Added HandshakeTimeout / SetHandshakeTimeout no-ops on singbridge.tlsConfigWrapper for the new sing/common/tls.Config surface.
  2. Versions: 4 and 6 only (removed v3/v5).
  3. Strict option parsing
    • version: 4 | 6 only (0 → 4)
    • obfsMode: exact none | http | tls (empty → none; no "off" alias; case-sensitive)
    • mode (v6): exact default | unshaped | unsafe-raw
    • Reject non-empty v4-only fields on v6 (obfsHost, non-none obfsMode) and v6-only mode on v4
  4. Renamed JSON field obfsobfsMode.
  5. Removed server-speaks-first first-payload logic — Snell is client-speaks-first (CONNECT header before app data).
  6. Added userPSK (sing-box private extension / UserKey); optional for now.
  7. Empty PSK allowed — sing-snell does not reject empty PSK; key material is Argon2id-derived and servers may accept empty.
  8. UDP domains: Snell UDP ATYP is IPv4/IPv6 only. Outbound now resolves FQDN destinations (session resolver if present, else system) to IP before WritePacket.
  9. v6 beta: still exposed (marked beta in the app UI). Happy to gate it behind a build flag or drop from UI until sing-snell/sing-box call it stable — preference?

Companion app PR: ExclaveNetwork/Exclave#446

Todo: UDP domain address
@dyhkwong dyhkwong merged commit 7feccd4 into ExclaveNetwork:main Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants