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