-
Notifications
You must be signed in to change notification settings - Fork 88
Implement SCTP Negotiation Acceleration Protocol (SNAP) #449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
fippo
wants to merge
6
commits into
pion:master
Choose a base branch
from
fippo:snap
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+237
−58
Draft
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
7d76ca4
Implement SCTP Negotiation Acceleration Protocol
fippo 9cd1454
Avoid forbidden 0 init tag
fippo 4da988d
Rearrange things a bit
fippo 758fcf0
Move things around
fippo cb1762e
Breaking API change, add SctpOptions for paramters
fippo 888d784
SctpOptions->SctpParamters
fippo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,15 @@ const defaultSCTPSrcDstPort = 5000 | |
| // Use global random generator to properly seed by crypto grade random. | ||
| var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals | ||
|
|
||
| // Generates a non-zero Initiate tag. | ||
| func generateInitiateTag() uint32 { | ||
| for { | ||
| if u := globalMathRandomGenerator.Uint32(); u != 0 { | ||
| return u | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Association errors. | ||
| var ( | ||
| ErrChunk = errors.New("abort chunk, with following errors") | ||
|
|
@@ -321,7 +330,6 @@ type Config struct { | |
| Name string | ||
| NetConn net.Conn | ||
| MaxReceiveBufferSize uint32 | ||
| MaxMessageSize uint32 | ||
| EnableZeroChecksum bool | ||
| LoggerFactory logging.LoggerFactory | ||
| BlockWrite bool | ||
|
|
@@ -347,9 +355,23 @@ type Config struct { | |
| rackWCDelAck time.Duration | ||
| } | ||
|
|
||
| // SctpParameters represents negotiated (e.g. via SDP) SCTP parameters. | ||
| type SctpParameters struct { | ||
| // a=sctp-port | ||
| LocalSctpPort int16 | ||
| RemoteSctpPort int16 | ||
|
|
||
| // a=max-message-size, negotiated. | ||
| MaxMessageSize uint32 | ||
|
|
||
| // a=sctp-ini, decoded from base64. | ||
| LocalSctpInit []byte | ||
| RemoteSctpInit []byte | ||
| } | ||
|
|
||
| // Server accepts a SCTP stream over a conn. | ||
| func Server(config Config) (*Association, error) { | ||
| a := createAssociation(config) | ||
| a := createAssociation(config, SctpParameters{}) | ||
| a.init(false) | ||
|
|
||
| select { | ||
|
|
@@ -365,12 +387,29 @@ func Server(config Config) (*Association, error) { | |
| } | ||
|
|
||
| // Client opens a SCTP stream over a conn. | ||
| func Client(config Config) (*Association, error) { | ||
| return createClientWithContext(context.Background(), config) | ||
| func Client(config Config, options SctpParameters) (*Association, error) { | ||
| return createClientWithContext(context.Background(), config, options) | ||
| } | ||
|
|
||
| func createClientWithContext(ctx context.Context, config Config) (*Association, error) { | ||
| assoc := createAssociation(config) | ||
| func createClientWithContext(ctx context.Context, config Config, options SctpParameters) (*Association, error) { | ||
| if len(options.RemoteSctpInit) != 0 && len(options.LocalSctpInit) != 0 { | ||
| // SNAP, aka sctp-init in the SDP. | ||
| remote := &chunkInit{} | ||
| err := remote.unmarshal(options.RemoteSctpInit) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| local := &chunkInit{} | ||
| err = local.unmarshal(options.LocalSctpInit) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| assoc := createAssociationWithTSN(config, options, local.initialTSN) | ||
| assoc.initWithOutOfBandTokens(local, remote) | ||
|
|
||
| return assoc, nil | ||
| } | ||
| assoc := createAssociation(config, options) | ||
| assoc.init(true) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Related to the previous comment but we skip |
||
|
|
||
| select { | ||
|
|
@@ -390,13 +429,19 @@ func createClientWithContext(ctx context.Context, config Config) (*Association, | |
| } | ||
| } | ||
|
|
||
| func createAssociation(config Config) *Association { | ||
| func createAssociation(config Config, options SctpParameters) *Association { | ||
| tsn := globalMathRandomGenerator.Uint32() | ||
|
|
||
| return createAssociationWithTSN(config, options, tsn) | ||
| } | ||
|
|
||
| func createAssociationWithTSN(config Config, options SctpParameters, tsn uint32) *Association { | ||
| maxReceiveBufferSize := config.MaxReceiveBufferSize | ||
| if maxReceiveBufferSize == 0 { | ||
| maxReceiveBufferSize = initialRecvBufSize | ||
| } | ||
|
|
||
| maxMessageSize := config.MaxMessageSize | ||
| maxMessageSize := options.MaxMessageSize | ||
| if maxMessageSize == 0 { | ||
| maxMessageSize = defaultMaxMessageSize | ||
| } | ||
|
|
@@ -406,7 +451,6 @@ func createAssociation(config Config) *Association { | |
| mtu = initialMTU | ||
| } | ||
|
|
||
| tsn := globalMathRandomGenerator.Uint32() | ||
| assoc := &Association{ | ||
| netConn: config.NetConn, | ||
| maxReceiveBufferSize: maxReceiveBufferSize, | ||
|
|
@@ -428,7 +472,7 @@ func createAssociation(config Config) *Association { | |
| controlQueue: newControlQueue(), | ||
| mtu: mtu, | ||
| maxPayloadSize: mtu - (commonHeaderSize + dataChunkHeaderSize), | ||
| myVerificationTag: globalMathRandomGenerator.Uint32(), | ||
| myVerificationTag: generateInitiateTag(), | ||
| initialTSN: tsn, | ||
| myNextTSN: tsn, | ||
| myNextRSN: tsn, | ||
|
|
@@ -477,7 +521,7 @@ func createAssociation(config Config) *Association { | |
| assoc.name = fmt.Sprintf("%p", assoc) | ||
| } | ||
|
|
||
| // RFC 4690 Sec 7.2.1 | ||
| // RFC 4960 Sec 7.2.1 | ||
| // o The initial cwnd before DATA transmission or after a sufficiently | ||
| // long idle period MUST be set to min(4*MTU, max (2*MTU, 4380 | ||
| // bytes)). | ||
|
|
@@ -532,6 +576,43 @@ func (a *Association) init(isClient bool) { | |
| } | ||
| } | ||
|
|
||
| func (a *Association) initWithOutOfBandTokens(localInit *chunkInit, remoteInit *chunkInit) { | ||
| a.lock.Lock() | ||
| defer a.lock.Unlock() | ||
|
|
||
| go a.readLoop() | ||
| go a.writeLoop() | ||
|
|
||
| a.payloadQueue.init(remoteInit.initialTSN - 1) | ||
| a.myMaxNumInboundStreams = min16(localInit.numInboundStreams, remoteInit.numInboundStreams) | ||
| a.myMaxNumOutboundStreams = min16(localInit.numOutboundStreams, remoteInit.numOutboundStreams) | ||
| a.setRWND(min32(localInit.advertisedReceiverWindowCredit, remoteInit.advertisedReceiverWindowCredit)) | ||
| a.peerVerificationTag = remoteInit.initiateTag | ||
| a.sourcePort = defaultSCTPSrcDstPort | ||
| a.destinationPort = defaultSCTPSrcDstPort | ||
| for _, param := range remoteInit.params { | ||
| switch v := param.(type) { // nolint:gocritic | ||
| case *paramSupportedExtensions: | ||
| for _, t := range v.ChunkTypes { | ||
| if t == ctForwardTSN { | ||
| a.log.Debugf("[%s] use ForwardTSN (on init)", a.name) | ||
| a.useForwardTSN = true | ||
| } | ||
| } | ||
| case *paramZeroChecksumAcceptable: | ||
| a.sendZeroChecksum = v.edmid == dtlsErrorDetectionMethod | ||
| } | ||
| } | ||
|
|
||
| if !a.useForwardTSN { | ||
| a.log.Warnf("[%s] not using ForwardTSN (on init)", a.name) | ||
| } | ||
|
|
||
| a.ssthresh = a.RWND() | ||
|
|
||
| a.setState(established) | ||
| } | ||
|
|
||
| // caller must hold a.lock. | ||
| func (a *Association) sendInit() error { | ||
| a.log.Debugf("[%s] sending INIT", a.name) | ||
|
|
@@ -1500,7 +1581,7 @@ func (a *Association) handleInitAck(pkt *packet, initChunkAck *chunkInitAck) err | |
| a.setRWND(initChunkAck.advertisedReceiverWindowCredit) | ||
| a.log.Debugf("[%s] initial rwnd=%d", a.name, a.RWND()) | ||
|
|
||
| // RFC 4690 Sec 7.2.1 | ||
| // RFC 4960 Sec 7.2.1 | ||
| // o The initial value of ssthresh MAY be arbitrarily high (for | ||
| // example, implementations MAY use the size of the receiver | ||
| // advertised window). | ||
|
|
@@ -3997,3 +4078,21 @@ func (a *Association) sendActiveHeartbeatLocked() { | |
| }) | ||
| a.awakeWriteLoop() | ||
| } | ||
|
|
||
| // GenerateOutOfBandToken generates an out-of-band connection token (i.e. a | ||
| // serialized SCTP INIT chunk) for use with SNAP. | ||
| func GenerateOutOfBandToken(config Config) ([]byte, error) { | ||
| init := &chunkInit{} | ||
| init.initialTSN = globalMathRandomGenerator.Uint32() | ||
| init.numOutboundStreams = math.MaxUint16 | ||
| init.numInboundStreams = math.MaxUint16 | ||
| init.initiateTag = generateInitiateTag() | ||
| init.advertisedReceiverWindowCredit = config.MaxReceiveBufferSize | ||
| setSupportedExtensions(&init.chunkInitCommon) | ||
|
|
||
| if config.EnableZeroChecksum { | ||
| init.params = append(init.params, ¶mZeroChecksumAcceptable{edmid: dtlsErrorDetectionMethod}) | ||
| } | ||
|
|
||
| return init.marshal() | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will break the compatibility checks, but we plan to deprecate the config struct this week for the favor of options pattern so this could be
WithSCTPInitwhen we update or something!#446
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall this feels like we should separate options (configured by the API) from negotiated parameters (maxMessageSize, remote sctpPort, sctpInit). Still a breaking change but if we need one anyway...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#450