Normalize Softnet default-route isolation
This commit is contained in:
parent
6fe0db016d
commit
50628e08fc
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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<Target>, block: Vec<Target>) -> 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(&[], &[]);
|
||||
|
|
|
|||
|
|
@ -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<RawFd>,
|
||||
) -> Result<Proxy<'proxy>> {
|
||||
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()?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue