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 cache_b = cache_a.clone();
200 let config_a = cache_a
201 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
202 .unwrap();
203 assert_eq!(Arc::strong_count(&config_a.0), 2);
204 let _config_b = cache_b
205 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
206 .unwrap();
207 assert_eq!(Arc::strong_count(&config_a.0), 3);
208 }
209
210 #[test]
different_keys()211 fn different_keys() {
212 let cache = Cache::new();
213 let key_a = Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: false };
214 let key_b =
215 Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000, enable_early_data: false };
216 let key_c =
217 Key { cert_path: Some("a".to_string()), max_idle_timeout: 5000, enable_early_data: false };
218 let key_d =
219 Key { cert_path: Some("a".to_string()), max_idle_timeout: 5000, enable_early_data: true };
220 let config_a = cache.get(&key_a).unwrap();
221 let config_b = cache.get(&key_b).unwrap();
222 let _config_b = cache.get(&key_b).unwrap();
223 let config_c = cache.get(&key_c).unwrap();
224 let _config_c = cache.get(&key_c).unwrap();
225 let config_d = cache.get(&key_d).unwrap();
226 let _config_d = cache.get(&key_d).unwrap();
227
228 assert_eq!(Arc::strong_count(&config_a.0), 1);
229 assert_eq!(Arc::strong_count(&config_b.0), 2);
230 assert_eq!(Arc::strong_count(&config_c.0), 2);
231
232 // config_d was most recently created, so it should have an extra strong reference due to
233 // keep-alive in the cache.
234 assert_eq!(Arc::strong_count(&config_d.0), 3);
235 }
236
237 #[test]
lifetimes()238 fn lifetimes() {
239 let cache = Cache::new();
240 let key_a =
241 Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000, enable_early_data: true };
242 let key_b =
243 Key { cert_path: Some("b".to_string()), max_idle_timeout: 1000, enable_early_data: true };
244 let config_none = cache
245 .get(&Key { cert_path: None, max_idle_timeout: 1000, enable_early_data: true })
246 .unwrap();
247 let config_a = cache.get(&key_a).unwrap();
248 let config_b = cache.get(&key_b).unwrap();
249
250 // The first two we created should have a strong count of one - those handles are the only
251 // thing keeping them alive.
252 assert_eq!(Arc::strong_count(&config_none.0), 1);
253 assert_eq!(Arc::strong_count(&config_a.0), 1);
254
255 // If we try to get another handle we already have, it should be the same one.
256 let _config_a2 = cache.get(&key_a).unwrap();
257 assert_eq!(Arc::strong_count(&config_a.0), 2);
258
259 // config_b was most recently created, so it should have a keep-alive
260 // inside the cache.
261 assert_eq!(Arc::strong_count(&config_b.0), 2);
262
263 // If we weaken one of the first handles, then drop it, the weak handle should break
264 let config_none_weak = Config::to_weak(&config_none);
265 assert_eq!(config_none_weak.strong_count(), 1);
266 drop(config_none);
267 assert_eq!(config_none_weak.strong_count(), 0);
268 assert!(Config::from_weak(&config_none_weak).is_none());
269
270 // If we weaken the most *recent* handle, it should keep working
271 let config_b_weak = Config::to_weak(&config_b);
272 assert_eq!(config_b_weak.strong_count(), 2);
273 drop(config_b);
274 assert_eq!(config_b_weak.strong_count(), 1);
275 assert!(Config::from_weak(&config_b_weak).is_some());
276 assert_eq!(config_b_weak.strong_count(), 1);
277
278 // If we try to get a config which is still kept alive by the cache, we should get the same
279 // one.
280 let _config_b2 = cache.get(&key_b).unwrap();
281 assert_eq!(config_b_weak.strong_count(), 2);
282
283 // We broke None, but "a" and "b" should still both be alive. Check that
284 // this is still the case in the mapping after garbage collection.
285 cache.garbage_collect();
286 assert_eq!(cache.state.read().unwrap().key_to_config.len(), 2);
287 }
288
289 #[tokio::test]
quiche_connect()290 async fn quiche_connect() {
291 use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
292 let mut config =
293 Config::from_key(&Key { cert_path: None, max_idle_timeout: 10, enable_early_data: true })
294 .unwrap();
295 let local = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 42));
296 let peer = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 41));
297 let conn_id = quiche::ConnectionId::from_ref(&[]);
298
299 quiche::connect(None, &conn_id, local, peer, config.take().await.deref_mut()).unwrap();
300 }
301