• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
67         // Some of these configs are necessary, or the server can't respond the HTTP/3 request.
68         config.set_max_idle_timeout(key.max_idle_timeout);
69         config.set_max_recv_udp_payload_size(MAX_DATAGRAM_SIZE);
70         config.set_initial_max_data(MAX_INCOMING_BUFFER_SIZE_WHOLE);
71         config.set_initial_max_stream_data_bidi_local(MAX_INCOMING_BUFFER_SIZE_EACH);
72         config.set_initial_max_stream_data_bidi_remote(MAX_INCOMING_BUFFER_SIZE_EACH);
73         config.set_initial_max_stream_data_uni(MAX_INCOMING_BUFFER_SIZE_EACH);
74         config.set_initial_max_streams_bidi(MAX_CONCURRENT_STREAM_SIZE);
75         config.set_initial_max_streams_uni(MAX_CONCURRENT_STREAM_SIZE);
76         config.set_disable_active_migration(true);
77         Ok(Self(Arc::new(Mutex::new(config))))
78     }
79 
80     /// Take the underlying config, usable as `&mut quiche::Config` for use
81     /// with `quiche::connect`.
take(&mut self) -> impl DerefMut<Target = quiche::Config> + '_82     pub async fn take(&mut self) -> impl DerefMut<Target = quiche::Config> + '_ {
83         self.0.lock().await
84     }
85 }
86 
87 #[derive(Clone, Default)]
88 struct State {
89     // Mapping from cert_path to configs
90     key_to_config: HashMap<Key, WeakConfig>,
91     // Keep latest config alive to minimize reparsing when flapping
92     // If more keep-alive is needed, replace with a LRU LinkedList
93     latest: Option<Config>,
94 }
95 
96 impl State {
get_config(&self, key: &Key) -> Option<Config>97     fn get_config(&self, key: &Key) -> Option<Config> {
98         self.key_to_config.get(key).and_then(Config::from_weak)
99     }
100 
keep_alive(&mut self, config: Config)101     fn keep_alive(&mut self, config: Config) {
102         self.latest = Some(config);
103     }
104 
garbage_collect(&mut self)105     fn garbage_collect(&mut self) {
106         self.key_to_config.retain(|_, config| config.strong_count() != 0)
107     }
108 }
109 
110 /// Cache of Quiche Config objects
111 ///
112 /// Cloning this cache will create another handle to the same cache.
113 ///
114 /// Loading a config object through this caching layer will only keep the
115 /// latest config loaded alive directly, but will still act as a cache
116 /// for any configurations still in use - if the returned `Config` is still
117 /// live, queries to `Cache` will not reconstruct it.
118 #[derive(Clone, Default)]
119 pub struct Cache {
120     // Shared state amongst cache handles
121     state: Arc<RwLock<State>>,
122 }
123 
124 /// Key used for getting an associated Quiche Config from Cache.
125 #[derive(Clone, PartialEq, Eq, Hash)]
126 pub struct Key {
127     pub cert_path: Option<String>,
128     pub max_idle_timeout: u64,
129 }
130 
131 impl Cache {
132     /// Creates a fresh empty cache
new() -> Self133     pub fn new() -> Self {
134         Default::default()
135     }
136 
137     /// Behaves as `Config::from_cert_path`, but with a cache.
138     /// If any object previously given out by this cache is still live,
139     /// a duplicate will not be made.
get(&self, key: &Key) -> Result<Config>140     pub fn get(&self, key: &Key) -> Result<Config> {
141         // Fast path - read-only access to state retrieves config
142         if let Some(config) = self.state.read().unwrap().get_config(key) {
143             return Ok(config);
144         }
145 
146         // Unlocked, calculate config. If we have two racing attempts to load
147         // the cert path, we'll arbitrate that in the next step, but this
148         // makes sure loading a new cert path doesn't block other loads to
149         // refresh connections.
150         let config = Config::from_key(key)?;
151 
152         let mut state = self.state.write().unwrap();
153         // We now have exclusive access to the state.
154         // If someone else calculated a config at the same time as us, we
155         // want to discard ours and use theirs, since it will result in
156         // less total memory used.
157         if let Some(config) = state.get_config(key) {
158             return Ok(config);
159         }
160 
161         // We have exclusive access and a fresh config. Install it into
162         // the cache.
163         state.keep_alive(config.clone());
164         state.key_to_config.insert(key.clone(), config.to_weak());
165         Ok(config)
166     }
167 
168     /// Purges any config paths which no longer point to a config entry.
garbage_collect(&self)169     pub fn garbage_collect(&self) {
170         self.state.write().unwrap().garbage_collect();
171     }
172 }
173 
174 #[test]
create_quiche_config()175 fn create_quiche_config() {
176     assert!(
177         Config::from_key(&Key { cert_path: None, max_idle_timeout: 1000 }).is_ok(),
178         "quiche config without cert creating failed"
179     );
180     assert!(
181         Config::from_key(&Key {
182             cert_path: Some("data/local/tmp/".to_string()),
183             max_idle_timeout: 1000
184         })
185         .is_ok(),
186         "quiche config with cert creating failed"
187     );
188 }
189 
190 #[test]
shared_cache()191 fn shared_cache() {
192     let cache_a = Cache::new();
193     let cache_b = cache_a.clone();
194     let config_a = cache_a.get(&Key { cert_path: None, max_idle_timeout: 1000 }).unwrap();
195     assert_eq!(Arc::strong_count(&config_a.0), 2);
196     let _config_b = cache_b.get(&Key { cert_path: None, max_idle_timeout: 1000 }).unwrap();
197     assert_eq!(Arc::strong_count(&config_a.0), 3);
198 }
199 
200 #[test]
different_keys()201 fn different_keys() {
202     let cache = Cache::new();
203     let key_a = Key { cert_path: None, max_idle_timeout: 1000 };
204     let key_b = Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000 };
205     let key_c = Key { cert_path: Some("a".to_string()), max_idle_timeout: 5000 };
206     let config_a = cache.get(&key_a).unwrap();
207     let config_b = cache.get(&key_b).unwrap();
208     let _config_b = cache.get(&key_b).unwrap();
209     let config_c = cache.get(&key_c).unwrap();
210     let _config_c = cache.get(&key_c).unwrap();
211 
212     assert_eq!(Arc::strong_count(&config_a.0), 1);
213     assert_eq!(Arc::strong_count(&config_b.0), 2);
214 
215     // config_c was most recently created, so it should have an extra strong reference due to
216     // keep-alive in the cache.
217     assert_eq!(Arc::strong_count(&config_c.0), 3);
218 }
219 
220 #[test]
lifetimes()221 fn lifetimes() {
222     let cache = Cache::new();
223     let key_a = Key { cert_path: Some("a".to_string()), max_idle_timeout: 1000 };
224     let key_b = Key { cert_path: Some("b".to_string()), max_idle_timeout: 1000 };
225     let config_none = cache.get(&Key { cert_path: None, max_idle_timeout: 1000 }).unwrap();
226     let config_a = cache.get(&key_a).unwrap();
227     let config_b = cache.get(&key_b).unwrap();
228 
229     // The first two we created should have a strong count of one - those handles are the only
230     // thing keeping them alive.
231     assert_eq!(Arc::strong_count(&config_none.0), 1);
232     assert_eq!(Arc::strong_count(&config_a.0), 1);
233 
234     // If we try to get another handle we already have, it should be the same one.
235     let _config_a2 = cache.get(&key_a).unwrap();
236     assert_eq!(Arc::strong_count(&config_a.0), 2);
237 
238     // config_b was most recently created, so it should have a keep-alive
239     // inside the cache.
240     assert_eq!(Arc::strong_count(&config_b.0), 2);
241 
242     // If we weaken one of the first handles, then drop it, the weak handle should break
243     let config_none_weak = Config::to_weak(&config_none);
244     assert_eq!(config_none_weak.strong_count(), 1);
245     drop(config_none);
246     assert_eq!(config_none_weak.strong_count(), 0);
247     assert!(Config::from_weak(&config_none_weak).is_none());
248 
249     // If we weaken the most *recent* handle, it should keep working
250     let config_b_weak = Config::to_weak(&config_b);
251     assert_eq!(config_b_weak.strong_count(), 2);
252     drop(config_b);
253     assert_eq!(config_b_weak.strong_count(), 1);
254     assert!(Config::from_weak(&config_b_weak).is_some());
255     assert_eq!(config_b_weak.strong_count(), 1);
256 
257     // If we try to get a config which is still kept alive by the cache, we should get the same
258     // one.
259     let _config_b2 = cache.get(&key_b).unwrap();
260     assert_eq!(config_b_weak.strong_count(), 2);
261 
262     // We broke None, but "a" and "b" should still both be alive. Check that
263     // this is still the case in the mapping after garbage collection.
264     cache.garbage_collect();
265     assert_eq!(cache.state.read().unwrap().key_to_config.len(), 2);
266 }
267 
268 #[tokio::test]
quiche_connect()269 async fn quiche_connect() {
270     use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
271     let mut config = Config::from_key(&Key { cert_path: None, max_idle_timeout: 10 }).unwrap();
272     let socket_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 42));
273     let conn_id = quiche::ConnectionId::from_ref(&[]);
274     quiche::connect(None, &conn_id, socket_addr, config.take().await.deref_mut()).unwrap();
275 }
276