dcsctp: Add Socket

This completes the basic implementation of the dcSCTP library. There
are a few remaining commits to e.g. add compatibility tests and
benchmarks, as well as more support for e.g. RFC8260, but those are not
strictly vital for evaluation of the library.

The Socket contains the connection establishment and teardown sequences
as well as the general chunk dispatcher.

Bug: webrtc:12614
Change-Id: I313b6c8f4accc144e3bb88ddba22269ebb8eb3cd
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214342
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33890}
This commit is contained in:
Victor Boivie
2021-04-08 09:56:59 +02:00
committed by WebRTC LUCI CQ
parent 694ecad834
commit b6580ccb29
11 changed files with 3384 additions and 44 deletions

View File

@ -43,11 +43,33 @@ struct DcSctpOptions {
// port number as destination port. // port number as destination port.
int remote_port = 5000; int remote_port = 5000;
// The announced maximum number of incoming streams. Note that this value is
// constant and can't be currently increased in run-time as "Add Incoming
// Streams Request" in RFC6525 isn't supported.
//
// The socket implementation doesn't have any per-stream fixed costs, which is
// why the default value is set to be the maximum value.
uint16_t announced_maximum_incoming_streams = 65535;
// The announced maximum number of outgoing streams. Note that this value is
// constant and can't be currently increased in run-time as "Add Outgoing
// Streams Request" in RFC6525 isn't supported.
//
// The socket implementation doesn't have any per-stream fixed costs, which is
// why the default value is set to be the maximum value.
uint16_t announced_maximum_outgoing_streams = 65535;
// Maximum SCTP packet size. The library will limit the size of generated // Maximum SCTP packet size. The library will limit the size of generated
// packets to be less than or equal to this number. This does not include any // packets to be less than or equal to this number. This does not include any
// overhead of DTLS, TURN, UDP or IP headers. // overhead of DTLS, TURN, UDP or IP headers.
size_t mtu = kMaxSafeMTUSize; size_t mtu = kMaxSafeMTUSize;
// The largest allowed message payload to be sent. Messages will be rejected
// if their payload is larger than this value. Note that this doesn't affect
// incoming messages, which may larger than this value (but smaller than
// `max_receiver_window_buffer_size`).
size_t max_message_size = 256 * 1024;
// Maximum received window buffer size. This should be a bit larger than the // Maximum received window buffer size. This should be a bit larger than the
// largest sized message you want to be able to receive. This essentially // largest sized message you want to be able to receive. This essentially
// limits the memory usage on the receive side. Note that memory is allocated // limits the memory usage on the receive side. Note that memory is allocated
@ -65,7 +87,7 @@ struct DcSctpOptions {
// than this value, it will be discarded and not used for e.g. any RTO // than this value, it will be discarded and not used for e.g. any RTO
// calculation. The default value is an extreme maximum but can be adapted // calculation. The default value is an extreme maximum but can be adapted
// to better match the environment. // to better match the environment.
DurationMs rtt_max = DurationMs(8'000); DurationMs rtt_max = DurationMs(8000);
// Initial RTO value. // Initial RTO value.
DurationMs rto_initial = DurationMs(500); DurationMs rto_initial = DurationMs(500);
@ -86,7 +108,7 @@ struct DcSctpOptions {
DurationMs t2_shutdown_timeout = DurationMs(1000); DurationMs t2_shutdown_timeout = DurationMs(1000);
// Hearbeat interval (on idle connections only). // Hearbeat interval (on idle connections only).
DurationMs heartbeat_interval = DurationMs(30'000); DurationMs heartbeat_interval = DurationMs(30000);
// The maximum time when a SACK will be sent from the arrival of an // The maximum time when a SACK will be sent from the arrival of an
// unacknowledged packet. Whatever is smallest of RTO/2 and this will be used. // unacknowledged packet. Whatever is smallest of RTO/2 and this will be used.

View File

@ -18,12 +18,27 @@
#include "absl/types/optional.h" #include "absl/types/optional.h"
#include "api/array_view.h" #include "api/array_view.h"
#include "net/dcsctp/public/dcsctp_message.h" #include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/packet_observer.h" #include "net/dcsctp/public/packet_observer.h"
#include "net/dcsctp/public/timeout.h" #include "net/dcsctp/public/timeout.h"
#include "net/dcsctp/public/types.h" #include "net/dcsctp/public/types.h"
namespace dcsctp { namespace dcsctp {
// The socket/association state
enum class SocketState {
// The socket is closed.
kClosed,
// The socket has initiated a connection, which is not yet established. Note
// that for incoming connections and for reconnections when the socket is
// already connected, the socket will not transition to this state.
kConnecting,
// The socket is connected, and the connection is established.
kConnected,
// The socket is shutting down, and the connection is not yet closed.
kShuttingDown,
};
// Send options for sending messages // Send options for sending messages
struct SendOptions { struct SendOptions {
// If the message should be sent with unordered message delivery. // If the message should be sent with unordered message delivery.
@ -59,6 +74,8 @@ enum class ErrorKind {
kProtocolViolation, kProtocolViolation,
// The receive or send buffers have been exhausted. // The receive or send buffers have been exhausted.
kResourceExhaustion, kResourceExhaustion,
// The client has performed an invalid operation.
kUnsupportedOperation,
}; };
inline constexpr absl::string_view ToString(ErrorKind error) { inline constexpr absl::string_view ToString(ErrorKind error) {
@ -79,19 +96,65 @@ inline constexpr absl::string_view ToString(ErrorKind error) {
return "PROTOCOL_VIOLATION"; return "PROTOCOL_VIOLATION";
case ErrorKind::kResourceExhaustion: case ErrorKind::kResourceExhaustion:
return "RESOURCE_EXHAUSTION"; return "RESOURCE_EXHAUSTION";
case ErrorKind::kUnsupportedOperation:
return "UNSUPPORTED_OPERATION";
} }
} }
// Return value of SupportsStreamReset. enum class SendStatus {
enum class StreamResetSupport { // The message was enqueued successfully. As sending the message is done
// asynchronously, this is no guarantee that the message has been actually
// sent.
kSuccess,
// The message was rejected as the payload was empty (which is not allowed in
// SCTP).
kErrorMessageEmpty,
// The message was rejected as the payload was larger than what has been set
// as `DcSctpOptions.max_message_size`.
kErrorMessageTooLarge,
// The message could not be enqueued as the socket is out of resources. This
// mainly indicates that the send queue is full.
kErrorResourceExhaustion,
// The message could not be sent as the socket is shutting down.
kErrorShuttingDown,
};
inline constexpr absl::string_view ToString(SendStatus error) {
switch (error) {
case SendStatus::kSuccess:
return "SUCCESS";
case SendStatus::kErrorMessageEmpty:
return "ERROR_MESSAGE_EMPTY";
case SendStatus::kErrorMessageTooLarge:
return "ERROR_MESSAGE_TOO_LARGE";
case SendStatus::kErrorResourceExhaustion:
return "ERROR_RESOURCE_EXHAUSTION";
case SendStatus::kErrorShuttingDown:
return "ERROR_SHUTTING_DOWN";
}
}
// Return value of ResetStreams.
enum class ResetStreamsStatus {
// If the connection is not yet established, this will be returned. // If the connection is not yet established, this will be returned.
kUnknown, kNotConnected,
// Indicates that Stream Reset is supported by the peer. // Indicates that ResetStreams operation has been successfully initiated.
kSupported, kPerformed,
// Indicates that Stream Reset is not supported by the peer. // Indicates that ResetStreams has failed as it's not supported by the peer.
kNotSupported, kNotSupported,
}; };
inline constexpr absl::string_view ToString(ResetStreamsStatus error) {
switch (error) {
case ResetStreamsStatus::kNotConnected:
return "NOT_CONNECTED";
case ResetStreamsStatus::kPerformed:
return "PERFORMED";
case ResetStreamsStatus::kNotSupported:
return "NOT_SUPPORTED";
}
}
// Callbacks that the DcSctpSocket will be done synchronously to the owning // Callbacks that the DcSctpSocket will be done synchronously to the owning
// client. It is allowed to call back into the library from callbacks that start // client. It is allowed to call back into the library from callbacks that start
// with "On". It has been explicitly documented when it's not allowed to call // with "On". It has been explicitly documented when it's not allowed to call
@ -123,9 +186,9 @@ class DcSctpSocketCallbacks {
virtual TimeMs TimeMillis() = 0; virtual TimeMs TimeMillis() = 0;
// Called when the library needs a random number uniformly distributed between // Called when the library needs a random number uniformly distributed between
// `low` (inclusive) and `high` (exclusive). The random number used by the // `low` (inclusive) and `high` (exclusive). The random numbers used by the
// library are not used for cryptographic purposes there are no requirements // library are not used for cryptographic purposes. There are no requirements
// on a secure random number generator. // that the random number generator must be secure.
// //
// Note that it's NOT ALLOWED to call into this library from within this // Note that it's NOT ALLOWED to call into this library from within this
// callback. // callback.
@ -200,15 +263,6 @@ class DcSctpSocketCallbacks {
// It is allowed to call into this library from within this callback. // It is allowed to call into this library from within this callback.
virtual void OnIncomingStreamsReset( virtual void OnIncomingStreamsReset(
rtc::ArrayView<const StreamID> incoming_streams) = 0; rtc::ArrayView<const StreamID> incoming_streams) = 0;
// If an outgoing message has expired before being completely sent.
// TODO(boivie) Add some kind of message identifier.
// TODO(boivie) Add callbacks for OnMessageSent and OnSentMessageAcked
//
// It is allowed to call into this library from within this callback.
virtual void OnSentMessageExpired(StreamID stream_id,
PPID ppid,
bool unsent) = 0;
}; };
// The DcSctpSocket implementation implements the following interface. // The DcSctpSocket implementation implements the following interface.
@ -236,6 +290,22 @@ class DcSctpSocketInterface {
// not already closed. No callbacks will be made after Close() has returned. // not already closed. No callbacks will be made after Close() has returned.
virtual void Close() = 0; virtual void Close() = 0;
// The socket state.
virtual SocketState state() const = 0;
// The options it was created with.
virtual const DcSctpOptions& options() const = 0;
// Sends the message `message` using the provided send options.
// Sending a message is an asynchrous operation, and the `OnError` callback
// may be invoked to indicate any errors in sending the message.
//
// The association does not have to be established before calling this method.
// If it's called before there is an established association, the message will
// be queued.
virtual SendStatus Send(DcSctpMessage message,
const SendOptions& send_options) = 0;
// Resetting streams is an asynchronous operation and the results will // Resetting streams is an asynchronous operation and the results will
// be notified using `DcSctpSocketCallbacks::OnStreamsResetDone()` on success // be notified using `DcSctpSocketCallbacks::OnStreamsResetDone()` on success
// and `DcSctpSocketCallbacks::OnStreamsResetFailed()` on failure. Note that // and `DcSctpSocketCallbacks::OnStreamsResetFailed()` on failure. Note that
@ -251,27 +321,8 @@ class DcSctpSocketInterface {
// Resetting streams can only be done on an established association that // Resetting streams can only be done on an established association that
// supports stream resetting. Calling this method on e.g. a closed association // supports stream resetting. Calling this method on e.g. a closed association
// or streams that don't support resetting will not perform any operation. // or streams that don't support resetting will not perform any operation.
virtual void ResetStreams( virtual ResetStreamsStatus ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) = 0; rtc::ArrayView<const StreamID> outgoing_streams) = 0;
// Indicates if the peer supports resetting streams (RFC6525). Please note
// that the connection must be established for support to be known.
virtual StreamResetSupport SupportsStreamReset() const = 0;
// Sends the message `message` using the provided send options.
// Sending a message is an asynchrous operation, and the `OnError` callback
// may be invoked to indicate any errors in sending the message.
//
// The association does not have to be established before calling this method.
// If it's called before there is an established association, the message will
// be queued.
void Send(DcSctpMessage message, const SendOptions& send_options = {}) {
SendMessage(std::move(message), send_options);
}
private:
virtual void SendMessage(DcSctpMessage message,
const SendOptions& send_options) = 0;
}; };
} // namespace dcsctp } // namespace dcsctp

View File

@ -76,6 +76,27 @@ rtc_library("transmission_control_block") {
] ]
} }
rtc_library("dcsctp_socket") {
deps = [
":context",
":transmission_control_block",
"../../../api:array_view",
"../../../rtc_base",
"../../../rtc_base:checks",
"../../../rtc_base:rtc_base_approved",
"../packet:chunk_validators",
"../public:types",
"../tx:fcfs_send_queue",
]
sources = [
"callback_deferrer.h",
"dcsctp_socket.cc",
"dcsctp_socket.h",
"state_cookie.cc",
"state_cookie.h",
]
}
if (rtc_include_tests) { if (rtc_include_tests) {
rtc_source_set("mock_callbacks") { rtc_source_set("mock_callbacks") {
testonly = true testonly = true
@ -100,6 +121,7 @@ if (rtc_include_tests) {
testonly = true testonly = true
deps = [ deps = [
":dcsctp_socket",
":heartbeat_handler", ":heartbeat_handler",
":stream_reset_handler", ":stream_reset_handler",
"../../../api:array_view", "../../../api:array_view",
@ -109,7 +131,9 @@ if (rtc_include_tests) {
"../../../test:test_support", "../../../test:test_support",
] ]
sources = [ sources = [
"dcsctp_socket_test.cc",
"heartbeat_handler_test.cc", "heartbeat_handler_test.cc",
"state_cookie_test.cc",
"stream_reset_handler_test.cc", "stream_reset_handler_test.cc",
] ]
} }

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_SOCKET_CALLBACK_DEFERRER_H_
#define NET_DCSCTP_SOCKET_CALLBACK_DEFERRER_H_
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/ref_counted_base.h"
#include "api/scoped_refptr.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "rtc_base/ref_counted_object.h"
namespace dcsctp {
// Defers callbacks until they can be safely triggered.
//
// There are a lot of callbacks from the dcSCTP library to the client,
// such as when messages are received or streams are closed. When the client
// receives these callbacks, the client is expected to be able to call into the
// library - from within the callback. For example, sending a reply message when
// a certain SCTP message has been received, or to reconnect when the connection
// was closed for any reason. This means that the dcSCTP library must always be
// in a consistent and stable state when these callbacks are delivered, and to
// ensure that's the case, callbacks are not immediately delivered from where
// they originate, but instead queued (deferred) by this class. At the end of
// any public API method that may result in callbacks, they are triggered and
// then delivered.
//
// There are a number of exceptions, which is clearly annotated in the API.
class CallbackDeferrer : public DcSctpSocketCallbacks {
public:
explicit CallbackDeferrer(DcSctpSocketCallbacks& underlying)
: underlying_(underlying) {}
void TriggerDeferred() {
// Need to swap here. The client may call into the library from within a
// callback, and that might result in adding new callbacks to this instance,
// and the vector can't be modified while iterated on.
std::vector<std::function<void(DcSctpSocketCallbacks & cb)>> deferred;
deferred.swap(deferred_);
for (auto& cb : deferred) {
cb(underlying_);
}
}
void SendPacket(rtc::ArrayView<const uint8_t> data) override {
// Will not be deferred - call directly.
underlying_.SendPacket(data);
}
std::unique_ptr<Timeout> CreateTimeout() override {
// Will not be deferred - call directly.
return underlying_.CreateTimeout();
}
TimeMs TimeMillis() override {
// Will not be deferred - call directly.
return underlying_.TimeMillis();
}
uint32_t GetRandomInt(uint32_t low, uint32_t high) override {
// Will not be deferred - call directly.
return underlying_.GetRandomInt(low, high);
}
void NotifyOutgoingMessageBufferEmpty() override {
// Will not be deferred - call directly.
underlying_.NotifyOutgoingMessageBufferEmpty();
}
void OnMessageReceived(DcSctpMessage message) override {
deferred_.emplace_back(
[deliverer = MessageDeliverer(std::move(message))](
DcSctpSocketCallbacks& cb) mutable { deliverer.Deliver(cb); });
}
void OnError(ErrorKind error, absl::string_view message) override {
deferred_.emplace_back(
[error, message = std::string(message)](DcSctpSocketCallbacks& cb) {
cb.OnError(error, message);
});
}
void OnAborted(ErrorKind error, absl::string_view message) override {
deferred_.emplace_back(
[error, message = std::string(message)](DcSctpSocketCallbacks& cb) {
cb.OnAborted(error, message);
});
}
void OnConnected() override {
deferred_.emplace_back([](DcSctpSocketCallbacks& cb) { cb.OnConnected(); });
}
void OnClosed() override {
deferred_.emplace_back([](DcSctpSocketCallbacks& cb) { cb.OnClosed(); });
}
void OnConnectionRestarted() override {
deferred_.emplace_back(
[](DcSctpSocketCallbacks& cb) { cb.OnConnectionRestarted(); });
}
void OnStreamsResetFailed(rtc::ArrayView<const StreamID> outgoing_streams,
absl::string_view reason) override {
deferred_.emplace_back(
[streams = std::vector<StreamID>(outgoing_streams.begin(),
outgoing_streams.end()),
reason = std::string(reason)](DcSctpSocketCallbacks& cb) {
cb.OnStreamsResetFailed(streams, reason);
});
}
void OnStreamsResetPerformed(
rtc::ArrayView<const StreamID> outgoing_streams) override {
deferred_.emplace_back(
[streams = std::vector<StreamID>(outgoing_streams.begin(),
outgoing_streams.end())](
DcSctpSocketCallbacks& cb) {
cb.OnStreamsResetPerformed(streams);
});
}
void OnIncomingStreamsReset(
rtc::ArrayView<const StreamID> incoming_streams) override {
deferred_.emplace_back(
[streams = std::vector<StreamID>(incoming_streams.begin(),
incoming_streams.end())](
DcSctpSocketCallbacks& cb) { cb.OnIncomingStreamsReset(streams); });
}
private:
// A wrapper around the move-only DcSctpMessage, to let it be captured in a
// lambda.
class MessageDeliverer {
public:
explicit MessageDeliverer(DcSctpMessage&& message)
: state_(rtc::make_ref_counted<State>(std::move(message))) {}
void Deliver(DcSctpSocketCallbacks& c) {
// Really ensure that it's only called once.
RTC_DCHECK(!state_->has_delivered);
state_->has_delivered = true;
c.OnMessageReceived(std::move(state_->message));
}
private:
struct State : public rtc::RefCountInterface {
explicit State(DcSctpMessage&& m)
: has_delivered(false), message(std::move(m)) {}
bool has_delivered;
DcSctpMessage message;
};
rtc::scoped_refptr<State> state_;
};
DcSctpSocketCallbacks& underlying_;
std::vector<std::function<void(DcSctpSocketCallbacks& cb)>> deferred_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_CALLBACK_DEFERRER_H_

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,275 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_SOCKET_DCSCTP_SOCKET_H_
#define NET_DCSCTP_SOCKET_DCSCTP_SOCKET_H_
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "net/dcsctp/packet/chunk/abort_chunk.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
#include "net/dcsctp/packet/chunk/data_chunk.h"
#include "net/dcsctp/packet/chunk/data_common.h"
#include "net/dcsctp/packet/chunk/error_chunk.h"
#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
#include "net/dcsctp/packet/chunk/idata_chunk.h"
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/init_ack_chunk.h"
#include "net/dcsctp/packet/chunk/init_chunk.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_ack_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_complete_chunk.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/public/packet_observer.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/callback_deferrer.h"
#include "net/dcsctp/socket/state_cookie.h"
#include "net/dcsctp/socket/transmission_control_block.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/fcfs_send_queue.h"
#include "net/dcsctp/tx/retransmission_error_counter.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "net/dcsctp/tx/retransmission_timeout.h"
namespace dcsctp {
// DcSctpSocket represents a single SCTP socket, to be used over DTLS.
//
// Every dcSCTP is completely isolated from any other socket.
//
// This class manages all packet and chunk dispatching and mainly handles the
// connection sequences (connect, close, shutdown, etc) as well as managing
// the Transmission Control Block (tcb).
//
// This class is thread-compatible.
class DcSctpSocket : public DcSctpSocketInterface {
public:
// Instantiates a DcSctpSocket, which interacts with the world through the
// `callbacks` interface and is configured using `options`.
//
// For debugging, `log_prefix` will prefix all debug logs, and a
// `packet_observer` can be attached to e.g. dump sent and received packets.
DcSctpSocket(absl::string_view log_prefix,
DcSctpSocketCallbacks& callbacks,
std::unique_ptr<PacketObserver> packet_observer,
const DcSctpOptions& options);
DcSctpSocket(const DcSctpSocket&) = delete;
DcSctpSocket& operator=(const DcSctpSocket&) = delete;
// Implementation of `DcSctpSocketInterface`.
void ReceivePacket(rtc::ArrayView<const uint8_t> data) override;
void HandleTimeout(TimeoutID timeout_id) override;
void Connect() override;
void Shutdown() override;
void Close() override;
SendStatus Send(DcSctpMessage message,
const SendOptions& send_options) override;
ResetStreamsStatus ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) override;
SocketState state() const override;
const DcSctpOptions& options() const override { return options_; }
// Returns this socket's verification tag, or zero if not yet connected.
VerificationTag verification_tag() const {
return tcb_ != nullptr ? tcb_->my_verification_tag() : VerificationTag(0);
}
private:
// Parameter proposals valid during the connect phase.
struct ConnectParameters {
TSN initial_tsn = TSN(0);
VerificationTag verification_tag = VerificationTag(0);
};
// Detailed state (separate from SocketState, which is the public state).
enum class State {
kClosed,
kCookieWait,
// TCB valid in these:
kCookieEchoed,
kEstablished,
kShutdownPending,
kShutdownSent,
kShutdownReceived,
kShutdownAckSent,
};
// Returns the log prefix used for debug logging.
std::string log_prefix() const;
bool IsConsistent() const;
static constexpr absl::string_view ToString(DcSctpSocket::State state);
// Changes the socket state, given a `reason` (for debugging/logging).
void SetState(State state, absl::string_view reason);
// Fills in `connect_params` with random verification tag and initial TSN.
void MakeConnectionParameters();
// Closes the association. Note that the TCB will not be valid past this call.
void InternalClose(ErrorKind error, absl::string_view message);
// Closes the association, because of too many retransmission errors.
void CloseConnectionBecauseOfTooManyTransmissionErrors();
// Timer expiration handlers
absl::optional<DurationMs> OnInitTimerExpiry();
absl::optional<DurationMs> OnCookieTimerExpiry();
absl::optional<DurationMs> OnShutdownTimerExpiry();
// Builds the packet from `builder` and sends it (through callbacks).
void SendPacket(SctpPacket::Builder& builder);
// Sends SHUTDOWN or SHUTDOWN-ACK if the socket is shutting down and if all
// outstanding data has been acknowledged.
void MaybeSendShutdownOrAck();
// If the socket is shutting down, responds SHUTDOWN to any incoming DATA.
void MaybeSendShutdownOnPacketReceived(const SctpPacket& packet);
// Sends a INIT chunk.
void SendInit();
// Sends a CookieEcho chunk.
void SendCookieEcho();
// Sends a SHUTDOWN chunk.
void SendShutdown();
// Sends a SHUTDOWN-ACK chunk.
void SendShutdownAck();
// Validates the SCTP packet, as a whole - not the validity of individual
// chunks within it, as that's done in the different chunk handlers.
bool ValidatePacket(const SctpPacket& packet);
// Parses `payload`, which is a serialized packet that is just going to be
// sent and prints all chunks.
void DebugPrintOutgoing(rtc::ArrayView<const uint8_t> payload);
// Called whenever there may be reassembled messages, and delivers those.
void DeliverReassembledMessages();
// Returns true if there is a TCB, and false otherwise (and reports an error).
bool ValidateHasTCB();
// Returns true if the parsing of a chunk of type `T` succeeded. If it didn't,
// it reports an error and returns false.
template <class T>
bool ValidateParseSuccess(const absl::optional<T>& c) {
if (c.has_value()) {
return true;
}
ReportFailedToParseChunk(T::kType);
return false;
}
// Reports failing to have parsed a chunk with the provided `chunk_type`.
void ReportFailedToParseChunk(int chunk_type);
// Called when unknown chunks are received. May report an error.
bool HandleUnrecognizedChunk(const SctpPacket::ChunkDescriptor& descriptor);
// Will dispatch more specific chunk handlers.
bool Dispatch(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming DATA chunks.
void HandleData(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming I-DATA chunks.
void HandleIData(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Common handler for DATA and I-DATA chunks.
void HandleDataCommon(AnyDataChunk& chunk);
// Handles incoming INIT chunks.
void HandleInit(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming INIT-ACK chunks.
void HandleInitAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SACK chunks.
void HandleSack(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming HEARTBEAT chunks.
void HandleHeartbeatRequest(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming HEARTBEAT-ACK chunks.
void HandleHeartbeatAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming ABORT chunks.
void HandleAbort(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming ERROR chunks.
void HandleError(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming COOKIE-ECHO chunks.
void HandleCookieEcho(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles receiving COOKIE-ECHO when there already is a TCB. The return value
// indicates if the processing should continue.
bool HandleCookieEchoWithTCB(const CommonHeader& header,
const StateCookie& cookie);
// Handles incoming COOKIE-ACK chunks.
void HandleCookieAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SHUTDOWN chunks.
void HandleShutdown(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SHUTDOWN-ACK chunks.
void HandleShutdownAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming FORWARD-TSN chunks.
void HandleForwardTsn(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming I-FORWARD-TSN chunks.
void HandleIForwardTsn(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming RE-CONFIG chunks.
void HandleReconfig(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Common handled for FORWARD-TSN/I-FORWARD-TSN.
void HandleForwardTsnCommon(const AnyForwardTsnChunk& chunk);
// Handles incoming SHUTDOWN-COMPLETE chunks
void HandleShutdownComplete(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
const std::string log_prefix_;
const std::unique_ptr<PacketObserver> packet_observer_;
const DcSctpOptions options_;
// Enqueues callbacks and dispatches them just before returning to the caller.
CallbackDeferrer callbacks_;
TimerManager timer_manager_;
const std::unique_ptr<Timer> t1_init_;
const std::unique_ptr<Timer> t1_cookie_;
const std::unique_ptr<Timer> t2_shutdown_;
// The actual SendQueue implementation. As data can be sent on a socket before
// the connection is established, this component is not in the TCB.
FCFSSendQueue send_queue_;
// Only valid when state == State::kCookieEchoed
// A cached Cookie Echo Chunk, to be re-sent on timer expiry.
absl::optional<CookieEchoChunk> cookie_echo_chunk_ = absl::nullopt;
// Contains verification tag and initial TSN between having sent the INIT
// until the connection is established (there is no TCB at this point).
ConnectParameters connect_params_;
// The socket state.
State state_ = State::kClosed;
// If the connection is established, contains a transmission control block.
std::unique_ptr<TransmissionControlBlock> tcb_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_DCSCTP_SOCKET_H_

File diff suppressed because it is too large Load Diff

View File

@ -111,10 +111,6 @@ class MockDcSctpSocketCallbacks : public DcSctpSocketCallbacks {
OnIncomingStreamsReset, OnIncomingStreamsReset,
(rtc::ArrayView<const StreamID> incoming_streams), (rtc::ArrayView<const StreamID> incoming_streams),
(override)); (override));
MOCK_METHOD(void,
OnSentMessageExpired,
(StreamID stream_id, PPID ppid, bool unsent),
(override));
bool HasPacket() const { return !sent_packets_.empty(); } bool HasPacket() const { return !sent_packets_.empty(); }

View File

@ -0,0 +1,78 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/socket/state_cookie.h"
#include <cstdint>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/packet/bounded_byte_reader.h"
#include "net/dcsctp/packet/bounded_byte_writer.h"
#include "net/dcsctp/socket/capabilities.h"
#include "rtc_base/logging.h"
namespace dcsctp {
// Magic values, which the state cookie is prefixed with.
constexpr uint32_t kMagic1 = 1684230979;
constexpr uint32_t kMagic2 = 1414541360;
constexpr size_t StateCookie::kCookieSize;
std::vector<uint8_t> StateCookie::Serialize() {
std::vector<uint8_t> cookie;
cookie.resize(kCookieSize);
BoundedByteWriter<kCookieSize> buffer(cookie);
buffer.Store32<0>(kMagic1);
buffer.Store32<4>(kMagic2);
buffer.Store32<8>(*initiate_tag_);
buffer.Store32<12>(*initial_tsn_);
buffer.Store32<16>(a_rwnd_);
buffer.Store32<20>(static_cast<uint32_t>(*tie_tag_ >> 32));
buffer.Store32<24>(static_cast<uint32_t>(*tie_tag_));
buffer.Store8<28>(capabilities_.partial_reliability);
buffer.Store8<29>(capabilities_.message_interleaving);
buffer.Store8<30>(capabilities_.reconfig);
return cookie;
}
absl::optional<StateCookie> StateCookie::Deserialize(
rtc::ArrayView<const uint8_t> cookie) {
if (cookie.size() != kCookieSize) {
RTC_DLOG(LS_WARNING) << "Invalid state cookie: " << cookie.size()
<< " bytes";
return absl::nullopt;
}
BoundedByteReader<kCookieSize> buffer(cookie);
uint32_t magic1 = buffer.Load32<0>();
uint32_t magic2 = buffer.Load32<4>();
if (magic1 != kMagic1 || magic2 != kMagic2) {
RTC_DLOG(LS_WARNING) << "Invalid state cookie; wrong magic";
return absl::nullopt;
}
VerificationTag verification_tag(buffer.Load32<8>());
TSN initial_tsn(buffer.Load32<12>());
uint32_t a_rwnd = buffer.Load32<16>();
uint32_t tie_tag_upper = buffer.Load32<20>();
uint32_t tie_tag_lower = buffer.Load32<24>();
TieTag tie_tag(static_cast<uint64_t>(tie_tag_upper) << 32 |
static_cast<uint64_t>(tie_tag_lower));
Capabilities capabilities;
capabilities.partial_reliability = buffer.Load8<28>() != 0;
capabilities.message_interleaving = buffer.Load8<29>() != 0;
capabilities.reconfig = buffer.Load8<30>() != 0;
return StateCookie(verification_tag, initial_tsn, a_rwnd, tie_tag,
capabilities);
}
} // namespace dcsctp

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef NET_DCSCTP_SOCKET_STATE_COOKIE_H_
#define NET_DCSCTP_SOCKET_STATE_COOKIE_H_
#include <cstdint>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/socket/capabilities.h"
namespace dcsctp {
// This is serialized as a state cookie and put in INIT_ACK. The client then
// responds with this in COOKIE_ECHO.
//
// NOTE: Expect that the client will modify it to try to exploit the library.
// Do not trust anything in it; no pointers or anything like that.
class StateCookie {
public:
static constexpr size_t kCookieSize = 31;
StateCookie(VerificationTag initiate_tag,
TSN initial_tsn,
uint32_t a_rwnd,
TieTag tie_tag,
Capabilities capabilities)
: initiate_tag_(initiate_tag),
initial_tsn_(initial_tsn),
a_rwnd_(a_rwnd),
tie_tag_(tie_tag),
capabilities_(capabilities) {}
// Returns a serialized version of this cookie.
std::vector<uint8_t> Serialize();
// Deserializes the cookie, and returns absl::nullopt if that failed.
static absl::optional<StateCookie> Deserialize(
rtc::ArrayView<const uint8_t> cookie);
VerificationTag initiate_tag() const { return initiate_tag_; }
TSN initial_tsn() const { return initial_tsn_; }
uint32_t a_rwnd() const { return a_rwnd_; }
TieTag tie_tag() const { return tie_tag_; }
const Capabilities& capabilities() const { return capabilities_; }
private:
const VerificationTag initiate_tag_;
const TSN initial_tsn_;
const uint32_t a_rwnd_;
const TieTag tie_tag_;
const Capabilities capabilities_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_STATE_COOKIE_H_

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "net/dcsctp/socket/state_cookie.h"
#include "net/dcsctp/testing/testing_macros.h"
#include "rtc_base/gunit.h"
#include "test/gmock.h"
namespace dcsctp {
namespace {
using ::testing::SizeIs;
TEST(StateCookieTest, SerializeAndDeserialize) {
Capabilities capabilities = {/*partial_reliability=*/true,
/*message_interleaving=*/false,
/*reconfig=*/true};
StateCookie cookie(VerificationTag(123), TSN(456),
/*a_rwnd=*/789, TieTag(101112), capabilities);
std::vector<uint8_t> serialized = cookie.Serialize();
EXPECT_THAT(serialized, SizeIs(StateCookie::kCookieSize));
ASSERT_HAS_VALUE_AND_ASSIGN(StateCookie deserialized,
StateCookie::Deserialize(serialized));
EXPECT_EQ(deserialized.initiate_tag(), VerificationTag(123));
EXPECT_EQ(deserialized.initial_tsn(), TSN(456));
EXPECT_EQ(deserialized.a_rwnd(), 789u);
EXPECT_EQ(deserialized.tie_tag(), TieTag(101112));
EXPECT_TRUE(deserialized.capabilities().partial_reliability);
EXPECT_FALSE(deserialized.capabilities().message_interleaving);
EXPECT_TRUE(deserialized.capabilities().reconfig);
}
} // namespace
} // namespace dcsctp