refactor!: use rtnetlink for client interfaces, add deregistration
This commit is contained in:
27
registry/src/endpoints/deregister.rs
Normal file
27
registry/src/endpoints/deregister.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use actix_web::{HttpResponse, web};
|
||||
use serde::Deserialize;
|
||||
use wireguard_control::Key;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
error::{Error, Result},
|
||||
storage::StorageImpl,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeregisterRequest {
|
||||
public_key: String,
|
||||
}
|
||||
|
||||
pub async fn deregister(
|
||||
app_state: web::Data<AppState>,
|
||||
query: web::Query<DeregisterRequest>,
|
||||
) -> Result<HttpResponse> {
|
||||
Key::from_base64(&query.public_key).map_err(|_| Error::invalid_key(&query.public_key))?;
|
||||
|
||||
app_state
|
||||
.storage
|
||||
.deregister_device(&query.public_key)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod deregister;
|
||||
pub mod peers;
|
||||
pub mod register;
|
||||
pub mod ws;
|
||||
|
||||
@@ -4,13 +4,14 @@ use crate::{
|
||||
storage::{RegisterRequest, StorageImpl},
|
||||
};
|
||||
use actix_web::{HttpResponse, web};
|
||||
use registry::Peer;
|
||||
use registry::{Peer, RegisterResponse};
|
||||
|
||||
pub async fn register_peer(
|
||||
app_state: web::Data<AppState>,
|
||||
request: web::Json<RegisterRequest>,
|
||||
) -> Result<HttpResponse> {
|
||||
app_state.storage.register_device(&request).await?;
|
||||
let mesh_ip = app_state.storage.register_device(&request).await?;
|
||||
|
||||
app_state
|
||||
.peer_updates
|
||||
.send(PeerUpdate {
|
||||
@@ -18,10 +19,11 @@ pub async fn register_peer(
|
||||
public_key: request.public_key.as_str().to_string(),
|
||||
public_ip: request.public_ip.to_string(),
|
||||
port: request.port.clone(),
|
||||
mesh_ip,
|
||||
allowed_ips: request.allowed_ips.clone(),
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
.ok();
|
||||
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
Ok(HttpResponse::Ok().json(RegisterResponse { mesh_ip }))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use serde::Serialize;
|
||||
use thiserror::Error;
|
||||
use thiserror_ext::{Box, Construct};
|
||||
|
||||
@@ -32,12 +33,30 @@ pub enum ErrorKind {
|
||||
},
|
||||
#[error("error handling ws")]
|
||||
Ws(#[source] actix_web::Error),
|
||||
#[error("IP pool exhausted: no available addresses in {pool}")]
|
||||
IpPoolExhausted { pool: String },
|
||||
#[error("error deregistering device {public_key}")]
|
||||
DeregisterDevice {
|
||||
public_key: String,
|
||||
#[source]
|
||||
source: redis::RedisError,
|
||||
},
|
||||
#[error("error invalid key")]
|
||||
InvalidKey(String),
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ErrorResponse {
|
||||
error: String,
|
||||
}
|
||||
|
||||
impl ResponseError for Error {
|
||||
fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
|
||||
match self.inner() {
|
||||
ErrorKind::Ws(e) => e.error_response(),
|
||||
ErrorKind::InvalidKey(key) => HttpResponse::BadRequest().json(ErrorResponse {
|
||||
error: format!("error invalid key: {key}"),
|
||||
}),
|
||||
_ => HttpResponse::InternalServerError().finish(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod types;
|
||||
mod utils;
|
||||
|
||||
pub use types::peer_message::*;
|
||||
pub use types::peer_message::{Peer, PeerMessage, RegisterResponse};
|
||||
|
||||
@@ -47,6 +47,10 @@ async fn run() -> crate::error::Result<()> {
|
||||
"/register",
|
||||
web::post().to(endpoints::register::register_peer),
|
||||
)
|
||||
.route(
|
||||
"/deregister",
|
||||
web::delete().to(endpoints::deregister::deregister),
|
||||
)
|
||||
.route("/peers", web::get().to(endpoints::peers::get_peers))
|
||||
.route("/ws/peers", web::get().to(endpoints::ws::peers::peers))
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
mod valkey;
|
||||
@@ -10,12 +12,13 @@ pub enum Storage {
|
||||
}
|
||||
|
||||
pub trait StorageImpl {
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()>;
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr>;
|
||||
async fn deregister_device(&self, public_key: &str) -> Result<()>;
|
||||
async fn get_peers(&self) -> Result<Vec<Peer>>;
|
||||
}
|
||||
|
||||
impl StorageImpl for Storage {
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()> {
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr> {
|
||||
match self {
|
||||
Self::Valkey(storage) => storage.register_device(request).await,
|
||||
}
|
||||
@@ -26,6 +29,12 @@ impl StorageImpl for Storage {
|
||||
Self::Valkey(storage) => storage.get_peers().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn deregister_device(&self, public_key: &str) -> Result<()> {
|
||||
match self {
|
||||
Self::Valkey(storage) => storage.deregister_device(public_key).await,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_storage_from_env() -> Result<Storage> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::net::IpAddr;
|
||||
use std::net::{IpAddr, Ipv4Addr};
|
||||
|
||||
use futures::TryFutureExt;
|
||||
use ipnetwork::IpNetwork;
|
||||
use redis::AsyncTypedCommands;
|
||||
use registry::Peer;
|
||||
@@ -10,6 +11,10 @@ use crate::error::Result;
|
||||
use crate::utils::WireguardPublicKey;
|
||||
use crate::{error::Error, storage::StorageImpl};
|
||||
|
||||
const MESH_NETWORK_BASE: [u8; 4] = [10, 100, 0, 0];
|
||||
const MESH_POOL_START: u8 = 1;
|
||||
const MESH_POOL_END: u8 = 254;
|
||||
|
||||
pub struct ValkeyStorage {
|
||||
pub valkey_client: redis::Client,
|
||||
}
|
||||
@@ -23,22 +28,38 @@ pub struct RegisterRequest {
|
||||
}
|
||||
|
||||
impl StorageImpl for ValkeyStorage {
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<()> {
|
||||
async fn register_device(&self, request: &RegisterRequest) -> Result<Ipv4Addr> {
|
||||
let mut conn = self
|
||||
.valkey_client
|
||||
.get_multiplexed_async_connection()
|
||||
.await
|
||||
.map_err(|e| Error::valkey_get_connection(e))?;
|
||||
|
||||
let peer_key = format!("peer:{}", request.public_key.as_str());
|
||||
|
||||
let existing_mesh_ip: Option<String> = conn
|
||||
.hget(&peer_key, "mesh_ip")
|
||||
.await
|
||||
.map_err(|e| Error::get_peer(e))?;
|
||||
|
||||
let mesh_ip = if let Some(ip_str) = existing_mesh_ip {
|
||||
ip_str.parse::<Ipv4Addr>().unwrap()
|
||||
} else {
|
||||
let allocated_ip = self.allocate_mesh_ip(&mut conn).await?;
|
||||
allocated_ip
|
||||
};
|
||||
|
||||
conn.hset_multiple::<_, _, _>(
|
||||
format!("peer:{}", request.public_key.as_str()),
|
||||
&peer_key,
|
||||
&[
|
||||
("public_ip", &request.public_ip.to_string()),
|
||||
("public_ip", request.public_ip.to_string()),
|
||||
(
|
||||
"allowed_ips",
|
||||
&serde_json::to_string(&request.allowed_ips)
|
||||
serde_json::to_string(&request.allowed_ips)
|
||||
.map_err(|e| Error::serialize_json(e, "serializing allowed_ips"))?,
|
||||
),
|
||||
("port", &request.port),
|
||||
("port", request.port.clone()),
|
||||
("mesh_ip", mesh_ip.to_string()),
|
||||
],
|
||||
)
|
||||
.await
|
||||
@@ -49,7 +70,8 @@ impl StorageImpl for ValkeyStorage {
|
||||
request.public_ip.to_string(),
|
||||
)
|
||||
})?;
|
||||
conn.sadd("peers", &request.public_key.as_str())
|
||||
|
||||
conn.sadd("peers", request.public_key.as_str())
|
||||
.await
|
||||
.map_err(|e| {
|
||||
Error::add_peer(
|
||||
@@ -59,6 +81,25 @@ impl StorageImpl for ValkeyStorage {
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(mesh_ip)
|
||||
}
|
||||
|
||||
async fn deregister_device(&self, public_key: &str) -> Result<()> {
|
||||
let mut conn = self
|
||||
.valkey_client
|
||||
.get_multiplexed_async_connection()
|
||||
.await
|
||||
.map_err(|e| Error::valkey_get_connection(e))?;
|
||||
let hash_key = format!("peer:{public_key}");
|
||||
conn.srem("peers", public_key)
|
||||
.map_err(|e| Error::deregister_device(e, public_key))
|
||||
.await?;
|
||||
let response = conn
|
||||
.del(hash_key)
|
||||
.await
|
||||
.map_err(|e| Error::deregister_device(e, public_key))?;
|
||||
tracing::debug!("deleted hash {keys} key(s) removed", keys = response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -89,21 +130,72 @@ impl StorageImpl for ValkeyStorage {
|
||||
.map_err(|e| Error::get_peer(e))?
|
||||
.into_iter()
|
||||
.zip(keys.iter())
|
||||
.map(|(peer, key): (HashMap<String, String>, &String)| {
|
||||
.filter_map(|(peer, key): (HashMap<String, String>, &String)| {
|
||||
let allowed_ips: Vec<IpNetwork> = peer
|
||||
.get("allowed_ips")
|
||||
.map(|s| serde_json::from_str(s).unwrap_or_default())
|
||||
.unwrap_or_default();
|
||||
|
||||
Peer {
|
||||
let mesh_ip: Ipv4Addr = peer.get("mesh_ip")?.parse().ok()?;
|
||||
Some(Peer {
|
||||
public_key: key.clone(),
|
||||
public_ip: peer.get("public_ip").unwrap().to_string(),
|
||||
port: peer.get("port").unwrap().to_string(),
|
||||
public_ip: peer.get("public_ip")?.to_string(),
|
||||
port: peer.get("port")?.to_string(),
|
||||
mesh_ip,
|
||||
allowed_ips,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(peers)
|
||||
}
|
||||
}
|
||||
|
||||
impl ValkeyStorage {
|
||||
async fn allocate_mesh_ip(
|
||||
&self,
|
||||
conn: &mut redis::aio::MultiplexedConnection,
|
||||
) -> Result<Ipv4Addr> {
|
||||
let keys: HashSet<String> = conn
|
||||
.smembers("peers")
|
||||
.await
|
||||
.map_err(|e| Error::get_peer(e))?;
|
||||
|
||||
let mut assigned_ips: HashSet<Ipv4Addr> = HashSet::new();
|
||||
|
||||
if !keys.is_empty() {
|
||||
let mut pipe = redis::pipe();
|
||||
for key in keys.iter() {
|
||||
pipe.hget(format!("peer:{key}"), "mesh_ip");
|
||||
}
|
||||
|
||||
let ips: Vec<Option<String>> = pipe
|
||||
.query_async(conn)
|
||||
.await
|
||||
.map_err(|e| Error::get_peer(e))?;
|
||||
|
||||
for ip_opt in ips {
|
||||
if let Some(ip_str) = ip_opt {
|
||||
if let Ok(ip) = ip_str.parse::<Ipv4Addr>() {
|
||||
assigned_ips.insert(ip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for last_octet in MESH_POOL_START..=MESH_POOL_END {
|
||||
let candidate = Ipv4Addr::new(
|
||||
MESH_NETWORK_BASE[0],
|
||||
MESH_NETWORK_BASE[1],
|
||||
MESH_NETWORK_BASE[2],
|
||||
last_octet,
|
||||
);
|
||||
|
||||
if !assigned_ips.contains(&candidate) {
|
||||
return Ok(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::ip_pool_exhausted("10.100.0.0/24".to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use ipnetwork::IpNetwork;
|
||||
@@ -7,9 +9,15 @@ pub struct Peer {
|
||||
pub public_key: String,
|
||||
pub public_ip: String,
|
||||
pub port: String,
|
||||
pub mesh_ip: Ipv4Addr,
|
||||
pub allowed_ips: Vec<IpNetwork>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RegisterResponse {
|
||||
pub mesh_ip: Ipv4Addr,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum PeerMessage {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use base64::Engine;
|
||||
use serde::{Deserialize, de};
|
||||
use serde::{de, Deserialize};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WireguardPublicKey(String);
|
||||
|
||||
Reference in New Issue
Block a user