1use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
7
8use ipnetwork::{Ipv4Network, Ipv6Network};
9use serde::{Deserialize, Serialize};
10
11use crate::dns::Nameserver;
12
13use crate::policy::NetworkPolicy;
14use crate::secrets::config::SecretsConfig;
15use microsandbox_types::{NetworkRateLimiterConfig, TlsConfig};
16
17pub const MAX_NETWORK_CONNECTIONS: usize = 4096;
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct NetworkConfig {
40 #[serde(default = "default_true")]
42 pub enabled: bool,
43
44 #[serde(default)]
46 pub interface: InterfaceOverrides,
47
48 #[serde(default)]
50 pub ports: Vec<PublishedPort>,
51
52 #[serde(default)]
54 pub policy: NetworkPolicy,
55
56 #[serde(default)]
58 pub dns: DnsConfig,
59
60 #[serde(default)]
62 pub tls: TlsConfig,
63
64 #[serde(default)]
66 pub secrets: SecretsConfig,
67
68 #[serde(default)]
70 pub max_connections: Option<usize>,
71
72 #[serde(default)]
74 pub rate_limiter: Option<NetworkRateLimiterConfig>,
75
76 #[serde(default)]
83 pub trust_host_cas: bool,
84}
85
86#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct InterfaceOverrides {
91 #[serde(default)]
93 pub mac: Option<[u8; 6]>,
94
95 #[serde(default)]
97 pub mtu: Option<u16>,
98
99 #[serde(default)]
101 pub ipv4_address: Option<Ipv4Addr>,
102
103 #[serde(default)]
105 pub ipv4_pool: Option<Ipv4Network>,
106
107 #[serde(default)]
109 pub ipv6_address: Option<Ipv6Addr>,
110
111 #[serde(default)]
113 pub ipv6_pool: Option<Ipv6Network>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct DnsConfig {
119 #[serde(default = "default_true")]
121 pub rebind_protection: bool,
122
123 #[serde(default)]
130 pub nameservers: Vec<Nameserver>,
131
132 #[serde(default = "default_query_timeout_ms")]
134 pub query_timeout_ms: u64,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize)]
139pub struct PublishedPort {
140 pub host_port: u16,
142
143 pub guest_port: u16,
145
146 #[serde(default)]
148 pub protocol: PortProtocol,
149
150 #[serde(default = "default_host_bind")]
152 pub host_bind: IpAddr,
153}
154
155#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
157pub enum PortProtocol {
158 #[default]
160 #[serde(rename = "tcp", alias = "Tcp")]
161 Tcp,
162
163 #[serde(rename = "udp", alias = "Udp")]
165 Udp,
166}
167
168impl Default for NetworkConfig {
173 fn default() -> Self {
174 Self {
175 enabled: true,
176 interface: InterfaceOverrides::default(),
177 ports: Vec::new(),
178 policy: NetworkPolicy::default(),
179 dns: DnsConfig::default(),
180 tls: TlsConfig::default(),
181 secrets: SecretsConfig::default(),
182 max_connections: None,
183 rate_limiter: None,
184 trust_host_cas: false,
185 }
186 }
187}
188
189impl Default for DnsConfig {
190 fn default() -> Self {
191 Self {
192 rebind_protection: true,
193 nameservers: Vec::new(),
194 query_timeout_ms: default_query_timeout_ms(),
195 }
196 }
197}
198
199fn default_true() -> bool {
204 true
205}
206
207fn default_host_bind() -> IpAddr {
208 IpAddr::V4(Ipv4Addr::LOCALHOST)
209}
210
211fn default_query_timeout_ms() -> u64 {
212 5000
213}
214
215#[cfg(test)]
220mod tests {
221 use super::{InterfaceOverrides, NetworkConfig, PortProtocol};
222 use crate::dns::Nameserver;
223 use crate::policy::{Destination, NetworkPolicy, Rule};
224
225 #[test]
230 fn engine_network_subdocs_round_trip_through_wire_types() {
231 let mut config = NetworkConfig::default();
232 let mut policy = NetworkPolicy::default()
236 .allow_domain("example.com")
237 .expect("valid domain")
238 .allow_domain_suffix("staging.example.com")
239 .expect("valid suffix");
240 policy.rules.push(Rule::allow_egress(Destination::Cidr(
241 "151.101.0.0/16".parse().unwrap(),
242 )));
243 config.policy = policy;
244 config.dns.nameservers = vec![
245 "1.1.1.1:53".parse::<Nameserver>().unwrap(),
246 "dns.google".parse::<Nameserver>().unwrap(),
247 ];
248 config.interface.ipv4_address = Some("172.16.0.2".parse().unwrap());
249 config.interface.ipv4_pool = Some("172.16.0.0/12".parse().unwrap());
250 config.interface.mac = Some([0x02, 0, 0, 0, 0, 0x01]);
251
252 let policy_json = serde_json::to_value(&config.policy).unwrap();
254 let dns_json = serde_json::to_value(&config.dns).unwrap();
255 let iface_json = serde_json::to_value(&config.interface).unwrap();
256
257 let wire_policy: microsandbox_types::NetworkPolicy =
260 serde_json::from_value(policy_json.clone()).unwrap();
261 let wire_dns: microsandbox_types::DnsConfig =
262 serde_json::from_value(dns_json.clone()).unwrap();
263 assert_eq!(policy_json, serde_json::to_value(&wire_policy).unwrap());
264 assert_eq!(dns_json, serde_json::to_value(&wire_dns).unwrap());
265
266 let wire_iface: microsandbox_types::InterfaceOverrides =
269 serde_json::from_value(iface_json.clone()).unwrap();
270 let back: InterfaceOverrides =
271 serde_json::from_value(serde_json::to_value(&wire_iface).unwrap()).unwrap();
272 assert_eq!(iface_json, serde_json::to_value(&back).unwrap());
273
274 assert_eq!(
276 serde_json::to_string(&Destination::DomainSuffix(
277 "staging.example.com".parse().unwrap()
278 ))
279 .unwrap(),
280 r#"{"domain_suffix":"staging.example.com"}"#
281 );
282 let legacy: microsandbox_types::Destination =
283 serde_json::from_str(r#"{"domain_suffix":"old.example.com"}"#).unwrap();
284 assert!(matches!(
285 legacy,
286 microsandbox_types::Destination::DomainSuffix(_)
287 ));
288 let legacy_group: microsandbox_types::DestinationGroup =
289 serde_json::from_str(r#""link_local""#).unwrap();
290 assert_eq!(
291 legacy_group,
292 microsandbox_types::DestinationGroup::LinkLocal
293 );
294 }
295
296 #[test]
299 fn config_without_rate_limiter_fields_stays_unlimited() {
300 let config: NetworkConfig = serde_json::from_value(serde_json::json!({})).unwrap();
301 assert!(config.rate_limiter.is_none());
302 }
303
304 #[test]
305 fn rate_limiters_survive_the_wire_spec_round_trip() {
306 use microsandbox_types::{NetworkRateLimiterConfig, RateLimiterConfig, TokenBucketConfig};
307
308 let config = NetworkConfig {
309 rate_limiter: Some(NetworkRateLimiterConfig {
310 egress: Some(RateLimiterConfig {
311 bandwidth: Some(TokenBucketConfig {
312 size: 1024 * 1024,
313 refill_time_ms: 1000,
314 one_time_burst: 512 * 1024,
315 }),
316 ops: None,
317 }),
318 ingress: Some(RateLimiterConfig {
319 bandwidth: None,
320 ops: Some(TokenBucketConfig {
321 size: 1000,
322 refill_time_ms: 1000,
323 one_time_burst: 0,
324 }),
325 }),
326 }),
327 ..NetworkConfig::default()
328 };
329
330 let spec: microsandbox_types::NetworkSpec =
331 serde_json::from_value(serde_json::to_value(&config).unwrap()).unwrap();
332 assert_eq!(spec.rate_limiter, config.rate_limiter);
333
334 let back: NetworkConfig =
335 serde_json::from_value(serde_json::to_value(&spec).unwrap()).unwrap();
336 assert_eq!(back.rate_limiter, config.rate_limiter);
337 }
338
339 #[test]
340 fn port_protocol_serializes_lowercase_and_accepts_legacy_case() {
341 assert_eq!(
342 serde_json::to_string(&PortProtocol::Tcp).unwrap(),
343 "\"tcp\""
344 );
345 assert_eq!(
346 serde_json::to_string(&PortProtocol::Udp).unwrap(),
347 "\"udp\""
348 );
349 assert_eq!(
350 serde_json::from_str::<PortProtocol>("\"Tcp\"").unwrap(),
351 PortProtocol::Tcp
352 );
353 assert_eq!(
354 serde_json::from_str::<PortProtocol>("\"Udp\"").unwrap(),
355 PortProtocol::Udp
356 );
357 }
358}