KeiSeiKit-1.0/_primitives/_rust/kei-net-ipsec/src/network.rs
Parfii-bot a4e667de10 KeiSeiKit-public — clean state
Single-commit clean baseline after security scrub of niche-tells,
project codenames, internal jargon, and contributor-email leaks.

Contents:
- 100 Rust crates (_primitives/_rust/)
- 37 agent manifests (_manifests/) + generated specs (_generated/)
- 67 user-invocable skills (skills/)
- 33 hooks (hooks/)
- Composition blocks (_blocks/)
- Documentation (docs/, README.md)
- TS adapter packages (_ts_packages/)
- Assembler (_assembler/)
- Roles (_roles/)
- Templates (_templates/)
- Forgejo CI (.forgejo/)

Author: Denis Parfionovich <info@greendragon.info>

License: see LICENSE.
2026-05-01 12:09:03 +08:00

146 lines
4.9 KiB
Rust

// SPDX-License-Identifier: Apache-2.0
// Copyright 2026 <author org>
//
//! [`IpsecMode`] — DNA-bearing [`NetworkMode`] impl that brings a
//! strongSwan child SA up / down via `swanctl`.
//!
//! Mechanism (per RFC: each step is a single `swanctl` invocation routed
//! through [`Runner`] for testability):
//!
//! * `configure` → `swanctl --load-all` (refresh `/etc/swanctl/`),
//! then `swanctl --initiate --child <child_name>`.
//! * `teardown` → `swanctl --terminate --child <child_name>`.
//! * `peers` → `swanctl --list-sas`, parsed by [`crate::parse`].
//!
//! `is_public() = true`. Sibling tailscale / wireguard NetworkMode adapters
//! return `false`.
use crate::error::{Error, Result as IpsecResult};
use crate::parse::parse_sas_output;
use crate::runner::Runner;
use async_trait::async_trait;
use kei_runtime_core::traits::network::{NetworkConfig, NetworkMode, PeerStatus};
use kei_runtime_core::{Dna, DnaBuilder, HasDna, Result as CoreResult};
use std::sync::Arc;
/// Default config root for `swanctl` (overridable via env
/// `SWANCTL_CONFIG_DIR`). Used informationally — strongSwan reads this
/// path automatically; we surface the env var so operators can move it.
pub const DEFAULT_CONFIG_DIR: &str = "/etc/swanctl";
/// Default child SA name (overridable via env `IPSEC_CHILD_NAME`).
pub const DEFAULT_CHILD_NAME: &str = "home";
/// strongSwan / swanctl `NetworkMode`. Construction injects a
/// [`Runner`] so unit tests can swap in [`crate::runner::MockRunner`].
pub struct IpsecMode {
dna: Dna,
parent: Option<Dna>,
runner: Arc<dyn Runner + Send + Sync>,
child_name: String,
config_dir: String,
}
impl IpsecMode {
/// Construct with explicit runner + child name.
pub fn new(
runner: Arc<dyn Runner + Send + Sync>,
parent: Option<Dna>,
child_name: impl Into<String>,
) -> IpsecResult<Self> {
let dna = DnaBuilder::new("primitive")
.caps(["PR", "AP", "IP"])
.scope("keiseikit.dev/primitives/kei-net-ipsec")
.body(b"ipsec-strongswan-v1")
.build()?;
Ok(Self {
dna,
parent,
runner,
child_name: child_name.into(),
config_dir: DEFAULT_CONFIG_DIR.into(),
})
}
/// Construct from environment: `IPSEC_CHILD_NAME` (default `home`)
/// and `SWANCTL_CONFIG_DIR` (default `/etc/swanctl`).
pub fn from_env(
runner: Arc<dyn Runner + Send + Sync>,
parent: Option<Dna>,
) -> IpsecResult<Self> {
let child = std::env::var("IPSEC_CHILD_NAME").unwrap_or_else(|_| DEFAULT_CHILD_NAME.into());
let cfg = std::env::var("SWANCTL_CONFIG_DIR")
.unwrap_or_else(|_| DEFAULT_CONFIG_DIR.into());
let mut m = Self::new(runner, parent, child)?;
m.config_dir = cfg;
Ok(m)
}
/// Inspect the child SA name this mode operates on.
pub fn child_name(&self) -> &str {
&self.child_name
}
/// Inspect the swanctl config directory (informational).
pub fn config_dir(&self) -> &str {
&self.config_dir
}
fn invoke(&self, args: &[&str]) -> IpsecResult<String> {
let out = self
.runner
.run("swanctl", args)
.map_err(|e| Error::SwanctlFailed(e.to_string()))?;
if !out.is_success() {
return Err(Error::SwanctlFailed(format!(
"swanctl {} exited code={:?} stderr={}",
args.join(" "),
out.code,
out.stderr.trim()
)));
}
Ok(out.stdout)
}
}
impl HasDna for IpsecMode {
fn dna(&self) -> &Dna {
&self.dna
}
fn parent_dna(&self) -> Option<&Dna> {
self.parent.as_ref()
}
}
#[async_trait]
impl NetworkMode for IpsecMode {
fn mode_name(&self) -> &'static str {
"ipsec"
}
async fn configure(&self, _cfg: &NetworkConfig) -> CoreResult<()> {
// Refresh strongSwan in-memory config from disk, then bring the
// child SA up. `--load-all` is idempotent; running it before
// `--initiate` covers the common case of an operator having
// edited `swanctl.conf` since the daemon last refreshed.
self.invoke(&["--load-all"]).map_err(|e: Error| -> kei_runtime_core::Error { e.into() })?;
self.invoke(&["--initiate", "--child", &self.child_name])
.map_err(|e: Error| -> kei_runtime_core::Error { e.into() })?;
Ok(())
}
async fn teardown(&self) -> CoreResult<()> {
self.invoke(&["--terminate", "--child", &self.child_name])
.map_err(|e: Error| -> kei_runtime_core::Error { e.into() })?;
Ok(())
}
async fn peers(&self) -> CoreResult<Vec<PeerStatus>> {
let stdout = self.invoke(&["--list-sas"]).map_err(|e: Error| -> kei_runtime_core::Error { e.into() })?;
Ok(parse_sas_output(&stdout))
}
fn is_public(&self) -> bool {
true
}
}