From a92f4e0c99188cb32274cd9d419490d36f02b28d Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Mon, 11 Mar 2024 22:07:39 +0400 Subject: [PATCH] Introduce --allow command-line argument to allow traffic to CIDRs (#34) --- Cargo.lock | 25 +++++++++++++++++++++++-- Cargo.toml | 2 ++ lib/host.rs | 18 +++++++++++++----- lib/proxy/mod.rs | 11 ++++++++++- lib/proxy/vm.rs | 16 ++++++++++++---- src/main.rs | 21 ++++++++++++++++++--- 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a59442..04f7631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -603,9 +603,9 @@ checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" [[package]] name = "ipnet" -version = "2.7.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" @@ -773,6 +773,15 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.15.0" @@ -924,6 +933,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prefix-trie" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cb065e4407d69a5a5265221262cceeafff7f1aabc545d01ed955cce92ee78b" +dependencies = [ + "ipnet", + "num-traits", +] + [[package]] name = "privdrop" version = "0.5.3" @@ -1341,11 +1360,13 @@ dependencies = [ "clap", "dhcproto", "ip_network", + "ipnet", "libc", "mac_address", "nix 0.26.2", "num_enum", "polling", + "prefix-trie", "privdrop", "sentry", "sentry-anyhow", diff --git a/Cargo.toml b/Cargo.toml index 50dc095..dbf21d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,5 @@ num_enum = "0.5.7" sentry = { version = "0.29.1", features = ["debug-images"] } sentry-anyhow = { version = "0.29.1", features = ["backtrace"] } nix = "0.26.2" +prefix-trie = "0.3.0" +ipnet = "2.9.0" diff --git a/lib/host.rs b/lib/host.rs index 2d60ba0..ab1fb2a 100644 --- a/lib/host.rs +++ b/lib/host.rs @@ -1,5 +1,5 @@ -use clap::ArgEnum; use anyhow::{anyhow, Context, Result}; +use clap::ArgEnum; use std::net::Ipv4Addr; use std::os::unix::io::{AsRawFd, RawFd}; use std::os::unix::net::UnixDatagram; @@ -46,15 +46,23 @@ impl Host { .context("failed to initialize vmnet interface")?; // Retrieve first IP (gateway) used for this interface - let Some(Parameter::StartAddress(gateway_ip)) = interface.parameters().get(ParameterKind::StartAddress) else { - return Err(anyhow!("failed to retrieve vmnet's interface start address")); + let Some(Parameter::StartAddress(gateway_ip)) = + interface.parameters().get(ParameterKind::StartAddress) + else { + return Err(anyhow!( + "failed to retrieve vmnet's interface start address" + )); }; let gateway_ip = Ipv4Addr::from_str(&gateway_ip) .context("failed to parse vmnet's interface start address")?; // Retrieve max packet size for this interface - let Some(Parameter::MaxPacketSize(max_packet_size)) = interface.parameters().get(ParameterKind::MaxPacketSize) else { - return Err(anyhow!("failed to retrieve vmnet's interface max packet size")); + let Some(Parameter::MaxPacketSize(max_packet_size)) = + interface.parameters().get(ParameterKind::MaxPacketSize) + else { + return Err(anyhow!( + "failed to retrieve vmnet's interface max packet size" + )); }; // Set up a socketpair() to emulate polling of the vmnet interface diff --git a/lib/proxy/mod.rs b/lib/proxy/mod.rs index 6a79ceb..d849420 100644 --- a/lib/proxy/mod.rs +++ b/lib/proxy/mod.rs @@ -8,7 +8,9 @@ use crate::host::NetType; use crate::poller::Poller; use crate::vm::VM; use anyhow::Result; +use ipnet::Ipv4Net; use mac_address::MacAddress; +use prefix_trie::PrefixSet; use smoltcp::wire::EthernetFrame; use std::io::ErrorKind; use std::os::unix::io::{AsRawFd, RawFd}; @@ -19,11 +21,17 @@ pub struct Proxy { poller: Poller, vm_mac_address: smoltcp::wire::EthernetAddress, dhcp_snooper: DhcpSnooper, + allow: PrefixSet, enobufs_encountered: bool, } impl Proxy { - pub fn new(vm_fd: RawFd, vm_mac_address: MacAddress, vm_net_type: NetType) -> Result { + pub fn new( + vm_fd: RawFd, + vm_mac_address: MacAddress, + vm_net_type: NetType, + allow: PrefixSet, + ) -> Result { let vm = VM::new(vm_fd)?; let host = Host::new(vm_net_type)?; let poller = Poller::new(vm.as_raw_fd(), host.as_raw_fd())?; @@ -34,6 +42,7 @@ impl Proxy { poller, vm_mac_address: smoltcp::wire::EthernetAddress(vm_mac_address.bytes()), dhcp_snooper: Default::default(), + allow, enobufs_encountered: false, }) } diff --git a/lib/proxy/vm.rs b/lib/proxy/vm.rs index 17fffbb..b11b503 100644 --- a/lib/proxy/vm.rs +++ b/lib/proxy/vm.rs @@ -2,6 +2,7 @@ use crate::proxy::udp_packet_helper::UdpPacketHelper; use crate::proxy::Proxy; use anyhow::Context; use anyhow::Result; +use ipnet::Ipv4Net; use smoltcp::wire::{ ArpPacket, EthernetFrame, EthernetProtocol, IpProtocol, Ipv4Packet, UdpPacket, }; @@ -58,15 +59,22 @@ impl Proxy { } fn allowed_from_vm_ipv4(&self, ipv4_pkt: Ipv4Packet<&[u8]>) -> Option<()> { - // Once we've learned the VM's IP from the DHCP snooping, - // allow all global traffic for that VM's IP + // Have we learned the VM's IP from the DHCP snooping? if let Some(lease) = &self.dhcp_snooper.lease() { - let dst_is_global = - ip_network::IpNetwork::from(Ipv4Addr::from(ipv4_pkt.dst_addr().0)).is_global(); + // If so, allow all global traffic + let dst_addr = Ipv4Addr::from(ipv4_pkt.dst_addr().0); + let dst_is_global = ip_network::IpNetwork::from(dst_addr).is_global(); if lease.valid_ip_source(ipv4_pkt.src_addr()) && dst_is_global { return Some(()); } + + // Also allow all traffic to the user-specified CIDRs + let dst_net = Ipv4Net::from(dst_addr); + + if self.allow.get_spm(&dst_net).is_some() { + return Some(()); + } } // Allow communication with host diff --git a/src/main.rs b/src/main.rs index cca1369..8a26c06 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ use anyhow::{anyhow, Context}; use clap::Parser; +use ipnet::Ipv4Net; use nix::sys::signal::{signal, SigHandler, Signal}; +use prefix_trie::PrefixSet; use privdrop::PrivDrop; -use softnet::NetType; use softnet::proxy::Proxy; +use softnet::NetType; use std::borrow::Cow; use std::env; + use std::os::raw::c_int; use std::os::unix::io::RawFd; use std::os::unix::process::CommandExt; @@ -45,6 +48,13 @@ struct Args { #[clap(long, help = "group name to drop privileges to")] group: Option, + #[clap( + long, + help = "comma-separated list of CIDRs to allow the traffic to (e.g. --allow=192.168.0.0/24)", + use_value_delimiter = true + )] + allow: Vec, + #[clap(long, hide = true)] sudo_escalation_probing: bool, @@ -144,8 +154,13 @@ fn try_main() -> anyhow::Result<()> { set_bootpd_lease_time(args.bootpd_lease_time); // Initialize the proxy while still having the root privileges - let mut proxy = Proxy::new(args.vm_fd as RawFd, args.vm_mac_address, args.vm_net_type) - .context("failed to initialize proxy")?; + let mut proxy = Proxy::new( + args.vm_fd as RawFd, + args.vm_mac_address, + args.vm_net_type, + PrefixSet::from_iter(args.allow), + ) + .context("failed to initialize proxy")?; // Drop effective privileges to the user // and group which have had invoked us