diff --git a/README.md b/README.md index a9539a3..0569a01 100644 --- a/README.md +++ b/README.md @@ -57,4 +57,4 @@ The supported methods are `softnet.policy.get` and `softnet.policy.set`. A compl Every request must include a non-null string (at most 256 bytes) or non-negative integer `id`; notifications are rejected so policy changes always have an acknowledgment. Policy updates are atomic: all targets are parsed and a new prefix map is built before the active policy changes. Longest-prefix matching and block precedence for identical prefixes are preserved. Targets are normalized and deduplicated, so retrying the active revision with the same policy is idempotent; reusing it with a different policy or retrying a superseded revision returns a JSON-RPC conflict error. A policy may contain at most 4096 combined allow/block targets, and a request frame may not exceed 1 MiB. -`--allow=0.0.0.0/0` additionally disables vmnet bridge isolation during interface creation. Bridge isolation cannot be changed for a running VM, so policy updates that would toggle it are rejected without changing the active policy. Use `block=["0.0.0.0/0"]` with specific allow targets for a default-deny policy. Closing the control socket leaves the last accepted policy active. +An allow target that normalizes to `0.0.0.0/0` additionally disables vmnet bridge isolation during interface creation. Bridge isolation cannot be changed for a running VM, so policy updates that would toggle it are rejected without changing the active policy. Use `block=["0.0.0.0/0"]` with specific allow targets for a default-deny policy. Closing the control socket leaves the last accepted policy active. diff --git a/lib/proxy/control.rs b/lib/proxy/control.rs index 2307f85..4b6e79b 100644 --- a/lib/proxy/control.rs +++ b/lib/proxy/control.rs @@ -8,7 +8,7 @@ use jsonrpsee_types::{ METHOD_NOT_FOUND_CODE as METHOD_NOT_FOUND, PARSE_ERROR_CODE as PARSE_ERROR, }, }; -use prefix_trie::{Prefix, PrefixMap}; +use prefix_trie::PrefixMap; use serde::Deserialize; use serde_json::{Value, json}; use smoltcp::wire::Ipv4Address; @@ -47,9 +47,9 @@ struct PolicyUpdate { impl Policy { pub(super) fn new(gateway_ip: Ipv4Address, allow: Vec, block: Vec) -> Self { - let bridge_isolation = !allow.contains(&Target::Prefix(Ipv4Net::zero())); let allow = normalize_targets(allow); let block = normalize_targets(block); + let bridge_isolation = Self::bridge_isolation(&allow); let rules = build_rules(gateway_ip, &allow, &block); Policy { @@ -85,7 +85,7 @@ impl Policy { let allow = parse_targets(allow)?; let block = parse_targets(block)?; - let bridge_isolation = !allow.contains(&Target::Prefix(Ipv4Net::zero())); + let bridge_isolation = Self::bridge_isolation(&allow); let rules = build_rules(self.gateway_ip, &allow, &block); if self.desired_revision.as_deref() == Some(desired_revision.as_str()) { @@ -141,6 +141,12 @@ impl Policy { self.bridge_isolation, ) } + + pub(super) fn bridge_isolation(allow: &[Target]) -> bool { + !allow + .iter() + .any(|target| matches!(target, Target::Prefix(prefix) if prefix.prefix_len() == 0)) + } } impl PolicyUpdate { @@ -818,6 +824,31 @@ mod tests { assert_eq!(policy.result(), before); } + #[test] + fn noncanonical_default_route_disables_isolation_and_round_trips() { + let raw_default = Target::Prefix(Ipv4Net::from_str("10.1.2.3/0").unwrap()); + assert!(!Policy::bridge_isolation(&[raw_default])); + + let mut policy = policy(&["10.1.2.3/0"], &[]); + let initial = policy.result(); + assert_eq!(initial["allow"], json!(["0.0.0.0/0"])); + assert_eq!(initial["bridgeIsolation"], false); + + let response = request( + &mut policy, + json!({ + "jsonrpc": "2.0", + "id": 1, + "method": "softnet.policy.set", + "params": {"allow": ["0.0.0.0/0"], "block": [], "desiredRevision": "vm-uid:42"} + }), + ); + + assert_eq!(response["result"]["allow"], json!(["0.0.0.0/0"])); + assert_eq!(response["result"]["bridgeIsolation"], false); + assert_eq!(response["result"]["desiredRevision"], "vm-uid:42"); + } + #[test] fn validates_json_rpc_envelope_method_and_parameters() { let mut policy = policy(&[], &[]); diff --git a/lib/proxy/mod.rs b/lib/proxy/mod.rs index fdddf17..fb1d2ef 100644 --- a/lib/proxy/mod.rs +++ b/lib/proxy/mod.rs @@ -16,7 +16,6 @@ pub use exposed_port::ExposedPort; use ipnet::Ipv4Net; use mac_address::MacAddress; use port_forwarder::PortForwarder; -use prefix_trie::Prefix; use smoltcp::wire::EthernetFrame; use std::io::ErrorKind; use std::os::unix::io::{AsRawFd, RawFd}; @@ -71,10 +70,7 @@ impl Proxy<'_> { control_fd: Option, ) -> Result> { let vm = VM::new(vm_fd)?; - let host = Host::new( - vm_net_type, - !allow.contains(&Target::Prefix(Ipv4Net::zero())), - )?; + let host = Host::new(vm_net_type, Policy::bridge_isolation(&allow))?; let poller_timeout = Duration::from_millis(100); let policy = Policy::new(host.gateway_ip, allow, block); let control = control_fd.map(Control::new).transpose()?;