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

View file

@ -20,3 +20,4 @@ pnet = "0.35.0"
rupnp = "2.0.0"
tokio = { version = "1.41.1", features = ["rt", "rt-multi-thread", "macros"] }
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 IGD_INTERVAL: Duration = Duration::from_secs(60);
const IGD_LEASE_DURATION: Duration = Duration::from_secs(300);
type Pubkey = String;
#[derive(Deserialize)]
@ -56,6 +58,9 @@ struct Config {
/// Settings for the Wireguard interfaces (currently only necessary if you want to use igd features)
#[serde(default)]
interfaces: Vec<InterfaceSetting>,
#[serde(default)]
forbidden_nets: Vec<ipnet::IpNet>,
}
#[derive(Deserialize)]
@ -168,7 +173,11 @@ fn wg_dump(interface: &str) -> Result<IfInfo> {
})
.collect::<Vec<_>>();
Ok(IfInfo { our_pubkey, listen_port, peers })
Ok(IfInfo {
our_pubkey,
listen_port,
peers,
})
}
// ============ DAEMON CODE =================
@ -210,26 +219,39 @@ impl Daemon {
fn new(config: Config) -> Result<Self> {
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 interfaces = interface_names.into_iter().map(|interface_name| wg_dump(&interface_name).map(|ifinfo| (interface_name, ifinfo))).collect::<Result<HashMap<_, _>>>()?;
let interface_names = config
.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))?;
socket.set_broadcast(true)?;
socket.set_ttl(1)?;
//socket.set_ttl(1)?;
let our_pubkey = interfaces.iter().next().unwrap().1.our_pubkey.clone();
let peers = config.peers.iter().map(|peer_cfg| {
(
peer_cfg.pubkey.clone(),
PeerInfo {
gossip_ip: peer_cfg.address,
gossip_prio: fasthash(format!("{}-{}", our_pubkey, peer_cfg.pubkey).as_bytes()),
endpoint: None, // Is resolved as DNS name later
last_seen: u64::MAX,
lan_endpoint: None,
}
)
}).collect();
let peers = config
.peers
.iter()
.map(|peer_cfg| {
(
peer_cfg.pubkey.clone(),
PeerInfo {
gossip_ip: peer_cfg.address,
gossip_prio: fasthash(
format!("{}-{}", our_pubkey, peer_cfg.pubkey).as_bytes(),
),
endpoint: None, // Is resolved as DNS name later
last_seen: u64::MAX,
lan_endpoint: None,
},
)
})
.collect();
Ok(Daemon {
config,
@ -357,9 +379,7 @@ impl Daemon {
self.socket.send_to(&packet, from)?;
}
}
Gossip::LanBroadcast {
pubkey,
} => {
Gossip::LanBroadcast { pubkey } => {
if self.config.lan_discovery {
if let Some(peer) = state.peers.get_mut(&pubkey) {
peer.lan_endpoint = Some((from.ip(), time()));
@ -528,7 +548,20 @@ impl State {
let mut peer_vec = self
.peers
.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))
.collect::<Vec<_>>();
peer_vec.sort_by_key(|(_, prio)| *prio);
@ -589,7 +622,6 @@ impl State {
}
fn read_wg_peers(&mut self) -> Result<()> {
// Clear old known endpoints if any
for (_, peer) in self.peers.iter_mut() {
peer.endpoint = None;
@ -630,10 +662,22 @@ impl State {
(Some((addr1, _)), Some(addr2)) => addr1 != addr2,
_ => 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,
// 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")
.args([
"set",
@ -673,6 +717,16 @@ impl State {
}
}
endpoints.sort();
endpoints = endpoints
.into_iter()
.filter(|(ep, _)| {
!daemon
.config
.forbidden_nets
.iter()
.any(|net| net.contains(ep))
})
.collect();
endpoints
}
};
@ -702,7 +756,7 @@ impl State {
"persistent-keepalive",
"10",
"allowed-ips",
"::/0,0.0.0.0/0"
"::/0,0.0.0.0/0",
])
.output()?;
let packet = daemon.make_packet(&Gossip::Ping)?;
@ -720,7 +774,7 @@ impl State {
"peer",
&peer_cfg.pubkey,
"allowed-ips",
"::/0,0.0.0.0/0"
"::/0,0.0.0.0/0",
])
.output()?;
}