Compare commits

...

4 commits

Author SHA1 Message Date
Lyn
a1a95f2865 update Cargo.lock 2025-01-15 18:14:15 +01:00
Lyn
ee74ad6adb Merge remote-tracking branch 'yuka/main'
# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/main.rs
2025-01-15 18:13:42 +01:00
Yureka
c811f2d0a1 add forbidden_nets config option 2025-01-15 10:44:39 +01:00
Yureka
4c08a63811 format 2025-01-15 10:44:13 +01:00
3 changed files with 90 additions and 25 deletions

12
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 4
[[package]] [[package]]
name = "addr2line" name = "addr2line"
@ -452,6 +452,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "ipnet"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "ipnetwork" name = "ipnetwork"
version = "0.20.0" version = "0.20.0"
@ -1001,6 +1010,7 @@ dependencies = [
"bincode", "bincode",
"blake3", "blake3",
"futures", "futures",
"ipnet",
"log", "log",
"pnet", "pnet",
"pretty_env_logger", "pretty_env_logger",

View file

@ -20,3 +20,4 @@ pnet = "0.35.0"
rupnp = "2.0.0" rupnp = "2.0.0"
tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"] } tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"] }
futures = "0.3.31" futures = "0.3.31"
ipnet = { version = "2.10.1", features = ["serde"] }

View file

