1
1
package client
2
2
3
3
import (
4
+ "bytes"
4
5
"crypto/cipher"
6
+ "encoding/binary"
5
7
"errors"
8
+ "fmt"
6
9
"io"
10
+ "math"
7
11
"math/rand"
8
12
"net"
9
13
"time"
@@ -30,7 +34,8 @@ const (
30
34
// was ~1 ms.) If no client payload is received by this time, we connect without it.
31
35
helloWait = 100 * time .Millisecond
32
36
33
- ShadowsocksPacketConnFrontReserve = 24 + 8 + 8 + 1 + 8 + 2 + ss .MaxPaddingLength + socks .MaxAddrLen
37
+ // Defines the space to reserve in front of a slice for making an outgoing Shadowsocks client message.
38
+ ShadowsocksPacketConnFrontReserve = 24 + ss .UDPClientMessageHeaderFixedLength + ss .MaxPaddingLength + socks .MaxAddrLen
34
39
)
35
40
36
41
var (
@@ -229,8 +234,8 @@ type ShadowsocksPacketConn interface {
229
234
// WriteToZeroCopy minimizes copying by requiring that enough space is reserved in b.
230
235
// The socks address is still being copied into the buffer.
231
236
//
232
- // You should reserve 24 + 8 + 8 + 1 + 8 + 2 + ss.MaxPaddingLength + socks.MaxAddrLen in the beginning,
233
- // and cipher.TagSize() in the end.
237
+ // You should reserve 24 + ss.UDPClientMessageHeaderFixedLength + ss.MaxPaddingLength + socks.MaxAddrLen
238
+ // in the beginning, and cipher.TagSize() in the end.
234
239
//
235
240
// start points to where the actual payload (excluding header) starts.
236
241
// length is payload length.
@@ -241,41 +246,64 @@ type packetConn struct {
241
246
* net.UDPConn
242
247
cipher * ss.Cipher
243
248
244
- // sid stores session ID for Shadowsocks 2022 Edition methods.
245
- sid []byte
249
+ // csid stores client session ID for Shadowsocks 2022 Edition methods.
250
+ csid []byte
246
251
247
- // pid stores packet ID for Shadowsocks 2022 Edition methods.
248
- pid uint64
252
+ // cpid stores client packet ID for Shadowsocks 2022 Edition methods.
253
+ cpid uint64
249
254
250
- // Provides sliding window replay protection.
251
- filter * wgreplay.Filter
255
+ // caead is the client session's AEAD cipher.
256
+ // Only used by 2022-blake3-aes-256-gcm.
257
+ // Initialized with client session subkey.
258
+ caead cipher.AEAD
259
+
260
+ // cssid stores the current server session ID for Shadowsocks 2022 Edition methods.
261
+ cssid []byte
262
+
263
+ // csaead is the current server session's AEAD cipher.
264
+ // Only used by 2022-blake3-aes-256-gcm.
265
+ // Initialized with the current server session subkey.
266
+ csaead cipher.AEAD
252
267
268
+ // csfilter is the current server session's sliding window filter.
269
+ csfilter * wgreplay.Filter
270
+
271
+ // ossid stores the old server session ID for Shadowsocks 2022 Edition methods.
272
+ ossid []byte
273
+
274
+ // osaead is the oldl server session's AEAD cipher.
253
275
// Only used by 2022-blake3-aes-256-gcm.
254
- // Initialized with session subkey.
255
- aead cipher.AEAD
276
+ // Initialized with the old server session subkey.
277
+ osaead cipher.AEAD
278
+
279
+ // osfilter is the old server session's sliding window filter.
280
+ osfilter * wgreplay.Filter
281
+
282
+ // osLastSeenTime is the last time when we received a packet from the old server session.
283
+ osLastSeenTime time.Time
256
284
}
257
285
258
286
func newPacketConn (proxyConn * net.UDPConn , c * ss.Cipher ) (* packetConn , error ) {
259
287
// Random session ID
260
- sid := make ([]byte , 8 )
261
- err := ss .Blake3KeyedHashSaltGenerator .GetSalt (sid )
288
+ csid := make ([]byte , 8 )
289
+ err := ss .Blake3KeyedHashSaltGenerator .GetSalt (csid )
262
290
if err != nil {
263
291
return nil , err
264
292
}
265
293
266
- // Separate header AEAD
267
- var aead cipher.AEAD
268
- aead , err = c .NewAEAD (sid )
294
+ // Separate header client AEAD
295
+ var caead cipher.AEAD
296
+ caead , err = c .NewAEAD (csid )
269
297
if err != nil {
270
298
return nil , err
271
299
}
272
300
273
301
return & packetConn {
274
- UDPConn : proxyConn ,
275
- cipher : c ,
276
- sid : sid ,
277
- filter : & wgreplay.Filter {},
278
- aead : aead ,
302
+ UDPConn : proxyConn ,
303
+ cipher : c ,
304
+ csid : csid ,
305
+ csfilter : & wgreplay.Filter {},
306
+ caead : caead ,
279
307
}, nil
280
308
}
281
309
@@ -303,12 +331,12 @@ func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
303
331
}
304
332
305
333
// Write header
306
- n , err := ss .WriteClientUDPHeader (cipherBuf [headerStart :], cipherConfig , c .sid , c .pid , addr , 1452 )
334
+ n , err := ss .WriteClientUDPHeader (cipherBuf [headerStart :], cipherConfig , c .csid , c .cpid , addr , 1452 )
307
335
if err != nil {
308
336
return 0 , err
309
337
}
310
338
if cipherConfig .IsSpec2022 {
311
- c .pid ++
339
+ c .cpid ++
312
340
}
313
341
314
342
// Copy payload
@@ -319,7 +347,7 @@ func (c *packetConn) WriteTo(b []byte, addr net.Addr) (int, error) {
319
347
320
348
switch {
321
349
case cipherConfig .UDPHasSeparateHeader :
322
- buf , err = ss .PackAesWithSeparateHeader (cipherBuf , cipherBuf [headerStart :headerStart + plaintextLen ], c .cipher , c .aead )
350
+ buf , err = ss .PackAesWithSeparateHeader (cipherBuf , cipherBuf [headerStart :headerStart + plaintextLen ], c .cipher , c .caead )
323
351
default :
324
352
buf , err = ss .Pack (cipherBuf , cipherBuf [headerStart :headerStart + plaintextLen ], c .cipher )
325
353
}
@@ -347,10 +375,10 @@ func (c *packetConn) WriteToZeroCopy(b []byte, start, length int, socksAddr []by
347
375
348
376
switch {
349
377
case cipherConfig .UDPHasSeparateHeader :
350
- headerStart = socksAddrStart - paddingLen - 2 - 8 - 1 - 8 - 8
378
+ headerStart = socksAddrStart - paddingLen - ss . UDPClientMessageHeaderFixedLength
351
379
packetStart = headerStart
352
380
case cipherConfig .IsSpec2022 :
353
- headerStart = socksAddrStart - paddingLen - 2 - 8 - 1 - 8 - 8
381
+ headerStart = socksAddrStart - paddingLen - ss . UDPClientMessageHeaderFixedLength
354
382
packetStart = headerStart - 24
355
383
default :
356
384
headerStart = socksAddrStart
@@ -360,16 +388,16 @@ func (c *packetConn) WriteToZeroCopy(b []byte, start, length int, socksAddr []by
360
388
// Write header
361
389
switch {
362
390
case cipherConfig .IsSpec2022 :
363
- ss .WriteUDPHeader (b [headerStart :], ss .HeaderTypeClientPacket , c .sid , c .pid , nil , socksAddr , paddingLen )
364
- c .pid ++
391
+ ss .WriteUDPHeader (b [headerStart :], ss .HeaderTypeClientPacket , c .csid , c .cpid , nil , nil , socksAddr , paddingLen )
392
+ c .cpid ++
365
393
default :
366
394
copy (b [headerStart :], socksAddr )
367
395
}
368
396
369
397
var buf []byte
370
398
switch {
371
399
case cipherConfig .UDPHasSeparateHeader :
372
- buf , err = ss .PackAesWithSeparateHeader (b [packetStart :], b [headerStart :start + length ], c .cipher , c .aead )
400
+ buf , err = ss .PackAesWithSeparateHeader (b [packetStart :], b [headerStart :start + length ], c .cipher , c .caead )
373
401
default :
374
402
buf , err = ss .Pack (b [packetStart :], b [headerStart :start + length ], c .cipher )
375
403
}
@@ -394,7 +422,7 @@ func (c *packetConn) ReadFrom(b []byte) (int, net.Addr, error) {
394
422
}
395
423
396
424
// Decrypt in-place.
397
- _ , socksAddr , payload , err := ss . UnpackAndValidatePacket ( c . cipher , c . aead , c . filter , c . sid , nil , cipherBuf [:n ])
425
+ _ , socksAddr , payload , err := c . unpackAndValidatePacket ( nil , cipherBuf [:n ])
398
426
if err != nil {
399
427
return 0 , nil , err
400
428
}
@@ -418,8 +446,145 @@ func (c *packetConn) ReadFromZeroCopy(b []byte) (socksAddrStart, payloadStart, p
418
446
return
419
447
}
420
448
421
- socksAddrStart , socksAddr , payload , err := ss . UnpackAndValidatePacket ( c . cipher , c . aead , c . filter , c . sid , nil , b [:n ])
449
+ socksAddrStart , socksAddr , payload , err := c . unpackAndValidatePacket ( nil , b [:n ])
422
450
payloadStart = socksAddrStart + len (socksAddr )
423
451
payloadLength = len (payload )
424
452
return
425
453
}
454
+
455
+ // unpackAndValidatePacket unpacks an encrypted packet, validates the packet,
456
+ // and returns the payload (without header) and the address.
457
+ func (c * packetConn ) unpackAndValidatePacket (dst , src []byte ) (socksAddrStart int , socksAddr socks.Addr , payload []byte , err error ) {
458
+ cipherConfig := c .cipher .Config ()
459
+ var plaintextStart int
460
+ var buf []byte
461
+
462
+ const (
463
+ currentServerSession = iota
464
+ oldServerSession
465
+ newServerSession
466
+ )
467
+
468
+ var sessionStatus int
469
+ var ssid []byte
470
+ var saead cipher.AEAD
471
+ var sfilter * wgreplay.Filter
472
+
473
+ switch {
474
+ case cipherConfig .UDPHasSeparateHeader :
475
+ if dst == nil {
476
+ dst = src
477
+ }
478
+
479
+ // Decrypt separate header
480
+ err = ss .DecryptSeparateHeader (c .cipher , dst , src )
481
+ if err != nil {
482
+ return
483
+ }
484
+
485
+ // Check server session id
486
+ switch {
487
+ case bytes .Equal (c .cssid , dst [:8 ]): // is current server session
488
+ sessionStatus = currentServerSession
489
+ ssid = c .cssid
490
+ saead = c .csaead
491
+ sfilter = c .csfilter
492
+ case bytes .Equal (c .ossid , dst [:8 ]): // is old server session
493
+ sessionStatus = oldServerSession
494
+ ssid = c .ossid
495
+ saead = c .osaead
496
+ sfilter = c .osfilter
497
+ default : // is a new server session
498
+ // When a server session changes, there's a replay window of less than 60 seconds,
499
+ // during which an adversary can replay packets with a valid timestamp from the old session.
500
+ // To protect against such attacks, and to simplify implementation and save resources,
501
+ // we only save information for one previous session.
502
+ //
503
+ // In an unlikely event where the server session changed more than once within 60s,
504
+ // we simply drop new server sessions.
505
+ if time .Since (c .osLastSeenTime ) < 60 * time .Second {
506
+ err = ss .ErrTooManyServerSessions
507
+ return
508
+ }
509
+ sessionStatus = newServerSession
510
+ ssid = dst [:8 ]
511
+ saead , err = c .cipher .NewAEAD (ssid )
512
+ if err != nil {
513
+ return
514
+ }
515
+ // Delay sfilter creation after validation to avoid a possibly unnecessary allocation.
516
+ }
517
+
518
+ // Unpack
519
+ buf , err = ss .UnpackAesWithSeparateHeader (dst , src , nil , c .cipher , saead )
520
+ if err != nil {
521
+ return
522
+ }
523
+
524
+ case cipherConfig .IsSpec2022 :
525
+ plaintextStart , buf , err = ss .Unpack (dst , src , c .cipher )
526
+ if err != nil {
527
+ return
528
+ }
529
+
530
+ // Check server session id
531
+ switch {
532
+ case bytes .Equal (c .cssid , buf [:8 ]): // is current server session
533
+ sessionStatus = currentServerSession
534
+ ssid = c .cssid
535
+ sfilter = c .csfilter
536
+ case bytes .Equal (c .ossid , buf [:8 ]): // is old server session
537
+ sessionStatus = oldServerSession
538
+ ssid = c .ossid
539
+ sfilter = c .osfilter
540
+ default : // is a new server session
541
+ if time .Since (c .osLastSeenTime ) < 60 * time .Second {
542
+ err = ss .ErrTooManyServerSessions
543
+ return
544
+ }
545
+ sessionStatus = newServerSession
546
+ ssid = buf [:8 ]
547
+ // Delay sfilter creation after validation to avoid a possibly unnecessary allocation.
548
+ }
549
+
550
+ default :
551
+ plaintextStart , buf , err = ss .Unpack (dst , src , c .cipher )
552
+ if err != nil {
553
+ return
554
+ }
555
+ }
556
+
557
+ socksAddrStart , socksAddr , payload , err = ss .ParseUDPHeader (buf , ss .HeaderTypeServerPacket , c .csid , cipherConfig )
558
+ if err != nil {
559
+ return
560
+ }
561
+ socksAddrStart += plaintextStart
562
+
563
+ if cipherConfig .IsSpec2022 {
564
+ pid := binary .BigEndian .Uint64 (buf [8 :])
565
+ if sessionStatus == newServerSession {
566
+ sfilter = & wgreplay.Filter {}
567
+ }
568
+ if ! sfilter .ValidateCounter (pid , math .MaxUint64 ) {
569
+ err = fmt .Errorf ("detected replay packet, server session id %v, packet id %d" , ssid , pid )
570
+ return
571
+ }
572
+ switch sessionStatus {
573
+ case oldServerSession :
574
+ // Update old session's last seen time.
575
+ c .osLastSeenTime = time .Now ()
576
+ case newServerSession :
577
+ // Move current to old.
578
+ c .ossid = c .cssid
579
+ c .osaead = c .csaead
580
+ c .osfilter = c .csfilter
581
+ c .osLastSeenTime = time .Now ()
582
+ // Save temporary vars to current.
583
+ c .cssid = ssid
584
+ c .csaead = saead
585
+ c .csfilter = sfilter
586
+ }
587
+ }
588
+
589
+ return
590
+ }
0 commit comments