Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ and this project adheres to
- Added Verification Tag validation on incoming packets as per RFC 9260 Section 8.5.
- Validate incoming DATA/IDATA/FORWARD-TSN/I-FORWARD-TSN chunk types match negotiated message interleaving capability.
- Validate incoming FORWARD-TSN/I-FORWARD-TSN to match negotiated partial reliability capability.
- Added prevention of state cookie forging and capability bypass.

### Fixed

Expand Down
118 changes: 115 additions & 3 deletions src/socket/capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use crate::api::Options;
use crate::api::ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE;
use crate::api::ZeroChecksumAlternateErrorDetectionMethod;
use crate::packet::forward_tsn_chunk;
use crate::packet::idata_chunk;
use crate::packet::parameter::Parameter;
use crate::packet::re_config_chunk;
use crate::packet::supported_extensions_parameter::SupportedExtensionsParameter;
use crate::packet::zero_checksum_acceptable_parameter::ZeroChecksumAcceptableParameter;
use std::collections::HashSet;

/// Indicates what the association supports, meaning that both parties support it and that feature
/// can be used.
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Clone, Copy)]
pub struct Capabilities {
/// RFC 3758 Partial Reliability Extension
pub partial_reliability: bool,
Expand All @@ -25,12 +36,113 @@ pub struct Capabilities {
/// RFC 6525 Stream Reconfiguration
pub reconfig: bool,

/// RFC 9653 Zero Checksum
pub zero_checksum: bool,
/// RFC 9653 Zero Checksum Alternate Error Detection Method
pub zero_checksum_method: ZeroChecksumAlternateErrorDetectionMethod,

/// Negotiated maximum incoming stream count.
pub negotiated_maximum_incoming_streams: u16,

/// Negotiated maximum outgoing stream count.
pub negotiated_maximum_outgoing_streams: u16,
}

impl Default for Capabilities {
fn default() -> Self {
Self {
partial_reliability: false,
message_interleaving: false,
reconfig: false,
zero_checksum_method: ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE,
negotiated_maximum_incoming_streams: 0,
negotiated_maximum_outgoing_streams: 0,
}
}
}

impl Capabilities {
/// Extracts capabilities from a list of parameters received from the peer.
///
/// This parses the parameters (typically from an INIT or INIT ACK chunk)
/// to determine what features the peer supports and what limits it imposes.
pub fn from_parameters(
nbr_outbound_streams: u16,
nbr_inbound_streams: u16,
parameters: &[Parameter],
) -> Self {
let supported: HashSet<u8> = parameters
.iter()
.find_map(|e| match e {
Parameter::SupportedExtensions(SupportedExtensionsParameter { chunk_types }) => {
Some(chunk_types)
}
_ => None,
})
.unwrap_or(&vec![])
.iter()
.cloned()
.collect();

let partial_reliability =
parameters.iter().any(|e| matches!(e, Parameter::ForwardTsnSupported(_)))
|| supported.contains(&forward_tsn_chunk::CHUNK_TYPE);

let message_interleaving = supported.contains(&idata_chunk::CHUNK_TYPE);

let reconfig = supported.contains(&re_config_chunk::CHUNK_TYPE);

let zero_checksum_method = *parameters
.iter()
.find_map(|e| match e {
Parameter::ZeroChecksumAcceptable(ZeroChecksumAcceptableParameter { method }) => {
Some(method)
}
_ => None,
})
.unwrap_or(&ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE);

Self {
partial_reliability,
message_interleaving,
reconfig,
zero_checksum_method,
negotiated_maximum_incoming_streams: nbr_outbound_streams,
negotiated_maximum_outgoing_streams: nbr_inbound_streams,
}
}

/// Negotiates the capabilities of the association.
///
/// When this struct represents the capabilities supported by the peer, and the local socket
/// options are passed in, this returns the mutually supported and negotiated capabilities.
pub fn negotiate(&self, options: &Options) -> Capabilities {
let partial_reliability = options.enable_partial_reliability && self.partial_reliability;
let message_interleaving = options.enable_message_interleaving && self.message_interleaving;
let zero_checksum_method = if options.zero_checksum_alternate_error_detection_method
== self.zero_checksum_method
{
self.zero_checksum_method
} else {
ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE
};

Capabilities {
partial_reliability,
message_interleaving,
reconfig: self.reconfig,
zero_checksum_method,
negotiated_maximum_incoming_streams: std::cmp::min(
options.announced_maximum_incoming_streams,
self.negotiated_maximum_incoming_streams,
),
negotiated_maximum_outgoing_streams: std::cmp::min(
options.announced_maximum_outgoing_streams,
self.negotiated_maximum_outgoing_streams,
),
}
}

/// Indicates if the RFC 9653 Zero Checksum is enabled.
pub fn zero_checksum_enabled(&self) -> bool {
self.zero_checksum_method != ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE
}
}
76 changes: 15 additions & 61 deletions src/socket/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ use crate::types::Tsn;
use log::info;
#[cfg(not(test))]
use log::warn;
use std::cmp::min;
use std::collections::HashSet;
#[cfg(test)]
use std::println as info;
#[cfg(test)]
Expand Down Expand Up @@ -185,14 +183,16 @@ pub(crate) fn handle_init(state: &mut State, ctx: &mut Context, chunk: InitChunk
}
}

