Skip to content

Commit

Permalink
Support Unknown Parameters in INIT Chunk
Browse files Browse the repository at this point in the history
Start to implement section-3.2.1 and respect chunks with a action
of Skip. We don't properly report the values back yet.

Relates to #2618
  • Loading branch information
Sean-Der committed Feb 9, 2024
1 parent 3a6b5ef commit 2927025
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 26 deletions.
20 changes: 12 additions & 8 deletions chunk_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var (
ErrInitInboundStreamRequestZero = errors.New("INIT ACK inbound stream request must be > 0")
ErrInitOutboundStreamRequestZero = errors.New("INIT ACK outbound stream request must be > 0")
ErrInitAdvertisedReceiver1500 = errors.New("INIT ACK Advertised Receiver Window Credit (a_rwnd) must be >= 1500")
ErrInitUnknownParam = errors.New("INIT with unknown param")
)

func (i *chunkInit) unmarshal(raw []byte) error {
Expand Down Expand Up @@ -89,8 +90,7 @@ func (i *chunkInit) check() (abort bool, err error) {
// to be 0, the receiver MUST treat it as an error and close the
// association by transmitting an ABORT.
if i.initiateTag == 0 {
abort = true
return abort, ErrChunkTypeInitInitateTagZero
return true, ErrChunkTypeInitInitateTagZero
}

// Defines the maximum number of streams the sender of this INIT
Expand All @@ -104,8 +104,7 @@ func (i *chunkInit) check() (abort bool, err error) {
// Note: A receiver of an INIT with the MIS value of 0 SHOULD abort
// the association.
if i.numInboundStreams == 0 {
abort = true
return abort, ErrInitInboundStreamRequestZero
return true, ErrInitInboundStreamRequestZero
}

// Defines the number of outbound streams the sender of this INIT
Expand All @@ -116,17 +115,22 @@ func (i *chunkInit) check() (abort bool, err error) {
// abort the association.

if i.numOutboundStreams == 0 {
abort = true
return abort, ErrInitOutboundStreamRequestZero
return true, ErrInitOutboundStreamRequestZero
}

// An SCTP receiver MUST be able to receive a minimum of 1500 bytes in
// one SCTP packet. This means that an SCTP endpoint MUST NOT indicate
// less than 1500 bytes in its initial a_rwnd sent in the INIT or INIT
// ACK.
if i.advertisedReceiverWindowCredit < 1500 {
abort = true
return abort, ErrInitAdvertisedReceiver1500
return true, ErrInitAdvertisedReceiver1500
}

for _, p := range i.unrecognizedParams {
if p.unrecognizedAction == paramHeaderUnrecognizedActionStop ||
p.unrecognizedAction == paramHeaderUnrecognizedActionStopAndReport {
return true, ErrInitUnknownParam
}
}

return false, nil
Expand Down
21 changes: 12 additions & 9 deletions chunk_init_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type chunkInitCommon struct {
numInboundStreams uint16
initialTSN uint32
params []param
unrecognizedParams []paramHeader
}

const (
Expand All @@ -59,7 +60,6 @@ const (
// Init chunk errors
var (
ErrInitChunkParseParamTypeFailed = errors.New("failed to parse param type")
ErrInitChunkUnmarshalParam = errors.New("failed unmarshalling param in Init Chunk")
ErrInitAckMarshalParam = errors.New("unable to marshal parameter for INIT/INITACK")
)

Expand Down Expand Up @@ -91,18 +91,21 @@ func (i *chunkInitCommon) unmarshal(raw []byte) error {
remaining := len(raw) - offset
for remaining > 0 {
if remaining > initOptionalVarHeaderLength {
pType, err := parseParamType(raw[offset:])
if err != nil {
var pHeader paramHeader
if err := pHeader.unmarshal(raw[offset:]); err != nil {
return fmt.Errorf("%w: %v", ErrInitChunkParseParamTypeFailed, err) //nolint:errorlint
}
p, err := buildParam(pType, raw[offset:])

p, err := buildParam(pHeader.typ, raw[offset:])
if err != nil {
return fmt.Errorf("%w: %v", ErrInitChunkUnmarshalParam, err) //nolint:errorlint
i.unrecognizedParams = append(i.unrecognizedParams, pHeader)
} else {
i.params = append(i.params, p)
}
i.params = append(i.params, p)
padding := getPadding(p.length())
offset += p.length() + padding
remaining -= p.length() + padding

padding := getPadding(pHeader.length())
offset += pHeader.length() + padding
remaining -= pHeader.length() + padding
} else {
break
}
Expand Down
35 changes: 35 additions & 0 deletions chunk_init_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT

package sctp

import (
"testing"
)

func TestChunkInit_UnrecognizedParameters(t *testing.T) {
initChunkHeader := []byte{
0x55, 0xb9, 0x64, 0xa5, 0x00, 0x02, 0x00, 0x00,
0x04, 0x00, 0x08, 0x00, 0xe8, 0x6d, 0x10, 0x30,
}

unrecognizedSkip := append([]byte{}, initChunkHeader...)
unrecognizedSkip = append(unrecognizedSkip, byte(paramHeaderUnrecognizedActionSkip), 0xFF, 0x00, 0x04, 0x00)

i := &chunkInitCommon{}
if err := i.unmarshal(unrecognizedSkip); err != nil {
t.Errorf("Unmarshal init Chunk failed: %v", err)
} else if len(i.unrecognizedParams) != 1 || i.unrecognizedParams[0].unrecognizedAction != paramHeaderUnrecognizedActionSkip {
t.Errorf("Unrecognized Param parsed incorrectly")
}

unrecognizedStop := append([]byte{}, initChunkHeader...)
unrecognizedStop = append(unrecognizedStop, byte(paramHeaderUnrecognizedActionStop), 0xFF, 0x00, 0x04, 0x00)

i = &chunkInitCommon{}
if err := i.unmarshal(unrecognizedStop); err != nil {
t.Errorf("Unmarshal init Chunk failed: %v", err)
} else if len(i.unrecognizedParams) != 1 || i.unrecognizedParams[0].unrecognizedAction != paramHeaderUnrecognizedActionStop {
t.Errorf("Unrecognized Param parsed incorrectly")
}
}
7 changes: 4 additions & 3 deletions param_ecn_capable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestParamECNCapabale_Success(t *testing.T) {
testParamECNCapabale(),
&paramECNCapable{
paramHeader: paramHeader{
typ: ecnCapable,
len: 4,
raw: []byte{},
typ: ecnCapable,
unrecognizedAction: paramHeaderUnrecognizedActionSkip,
len: 4,
raw: []byte{},
},
},
},
Expand Down
7 changes: 4 additions & 3 deletions param_forward_tsn_supported_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestParamForwardTSNSupported_Success(t *testing.T) {
testParamForwardTSNSupported(),
&paramForwardTSNSupported{
paramHeader: paramHeader{
typ: forwardTSNSupp,
len: 4,
raw: []byte{},
typ: forwardTSNSupp,
len: 4,
unrecognizedAction: paramHeaderUnrecognizedActionSkipAndReport,
raw: []byte{},
},
},
},
Expand Down
34 changes: 31 additions & 3 deletions paramheader.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,40 @@ import (
"fmt"
)

type paramHeaderUnrecognizedAction byte

type paramHeader struct {
typ paramType
len int
raw []byte
typ paramType
unrecognizedAction paramHeaderUnrecognizedAction
len int
raw []byte
}

/*
The Parameter Types are encoded such that the highest-order 2 bits specify
the action that is taken if the processing endpoint does not recognize the
Parameter Type.
00 - Stop processing this parameter and do not process any further parameters within this chunk.
01 - Stop processing this parameter, do not process any further parameters within this chunk, and
report the unrecognized parameter, as described in Section 3.2.2.
10 - Skip this parameter and continue processing.
11 - Skip this parameter and continue processing, but report the unrecognized
parameter, as described in Section 3.2.2.
https://www.rfc-editor.org/rfc/rfc9260.html#section-3.2.1
*/

const (
paramHeaderUnrecognizedActionMask = 0b11000000
paramHeaderUnrecognizedActionStop paramHeaderUnrecognizedAction = 0b00000000
paramHeaderUnrecognizedActionStopAndReport paramHeaderUnrecognizedAction = 0b01000000
paramHeaderUnrecognizedActionSkip paramHeaderUnrecognizedAction = 0b10000000
paramHeaderUnrecognizedActionSkipAndReport paramHeaderUnrecognizedAction = 0b11000000

paramHeaderLength = 4
)

Expand Down Expand Up @@ -57,6 +84,7 @@ func (p *paramHeader) unmarshal(raw []byte) error {
return fmt.Errorf("%w: %v", ErrParamHeaderParseFailed, err) //nolint:errorlint
}
p.typ = typ
p.unrecognizedAction = paramHeaderUnrecognizedAction(raw[0] & paramHeaderUnrecognizedActionMask)
p.raw = raw[paramHeaderLength:paramLengthPlusHeader]
p.len = int(paramLengthPlusHeader)

Expand Down

0 comments on commit 2927025

Please sign in to comment.