@@ -31,6 +31,15 @@ const defaultSCTPSrcDstPort = 5000
3131// Use global random generator to properly seed by crypto grade random.
3232var globalMathRandomGenerator = randutil .NewMathRandomGenerator () // nolint:gochecknoglobals
3333
34+ // Generates a non-zero Initiate tag.
35+ func generateInitiateTag () uint32 {
36+ for {
37+ if u := globalMathRandomGenerator .Uint32 (); u != 0 {
38+ return u
39+ }
40+ }
41+ }
42+
3443// Association errors.
3544var (
3645 ErrChunk = errors .New ("abort chunk, with following errors" )
@@ -315,6 +324,12 @@ type Association struct {
315324 tlrStartTime time.Time // time of first recovery RTT
316325}
317326
327+ type snapConfig struct {
328+ // Local and remote SCTP init to use for SNAP
329+ localInit []byte
330+ remoteInit []byte
331+ }
332+
318333// Config collects the arguments to createAssociation construction into
319334// a single structure.
320335type Config struct {
@@ -340,6 +355,9 @@ type Config struct {
340355
341356 // RACK config options
342357 rack rackSettings
358+
359+ // SNAP/sctp-init
360+ snapConfig * snapConfig
343361}
344362
345363// Server accepts a SCTP stream over a conn.
@@ -385,11 +403,37 @@ func createClientWithContext(ctx context.Context, config Config) (*Association,
385403 return createClientWithOptionsWithContext (ctx , config )
386404}
387405
406+ func createSNAPAssociation (config * Config ) (* Association , error ) {
407+ // SNAP, aka sctp-init in the SDP.
408+ remote := & chunkInit {}
409+ err := remote .unmarshal (config .snapConfig .remoteInit )
410+ if err != nil {
411+ return nil , err
412+ }
413+ local := & chunkInit {}
414+ err = local .unmarshal (config .snapConfig .localInit )
415+ if err != nil {
416+ return nil , err
417+ }
418+ assoc := createAssociationFromConfigWithTsn (config , local .initialTSN )
419+ assoc .initWithOutOfBandTokens (local , remote )
420+
421+ return assoc , nil
422+ }
423+
388424func createClientWithOptionsWithContext (ctx context.Context , opts ... ClientOption ) (* Association , error ) {
425+ config , err := buildClientConfig (opts ... )
426+ if err != nil {
427+ return nil , err
428+ }
429+ if config .snapConfig != nil && len (config .snapConfig .remoteInit ) != 0 && len (config .snapConfig .localInit ) != 0 {
430+ return createSNAPAssociation (config )
431+ }
389432 assoc , err := createClientAssociation (opts ... )
390433 if err != nil {
391434 return nil , err
392435 }
436+
393437 assoc .initClient ()
394438
395439 select {
@@ -431,7 +475,7 @@ func createServerAssociation(opts ...ServerOption) (*Association, error) {
431475 return nil , err
432476 }
433477
434- return createAssociationFromConfig (cfg ), nil
478+ return createAssociationFromConfig (cfg )
435479}
436480
437481func (a * Association ) initServer () {
@@ -512,7 +556,7 @@ func createClientAssociation(opts ...ClientOption) (*Association, error) {
512556 return nil , err
513557 }
514558
515- return createAssociationFromConfig (cfg ), nil
559+ return createAssociationFromConfig (cfg )
516560}
517561
518562func (a * Association ) initClient () {
@@ -589,6 +633,8 @@ func (c Config) applyClient(cfg *Config) error { //nolint:dupl,cyclop
589633
590634 cfg .rack = c .rack
591635
636+ cfg .snapConfig = c .snapConfig
637+
592638 return nil
593639}
594640
@@ -614,7 +660,13 @@ func buildClientConfig(opts ...ClientOption) (*Config, error) {
614660 return cfg , nil
615661}
616662
617- func createAssociationFromConfig (cfg * Config ) * Association {
663+ func createAssociationFromConfig (cfg * Config ) (* Association , error ) {
664+ tsn := globalMathRandomGenerator .Uint32 ()
665+
666+ return createAssociationFromConfigWithTsn (cfg , tsn ), nil
667+ }
668+
669+ func createAssociationFromConfigWithTsn (cfg * Config , tsn uint32 ) * Association {
618670 maxReceiveBufferSize := cfg .MaxReceiveBufferSize
619671 if maxReceiveBufferSize == 0 {
620672 maxReceiveBufferSize = initialRecvBufSize
@@ -632,7 +684,6 @@ func createAssociationFromConfig(cfg *Config) *Association {
632684
633685 rtoMax := cfg .RTOMax
634686
635- tsn := globalMathRandomGenerator .Uint32 ()
636687 assoc := & Association {
637688 netConn : cfg .NetConn ,
638689 maxReceiveBufferSize : maxReceiveBufferSize ,
@@ -650,7 +701,7 @@ func createAssociationFromConfig(cfg *Config) *Association {
650701 controlQueue : newControlQueue (),
651702 mtu : mtu ,
652703 maxPayloadSize : mtu - (commonHeaderSize + dataChunkHeaderSize ),
653- myVerificationTag : globalMathRandomGenerator . Uint32 (),
704+ myVerificationTag : generateInitiateTag (),
654705 initialTSN : tsn ,
655706 myNextTSN : tsn ,
656707 myNextRSN : tsn ,
@@ -717,6 +768,43 @@ func createAssociationFromConfig(cfg *Config) *Association {
717768 return assoc
718769}
719770
771+ func (a * Association ) initWithOutOfBandTokens (localInit * chunkInit , remoteInit * chunkInit ) {
772+ a .lock .Lock ()
773+ defer a .lock .Unlock ()
774+
775+ go a .readLoop ()
776+ go a .writeLoop ()
777+
778+ a .payloadQueue .init (remoteInit .initialTSN - 1 )
779+ a .myMaxNumInboundStreams = min16 (localInit .numInboundStreams , remoteInit .numInboundStreams )
780+ a .myMaxNumOutboundStreams = min16 (localInit .numOutboundStreams , remoteInit .numOutboundStreams )
781+ a .setRWND (remoteInit .advertisedReceiverWindowCredit )
782+ a .peerVerificationTag = remoteInit .initiateTag
783+ a .sourcePort = defaultSCTPSrcDstPort
784+ a .destinationPort = defaultSCTPSrcDstPort
785+ for _ , param := range remoteInit .params {
786+ switch v := param .(type ) { // nolint:gocritic
787+ case * paramSupportedExtensions :
788+ for _ , t := range v .ChunkTypes {
789+ if t == ctForwardTSN {
790+ a .log .Debugf ("[%s] use ForwardTSN (on init)" , a .name )
791+ a .useForwardTSN = true
792+ }
793+ }
794+ case * paramZeroChecksumAcceptable :
795+ a .sendZeroChecksum = v .edmid == dtlsErrorDetectionMethod
796+ }
797+ }
798+
799+ if ! a .useForwardTSN {
800+ a .log .Warnf ("[%s] not using ForwardTSN (on init)" , a .name )
801+ }
802+
803+ a .ssthresh = a .RWND ()
804+
805+ a .setState (established )
806+ }
807+
720808// caller must hold a.lock.
721809func (a * Association ) sendInit () error {
722810 a .log .Debugf ("[%s] sending INIT" , a .name )
@@ -1714,7 +1802,7 @@ func (a *Association) handleInitAck(pkt *packet, initChunkAck *chunkInitAck) err
17141802 a .setRWND (initChunkAck .advertisedReceiverWindowCredit )
17151803 a .log .Debugf ("[%s] initial rwnd=%d" , a .name , a .RWND ())
17161804
1717- // RFC 4690 Sec 7.2.1
1805+ // RFC 4960 Sec 7.2.1
17181806 // o The initial value of ssthresh MAY be arbitrarily high (for
17191807 // example, implementations MAY use the size of the receiver
17201808 // advertised window).
@@ -4222,3 +4310,39 @@ func (a *Association) sendActiveHeartbeatLocked() {
42224310 })
42234311 a .awakeWriteLoop ()
42244312}
4313+
4314+ // GenerateOutOfBandToken generates an out-of-band connection token (i.e. a
4315+ // serialized SCTP INIT chunk) for use with SNAP.
4316+ func GenerateOutOfBandToken (opts ... ClientOption ) ([]byte , error ) {
4317+ config := & Config {}
4318+ config .applyDefaults ()
4319+
4320+ for _ , opt := range opts {
4321+ if opt == nil {
4322+ continue
4323+ }
4324+ if err := opt .applyClient (config ); err != nil {
4325+ return nil , err
4326+ }
4327+ }
4328+
4329+ config .applyDefaults ()
4330+
4331+ init := & chunkInit {}
4332+ init .initialTSN = globalMathRandomGenerator .Uint32 ()
4333+ init .numOutboundStreams = math .MaxUint16
4334+ init .numInboundStreams = math .MaxUint16
4335+ init .initiateTag = generateInitiateTag ()
4336+ init .advertisedReceiverWindowCredit = config .MaxReceiveBufferSize
4337+ setSupportedExtensions (& init .chunkInitCommon )
4338+
4339+ if config .EnableZeroChecksum {
4340+ init .params = append (init .params , & paramZeroChecksumAcceptable {edmid : dtlsErrorDetectionMethod })
4341+ }
4342+ _ , err := init .check ()
4343+ if err != nil {
4344+ return nil , err
4345+ }
4346+
4347+ return init .marshal ()
4348+ }
0 commit comments