1 /*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 //! Quiche Config support
18 //!
19 //! Quiche config objects are needed mutably for constructing a Quiche
20 //! connection object, but not when they are actually being used. As these
21 //! objects include a `SSL_CTX` which can be somewhat expensive and large when
22 //! using a certificate path, it can be beneficial to cache them.
23 //!
24 //! This module provides a caching layer for loading and constructing
25 //! these configurations.
26
27 use quiche::{h3, Result};
28 use std::collections::HashMap;
29 use std::ops::DerefMut;
30 use std::sync::{Arc, RwLock, Weak};
31 use tokio::sync::Mutex;
32
33 type WeakConfig = Weak<Mutex<quiche::Config>>;
34
35 /// A cheaply clonable `quiche::Config`
36 #[derive(Clone)]
37 pub struct Config(Arc<Mutex<quiche::Config>>);
38
39 const MAX_INCOMING_BUFFER_SIZE_WHOLE: u64 = 10000000;
40 const MAX_INCOMING_BUFFER_SIZE_EACH: u64 = 1000000;
41 const MAX_CONCURRENT_STREAM_SIZE: u64 = 100;
42 /// Maximum datagram size we will accept.
43 pub const MAX_DATAGRAM_SIZE: usize = 1350;
44
45 impl Config {
from_weak(weak: &WeakConfig) -> Option<Self>46 fn from_weak(weak: &WeakConfig) -> Option<Self> {
47 weak.upgrade().map(Self)
48 }
49
to_weak(&self) -> WeakConfig50 fn to_weak(&self) -> WeakConfig {
51 Arc::downgrade(&self.0)
52 }
53
54 /// Construct a `Config` object from certificate path. If no path
55 /// is provided, peers will not be verified.
from_key(key: &Key) -> Result<Self>56 pub fn from_key(key: &Key) -> Result<Self> {
57 let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION)?;
58 config.set_application_protos(h3::APPLICATION_PROTOCOL)?;
59 match key.cert_path.as_deref() {
60 Some(path) => {
61 config.verify_peer(true);
62 config.load_verify_locations_from_directory(path)?;
63 }
64 None => config.verify_peer(false),
65 }
66 if key.enable_early_data {
67 config.enable_early_data();
68 }
69
70 // Some of these configs are necessary, or the server can't respond the HTTP/3 request.
71 config.set_max_idle_timeout(key.max_idle_timeout);
72 config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
73 config.set_initial_max_data(MAX_INCOMING_BUFFER_SIZE_WHOLE);
74 config.set_initial_max_stream_data_bidi_local(MAX_INCOMING_BUFFER_SIZE_EACH);
75 config.set_initial_max_stream_data_bidi_remote(MAX_INCOMING_BUFFER_SIZE_EACH);
76 config.set_initial_max_stream_data_uni(MAX_INCOMING_BUFFER_SIZE_EACH);
77 config.set_initial_max_streams_bidi(MAX_CONCURRENT_STREAM_SIZE);
78 config.set_initial_max_streams_uni(MAX_CONCURRENT_STREAM_SIZE);
79 config.set_disable_active_migration(true);
80 Ok(Self(Arc::new(Mutex::new(config))))
81 }
82
83 /// Take the underlying config, usable as `&mut quiche::Config` for use
84 /// with `quiche::connect`.
take(&mut self) -> impl DerefMut<Target = quiche::Config> + '_85 pub async fn take(&mut self) -> impl DerefMut<Target = quiche::Config> + '_ {
86 self.0.lock().await
87 }
88 }
89
90 #[derive(Clone, Default)]
91 struct State {
92 // Mapping from cert_path to configs
93 key_to_config: HashMap<Key, WeakConfig>,
94 // Keep latest config alive to minimize reparsing when flapping
95 // If more keep-alive is needed, replace with a LRU LinkedList
96 latest: Option<Config>,
97 }
98
99 impl State {
get_config(&self, key: &Key) -> Option<Config>100 fn get_config(&self, key: &Key) -> Option<Config> {
101 self.key_to_config.get(key).and_then(Config::from_weak)
102 }
103
keep_alive(&mut self, config: Config)104 fn keep_alive(&mut self, config: Config) {
105 self.latest = Some(config);
106 }
107
garbage_collect(&mut self)108 fn garbage_collect(&mut self) {
109 self.key_to_config.retain(|_, config| config.strong_count() != 0)
110 }
111 }
112
113 /// Cache of Quiche Config objects
114 ///
115 /// Cloning this cache will create another handle to the same cache.
116 ///
117 /// Loading a config object through this caching layer will only keep the
118 /// latest config loaded alive directly, but will still act as a cache
119 /// for any configurations still in use - if the returned `Config` is still
120 /// live, queries to `Cache` will not reconstruct it.
121 #[derive(Clone, Default)]
122 pub struct Cache {
123 // Shared state amongst cache handles
124 state: Arc<RwLock<State>>,
125 }
126
127 /// Key used for getting an associated Quiche Config from Cache.
128 #[derive(Clone, PartialEq, Eq, Hash)]
129 pub struct Key {
130 pub cert_path: Option<String>,
131 pub max_idle_timeout: u64,
132 pub enable_early_data: bool,
133 }
134
135 impl Cache {
136 /// Creates a fresh empty cache
new() -> Self137 pub fn new() -> Self {
138 Default::default()
139 }
140
141 /// Behaves as `Config::from_cert_path`, but with a cache.
142 /// If any object previously given out by this cache is still live,
143 /// a duplicate will not be made.
get(&self, key: &Key) -> Result<Config>144 pub fn get(&self, key: &Key) -> Result<Config> {
145 // Fast path - read-only access to state retrieves config
146 if let Some(config) = self.state.read().unwrap().get_config(key) {
147 return Ok(config);
148 }
149
150 // Unlocked, calculate config. If we have two racing attempts to load
151 // the cert path, we'll arbitrate that in the next step, but this
152 // makes sure loading a new cert path doesn't block other loads to
153 // refresh connections.
154 let config = Config::from_key(key)?;
155
156 let mut state = self.state.write().unwrap();
157 // We now have exclusive access to the state.
158 // If someone else calculated a config at the same time as us, we
159 // want to discard ours and use theirs, since it will result in
160 // less total memory used.
161 if let Some(config) = state.get_config(key) {
162 return Ok(config);
163 }
164
165 // We have exclusive access and a fresh config. Install it into
166 // the cache.
167 state.keep_alive(config.clone());
168 state.key_to_config.insert(key.clone(), config.to_weak());
169 Ok(config)
170 }
171
172 /// Purges any config paths which no longer point to a config entry.
garbage_collect(&self)173 pub fn garbage_collect(&self) {
174 self.state.write().unwrap().garbage_collect();
175 }
176 }
177
178 #[test]
create_quiche_config()179 fn create_quiche_config() {
180 assert!(
181 Config::from_key(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
182 .is_ok(),
183 "quiche config without cert creating failed"
184 );
185 assert!(
186 Config::from_key(&Key {
187 cert_path: Some("data/local/tmp/".to_string()),
188 max_idle_timeout: 1000,
189 enable_early_data: true,
190 })
191 .is_ok(),
192 "quiche config with cert creating failed"
193 );
194 }
195
196 #[test]
shared_cache()197 fn shared_cache() {
198 let cache_a = Cache::new();
199 let config_a = cache_a
200 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
201 .unwrap();
202 assert_eq!(Arc::strong_count(&config_a.0), 2);
203 let _config_b = cache_a
204 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
205 .unwrap();
206 assert_eq!(Arc::strong_count(&config_a.0), 3);
207 }
208
209 #[test]
different_keys()210 fn different_keys() {
211 let cache = Cache::new();
212 let key_a = Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: false };
213 let key_b =
214 Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000, enable_early_data: false };
215 let key_c =
216 Key { cert_path: Some("a".to_string()), max_idle_timeout: 5000, enable_early_data: false };
217 let key_d =
218 Key { cert_path: Some("a".to_string()), max_idle_timeout: 5000, enable_early_data: true };
219 let config_a = cache.get(&key_a).unwrap();
220 let config_b = cache.get(&key_b).unwrap();
221 let _config_b = cache.get(&key_b).unwrap();
222 let config_c = cache.get(&key_c).unwrap();
223 let _config_c = cache.get(&key_c).unwrap();
224 let config_d = cache.get(&key_d).unwrap();
225 let _config_d = cache.get(&key_d).unwrap();
226
227 assert_eq!(Arc::strong_count(&config_a.0), 1);
228 assert_eq!(Arc::strong_count(&config_b.0), 2);
229 assert_eq!(Arc::strong_count(&config_c.0), 2);
230
231 // config_d was most recently created, so it should have an extra strong reference due to
232 // keep-alive in the cache.
233 assert_eq!(Arc::strong_count(&config_d.0), 3);
234 }
235
236 #[test]
lifetimes()237 fn lifetimes() {
238 let cache = Cache::new();
239 let key_a =
240 Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000, enable_early_data: true };
241 let key_b =
242 Key { cert_path: Some("b".to_string()), max_idle_timeout: 1000, enable_early_data: true };
243 let config_none = cache
244 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
245 .unwrap();
246 let config_a = cache.get(&key_a).unwrap();
247 let config_b = cache.get(&key_b).unwrap();
248
249 // The first two we created should have a strong count of one - those handles are the only
250 // thing keeping them alive.
251 assert_eq!(Arc::strong_count(&config_none.0), 1);
252 assert_eq!(Arc::strong_count(&config_a.0), 1);
253
254 // If we try to get another handle we already have, it should be the same one.
255 let _config_a2 = cache.get(&key_a).unwrap();
256 assert_eq!(Arc::strong_count(&config_a.0), 2);
257
258 // config_b was most recently created, so it should have a keep-alive
259 // inside the cache.
260 assert_eq!(Arc::strong_count(&config_b.0), 2);
261
262 // If we weaken one of the first handles, then drop it, the weak handle should break
263 let config_none_weak = Config::to_weak(&config_none);
264 assert_eq!(config_none_weak.strong_count(), 1);
265 drop(config_none);
266 assert_eq!(config_none_weak.strong_count(), 0);
267 assert!(Config::from_weak(&config_none_weak).is_none());
268
269 // If we weaken the most *recent* handle, it should keep working
270 let config_b_weak = Config::to_weak(&config_b);
271 assert_eq!(config_b_weak.strong_count(), 2);
272 drop(config_b);
273 assert_eq!(config_b_weak.strong_count(), 1);
274 assert!(Config::from_weak(&config_b_weak).is_some());
275 assert_eq!(config_b_weak.strong_count(), 1);
276
277 // If we try to get a config which is still kept alive by the cache, we should get the same
278 // one.
279 let _config_b2 = cache.get(&key_b).unwrap();
280 assert_eq!(config_b_weak.strong_count(), 2);
281
282 // We broke None, but "a" and "b" should still both be alive. Check that
283 // this is still the case in the mapping after garbage collection.
284 cache.garbage_collect();
285 assert_eq!(cache.state.read().unwrap().key_to_config.len(), 2);
286 }
287
288 #[tokio::test]
quiche_connect()289 async fn quiche_connect() {
290 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
291 let mut config =
292 Config::from_key(&Key { cert_path: None, max_idle_timeout: 10, enable_early_data: true })
293 .unwrap();
294 let local = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 42));
295 let peer = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 41));
296 let conn_id = quiche::ConnectionId::from_ref(&[]);
297
298 quiche::connect(None, &conn_id, local, peer, config.take().await.deref_mut()).unwrap();
299 }
300