1 /** @file
2
3 A brief file description
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #include <chrono>
25 #include "QUICPathValidator.h"
26 #include "QUICPacket.h"
27
28 #define QUICDebug(fmt, ...) Debug("quic_path", "[%s] " fmt, this->_cinfo.cids().data(), ##__VA_ARGS__)
29
30 bool
has_more_challenges() const31 QUICPathValidator::ValidationJob::has_more_challenges() const
32 {
33 return this->_has_outgoing_challenge;
34 }
35
36 const uint8_t *
get_next_challenge() const37 QUICPathValidator::ValidationJob::get_next_challenge() const
38 {
39 if (this->_has_outgoing_challenge) {
40 return this->_outgoing_challenge + ((this->_has_outgoing_challenge - 1) * 8);
41 } else {
42 return nullptr;
43 }
44 }
45
46 void
consume_challenge()47 QUICPathValidator::ValidationJob::consume_challenge()
48 {
49 --(this->_has_outgoing_challenge);
50 }
51
52 bool
is_validating(const QUICPath & path) const53 QUICPathValidator::is_validating(const QUICPath &path) const
54 {
55 if (auto j = this->_jobs.find(path); j != this->_jobs.end()) {
56 return j->second.is_validating();
57 } else {
58 return false;
59 }
60 }
61
62 bool
is_validated(const QUICPath & path) const63 QUICPathValidator::is_validated(const QUICPath &path) const
64 {
65 if (auto j = this->_jobs.find(path); j != this->_jobs.end()) {
66 return j->second.is_validated();
67 } else {
68 return false;
69 }
70 }
71
72 void
validate(const QUICPath & path)73 QUICPathValidator::validate(const QUICPath &path)
74 {
75 if (this->_jobs.find(path) != this->_jobs.end()) {
76 // Do nothing
77 } else {
78 auto result = this->_jobs.emplace(std::piecewise_construct, std::forward_as_tuple(path), std::forward_as_tuple());
79 result.first->second.start();
80 // QUICDebug("Validating a path between %s and %s", path.local_ep(), path.remote_ep());
81 }
82 }
83
84 bool
is_validating() const85 QUICPathValidator::ValidationJob::is_validating() const
86 {
87 return this->_state == ValidationState::VALIDATING;
88 }
89
90 bool
is_validated() const91 QUICPathValidator::ValidationJob::is_validated() const
92 {
93 return this->_state == ValidationState::VALIDATED;
94 }
95
96 void
start()97 QUICPathValidator::ValidationJob::start()
98 {
99 this->_state = ValidationState::VALIDATING;
100 this->_generate_challenge();
101 }
102
103 void
_generate_challenge()104 QUICPathValidator::ValidationJob::_generate_challenge()
105 {
106 size_t seed =
107 std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
108 std::minstd_rand random(seed);
109
110 for (auto &i : this->_outgoing_challenge) {
111 i = random();
112 }
113 this->_has_outgoing_challenge = 3;
114 }
115
116 bool
validate_response(const uint8_t * data)117 QUICPathValidator::ValidationJob::validate_response(const uint8_t *data)
118 {
119 for (int i = 0; i < 3; ++i) {
120 if (memcmp(this->_outgoing_challenge + (QUICPathChallengeFrame::DATA_LEN * i), data, QUICPathChallengeFrame::DATA_LEN) == 0) {
121 this->_state = ValidationState::VALIDATED;
122 this->_has_outgoing_challenge = 0;
123 return true;
124 }
125 }
126 return false;
127 }
128
129 //
130 // QUICFrameHandler
131 //
132 std::vector<QUICFrameType>
interests()133 QUICPathValidator::interests()
134 {
135 return {QUICFrameType::PATH_CHALLENGE, QUICFrameType::PATH_RESPONSE};
136 }
137
138 QUICConnectionErrorUPtr
handle_frame(QUICEncryptionLevel level,const QUICFrame & frame)139 QUICPathValidator::handle_frame(QUICEncryptionLevel level, const QUICFrame &frame)
140 {
141 QUICConnectionErrorUPtr error = nullptr;
142
143 switch (frame.type()) {
144 case QUICFrameType::PATH_CHALLENGE:
145 this->_incoming_challenges.emplace(this->_incoming_challenges.begin(),
146 static_cast<const QUICPathChallengeFrame &>(frame).data());
147 break;
148 case QUICFrameType::PATH_RESPONSE:
149 if (auto item = this->_jobs.find({frame.packet()->to(), frame.packet()->from()}); item != this->_jobs.end()) {
150 if (item->second.validate_response(static_cast<const QUICPathResponseFrame &>(frame).data())) {
151 QUICDebug("validation succeeded");
152 this->_on_validation_callback(true);
153 } else {
154 QUICDebug("validation failed");
155 this->_on_validation_callback(false);
156 }
157 } else {
158 error = std::make_unique<QUICConnectionError>(QUICTransErrorCode::PROTOCOL_VIOLATION);
159 }
160 break;
161 default:
162 ink_assert(!"Can't happen");
163 }
164
165 return error;
166 }
167
168 //
169 // QUICFrameGenerator
170 //
171 bool
will_generate_frame(QUICEncryptionLevel level,size_t current_packet_size,bool ack_eliciting,uint32_t seq_num)172 QUICPathValidator::will_generate_frame(QUICEncryptionLevel level, size_t current_packet_size, bool ack_eliciting, uint32_t seq_num)
173 {
174 if (!this->_is_level_matched(level)) {
175 return false;
176 }
177
178 if (this->_latest_seq_num == seq_num) {
179 return false;
180 }
181
182 // Check challenges
183 for (auto &&item : this->_jobs) {
184 auto &j = item.second;
185 if (!j.is_validating() && !j.is_validated()) {
186 j.start();
187 return true;
188 }
189 if (j.has_more_challenges()) {
190 return true;
191 }
192 }
193
194 // Check responses
195 return !this->_incoming_challenges.empty();
196 }
197
198 /**
199 * @param connection_credit This is not used. Because PATH_CHALLENGE and PATH_RESPONSE frame are not flow-controlled
200 */
201 QUICFrame *
generate_frame(uint8_t * buf,QUICEncryptionLevel level,uint64_t,uint16_t maximum_frame_size,size_t current_packet_size,uint32_t seq_num)202 QUICPathValidator::generate_frame(uint8_t *buf, QUICEncryptionLevel level, uint64_t /* connection_credit */,
203 uint16_t maximum_frame_size, size_t current_packet_size, uint32_t seq_num)
204 {
205 QUICFrame *frame = nullptr;
206
207 if (!this->_is_level_matched(level)) {
208 return frame;
209 }
210
211 if (!this->_incoming_challenges.empty()) {
212 frame = QUICFrameFactory::create_path_response_frame(buf, this->_incoming_challenges.back());
213 if (frame && frame->size() > maximum_frame_size) {
214 // Cancel generating frame
215 frame = nullptr;
216 } else {
217 this->_incoming_challenges.pop_back();
218 }
219 } else {
220 for (auto &&item : this->_jobs) {
221 auto &j = item.second;
222 if (j.has_more_challenges()) {
223 frame = QUICFrameFactory::create_path_challenge_frame(buf, j.get_next_challenge());
224 if (frame && frame->size() > maximum_frame_size) {
225 // Cancel generating frame
226 frame = nullptr;
227 } else {
228 j.consume_challenge();
229 }
230 break;
231 }
232 }
233 }
234
235 this->_latest_seq_num = seq_num;
236
237 return frame;
238 }
239