xref: /aosp_15_r20/external/webrtc/net/dcsctp/socket/heartbeat_handler.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2021 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 #include "net/dcsctp/socket/heartbeat_handler.h"
11 
12 #include <stddef.h>
13 
14 #include <cstdint>
15 #include <memory>
16 #include <string>
17 #include <utility>
18 #include <vector>
19 
20 #include "absl/functional/bind_front.h"
21 #include "absl/strings/string_view.h"
22 #include "absl/types/optional.h"
23 #include "api/array_view.h"
24 #include "net/dcsctp/packet/bounded_byte_reader.h"
25 #include "net/dcsctp/packet/bounded_byte_writer.h"
26 #include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
27 #include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
28 #include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h"
29 #include "net/dcsctp/packet/parameter/parameter.h"
30 #include "net/dcsctp/packet/sctp_packet.h"
31 #include "net/dcsctp/public/dcsctp_options.h"
32 #include "net/dcsctp/public/dcsctp_socket.h"
33 #include "net/dcsctp/socket/context.h"
34 #include "net/dcsctp/timer/timer.h"
35 #include "rtc_base/logging.h"
36 
37 namespace dcsctp {
38 
39 // This is stored (in serialized form) as HeartbeatInfoParameter sent in
40 // HeartbeatRequestChunk and received back in HeartbeatAckChunk. It should be
41 // well understood that this data may be modified by the peer, so it can't
42 // be trusted.
43 //
44 // It currently only stores a timestamp, in millisecond precision, to allow for
45 // RTT measurements. If that would be manipulated by the peer, it would just
46 // result in incorrect RTT measurements, which isn't an issue.
47 class HeartbeatInfo {
48  public:
49   static constexpr size_t kBufferSize = sizeof(uint64_t);
50   static_assert(kBufferSize == 8, "Unexpected buffer size");
51 
HeartbeatInfo(TimeMs created_at)52   explicit HeartbeatInfo(TimeMs created_at) : created_at_(created_at) {}
53 
Serialize()54   std::vector<uint8_t> Serialize() {
55     uint32_t high_bits = static_cast<uint32_t>(*created_at_ >> 32);
56     uint32_t low_bits = static_cast<uint32_t>(*created_at_);
57 
58     std::vector<uint8_t> data(kBufferSize);
59     BoundedByteWriter<kBufferSize> writer(data);
60     writer.Store32<0>(high_bits);
61     writer.Store32<4>(low_bits);
62     return data;
63   }
64 
Deserialize(rtc::ArrayView<const uint8_t> data)65   static absl::optional<HeartbeatInfo> Deserialize(
66       rtc::ArrayView<const uint8_t> data) {
67     if (data.size() != kBufferSize) {
68       RTC_LOG(LS_WARNING) << "Invalid heartbeat info: " << data.size()
69                           << " bytes";
70       return absl::nullopt;
71     }
72 
73     BoundedByteReader<kBufferSize> reader(data);
74     uint32_t high_bits = reader.Load32<0>();
75     uint32_t low_bits = reader.Load32<4>();
76 
77     uint64_t created_at = static_cast<uint64_t>(high_bits) << 32 | low_bits;
78     return HeartbeatInfo(TimeMs(created_at));
79   }
80 
created_at() const81   TimeMs created_at() const { return created_at_; }
82 
83  private:
84   const TimeMs created_at_;
85 };
86 
HeartbeatHandler(absl::string_view log_prefix,const DcSctpOptions & options,Context * context,TimerManager * timer_manager)87 HeartbeatHandler::HeartbeatHandler(absl::string_view log_prefix,
88                                    const DcSctpOptions& options,
89                                    Context* context,
90                                    TimerManager* timer_manager)
91     : log_prefix_(std::string(log_prefix) + "heartbeat: "),
92       ctx_(context),
93       timer_manager_(timer_manager),
94       interval_duration_(options.heartbeat_interval),
95       interval_duration_should_include_rtt_(
96           options.heartbeat_interval_include_rtt),
97       interval_timer_(timer_manager_->CreateTimer(
98           "heartbeat-interval",
99           absl::bind_front(&HeartbeatHandler::OnIntervalTimerExpiry, this),
100           TimerOptions(interval_duration_, TimerBackoffAlgorithm::kFixed))),
101       timeout_timer_(timer_manager_->CreateTimer(
102           "heartbeat-timeout",
103           absl::bind_front(&HeartbeatHandler::OnTimeoutTimerExpiry, this),
104           TimerOptions(options.rto_initial,
105                        TimerBackoffAlgorithm::kExponential,
106                        /*max_restarts=*/0))) {
107   // The interval timer must always be running as long as the association is up.
108   RestartTimer();
109 }
110 
RestartTimer()111 void HeartbeatHandler::RestartTimer() {
112   if (interval_duration_ == DurationMs(0)) {
113     // Heartbeating has been disabled.
114     return;
115   }
116 
117   if (interval_duration_should_include_rtt_) {
118     // The RTT should be used, but it's not easy accessible. The RTO will
119     // suffice.
120     interval_timer_->set_duration(interval_duration_ + ctx_->current_rto());
121   } else {
122     interval_timer_->set_duration(interval_duration_);
123   }
124 
125   interval_timer_->Start();
126 }
127 
HandleHeartbeatRequest(HeartbeatRequestChunk chunk)128 void HeartbeatHandler::HandleHeartbeatRequest(HeartbeatRequestChunk chunk) {
129   // https://tools.ietf.org/html/rfc4960#section-8.3
130   // "The receiver of the HEARTBEAT should immediately respond with a
131   // HEARTBEAT ACK that contains the Heartbeat Information TLV, together with
132   // any other received TLVs, copied unchanged from the received HEARTBEAT
133   // chunk."
134   ctx_->Send(ctx_->PacketBuilder().Add(
135       HeartbeatAckChunk(std::move(chunk).extract_parameters())));
136 }
137 
HandleHeartbeatAck(HeartbeatAckChunk chunk)138 void HeartbeatHandler::HandleHeartbeatAck(HeartbeatAckChunk chunk) {
139   timeout_timer_->Stop();
140   absl::optional<HeartbeatInfoParameter> info_param = chunk.info();
141   if (!info_param.has_value()) {
142     ctx_->callbacks().OnError(
143         ErrorKind::kParseFailed,
144         "Failed to parse HEARTBEAT-ACK; No Heartbeat Info parameter");
145     return;
146   }
147   absl::optional<HeartbeatInfo> info =
148       HeartbeatInfo::Deserialize(info_param->info());
149   if (!info.has_value()) {
150     ctx_->callbacks().OnError(ErrorKind::kParseFailed,
151                               "Failed to parse HEARTBEAT-ACK; Failed to "
152                               "deserialized Heartbeat info parameter");
153     return;
154   }
155 
156   TimeMs now = ctx_->callbacks().TimeMillis();
157   if (info->created_at() > TimeMs(0) && info->created_at() <= now) {
158     ctx_->ObserveRTT(now - info->created_at());
159   }
160 
161   // https://tools.ietf.org/html/rfc4960#section-8.1
162   // "The counter shall be reset each time ... a HEARTBEAT ACK is received from
163   // the peer endpoint."
164   ctx_->ClearTxErrorCounter();
165 }
166 
OnIntervalTimerExpiry()167 absl::optional<DurationMs> HeartbeatHandler::OnIntervalTimerExpiry() {
168   if (ctx_->is_connection_established()) {
169     HeartbeatInfo info(ctx_->callbacks().TimeMillis());
170     timeout_timer_->set_duration(ctx_->current_rto());
171     timeout_timer_->Start();
172     RTC_DLOG(LS_INFO) << log_prefix_ << "Sending HEARTBEAT with timeout "
173                       << *timeout_timer_->duration();
174 
175     Parameters parameters = Parameters::Builder()
176                                 .Add(HeartbeatInfoParameter(info.Serialize()))
177                                 .Build();
178 
179     ctx_->Send(ctx_->PacketBuilder().Add(
180         HeartbeatRequestChunk(std::move(parameters))));
181   } else {
182     RTC_DLOG(LS_VERBOSE)
183         << log_prefix_
184         << "Will not send HEARTBEAT when connection not established";
185   }
186   return absl::nullopt;
187 }
188 
OnTimeoutTimerExpiry()189 absl::optional<DurationMs> HeartbeatHandler::OnTimeoutTimerExpiry() {
190   // Note that the timeout timer is not restarted. It will be started again when
191   // the interval timer expires.
192   RTC_DCHECK(!timeout_timer_->is_running());
193   ctx_->IncrementTxErrorCounter("HEARTBEAT timeout");
194   return absl::nullopt;
195 }
196 }  // namespace dcsctp
197