feat(server): add bridge forwarding mode and per-client destination policy overrides
This commit is contained in:
@@ -26,6 +26,9 @@ pub struct ClientSecurity {
|
||||
pub max_connections: Option<u32>,
|
||||
/// Per-client rate limiting.
|
||||
pub rate_limit: Option<ClientRateLimit>,
|
||||
/// Per-client destination routing policy override.
|
||||
/// When set, overrides the server-level DestinationPolicy for this client's traffic.
|
||||
pub destination_policy: Option<crate::server::DestinationPolicyConfig>,
|
||||
}
|
||||
|
||||
/// A registered client entry — the server-side source of truth.
|
||||
@@ -76,12 +79,14 @@ impl ClientEntry {
|
||||
}
|
||||
}
|
||||
|
||||
/// In-memory client registry with dual-key indexing.
|
||||
/// In-memory client registry with triple-key indexing.
|
||||
pub struct ClientRegistry {
|
||||
/// Primary index: clientId → ClientEntry
|
||||
entries: HashMap<String, ClientEntry>,
|
||||
/// Secondary index: publicKey (base64) → clientId (fast lookup during handshake)
|
||||
key_index: HashMap<String, String>,
|
||||
/// Tertiary index: assignedIp → clientId (fast lookup during NAT destination policy)
|
||||
ip_index: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ClientRegistry {
|
||||
@@ -89,6 +94,7 @@ impl ClientRegistry {
|
||||
Self {
|
||||
entries: HashMap::new(),
|
||||
key_index: HashMap::new(),
|
||||
ip_index: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +120,9 @@ impl ClientRegistry {
|
||||
anyhow::bail!("Public key already registered to another client");
|
||||
}
|
||||
self.key_index.insert(entry.public_key.clone(), entry.client_id.clone());
|
||||
if let Some(ref ip) = entry.assigned_ip {
|
||||
self.ip_index.insert(ip.clone(), entry.client_id.clone());
|
||||
}
|
||||
self.entries.insert(entry.client_id.clone(), entry);
|
||||
Ok(())
|
||||
}
|
||||
@@ -123,6 +132,9 @@ impl ClientRegistry {
|
||||
let entry = self.entries.remove(client_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Client '{}' not found", client_id))?;
|
||||
self.key_index.remove(&entry.public_key);
|
||||
if let Some(ref ip) = entry.assigned_ip {
|
||||
self.ip_index.remove(ip);
|
||||
}
|
||||
Ok(entry)
|
||||
}
|
||||
|
||||
@@ -137,6 +149,12 @@ impl ClientRegistry {
|
||||
self.entries.get(client_id)
|
||||
}
|
||||
|
||||
/// Get a client by assigned IP (used for per-client destination policy in NAT engine).
|
||||
pub fn get_by_assigned_ip(&self, ip: &str) -> Option<&ClientEntry> {
|
||||
let client_id = self.ip_index.get(ip)?;
|
||||
self.entries.get(client_id)
|
||||
}
|
||||
|
||||
/// Check if a public key is authorized (exists, enabled, not expired).
|
||||
pub fn is_authorized(&self, public_key: &str) -> bool {
|
||||
match self.get_by_key(public_key) {
|
||||
@@ -153,12 +171,22 @@ impl ClientRegistry {
|
||||
let entry = self.entries.get_mut(client_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("Client '{}' not found", client_id))?;
|
||||
let old_key = entry.public_key.clone();
|
||||
let old_ip = entry.assigned_ip.clone();
|
||||
updater(entry);
|
||||
// If public key changed, update the index
|
||||
// If public key changed, update the key index
|
||||
if entry.public_key != old_key {
|
||||
self.key_index.remove(&old_key);
|
||||
self.key_index.insert(entry.public_key.clone(), client_id.to_string());
|
||||
}
|
||||
// If assigned IP changed, update the IP index
|
||||
if entry.assigned_ip != old_ip {
|
||||
if let Some(ref old) = old_ip {
|
||||
self.ip_index.remove(old);
|
||||
}
|
||||
if let Some(ref new_ip) = entry.assigned_ip {
|
||||
self.ip_index.insert(new_ip.clone(), client_id.to_string());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -362,6 +390,7 @@ mod tests {
|
||||
bytes_per_sec: 1_000_000,
|
||||
burst_bytes: 2_000_000,
|
||||
}),
|
||||
destination_policy: None,
|
||||
});
|
||||
let mut reg = ClientRegistry::new();
|
||||
reg.add(entry).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user