diff --git a/easytier/src/common/config.rs b/easytier/src/common/config.rs index 980eea3..dfeeda6 100644 --- a/easytier/src/common/config.rs +++ b/easytier/src/common/config.rs @@ -1,5 +1,5 @@ use std::{ - net::SocketAddr, + net::{Ipv4Addr, SocketAddr}, path::PathBuf, sync::{Arc, Mutex}, }; @@ -58,6 +58,9 @@ pub trait ConfigLoader: Send + Sync { fn get_flags(&self) -> Flags; fn set_flags(&self, flags: Flags); + fn get_exit_nodes(&self) -> Vec; + fn set_exit_nodes(&self, nodes: Vec); + fn dump(&self) -> String; } @@ -155,6 +158,8 @@ pub struct Flags { pub mtu: u16, #[derivative(Default(value = "true"))] pub latency_first: bool, + #[derivative(Default(value = "false"))] + pub enable_exit_node: bool, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] @@ -167,6 +172,7 @@ struct Config { dhcp: Option, network_identity: Option, listeners: Option>, + exit_nodes: Option>, peer: Option>, proxy_network: Option>, @@ -459,6 +465,19 @@ impl ConfigLoader for TomlConfigLoader { self.config.lock().unwrap().flags = Some(flags); } + fn get_exit_nodes(&self) -> Vec { + self.config + .lock() + .unwrap() + .exit_nodes + .clone() + .unwrap_or_default() + } + + fn set_exit_nodes(&self, nodes: Vec) { + self.config.lock().unwrap().exit_nodes = Some(nodes); + } + fn dump(&self) -> String { toml::to_string_pretty(&*self.config.lock().unwrap()).unwrap() } diff --git a/easytier/src/easytier-core.rs b/easytier/src/easytier-core.rs index a0ccbbb..e70662f 100644 --- a/easytier/src/easytier-core.rs +++ b/easytier/src/easytier-core.rs @@ -3,7 +3,12 @@ #[cfg(test)] mod tests; -use std::{backtrace, io::Write as _, net::SocketAddr, path::PathBuf}; +use std::{ + backtrace, + io::Write as _, + net::{Ipv4Addr, SocketAddr}, + path::PathBuf, +}; use anyhow::Context; use clap::Parser; @@ -179,6 +184,20 @@ and the vpn client is in network of 10.14.14.0/24" default_value = "false" )] latency_first: bool, + + #[arg( + long, + help = "exit nodes to forward all traffic to, a virtual ipv4 address, priority is determined by the order of the list", + num_args = 0.. + )] + exit_nodes: Vec, + + #[arg( + long, + help = "allow this node to be an exit node, default is false", + default_value = "false" + )] + enable_exit_node: bool, } impl Cli { @@ -394,8 +413,11 @@ impl From for TomlConfigLoader { if let Some(mtu) = cli.mtu { f.mtu = mtu; } + f.enable_exit_node = cli.enable_exit_node; cfg.set_flags(f); + cfg.set_exit_nodes(cli.exit_nodes.clone()); + cfg } } diff --git a/easytier/src/gateway/icmp_proxy.rs b/easytier/src/gateway/icmp_proxy.rs index 7768d23..5ecdb82 100644 --- a/easytier/src/gateway/icmp_proxy.rs +++ b/easytier/src/gateway/icmp_proxy.rs @@ -248,6 +248,7 @@ impl IcmpProxy { async fn try_handle_peer_packet(&self, packet: &ZCPacket) -> Option<()> { let _ = self.global_ctx.get_ipv4()?; let hdr = packet.peer_manager_header().unwrap(); + let is_exit_node = hdr.is_exit_node(); if hdr.packet_type != PacketType::Data as u8 { return None; @@ -260,7 +261,7 @@ impl IcmpProxy { return None; } - if !self.cidr_set.contains_v4(ipv4.get_destination()) { + if !self.cidr_set.contains_v4(ipv4.get_destination()) && !is_exit_node { return None; } diff --git a/easytier/src/gateway/tcp_proxy.rs b/easytier/src/gateway/tcp_proxy.rs index 63452a3..8367fc9 100644 --- a/easytier/src/gateway/tcp_proxy.rs +++ b/easytier/src/gateway/tcp_proxy.rs @@ -358,6 +358,7 @@ impl TcpProxy { async fn try_handle_peer_packet(&self, packet: &mut ZCPacket) -> Option<()> { let ipv4_addr = self.global_ctx.get_ipv4()?; let hdr = packet.peer_manager_header().unwrap(); + let is_exit_node = hdr.is_exit_node(); if hdr.packet_type != PacketType::Data as u8 { return None; @@ -370,7 +371,7 @@ impl TcpProxy { return None; } - if !self.cidr_set.contains_v4(ipv4.get_destination()) { + if !self.cidr_set.contains_v4(ipv4.get_destination()) && !is_exit_node { return None; } diff --git a/easytier/src/gateway/udp_proxy.rs b/easytier/src/gateway/udp_proxy.rs index ba493c3..022f633 100644 --- a/easytier/src/gateway/udp_proxy.rs +++ b/easytier/src/gateway/udp_proxy.rs @@ -233,6 +233,7 @@ impl UdpProxy { let _ = self.global_ctx.get_ipv4()?; let hdr = packet.peer_manager_header().unwrap(); + let is_exit_node = hdr.is_exit_node(); if hdr.packet_type != PacketType::Data as u8 { return None; }; @@ -242,7 +243,7 @@ impl UdpProxy { return None; } - if !self.cidr_set.contains_v4(ipv4.get_destination()) { + if !self.cidr_set.contains_v4(ipv4.get_destination()) && !is_exit_node { return None; } diff --git a/easytier/src/instance/instance.rs b/easytier/src/instance/instance.rs index 025abd5..f1620b3 100644 --- a/easytier/src/instance/instance.rs +++ b/easytier/src/instance/instance.rs @@ -68,7 +68,9 @@ impl IpProxy { } async fn start(&self) -> Result<(), Error> { - if self.global_ctx.get_proxy_cidrs().is_empty() || self.started.load(Ordering::Relaxed) { + if (self.global_ctx.get_proxy_cidrs().is_empty() || self.started.load(Ordering::Relaxed)) + && !self.global_ctx.config.get_flags().enable_exit_node + { return Ok(()); } diff --git a/easytier/src/peers/peer_manager.rs b/easytier/src/peers/peer_manager.rs index e040d25..4ee0e18 100644 --- a/easytier/src/peers/peer_manager.rs +++ b/easytier/src/peers/peer_manager.rs @@ -155,6 +155,8 @@ pub struct PeerManager { foreign_network_client: Arc, encryptor: Arc>, + + exit_nodes: Vec, } impl Debug for PeerManager { @@ -238,6 +240,8 @@ impl PeerManager { my_peer_id, )); + let exit_nodes = global_ctx.config.get_exit_nodes(); + PeerManager { my_peer_id, @@ -262,6 +266,7 @@ impl PeerManager { foreign_network_client, encryptor, + exit_nodes, } } @@ -573,6 +578,7 @@ impl PeerManager { ipv4_addr ); + let mut is_exit_node = false; let mut dst_peers = vec![]; // NOTE: currently we only support ipv4 and cidr is 24 if ipv4_addr.is_broadcast() || ipv4_addr.is_multicast() || ipv4_addr.octets()[3] == 255 { @@ -585,6 +591,14 @@ impl PeerManager { ); } else if let Some(peer_id) = self.peers.get_peer_id_by_ipv4(&ipv4_addr).await { dst_peers.push(peer_id); + } else { + for exit_node in &self.exit_nodes { + if let Some(peer_id) = self.peers.get_peer_id_by_ipv4(exit_node).await { + dst_peers.push(peer_id); + is_exit_node = true; + break; + } + } } if dst_peers.is_empty() { @@ -605,7 +619,8 @@ impl PeerManager { let is_latency_first = self.global_ctx.get_flags().latency_first; msg.mut_peer_manager_header() .unwrap() - .set_latency_first(is_latency_first); + .set_latency_first(is_latency_first) + .set_exit_node(is_exit_node); let next_hop_policy = Self::get_next_hop_policy(is_latency_first); let mut errs: Vec = vec![]; diff --git a/easytier/src/tunnel/packet_def.rs b/easytier/src/tunnel/packet_def.rs index 9545ed2..53c15c0 100644 --- a/easytier/src/tunnel/packet_def.rs +++ b/easytier/src/tunnel/packet_def.rs @@ -60,6 +60,7 @@ bitflags::bitflags! { struct PeerManagerHeaderFlags: u8 { const ENCRYPTED = 0b0000_0001; const LATENCY_FIRST = 0b0000_0010; + const EXIT_NODE = 0b0000_0100; const _ = !0; } @@ -101,7 +102,13 @@ impl PeerManagerHeader { .contains(PeerManagerHeaderFlags::LATENCY_FIRST) } - pub fn set_latency_first(&mut self, latency_first: bool) { + pub fn is_exit_node(&self) -> bool { + PeerManagerHeaderFlags::from_bits(self.flags) + .unwrap() + .contains(PeerManagerHeaderFlags::EXIT_NODE) + } + + pub fn set_latency_first(&mut self, latency_first: bool) -> &mut Self { let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap(); if latency_first { flags.insert(PeerManagerHeaderFlags::LATENCY_FIRST); @@ -109,6 +116,18 @@ impl PeerManagerHeader { flags.remove(PeerManagerHeaderFlags::LATENCY_FIRST); } self.flags = flags.bits(); + self + } + + pub fn set_exit_node(&mut self, exit_node: bool) -> &mut Self { + let mut flags = PeerManagerHeaderFlags::from_bits(self.flags).unwrap(); + if exit_node { + flags.insert(PeerManagerHeaderFlags::EXIT_NODE); + } else { + flags.remove(PeerManagerHeaderFlags::EXIT_NODE); + } + self.flags = flags.bits(); + self } }