@ -29,6 +29,8 @@ const PERSIST_INTERVAL: Duration = Duration::from_secs(600);
const LAN_BROADCAST_INTERVAL: Duration = Duration::from_secs(60); const LAN_BROADCAST_INTERVAL: Duration = Duration::from_secs(60);
const IGD_INTERVAL: Duration = Duration::from_secs(60); const IGD_INTERVAL: Duration = Duration::from_secs(60);
const IGD_LEASE_DURATION: Duration = Duration::from_secs(300);
type Pubkey = String; type Pubkey = String;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -56,6 +58,9 @@ struct Config {
/// Settings for the Wireguard interfaces (currently only necessary if you want to use igd features) /// Settings for the Wireguard interfaces (currently only necessary if you want to use igd features)
#[serde(default)] #[serde(default)]
interfaces: Vec<InterfaceSetting>, interfaces: Vec<InterfaceSetting>,
#[serde(default)]
forbidden_nets: Vec<ipnet::IpNet>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -168,7 +173,11 @@ fn wg_dump(interface: &str) -> Result<IfInfo> {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
Ok(IfInfo { our_pubkey, listen_port, peers }) Ok(IfInfo {
our_pubkey,
listen_port,
peers,
})
} }
// ============ DAEMON CODE ================= // ============ DAEMON CODE =================
@ -210,26 +219,39 @@ impl Daemon {
fn new(config: Config) -> Result<Self> { fn new(config: Config) -> Result<Self> {
let gossip_key = kdf(config.gossip_secret.as_deref().unwrap_or_default()); let gossip_key = kdf(config.gossip_secret.as_deref().unwrap_or_default());
let interface_names = config.peers.iter().map(|peer| peer.interface.clone()).collect::<HashSet<_>>(); let interface_names = config
let interfaces = interface_names.into_iter().map(|interface_name| wg_dump(&interface_name).map(|ifinfo| (interface_name, ifinfo))).collect::<Result<HashMap<_, _>>>()?; .peers
.iter()
.map(|peer| peer.interface.clone())
.collect::<HashSet<_>>();
let interfaces = interface_names
.into_iter()
.map(|interface_name| wg_dump(&interface_name).map(|ifinfo| (interface_name, ifinfo)))
.collect::<Result<HashMap<_, _>>>()?;
let socket = UdpSocket::bind(SocketAddr::new("::".parse()?, config.gossip_port))?; let socket = UdpSocket::bind(SocketAddr::new("::".parse()?, config.gossip_port))?;
socket.set_broadcast(true)?; socket.set_broadcast(true)?;
socket.set_ttl(1)?; //socket.set_ttl(1)?;
let our_pubkey = interfaces.iter().next().unwrap().1.our_pubkey.clone(); let our_pubkey = interfaces.iter().next().unwrap().1.our_pubkey.clone();
let peers = config.peers.iter().map(|peer_cfg| { let peers = config
( .peers
peer_cfg.pubkey.clone(), .iter()
PeerInfo { .map(|peer_cfg| {
gossip_ip: peer_cfg.address, (
gossip_prio: fasthash(format!("{}-{}", our_pubkey, peer_cfg.pubkey).as_bytes()), peer_cfg.pubkey.clone(),
endpoint: None, // Is resolved as DNS name later PeerInfo {
last_seen: u64::MAX, gossip_ip: peer_cfg.address,
lan_endpoint: None, gossip_prio: fasthash(
} format!("{}-{}", our_pubkey, peer_cfg.pubkey).as_bytes(),
) ),
}).collect(); endpoint: None, // Is resolved as DNS name later
last_seen: u64::MAX,
lan_endpoint: None,
},
)
})
.collect();
Ok(Daemon { Ok(Daemon {
config, config,
@ -357,9 +379,7 @@ impl Daemon {
self.socket.send_to(&packet, from)?; self.socket.send_to(&packet, from)?;
} }
} }
Gossip::LanBroadcast { Gossip::LanBroadcast { pubkey } => {
pubkey,
} => {
if self.config.lan_discovery { if self.config.lan_discovery {
if let Some(peer) = state.peers.get_mut(&pubkey) { if let Some(peer) = state.peers.get_mut(&pubkey) {
peer.lan_endpoint = Some((from.ip(), time())); peer.lan_endpoint = Some((from.ip(), time()));
@ -528,7 +548,20 @@ impl State {
let mut peer_vec = self let mut peer_vec = self
.peers .peers
.iter() .iter()
.filter(|(_, info)| info.last_seen != u64::MAX && now < info.last_seen + TIMEOUT.as_secs() && info.endpoint.is_some()) .filter(|(_, info)| {
let seen = info.last_seen != u64::MAX && now < info.last_seen + TIMEOUT.as_secs();
let endpoint_valid = info
.endpoint
.map(|ep| {
!daemon
.config
.forbidden_nets
.iter()
.any(|net| net.contains(&ep))
})
.unwrap_or(false);
seen && endpoint_valid
})
.map(|(_, info)| (info.gossip_ip, info.gossip_prio)) .map(|(_, info)| (info.gossip_ip, info.gossip_prio))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
peer_vec.sort_by_key(|(_, prio)| *prio); peer_vec.sort_by_key(|(_, prio)| *prio);
@ -589,7 +622,6 @@ impl State {
} }
fn read_wg_peers(&mut self) -> Result<()> { fn read_wg_peers(&mut self) -> Result<()> {
// Clear old known endpoints if any // Clear old known endpoints if any
for (_, peer) in self.peers.iter_mut() { for (_, peer) in self.peers.iter_mut() {
peer.endpoint = None; peer.endpoint = None;
@ -630,10 +662,22 @@ impl State {
(Some((addr1, _)), Some(addr2)) => addr1 != addr2, (Some((addr1, _)), Some(addr2)) => addr1 != addr2,
_ => false, _ => false,
}; };
// If the current endpoint is in a forbidden net, reconfigure the peer even if it has a connection
let forbidden_endpoint = peer
.endpoint
.map(|ep| {
daemon
.config
.forbidden_nets
.iter()
.any(|net| net.contains(&ep))
})
.unwrap_or(false);
// if peer is connected and endpoint is the correct one, // if peer is connected and endpoint is the correct one,
// set higher keepalive and then skip reconfiguring it // set higher keepalive and then skip reconfiguring it
if !bad_endpoint && peer.last_seen != u64::MAX && now < peer.last_seen + TIMEOUT.as_secs() { if !bad_endpoint && peer.last_seen != u64::MAX && !forbidden_endpoint && now < peer.last_seen + TIMEOUT.as_secs()
{
Command::new("wg") Command::new("wg")
.args([ .args([
"set", "set",
@ -673,6 +717,16 @@ impl State {
} }
} }
endpoints.sort(); endpoints.sort();
endpoints = endpoints
.into_iter()
.filter(|(ep, _)| {
!daemon
.config
.forbidden_nets
.iter()
.any(|net| net.contains(ep))
})
.collect();
endpoints endpoints
} }
}; };
@ -702,7 +756,7 @@ impl State {
"persistent-keepalive", "persistent-keepalive",
"10", "10",
"allowed-ips", "allowed-ips",
"::/0,0.0.0.0/0" "::/0,0.0.0.0/0",
]) ])
.output()?; .output()?;
let packet = daemon.make_packet(&Gossip::Ping)?; let packet = daemon.make_packet(&Gossip::Ping)?;
@ -720,7 +774,7 @@ impl State {
"peer", "peer",
&peer_cfg.pubkey, &peer_cfg.pubkey,
"allowed-ips", "allowed-ips",
"::/0,0.0.0.0/0" "::/0,0.0.0.0/0",
]) ])
.output()?; .output()?;
} }