feat(server): add bridge forwarding mode and per-client destination policy overrides
This commit is contained in:
@@ -13,6 +13,10 @@ pub struct IpPool {
|
||||
allocated: HashMap<Ipv4Addr, String>,
|
||||
/// Next candidate offset (skipping .0 network and .1 gateway)
|
||||
next_offset: u32,
|
||||
/// Minimum allocation offset (inclusive). Default: 2 (skip .0 network and .1 gateway).
|
||||
min_offset: u32,
|
||||
/// Maximum allocation offset (exclusive). Default: broadcast offset.
|
||||
max_offset: u32,
|
||||
}
|
||||
|
||||
impl IpPool {
|
||||
@@ -28,11 +32,47 @@ impl IpPool {
|
||||
anyhow::bail!("Prefix too long for VPN pool: /{}", prefix_len);
|
||||
}
|
||||
|
||||
let host_bits = 32 - prefix_len as u32;
|
||||
let max_offset = (1u32 << host_bits) - 1; // broadcast offset
|
||||
|
||||
Ok(Self {
|
||||
network,
|
||||
prefix_len,
|
||||
allocated: HashMap::new(),
|
||||
next_offset: 2, // Skip .0 (network) and .1 (server/gateway)
|
||||
min_offset: 2,
|
||||
max_offset,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new IP pool with a restricted allocation range within the subnet.
|
||||
/// `range_start` and `range_end` are host offsets (e.g., 200 and 250 for .200-.250).
|
||||
pub fn new_with_range(subnet: &str, range_start: u32, range_end: u32) -> Result<Self> {
|
||||
let parts: Vec<&str> = subnet.split('/').collect();
|
||||
if parts.len() != 2 {
|
||||
anyhow::bail!("Invalid subnet format: {}", subnet);
|
||||
}
|
||||
let network: Ipv4Addr = parts[0].parse()?;
|
||||
let prefix_len: u8 = parts[1].parse()?;
|
||||
if prefix_len > 30 {
|
||||
anyhow::bail!("Prefix too long for VPN pool: /{}", prefix_len);
|
||||
}
|
||||
if range_start >= range_end {
|
||||
anyhow::bail!("Invalid IP range: start ({}) must be less than end ({})", range_start, range_end);
|
||||
}
|
||||
let host_bits = 32 - prefix_len as u32;
|
||||
let broadcast_offset = (1u32 << host_bits) - 1;
|
||||
if range_end > broadcast_offset {
|
||||
anyhow::bail!("IP range end ({}) exceeds subnet broadcast ({})", range_end, broadcast_offset);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
network,
|
||||
prefix_len,
|
||||
allocated: HashMap::new(),
|
||||
next_offset: range_start,
|
||||
min_offset: range_start,
|
||||
max_offset: range_end + 1, // exclusive
|
||||
})
|
||||
}
|
||||
|
||||
@@ -44,22 +84,17 @@ impl IpPool {
|
||||
|
||||
/// Total number of usable client addresses in the pool.
|
||||
pub fn capacity(&self) -> u32 {
|
||||
let host_bits = 32 - self.prefix_len as u32;
|
||||
let total = 1u32 << host_bits;
|
||||
total.saturating_sub(3) // minus network, gateway, broadcast
|
||||
self.max_offset.saturating_sub(self.min_offset)
|
||||
}
|
||||
|
||||
/// Allocate an IP for a client. Returns the assigned IP.
|
||||
pub fn allocate(&mut self, client_id: &str) -> Result<Ipv4Addr> {
|
||||
let host_bits = 32 - self.prefix_len as u32;
|
||||
let max_offset = (1u32 << host_bits) - 1; // broadcast offset
|
||||
|
||||
// Try to find a free IP starting from next_offset
|
||||
let start = self.next_offset;
|
||||
let mut offset = start;
|
||||
loop {
|
||||
if offset >= max_offset {
|
||||
offset = 2; // wrap around
|
||||
if offset >= self.max_offset {
|
||||
offset = self.min_offset; // wrap around
|
||||
}
|
||||
|
||||
let ip = Ipv4Addr::from(u32::from(self.network) + offset);
|
||||
|
||||
Reference in New Issue
Block a user