1pub mod actions;
6pub mod frame;
7
8use crate::llm::action_helper::call_llm;
9use crate::llm::actions::protocol_trait::ActionResult;
10use crate::logging::emit::Log;
11use crate::protocol::{Event, SpawnContext};
12use crate::server::connection::ConnectionId;
13use actions::{FrameContext, VxlanProtocol};
14use anyhow::{Context, Result};
15use frame::{Encap, Frame, Header, Mac, Payload};
16use serde_json::json;
17use std::collections::HashMap;
18use std::net::{Ipv4Addr, SocketAddr};
19use std::sync::{Arc, Mutex};
20use tokio::net::UdpSocket;
21
22#[derive(Clone)]
23struct Config {
24 encap: Encap,
25 default_mac: Mac,
26 macs: Arc<Mutex<HashMap<Ipv4Addr, Mac>>>,
27 sequence: Arc<Mutex<u32>>,
30}
31
32pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
33 let params = ctx.startup_params.as_ref();
34 let encap = Encap::parse(
35 ¶ms
36 .map(|p| p.get_optional_string("encapsulation"))
37 .transpose()?
38 .flatten()
39 .unwrap_or_else(|| actions::DEFAULT_ENCAPSULATION.into()),
40 )?;
41 let only_vni = params
42 .map(|p| p.get_optional_u64("vni"))
43 .transpose()?
44 .flatten();
45 if let Some(v) = only_vni {
46 anyhow::ensure!(
47 v <= encap.max_vni() as u64,
48 "vni must be at most {} for {}",
49 encap.max_vni(),
50 encap.name()
51 );
52 }
53 let default_mac = frame::parse_mac(
54 ¶ms
55 .map(|p| p.get_optional_string("overlay_mac"))
56 .transpose()?
57 .flatten()
58 .unwrap_or_else(|| frame::DEFAULT_MAC.into()),
59 )?;
60 let port = ctx.port.unwrap_or_else(|| ctx.legacy_listen_addr().port());
61 let host = ctx.host.clone().unwrap_or_else(|| "127.0.0.1".into());
62 let bind = tokio::net::lookup_host((host.as_str(), port))
63 .await
64 .with_context(|| format!("cannot resolve host {host}"))?
65 .next()
66 .with_context(|| format!("host {host} resolved to no address"))?;
67 let socket = Arc::new(
68 UdpSocket::bind(bind)
69 .await
70 .with_context(|| format!("{} failed to bind {bind}", encap.name()))?,
71 );
72 let local = socket.local_addr()?;
73 Log::new(Some(&ctx.status_tx)).info(format!(
74 "{} endpoint listening on {local}{}",
75 encap.name().to_uppercase(),
76 only_vni
77 .map(|v| format!(", {} only", frame::vni_str(encap, Some(v as u32))))
78 .unwrap_or_default()
79 ));
80 let config = Config {
81 encap,
82 default_mac,
83 macs: Default::default(),
84 sequence: Default::default(),
85 };
86 let state = ctx.state.clone();
87 let server_id = ctx.server_id;
88 let task = tokio::spawn(async move {
89 let mut buf = vec![0u8; 65_536];
90 loop {
91 let (n, from) = match socket.recv_from(&mut buf).await {
92 Ok(r) => r,
93 Err(e) => {
94 Log::new(Some(&ctx.status_tx))
95 .error(format!("{} receive error: {e}", encap.name()));
96 break;
97 }
98 };
99 let parsed = frame::decap(encap, &buf[..n])
100 .and_then(|(header, inner)| Ok((header, frame::parse_frame(inner)?)));
101 let (header, frame) = match parsed {
102 Ok(p) => p,
103 Err(e) => {
104 tracing::warn!("{} dropped a datagram from {from}: {e:#}", encap.name());
105 continue;
106 }
107 };
108 let vni = frame::vni_str(encap, header.vni);
109 if only_vni.is_some_and(|v| header.vni.map(u64::from) != Some(v)) {
111 tracing::debug!("{} dropped a frame for {vni} from {from}", encap.name());
112 continue;
113 }
114 let Some(event) = event_for(&frame, header.vni, from) else {
115 if let Payload::Other { what } = &frame.payload {
116 tracing::debug!("{} from {from} {vni}: {what}, not answered", encap.name());
117 }
118 continue;
119 };
120 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
121 record(&ctx, id, local, from, n).await;
122 let (child, socket, config) = (ctx.clone(), socket.clone(), config.clone());
123 let reply_to = SocketAddr::new(from.ip(), local.port());
126 let handle = tokio::spawn(async move {
127 answer(child, socket, config, header, frame, event, reply_to, id).await
128 });
129 ctx.state.register_server_task(ctx.server_id, handle).await;
130 }
131 });
132 state.register_server_task(server_id, task).await;
133 Ok(local)
134}
135
136fn event_for(f: &Frame, vni: Option<u32>, from: SocketAddr) -> Option<Event> {
137 let mut data = json!({
138 "vni": vni,
139 "vtep": from.to_string(),
140 "src_mac": frame::mac_str(&f.src_mac),
141 });
142 let ip = |a: Option<Ipv4Addr>| a.map(|a| a.to_string());
143 let t = match &f.payload {
144 Payload::Arp(arp) if arp.request => {
145 data["sender_ip"] = json!(arp.sender_ip.to_string());
146 data["target_ip"] = json!(arp.target_ip.to_string());
147 &*actions::ARP_EVENT
148 }
149 Payload::Echo {
150 request: true,
151 identifier,
152 sequence,
153 data: payload,
154 } => {
155 let (text, encoding) = frame::data_json(payload);
156 data["src_ip"] = json!(ip(f.src_ip));
157 data["dst_ip"] = json!(ip(f.dst_ip));
158 data["identifier"] = json!(identifier);
159 data["sequence"] = json!(sequence);
160 data["data"] = json!(text);
161 data["encoding"] = json!(encoding);
162 &*actions::ECHO_EVENT
163 }
164 Payload::Udp {
165 src_port,
166 dst_port,
167 data: payload,
168 } => {
169 let (text, encoding) = frame::data_json(payload);
170 data["src_ip"] = json!(ip(f.src_ip));
171 data["src_port"] = json!(src_port);
172 data["dst_ip"] = json!(ip(f.dst_ip));
173 data["dst_port"] = json!(dst_port);
174 data["data"] = json!(text);
175 data["encoding"] = json!(encoding);
176 &*actions::UDP_EVENT
177 }
178 _ => return None,
179 };
180 Some(Event::new(t, data))
181}
182
183async fn record(
184 ctx: &SpawnContext,
185 id: ConnectionId,
186 local: SocketAddr,
187 from: SocketAddr,
188 n: usize,
189) {
190 use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
191 let now = crate::utils::clock::Instant::now();
192 ctx.state
193 .add_connection_to_server(
194 ctx.server_id,
195 ConnectionState {
196 id,
197 remote_addr: from,
198 local_addr: local,
199 bytes_sent: 0,
200 bytes_received: n as u64,
201 packets_sent: 0,
202 packets_received: 1,
203 last_activity: now,
204 status: ConnectionStatus::Active,
205 status_changed_at: now,
206 protocol_info: ProtocolConnectionInfo::empty(),
207 },
208 )
209 .await;
210}
211
212#[allow(clippy::too_many_arguments)]
213async fn answer(
214 ctx: SpawnContext,
215 socket: Arc<UdpSocket>,
216 config: Config,
217 header: Header,
218 frame: Frame,
219 event: Event,
220 reply_to: SocketAddr,
221 id: ConnectionId,
222) {
223 let summary = format!(
224 "{} {} from {reply_to} {}",
225 config.encap.name(),
226 event.id(),
227 frame::vni_str(config.encap, header.vni)
228 );
229 let protocol = VxlanProtocol::for_frame(FrameContext {
230 encap: config.encap,
231 header,
232 frame,
233 default_mac: config.default_mac,
234 macs: config.macs.clone(),
235 });
236 let log = Log::new(Some(&ctx.status_tx));
237 let execution = match call_llm(
238 &ctx.llm_client,
239 &ctx.state,
240 ctx.server_id,
241 Some(id),
242 &event,
243 &protocol,
244 )
245 .await
246 {
247 Ok(e) => e,
248 Err(e) => {
249 log.error(format!(
250 "{summary} decision=fail_closed_llm_error (nothing sent: no host answers): {e}"
251 ));
252 return;
253 }
254 };
255 let mut sent = 0;
256 for result in &execution.protocol_results {
257 let items = match result {
258 ActionResult::Multiple(all) => all.iter().collect::<Vec<_>>(),
259 other => vec![other],
260 };
261 for item in items {
262 if let ActionResult::Output(bytes) = item {
263 match send(&socket, &config, bytes.clone(), reply_to).await {
264 Ok(n) => {
265 sent += 1;
266 ctx.state
267 .update_connection_stats(
268 ctx.server_id,
269 id,
270 None,
271 Some(n as u64),
272 None,
273 Some(1),
274 )
275 .await;
276 }
277 Err(e) => log.warn(format!("{summary}: sending to {reply_to} failed: {e}")),
278 }
279 }
280 }
281 }
282 let decision = match (sent, execution.failures.is_empty()) {
283 (0, true) => "model_silent",
284 (0, false) => "fail_closed_action_error",
285 _ => "model_answered",
286 };
287 log.info(format!("{summary} decision={decision}"));
288}
289
290async fn send(
294 socket: &UdpSocket,
295 config: &Config,
296 mut datagram: Vec<u8>,
297 to: SocketAddr,
298) -> std::io::Result<usize> {
299 loop {
300 {
301 let mut next = config.sequence.lock().unwrap_or_else(|e| e.into_inner());
302 let numbered = frame::stamp_sequence(config.encap, &mut datagram, *next);
303 match socket.try_send_to(&datagram, to) {
304 Ok(n) => {
305 if numbered {
306 *next = next.wrapping_add(1);
307 }
308 return Ok(n);
309 }
310 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
311 Err(e) => return Err(e),
312 }
313 }
314 socket.writable().await?;
315 }
316}