use serde_json::json;
use tracing::debug;
use crate::command::{Command, CommandResult};
use crate::core_error::CoreError;
use crate::model::EntityId;
use crate::session::SessionClient;
use super::super::{CommandContext, require_session};
pub(super) async fn route(ctx: &CommandContext, cmd: Command) -> Result<CommandResult, CoreError> {
let session = ctx.session.as_ref();
match cmd {
Command::CreateNatPolicy(req) => {
let session = require_session(session)?;
let nat_type = match req.nat_type.to_lowercase().as_str() {
"masquerade" => "MASQUERADE",
"source" | "source_nat" | "snat" => "SNAT",
_ => "DNAT",
};
let protocol = req
.protocol
.as_deref()
.map(|p| match p.to_lowercase().as_str() {
"tcp" => "tcp",
"udp" => "udp",
"tcp_udp" | "tcp_and_udp" => "tcp_udp",
_ => "all",
});
let rule_index = next_rule_index(session).await?;
let mut body = json!({
"description": req.name,
"enabled": req.enabled,
"type": nat_type,
"ip_version": "IPV4",
"is_predefined": false,
"rule_index": rule_index,
"setting_preference": "manual",
"logging": false,
"exclude": false,
"pppoe_use_base_interface": false,
});
if let Some(proto) = protocol {
body["protocol"] = json!(proto);
}
if let Some(addr) = &req.translated_address {
body["ip_address"] = json!(addr);
}
if let Some(port) = &req.translated_port {
body["port"] = json!(port);
}
if let Some(iface) = &req.interface_id {
let session_id = resolve_interface_id(session, iface).await?;
let key = if nat_type == "DNAT" {
"in_interface"
} else {
"out_interface"
};
body[key] = json!(session_id);
}
body["source_filter"] =
build_filter(req.src_address.as_deref(), req.src_port.as_deref());
body["destination_filter"] =
build_filter(req.dst_address.as_deref(), req.dst_port.as_deref());
session.create_nat_rule(&body).await?;
Ok(CommandResult::Ok)
}
Command::DeleteNatPolicy { id } => {
let session = require_session(session)?;
session.delete_nat_rule(&id.to_string()).await?;
Ok(CommandResult::Ok)
}
_ => unreachable!("nat::route received non-NAT command"),
}
}
fn build_filter(address: Option<&str>, port: Option<&str>) -> serde_json::Value {
if address.is_none() && port.is_none() {
return json!({
"filter_type": "NONE",
"firewall_group_ids": [],
"invert_address": false,
"invert_port": false,
});
}
let mut filter = json!({
"filter_type": "ADDRESS_AND_PORT",
"firewall_group_ids": [],
"invert_address": false,
"invert_port": false,
});
if let Some(addr) = address {
filter["address"] = json!(addr);
}
if let Some(p) = port {
filter["port"] = json!(p);
}
filter
}
async fn next_rule_index(session: &SessionClient) -> Result<u64, CoreError> {
let rules = session.list_nat_rules().await.map_err(CoreError::from)?;
let max_idx = rules
.iter()
.filter_map(|r| r.get("rule_index").and_then(serde_json::Value::as_u64))
.max()
.unwrap_or(0);
Ok(max_idx + 1)
}
async fn resolve_interface_id(
session: &SessionClient,
iface: &EntityId,
) -> Result<String, CoreError> {
match iface {
EntityId::Legacy(id) => Ok(id.clone()),
EntityId::Uuid(uuid) => {
let uuid_str = uuid.to_string();
debug!(uuid = %uuid_str, "resolving Integration network UUID to Session _id");
let records = session.list_network_conf().await.map_err(CoreError::from)?;
for record in &records {
if record
.get("external_id")
.and_then(serde_json::Value::as_str)
== Some(&uuid_str)
&& let Some(id) = record.get("_id").and_then(serde_json::Value::as_str)
{
debug!(session_id = id, "resolved interface ID");
return Ok(id.to_owned());
}
}
Err(CoreError::NotFound {
entity_type: "network".into(),
identifier: uuid_str,
})
}
}
}