--allow: support "@host" syntax (#160)
* --allow: support "@host" syntax To make communication with the guest VM possible when using --block=0.0.0.0/0. * Use "@-alias" wording instead of "@host"
This commit is contained in:
parent
bbff9996ab
commit
df84a30016
|
|
@ -14,10 +14,11 @@ pub use exposed_port::ExposedPort;
|
|||
use ipnet::Ipv4Net;
|
||||
use mac_address::MacAddress;
|
||||
use port_forwarder::PortForwarder;
|
||||
use prefix_trie::{Prefix, PrefixMap, PrefixSet};
|
||||
use prefix_trie::{Prefix, PrefixMap};
|
||||
use smoltcp::wire::EthernetFrame;
|
||||
use std::io::ErrorKind;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use vmnet::Batch;
|
||||
|
||||
|
|
@ -32,6 +33,24 @@ pub struct Proxy<'proxy> {
|
|||
port_forwarder: PortForwarder,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Target {
|
||||
Prefix(Ipv4Net),
|
||||
Host,
|
||||
}
|
||||
|
||||
impl FromStr for Target {
|
||||
type Err = ipnet::AddrParseError;
|
||||
|
||||
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
|
||||
if s == "@host" {
|
||||
return Ok(Target::Host);
|
||||
}
|
||||
|
||||
Ipv4Net::from_str(s).map(Target::Prefix)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) enum Action {
|
||||
Block,
|
||||
|
|
@ -43,12 +62,15 @@ impl Proxy<'_> {
|
|||
vm_fd: RawFd,
|
||||
vm_mac_address: MacAddress,
|
||||
vm_net_type: NetType,
|
||||
allow: PrefixSet<Ipv4Net>,
|
||||
block: PrefixSet<Ipv4Net>,
|
||||
allow: Vec<Target>,
|
||||
block: Vec<Target>,
|
||||
exposed_ports: Vec<ExposedPort>,
|
||||
) -> Result<Proxy<'proxy>> {
|
||||
let vm = VM::new(vm_fd)?;
|
||||
let host = Host::new(vm_net_type, !allow.contains(&Ipv4Net::zero()))?;
|
||||
let host = Host::new(
|
||||
vm_net_type,
|
||||
!allow.contains(&Target::Prefix(Ipv4Net::zero())),
|
||||
)?;
|
||||
let poller_timeout = Duration::from_millis(100);
|
||||
let poller = Poller::new(vm.as_raw_fd(), host.as_raw_fd(), poller_timeout)?;
|
||||
|
||||
|
|
@ -58,12 +80,22 @@ impl Proxy<'_> {
|
|||
// over allowing rules when prefixes are identical.
|
||||
let mut rules = PrefixMap::new();
|
||||
|
||||
for allow_net in allow {
|
||||
rules.insert(allow_net, Action::Allow);
|
||||
for allow_target in allow {
|
||||
let allow_prefix = match allow_target {
|
||||
Target::Prefix(prefix) => prefix,
|
||||
Target::Host => host.gateway_ip.into(),
|
||||
};
|
||||
|
||||
rules.insert(allow_prefix, Action::Allow);
|
||||
}
|
||||
|
||||
for block_net in block {
|
||||
rules.insert(block_net, Action::Block);
|
||||
for block_target in block {
|
||||
let block_prefix = match block_target {
|
||||
Target::Prefix(prefix) => prefix,
|
||||
Target::Host => host.gateway_ip.into(),
|
||||
};
|
||||
|
||||
rules.insert(block_prefix, Action::Block);
|
||||
}
|
||||
|
||||
Ok(Proxy {
|
||||
|
|
@ -175,7 +207,7 @@ mod tests {
|
|||
use ipnet::Ipv4Net;
|
||||
use mac_address::MacAddress;
|
||||
use nix::sys::socket::{AddressFamily, SockFlag, SockType, socketpair};
|
||||
use prefix_trie::{PrefixMap, PrefixSet};
|
||||
use prefix_trie::PrefixMap;
|
||||
use serial_test::serial;
|
||||
use smoltcp::wire::{Ipv4Address, Ipv4Packet};
|
||||
use std::collections::HashSet;
|
||||
|
|
@ -219,6 +251,27 @@ mod tests {
|
|||
assert!(allowed_from_vm_ipv4(&proxy, vm_ip, "33.33.33.34").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_allow_host() {
|
||||
let vm_ip = Ipv4Address::from_str("192.168.0.2").unwrap();
|
||||
let proxy = create_proxy(vm_ip, vec!["@host"], vec!["0.0.0.0/0"]);
|
||||
|
||||
assert_eq!(
|
||||
proxy.rules,
|
||||
PrefixMap::from_iter(vec![
|
||||
(proxy.host.gateway_ip.into(), Action::Allow),
|
||||
(Ipv4Net::from_str("0.0.0.0/0").unwrap(), Action::Block),
|
||||
])
|
||||
);
|
||||
|
||||
// Access to global IPs should be disallowed because of --block=0.0.0.0/0
|
||||
assert!(allowed_from_vm_ipv4(&proxy, vm_ip, "8.8.8.8").is_none());
|
||||
|
||||
// Despite the above, access to host IP address should be possible because of --allow=@host
|
||||
assert!(allowed_from_vm_ipv4(&proxy, vm_ip, &proxy.host.gateway_ip.to_string()).is_some());
|
||||
}
|
||||
|
||||
fn create_proxy<'test>(vm_ip: Ipv4Address, allow: Vec<&str>, block: Vec<&str>) -> Proxy<'test> {
|
||||
let (vm_fd, _) = socketpair(
|
||||
AddressFamily::Unix,
|
||||
|
|
@ -233,16 +286,14 @@ mod tests {
|
|||
vm_fd.as_raw_fd(),
|
||||
MacAddress::from_str("02:00:00:00:00:01").unwrap(),
|
||||
NetType::Nat,
|
||||
PrefixSet::from_iter(
|
||||
allow
|
||||
.into_iter()
|
||||
.map(|cidr| Ipv4Net::from_str(cidr).unwrap()),
|
||||
),
|
||||
PrefixSet::from_iter(
|
||||
.map(|cidr| cidr.parse().unwrap())
|
||||
.collect(),
|
||||
block
|
||||
.into_iter()
|
||||
.map(|cidr| Ipv4Net::from_str(cidr).unwrap()),
|
||||
),
|
||||
.map(|cidr| cidr.parse().unwrap())
|
||||
.collect(),
|
||||
Vec::default(),
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
27
src/main.rs
27
src/main.rs
|
|
@ -1,14 +1,13 @@
|
|||
use anyhow::{Context, anyhow};
|
||||
use clap::Parser;
|
||||
use ipnet::Ipv4Net;
|
||||
use log::LevelFilter;
|
||||
use nix::sys::signal::{SigHandler, Signal, signal};
|
||||
use oslog::OsLogger;
|
||||
use prefix_trie::PrefixSet;
|
||||
use privdrop::PrivDrop;
|
||||
use softnet::NetType;
|
||||
use softnet::proxy::ExposedPort;
|
||||
use softnet::proxy::Proxy;
|
||||
use softnet::proxy::Target;
|
||||
use std::borrow::Cow;
|
||||
use std::env;
|
||||
use std::os::raw::c_int;
|
||||
|
|
@ -53,29 +52,33 @@ struct Args {
|
|||
#[clap(
|
||||
long,
|
||||
help = "Comma-separated list of CIDRs to allow the traffic to \
|
||||
(e.g. --allow=192.168.0.0/24 may be used to allow a LAN access for a VM). \
|
||||
(e.g. --allow=192.168.0.0/24 may be used to allow a LAN access for a VM), \
|
||||
plus supported @-aliases. Currently the only supported @-alias is @host, \
|
||||
which matches the vmnet bridge gateway IP. \
|
||||
When used with --block, the longest prefix match always wins. \
|
||||
In case an identical prefix is both --allow'ed and --block'ed, \
|
||||
blocking will take precedence. --allow=0.0.0.0/0 is a special case, \
|
||||
it additionally disables bridge isolation (even when --block=0.0.0.0/0 is specified).",
|
||||
value_name = "comma-separated CIDRs",
|
||||
value_name = "comma-separated CIDRs or @-alias",
|
||||
use_value_delimiter = true,
|
||||
action = clap::ArgAction::Set
|
||||
)]
|
||||
allow: Vec<Ipv4Net>,
|
||||
allow: Vec<Target>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "Comma-separated list of CIDRs to block the traffic to \
|
||||
(e.g. --block=0.0.0.0/0 may be used to establish a default deny policy \
|
||||
that is further relaxed with --allow). When used with --allow, \
|
||||
the longest prefix match always wins. In case the same prefix is both \
|
||||
--allow'ed and --block'ed, blocking takes precedence.",
|
||||
value_name = "comma-separated CIDRs",
|
||||
that is further relaxed with --allow), plus supported @-aliases. \
|
||||
Currently the only supported @-alias is @host, which matches the vmnet bridge gateway IP. \
|
||||
When used with --allow, the longest prefix match always wins. \
|
||||
In case an identical prefix is both --allow'ed and --block'ed, \
|
||||
blocking will take precedence.",
|
||||
value_name = "comma-separated CIDRs or @-alias",
|
||||
use_value_delimiter = true,
|
||||
action = clap::ArgAction::Set
|
||||
)]
|
||||
block: Vec<Ipv4Net>,
|
||||
block: Vec<Target>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
|
|
@ -196,8 +199,8 @@ fn try_main() -> anyhow::Result<()> {
|
|||
args.vm_fd as RawFd,
|
||||
args.vm_mac_address,
|
||||
args.vm_net_type,
|
||||
PrefixSet::from_iter(args.allow),
|
||||
PrefixSet::from_iter(args.block),
|
||||
args.allow,
|
||||
args.block,
|
||||
args.expose,
|
||||
)
|
||||
.context("failed to initialize proxy")?;
|
||||
|
|
|
|||
Loading…
Reference in New Issue