@@ -46,6 +46,7 @@ import (
4646 "github.com/pion/dtls/v3/pkg/crypto/selfsign"
4747 "github.com/pion/rtp"
4848 "github.com/pion/srtp/v3"
49+ M "github.com/sagernet/sing/common/metadata"
4950)
5051
5152// ============================================================================
@@ -152,14 +153,27 @@ const (
152153// Dial — client-side
153154// ============================================================================
154155
155- // Dial opens a lanturn client connection. The returned net.Conn carries
156- // caller bytes through the full lanturn stack: caller bytes →
157- // SRTP-paced chunks (Opus profile, 50pps) → AES-128-CM-HMAC-SHA1-80
158- // encryption → DTLS-derived keying → TURN ChannelData → plain UDP/3478.
156+ // Dial opens a lanturn client connection toward `destination`. The
157+ // returned net.Conn carries caller bytes through the full lanturn
158+ // stack: caller bytes → SRTP-paced chunks (Opus profile, 50pps) →
159+ // AES-128-CM-HMAC-SHA1-80 encryption → DTLS-derived keying → TURN
160+ // ChannelData → plain UDP/3478.
161+ //
162+ // Destination forwarding: the first bytes the egress receives are
163+ // `M.SocksaddrSerializer.WriteAddrPort(destination)` — the sing-box-
164+ // native address-prefix pattern used by trojan / anytls / vmess /
165+ // shadowsocks (NOT a full SOCKS5 handshake, which is heavier and only
166+ // used when the egress is a generic SOCKS5 server like in Unbounded).
167+ // The egress reads the prefix in `Listener.Accept` and dials the
168+ // destination itself; from the caller's perspective the returned conn
169+ // is a transparent byte pipe to `destination`.
159170//
160171// MVP: single session, UDP-only, Opus-only profile. Caller redials
161172// to start a fresh session.
162- func Dial (ctx context.Context , cfg ClientConfig ) (net.Conn , error ) {
173+ func Dial (ctx context.Context , cfg ClientConfig , destination M.Socksaddr ) (net.Conn , error ) {
174+ if ! destination .IsValid () {
175+ return nil , fmt .Errorf ("lanturn: invalid destination %s" , destination )
176+ }
163177 if len (cfg .CoturnEndpoints ) == 0 {
164178 return nil , fmt .Errorf ("lanturn: no coturn endpoints configured" )
165179 }
@@ -270,22 +284,47 @@ func Dial(ctx context.Context, cfg ClientConfig) (net.Conn, error) {
270284 return nil , fmt .Errorf ("lanturn: SRTP TX context: %w" , err )
271285 }
272286
287+ // Encode the destination prefix that the egress will read first.
288+ // Sing-box-native serializer matches what trojan / anytls / vmess /
289+ // shadowsocks use — 1B address-type + addr + 2B port, no SOCKS5
290+ // ceremony.
291+ prefix := encodeDestination (destination )
292+
273293 // Build the streaming-chunker on top of the SRTP context.
274- conn := newClientConn (ctx , mux , txCtx , alloc , dtlsConn , logf )
294+ conn := newClientConn (ctx , mux , txCtx , alloc , dtlsConn , logf , prefix )
275295 return conn , nil
276296}
277297
298+ // encodeDestination produces the M.SocksaddrSerializer wire format for
299+ // the given destination — the prefix the egress reads first.
300+ func encodeDestination (dst M.Socksaddr ) []byte {
301+ buf := make ([]byte , 0 , M .SocksaddrSerializer .AddrPortLen (dst ))
302+ w := bytesWriter {buf : & buf }
303+ _ = M .SocksaddrSerializer .WriteAddrPort (& w , dst )
304+ return buf
305+ }
306+
307+ type bytesWriter struct { buf * []byte }
308+
309+ func (w * bytesWriter ) Write (p []byte ) (int , error ) {
310+ * w .buf = append (* w .buf , p ... )
311+ return len (p ), nil
312+ }
313+
278314// ============================================================================
279315// Listen — server-side (Lantern egress)
280316// ============================================================================
281317
282- // Listen opens a lanturn server listener. Each Accept returns a
283- // net.Conn for one inbound client session.
318+ // Listen opens a lanturn server listener. The returned *Listener's
319+ // Accept blocks until a new client session arrives and returns the
320+ // per-session net.Conn along with the destination the client wants
321+ // traffic forwarded to (read from the M.SocksaddrSerializer prefix
322+ // the client sends as the first inner-stream bytes).
284323//
285324// MVP: accepts one session at a time (sequential). Production would
286325// concurrently accept multiple via the egressDemuxer pattern from
287326// cmd/lanturn-phase2/main.go.
288- func Listen (cfg ServerConfig ) (net. Listener , error ) {
327+ func Listen (cfg ServerConfig ) (* Listener , error ) {
289328 if cfg .ListenUDP == "" {
290329 return nil , fmt .Errorf ("lanturn: ListenUDP required" )
291330 }
@@ -297,22 +336,35 @@ func Listen(cfg ServerConfig) (net.Listener, error) {
297336 if err != nil {
298337 return nil , fmt .Errorf ("lanturn: ListenPacket: %w" , err )
299338 }
300- return & listener {pc : pc , logf : logf , closed : make (chan struct {})}, nil
339+ return & Listener {pc : pc , logf : logf , closed : make (chan struct {})}, nil
301340}
302341
303- type listener struct {
342+ // Listener is the egress-side accept loop for lanturn client sessions.
343+ // Note that this is NOT a net.Listener — Accept returns three values
344+ // (conn, destination, err) so the egress process can dial the
345+ // destination itself before bridging bytes. lanturn's egress is not a
346+ // generic listener-pattern consumer; it's specifically the
347+ // "address-prefix + io.Copy" SOCKS-shaped proxy described in the
348+ // design doc §4.
349+ type Listener struct {
304350 pc net.PacketConn
305351 logf func (format string , args ... any )
306352 closeOnce sync.Once
307353 closed chan struct {}
308354}
309355
310- func (l * listener ) Accept () (net.Conn , error ) {
356+ // Accept blocks until a new lanturn client session arrives, runs the
357+ // inner DTLS-SRTP handshake, reads the destination prefix the client
358+ // sends per M.SocksaddrSerializer, and returns the session conn + the
359+ // destination + nil error. The egress is expected to immediately dial
360+ // `destination` (via whatever direct dialer it's configured with) and
361+ // io.Copy bidirectionally between that dial and the returned conn.
362+ func (l * Listener ) Accept () (net.Conn , M.Socksaddr , error ) {
311363 // Wait for first packet from a new source — this initiates a session.
312364 buf := make ([]byte , 4096 )
313365 n , srcAddr , err := l .pc .ReadFrom (buf )
314366 if err != nil {
315- return nil , err
367+ return nil , M. Socksaddr {}, err
316368 }
317369 l .logf ("lanturn: accepting session from %s (first pkt %dB leading=%#02x)" , srcAddr , n , buf [0 ])
318370
@@ -324,7 +376,7 @@ func (l *listener) Accept() (net.Conn, error) {
324376 cert , err := selfsign .GenerateSelfSigned ()
325377 if err != nil {
326378 mux .Close ()
327- return nil , fmt .Errorf ("lanturn: cert: %w" , err )
379+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: cert: %w" , err )
328380 }
329381 dtlsCfg := & dtls.Config {
330382 Certificates : []tls.Certificate {cert },
@@ -335,48 +387,61 @@ func (l *listener) Accept() (net.Conn, error) {
335387 dtlsConn , err := dtls .Server (mux .dtlsPacketConn (srcAddr ), srcAddr , dtlsCfg )
336388 if err != nil {
337389 mux .Close ()
338- return nil , fmt .Errorf ("lanturn: DTLS server setup: %w" , err )
390+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: DTLS server setup: %w" , err )
339391 }
340392 if err := dtlsConn .Handshake (); err != nil {
341393 dtlsConn .Close ()
342394 mux .Close ()
343- return nil , fmt .Errorf ("lanturn: DTLS handshake: %w" , err )
395+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: DTLS handshake: %w" , err )
344396 }
345397 l .logf ("lanturn: inner DTLS handshake OK" )
346398
347399 state , ok := dtlsConn .ConnectionState ()
348400 if ! ok {
349401 dtlsConn .Close ()
350402 mux .Close ()
351- return nil , fmt .Errorf ("lanturn: no DTLS connection state" )
403+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: no DTLS connection state" )
352404 }
353405 keyMat , err := state .ExportKeyingMaterial ("EXTRACTOR-dtls_srtp" , nil , srtpKeyMatLen )
354406 if err != nil {
355407 dtlsConn .Close ()
356408 mux .Close ()
357- return nil , fmt .Errorf ("lanturn: ExportKeyingMaterial: %w" , err )
409+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: ExportKeyingMaterial: %w" , err )
358410 }
359411 clientKey , clientSalt , _ , _ := splitSRTPKeys (keyMat )
360412 rxCtx , err := srtp .CreateContext (clientKey , clientSalt , srtpProfile )
361413 if err != nil {
362414 dtlsConn .Close ()
363415 mux .Close ()
364- return nil , fmt .Errorf ("lanturn: SRTP RX context: %w" , err )
416+ return nil , M. Socksaddr {}, fmt .Errorf ("lanturn: SRTP RX context: %w" , err )
365417 }
366418
367419 conn := newServerConn (mux , rxCtx , dtlsConn , l .logf )
368- return conn , nil
420+
421+ // Read the destination prefix the client sent as the first bytes
422+ // of the inner stream (M.SocksaddrSerializer). Times out if the
423+ // client doesn't send it promptly — that's a malformed session.
424+ _ = conn .SetReadDeadline (time .Now ().Add (10 * time .Second ))
425+ destination , err := M .SocksaddrSerializer .ReadAddrPort (conn )
426+ if err != nil {
427+ conn .Close ()
428+ return nil , M.Socksaddr {}, fmt .Errorf ("lanturn: read destination prefix: %w" , err )
429+ }
430+ _ = conn .SetReadDeadline (time.Time {})
431+ l .logf ("lanturn: session destination = %s" , destination )
432+
433+ return conn , destination , nil
369434}
370435
371- func (l * listener ) Close () error {
436+ func (l * Listener ) Close () error {
372437 l .closeOnce .Do (func () {
373438 close (l .closed )
374439 l .pc .Close ()
375440 })
376441 return nil
377442}
378443
379- func (l * listener ) Addr () net.Addr {
444+ func (l * Listener ) Addr () net.Addr {
380445 return l .pc .LocalAddr ()
381446}
382447
@@ -423,6 +488,12 @@ type clientConn struct {
423488
424489 writeBuf chan []byte // bounded buffer of pending caller bytes
425490
491+ // destinationPrefix is the M.SocksaddrSerializer wire-encoded
492+ // destination address that the egress reads first. The txLoop
493+ // seeds its `pending` buffer with these bytes before any caller
494+ // writes drain in.
495+ destinationPrefix []byte
496+
426497 // rxPipe is the byte stream the caller reads from. Filled by a
427498 // goroutine reading SRTP packets, draining real-data payloads.
428499 rxPipe * bytesPipe
@@ -432,7 +503,7 @@ type clientConn struct {
432503 rxCtx * srtp.Context
433504}
434505
435- func newClientConn (ctx context.Context , mux * packetMux , txCtx * srtp.Context , alloc * turn.Allocation , dtlsConn * dtls.Conn , logf func (format string , args ... any )) * clientConn {
506+ func newClientConn (ctx context.Context , mux * packetMux , txCtx * srtp.Context , alloc * turn.Allocation , dtlsConn * dtls.Conn , logf func (format string , args ... any ), destinationPrefix [] byte ) * clientConn {
436507 // We'll need an RX SRTP context to decrypt the egress's responses.
437508 // Egress side derives keys with the SAME EXTRACTOR layout; the
438509 // "server write" half of our extracted keying material is the
@@ -444,17 +515,18 @@ func newClientConn(ctx context.Context, mux *packetMux, txCtx *srtp.Context, all
444515
445516 cctx , cancel := context .WithCancel (ctx )
446517 c := & clientConn {
447- ctx : cctx ,
448- cancel : cancel ,
449- mux : mux ,
450- txCtx : txCtx ,
451- rxCtx : rxCtx ,
452- alloc : alloc ,
453- dtlsConn : dtlsConn ,
454- logf : logf ,
455- writeBuf : make (chan []byte , 64 ),
456- rxPipe : newBytesPipe (),
457- closed : make (chan struct {}),
518+ ctx : cctx ,
519+ cancel : cancel ,
520+ mux : mux ,
521+ txCtx : txCtx ,
522+ rxCtx : rxCtx ,
523+ alloc : alloc ,
524+ dtlsConn : dtlsConn ,
525+ logf : logf ,
526+ writeBuf : make (chan []byte , 64 ),
527+ rxPipe : newBytesPipe (),
528+ closed : make (chan struct {}),
529+ destinationPrefix : destinationPrefix ,
458530 }
459531 go c .txLoop ()
460532 go c .rxLoop ()
@@ -467,7 +539,10 @@ func (c *clientConn) txLoop() {
467539 ts := randUint32 ()
468540 seq := uint16 (randUint32 () & 0xFFFF )
469541
470- pending := make ([]byte , 0 , 4096 )
542+ // Seed pending with the destination prefix so the very first
543+ // real-data bytes the egress receives are the serialized address.
544+ pending := make ([]byte , 0 , 4096 + len (c .destinationPrefix ))
545+ pending = append (pending , c .destinationPrefix ... )
471546 ticker := time .NewTicker (opusFrameMs * time .Millisecond )
472547 defer ticker .Stop ()
473548
0 commit comments