let capabilities = compute_capabilities(
&ctx.options,
let peer_capabilities = Capabilities::from_parameters(
chunk.nbr_outbound_streams,
chunk.nbr_inbound_streams,
&chunk.parameters,
);
let write_checksum = !capabilities.zero_checksum;
let mut parameters = make_capability_parameters(&ctx.options, capabilities.zero_checksum);
let capabilities = peer_capabilities.negotiate(&ctx.options);
let write_checksum = !capabilities.zero_checksum_enabled();
let mut parameters =
make_capability_parameters(&ctx.options, capabilities.zero_checksum_enabled());

parameters.push(Parameter::StateCookie(StateCookieParameter {
cookie: StateCookie {
peer_tag: chunk.initiate_tag,
Expand All @@ -201,7 +201,7 @@ pub(crate) fn handle_init(state: &mut State, ctx: &mut Context, chunk: InitChunk
my_initial_tsn,
a_rwnd: chunk.a_rwnd,
tie_tag,
capabilities,
peer_capabilities,
Comment thread
boivie marked this conversation as resolved.
}
.serialize(),
}));
Expand Down Expand Up @@ -395,7 +395,7 @@ pub(crate) fn handle_cookie_echo(
unreachable!();
};

let write_checksum = !tcb.capabilities.zero_checksum;
let write_checksum = !tcb.capabilities.zero_checksum_enabled();
let mut b = SctpPacketBuilder::new(
cookie.peer_tag,
ctx.options.local_port,
Expand Down Expand Up @@ -474,22 +474,24 @@ fn establish_new_tcb(
cookie: &StateCookie,
reset_queue: bool,
) {
ctx.send_queue.enable_message_interleaving(cookie.capabilities.message_interleaving);
let capabilities = cookie.peer_capabilities.negotiate(&ctx.options);
ctx.send_queue.enable_message_interleaving(capabilities.message_interleaving);

if reset_queue {
ctx.send_queue.reset();
}

let tie_tag = fastrand::u64(..);
let a_rwnd = std::cmp::min(cookie.a_rwnd, ctx.options.max_send_buffer_size as u32);
let new_tcb = TransmissionControlBlock::new(
&ctx.options,
cookie.my_tag,
cookie.my_initial_tsn,
cookie.peer_tag,
cookie.peer_initial_tsn,
tie_tag,
cookie.a_rwnd,
cookie.capabilities,
a_rwnd,
capabilities,
ctx.events.clone(),
);

Expand Down Expand Up @@ -558,56 +560,8 @@ fn compute_capabilities(
peer_nbr_inbound_streams: u16,
parameters: &[Parameter],
) -> Capabilities {
let supported: HashSet<u8> = HashSet::from_iter(
parameters
.iter()
.find_map(|e| match e {
Parameter::SupportedExtensions(SupportedExtensionsParameter { chunk_types }) => {
Some(chunk_types)
}
_ => None,
})
.unwrap_or(&vec![])
.iter()
.cloned()
.collect::<HashSet<_>>(),
);

let partial_reliability = options.enable_partial_reliability
&& (parameters.iter().any(|e| matches!(e, Parameter::ForwardTsnSupported(_)))
|| supported.contains(&forward_tsn_chunk::CHUNK_TYPE));

let message_interleaving = options.enable_message_interleaving
&& supported.contains(&idata_chunk::CHUNK_TYPE)
&& supported.contains(&iforward_tsn_chunk::CHUNK_TYPE);

let peer_zero_checksum = *parameters
.iter()
.find_map(|e| match e {
Parameter::ZeroChecksumAcceptable(ZeroChecksumAcceptableParameter { method }) => {
Some(method)
}
_ => None,
})
.unwrap_or(&ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE);
let zero_checksum = (options.zero_checksum_alternate_error_detection_method
!= ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE)
&& (options.zero_checksum_alternate_error_detection_method == peer_zero_checksum);

Capabilities {
partial_reliability,
message_interleaving,
reconfig: supported.contains(&re_config_chunk::CHUNK_TYPE),
zero_checksum,
negotiated_maximum_incoming_streams: min(
options.announced_maximum_incoming_streams,
peer_nbr_outbound_streams,
),
negotiated_maximum_outgoing_streams: min(
options.announced_maximum_outgoing_streams,
peer_nbr_inbound_streams,
),
}
Capabilities::from_parameters(peer_nbr_outbound_streams, peer_nbr_inbound_streams, parameters)
.negotiate(options)
}

fn make_capability_parameters(options: &Options, support_zero_checksum: bool) -> Vec<Parameter> {
Expand Down
9 changes: 7 additions & 2 deletions src/socket/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use crate::api::SocketEvent;
use crate::api::SocketState;
use crate::api::SocketTime;
use crate::api::StreamId;
use crate::api::ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE;
use crate::api::handover::HandoverReadiness;
use crate::api::handover::HandoverSocketState;
use crate::api::handover::SocketHandoverState;
Expand Down Expand Up @@ -815,7 +816,7 @@ impl DcSctpSocket for Socket {
peer_rwnd_bytes: tcb.retransmission_queue.rwnd() as u32,
peer_implementation: self.ctx.peer_implementation,
uses_message_interleaving: tcb.capabilities.message_interleaving,
uses_zero_checksum: tcb.capabilities.zero_checksum,
uses_zero_checksum: tcb.capabilities.zero_checksum_enabled(),
negotiated_maximum_incoming_streams: tcb
.capabilities
.negotiated_maximum_incoming_streams,
Expand Down Expand Up @@ -849,7 +850,11 @@ impl DcSctpSocket for Socket {
partial_reliability: state.capabilities.partial_reliability,
message_interleaving: state.capabilities.message_interleaving,
reconfig: state.capabilities.reconfig,
zero_checksum: state.capabilities.zero_checksum,
zero_checksum_method: if state.capabilities.zero_checksum {
self.ctx.options.zero_checksum_alternate_error_detection_method
} else {
ZERO_CHECKSUM_ALTERNATE_ERROR_DETECTION_METHOD_NONE
},
negotiated_maximum_incoming_streams: state
.capabilities
.negotiated_maximum_incoming_streams,
Expand Down
Loading