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