Skip to content

Commit

Permalink
Support TCP port exposure via "--expose" command-line argument (#70)
Browse files Browse the repository at this point in the history
  • Loading branch information
edigaryev authored Jan 9, 2025
1 parent f3acef8 commit 8519fa2
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 7 deletions.
80 changes: 75 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ sentry-anyhow = { version = "0", features = ["backtrace"] }
nix = { version = "0", features = ["signal"] }
prefix-trie = "0"
ipnet = "2"
oslog = "0.2.0"
log = "0.4.22"
10 changes: 9 additions & 1 deletion lib/dhcp_snooper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,15 @@ impl Lease {
}
}

pub fn address(&self) -> Ipv4Address {
self.address
}

pub fn valid(&self) -> bool {
Instant::now() < self.valid_until
}

pub fn valid_ip_source(&self, address: Ipv4Address) -> bool {
self.address == address && Instant::now() < self.valid_until
self.address == address && self.valid()
}
}
31 changes: 31 additions & 0 deletions lib/host.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use anyhow::{anyhow, Context, Result};
use clap::ValueEnum;
use log::info;
use std::net::Ipv4Addr;
use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::net::UnixDatagram;
use std::str::FromStr;
use std::sync::mpsc::{sync_channel, SyncSender};
use vmnet::mode::Mode;
use vmnet::parameters::{Parameter, ParameterKind};
use vmnet::port_forwarding::{AddressFamily, Protocol};
use vmnet::{Events, Options};

#[derive(ValueEnum, Clone, Debug)]
Expand Down Expand Up @@ -100,6 +102,35 @@ impl Host {
}

impl Host {
pub fn port_forwarding_add_rule(
&mut self,
external_port: u16,
internal_addr: Ipv4Addr,
internal_port: u16,
) -> Result<()> {
let details = format!("external_port={external_port}, internal_addr={internal_addr}, internal_port={internal_port}");

self.interface
.port_forwarding_rule_add(
AddressFamily::Ipv4,
Protocol::Tcp,
external_port,
internal_addr.into(),
internal_port,
)
.map(|_| info!("added port forwarding rule {details}"))
.map_err(|err| anyhow!("failed to add port forwarding rule {details}: {err}"))
}

pub fn port_forwarding_remove_rule(&mut self, external_port: u16) -> Result<()> {
let details = format!("external_port={external_port}");

self.interface
.port_forwarding_rule_remove(AddressFamily::Ipv4, Protocol::Tcp, external_port)
.map(|_| info!("removed port forwarding rule {details}"))
.map_err(|err| anyhow!("failed to remove port forwarding rule {details}: {err}"))
}

pub fn read(&mut self, buf: &mut [u8]) -> vmnet::Result<usize> {
// Dequeue dummy datagram from the socket (if any)
// to free up buffer space and reduce false-positives
Expand Down
47 changes: 47 additions & 0 deletions lib/proxy/exposed_port.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use anyhow::{anyhow, Context, Error};
use std::str::FromStr;

#[derive(Debug, Clone, Copy, Default)]
pub struct ExposedPort {
pub external_port: u16,
pub internal_port: u16,
}

impl FromStr for ExposedPort {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let splits: Vec<&str> = s.split(':').collect();

match splits.len() {
2 => Ok(ExposedPort {
external_port: splits[0]
.parse()
.context(format!("invalid external port {:?}", splits[0]))?,
internal_port: splits[1]
.parse()
.context(format!("invalid internal port {:?}", splits[1]))?,
}),
_ => Err(anyhow!(
"invalid exposed port specification {:?}, the format should be EXTERNAL:INTERNAL",
s
)),
}
}
}

#[cfg(test)]
mod tests {
use crate::proxy::exposed_port::ExposedPort;

#[test]
fn exposed_port() {
assert_eq!(
ExposedPort {
external_port: 2222,
internal_port: 22
},
"2222:22".parse().unwrap()
);
}
}
13 changes: 13 additions & 0 deletions lib/proxy/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod exposed_port;
mod host;
mod port_forwarder;
mod udp_packet_helper;
mod vm;

Expand All @@ -8,8 +10,10 @@ use crate::host::NetType;
use crate::poller::Poller;
use crate::vm::VM;
use anyhow::Result;
pub use exposed_port::ExposedPort;
use ipnet::Ipv4Net;
use mac_address::MacAddress;
use port_forwarder::PortForwarder;
use prefix_trie::{Prefix, PrefixSet};
use smoltcp::wire::EthernetFrame;
use std::io::ErrorKind;
Expand All @@ -23,6 +27,7 @@ pub struct Proxy<'proxy> {
dhcp_snooper: DhcpSnooper,
allow: PrefixSet<Ipv4Net>,
enobufs_encountered: bool,
port_forwarder: PortForwarder,
}

impl Proxy<'_> {
Expand All @@ -31,6 +36,7 @@ impl Proxy<'_> {
vm_mac_address: MacAddress,
vm_net_type: NetType,
allow: PrefixSet<Ipv4Net>,
exposed_ports: Vec<ExposedPort>,
) -> Result<Proxy<'proxy>> {
let vm = VM::new(vm_fd)?;
let host = Host::new(vm_net_type, !allow.contains(&Ipv4Net::zero()))?;
Expand All @@ -44,6 +50,7 @@ impl Proxy<'_> {
dhcp_snooper: Default::default(),
allow,
enobufs_encountered: false,
port_forwarder: PortForwarder::new(exposed_ports),
})
}

Expand All @@ -68,6 +75,12 @@ impl Proxy<'_> {
return Ok(());
}

// Timeout
if !vm_readable && !host_readable && !interrupt {
self.port_forwarder
.tick(&mut self.host, self.dhcp_snooper.lease());
}

self.poller.rearm()?;
}
}
Expand Down
Loading

0 comments on commit 8519fa2

Please sign in to comment.