1pub mod actions;
7
8pub use actions::ArpClientProtocol;
9
10use anyhow::{Context, Result};
11use pcap::{Capture, Device};
12use pnet::packet::arp::{ArpHardwareTypes, ArpOperations, ArpPacket, MutableArpPacket};
13use pnet::packet::ethernet::{EtherTypes, EthernetPacket, MutableEthernetPacket};
14use pnet::packet::Packet;
15use pnet::util::MacAddr;
16use std::net::{Ipv4Addr, SocketAddr};
17use std::str::FromStr;
18use std::sync::Arc;
19use tokio::sync::{mpsc, Mutex};
20use tracing::{debug, error, info, trace};
21
22use crate::client::arp::actions::{ARP_CLIENT_RESPONSE_RECEIVED_EVENT, ARP_CLIENT_STARTED_EVENT};
23use crate::client::llm_budget::call_llm_for_client;
24use crate::llm::actions::client_trait::{Client, ClientActionResult};
25use crate::llm::ollama_client::OllamaClient;
26use crate::llm::ClientLlmResult;
27use crate::logging::emit::Log;
28use crate::protocol::Event;
29use crate::state::app_state::AppState;
30use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
31use crate::state::{AccessLogOwner, ClientId, ClientStatus};
32
33#[derive(Debug, Clone, PartialEq)]
35enum ConnectionState {
36 Idle,
37 Processing,
38}
39
40pub struct InjectedPacket {
52 pub frame: Vec<u8>,
53 pub ack: Option<tokio::sync::oneshot::Sender<std::result::Result<usize, String>>>,
54}
55
56const INJECTION_ACK_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(5);
58
59struct ClientData {
61 state: ConnectionState,
62 memory: String,
63}
64
65pub struct ArpClient;
67
68impl ArpClient {
69 pub fn list_devices() -> Result<Vec<Device>> {
71 Device::list().context("Failed to list network devices")
72 }
73
74 pub fn find_device(name: &str) -> Result<Device> {
76 let devices = Self::list_devices()?;
77 devices
78 .into_iter()
79 .find(|d| d.name == name)
80 .ok_or_else(|| anyhow::anyhow!("Device '{}' not found", name))
81 }
82
83 pub async fn start_with_llm_actions(
85 interface: String,
86 llm_client: OllamaClient,
87 app_state: Arc<AppState>,
88 status_tx: mpsc::UnboundedSender<String>,
89 client_id: ClientId,
90 ) -> Result<SocketAddr> {
91 info!(
92 "ARP client {} starting on interface: {}",
93 client_id, interface
94 );
95 let _ = status_tx.send(format!(
96 "[CLIENT] ARP client {} starting on interface: {}",
97 client_id, interface
98 ));
99
100 app_state
102 .update_client_status(client_id, ClientStatus::Connected)
103 .await;
104 let _ = status_tx.send("__UPDATE_UI__".to_string());
105
106 let client_data = Arc::new(Mutex::new(ClientData {
108 state: ConnectionState::Idle,
109 memory: String::new(),
110 }));
111
112 let instruction = app_state
114 .get_instruction_for_client(client_id)
115 .await
116 .unwrap_or_else(|| "Monitor ARP traffic".to_string());
117
118 let protocol = Arc::new(ArpClientProtocol::new());
120 let event = Event::new(
121 &ARP_CLIENT_STARTED_EVENT,
122 serde_json::json!({
123 "interface": interface,
124 }),
125 );
126
127 let (packet_tx, packet_rx) = mpsc::unbounded_channel::<InjectedPacket>();
131
132 let command_rx =
137 crate::client::command_support::register_command_channel(&app_state, client_id).await;
138 let cmd_task = tokio::spawn(Self::command_loop(
139 command_rx,
140 protocol.clone(),
141 packet_tx.clone(),
142 client_id,
143 app_state.clone(),
144 status_tx.clone(),
145 ));
146 app_state.register_client_task(client_id, cmd_task).await;
147
148 let memory_snapshot = client_data.lock().await.memory.clone();
154
155 match call_llm_for_client(
157 &llm_client,
158 &app_state,
159 client_id.to_string(),
160 &instruction,
161 &memory_snapshot,
162 Some(&event),
163 protocol.as_ref(),
164 &status_tx,
165 )
166 .await
167 {
168 Ok(ClientLlmResult {
169 actions,
170 memory_updates,
171 }) => {
172 if let Some(mem) = memory_updates {
174 client_data.lock().await.memory = mem;
175 }
176
177 Self::execute_client_actions(
183 actions,
184 protocol.as_ref(),
185 &packet_tx,
186 client_id,
187 &app_state,
188 &status_tx,
189 )
190 .await;
191 }
192 Err(e) => {
193 error!("ARP client {} initial LLM call failed: {}", client_id, e);
194 }
195 }
196
197 let interface_clone = interface.clone();
199 let protocol_clone = protocol.clone();
200 let app_state_clone = app_state.clone();
201 let status_tx_clone = status_tx.clone();
202 let llm_client_clone = llm_client.clone();
203 let client_data_clone = client_data.clone();
204
205 let (ready_tx, ready_rx) = tokio::sync::oneshot::channel::<Result<()>>();
211
212 let stop = crate::utils::StopSignal::new();
217 let stop_in_loop = stop.clone();
218
219 tokio::task::spawn_blocking(move || {
220 let open_captures = || -> Result<(Capture<pcap::Active>, Capture<pcap::Active>)> {
221 let device = Self::find_device(&interface_clone)
222 .with_context(|| format!("no such capture device '{}'", interface_clone))?;
223
224 let mut cap_rx = Capture::from_device(device.clone())
225 .map(|c| c.promisc(true).snaplen(65535).timeout(1000))
226 .and_then(|c| c.open())
227 .with_context(|| {
228 format!(
229 "failed to open pcap capture on '{}' (needs root, or read \
230 access to /dev/bpf* on macOS/BSD, or CAP_NET_RAW on Linux)",
231 interface_clone
232 )
233 })?;
234
235 cap_rx
236 .filter("arp", true)
237 .context("failed to apply the 'arp' BPF filter")?;
238
239 let cap_tx = Capture::from_device(device)
240 .map(|c| c.promisc(true).snaplen(65535).timeout(1000))
241 .and_then(|c| c.open())
242 .with_context(|| {
243 format!(
244 "failed to open pcap injection handle on '{}'",
245 interface_clone
246 )
247 })?;
248
249 Ok((cap_rx, cap_tx))
250 };
251
252 let (mut cap_rx, mut cap_tx) = match open_captures() {
253 Ok(handles) => {
254 let _ = ready_tx.send(Ok(()));
255 handles
256 }
257 Err(e) => {
258 Log::new(Some(&status_tx_clone))
259 .error(format!("ARP client capture startup failed: {:#}", e));
260 let _ = ready_tx.send(Err(e));
261 return;
262 }
263 };
264
265 let runtime = tokio::runtime::Handle::current();
266
267 let mut packet_rx = packet_rx;
271 std::thread::spawn(move || {
272 while let Some(packet) = packet_rx.blocking_recv() {
273 let len = packet.frame.len();
275 let result = match cap_tx.sendpacket(&packet.frame[..]) {
276 Ok(()) => Ok(len),
277 Err(e) => {
278 error!("Failed to send ARP packet: {}", e);
279 Err(e.to_string())
280 }
281 };
282 if let Some(ack) = packet.ack {
283 let _ = ack.send(result);
284 }
285 }
286 });
287
288 loop {
291 if stop_in_loop.is_stopped() {
292 info!(
293 "ARP client {} capture on {} stopping",
294 client_id, interface_clone
295 );
296 break;
297 }
298 match cap_rx.next_packet() {
299 Ok(packet) => {
300 let data = packet.data.to_vec();
301
302 let eth_packet = match EthernetPacket::new(&data) {
304 Some(p) => p,
305 None => {
306 debug!("Failed to parse Ethernet packet");
307 continue;
308 }
309 };
310
311 if eth_packet.get_ethertype() != EtherTypes::Arp {
313 continue;
314 }
315
316 let arp_packet = match ArpPacket::new(eth_packet.payload()) {
318 Some(p) => p,
319 None => {
320 debug!("Failed to parse ARP packet");
321 continue;
322 }
323 };
324
325 let operation = arp_packet.get_operation();
327 let sender_mac = arp_packet.get_sender_hw_addr();
328 let sender_ip = arp_packet.get_sender_proto_addr();
329 let target_mac = arp_packet.get_target_hw_addr();
330 let target_ip = arp_packet.get_target_proto_addr();
331
332 Log::new(Some(&status_tx_clone)).debug(format!(
334 "ARP client {} received {} from {} ({}) for {} ({})",
335 client_id,
336 operation_to_string(operation),
337 sender_mac,
338 sender_ip,
339 target_mac,
340 target_ip
341 ));
342
343 let hex_str = hex::encode(&data);
345 trace!("ARP packet (hex): {}", hex_str);
346
347 let llm_clone = llm_client_clone.clone();
348 let state_clone = app_state_clone.clone();
349 let status_clone = status_tx_clone.clone();
350 let protocol_task_clone = protocol_clone.clone();
351 let packet_tx_clone = packet_tx.clone();
352 let client_data_task_clone = client_data_clone.clone();
353
354 runtime.spawn(async move {
356 let mut client_data_lock = client_data_task_clone.lock().await;
358
359 match client_data_lock.state {
360 ConnectionState::Idle => {
361 client_data_lock.state = ConnectionState::Processing;
363 let current_memory = client_data_lock.memory.clone();
364 drop(client_data_lock);
365
366 let event = Event::new(
368 &ARP_CLIENT_RESPONSE_RECEIVED_EVENT,
369 serde_json::json!({
370 "operation": operation_to_string(operation),
371 "sender_mac": sender_mac.to_string(),
372 "sender_ip": sender_ip.to_string(),
373 "target_mac": target_mac.to_string(),
374 "target_ip": target_ip.to_string(),
375 }),
376 );
377
378 debug!(
379 "ARP client {} calling LLM for {} packet",
380 client_id,
381 operation_to_string(operation)
382 );
383
384 let instruction = state_clone
386 .get_instruction_for_client(client_id)
387 .await
388 .unwrap_or_else(|| "Monitor ARP traffic".to_string());
389
390 match call_llm_for_client(
391 &llm_clone,
392 &state_clone,
393 client_id.to_string(),
394 &instruction,
395 ¤t_memory,
396 Some(&event),
397 protocol_task_clone.as_ref(),
398 &status_clone,
399 )
400 .await
401 {
402 Ok(ClientLlmResult {
403 actions,
404 memory_updates,
405 }) => {
406 if let Some(mem) = memory_updates {
408 client_data_task_clone.lock().await.memory = mem;
409 }
410
411 if Self::execute_client_actions(
413 actions,
414 protocol_task_clone.as_ref(),
415 &packet_tx_clone,
416 client_id,
417 &state_clone,
418 &status_clone,
419 )
420 .await
421 {
422 return;
423 }
424 }
425 Err(e) => {
426 error!("LLM error for ARP client {}: {}", client_id, e);
427 }
428 }
429
430 client_data_task_clone.lock().await.state =
432 ConnectionState::Idle;
433 }
434 ConnectionState::Processing => {
435 debug!(
437 "ARP client {} is processing, skipping packet",
438 client_id
439 );
440 }
441 }
442 });
443 }
444 Err(pcap::Error::TimeoutExpired) => {
445 continue;
447 }
448 Err(e) => {
449 Log::new(Some(&status_tx_clone)).error(format!(
450 "ARP client {} packet capture error: {}",
451 client_id, e
452 ));
453 break;
454 }
455 }
456 }
457
458 runtime.block_on(app_state_clone.remove_client_handle(client_id));
462 let _ = status_tx_clone.send("__UPDATE_UI__".to_string());
463 });
464
465 match ready_rx.await {
476 Ok(Ok(())) => {}
477 Ok(Err(e)) => {
478 app_state.remove_client_handle(client_id).await;
479 return Err(e);
480 }
481 Err(_) => {
482 app_state.remove_client_handle(client_id).await;
483 return Err(anyhow::anyhow!(
484 "ARP capture task on '{}' exited before signalling readiness",
485 interface
486 ));
487 }
488 }
489
490 app_state
493 .register_client_task(client_id, stop.park_task())
494 .await;
495
496 Ok(SocketAddr::from(([0, 0, 0, 0], 0)))
498 }
499
500 async fn execute_client_actions(
507 actions: Vec<serde_json::Value>,
508 protocol: &ArpClientProtocol,
509 packet_tx: &mpsc::UnboundedSender<InjectedPacket>,
510 client_id: ClientId,
511 app_state: &Arc<AppState>,
512 status_tx: &mpsc::UnboundedSender<String>,
513 ) -> bool {
514 for action in actions {
515 match protocol.execute_action(action) {
516 Ok(ClientActionResult::Custom { name, data }) => {
517 match build_packet_for_custom_result(&name, &data) {
518 Some(packet) => {
519 if packet_tx
520 .send(InjectedPacket {
521 frame: packet,
522 ack: None,
523 })
524 .is_ok()
525 {
526 debug!("ARP client {} queued {}", client_id, name);
527 } else {
528 error!(
532 "ARP client {} could not queue '{}': the pcap injection \
533 thread is not running",
534 client_id, name
535 );
536 }
537 }
538 None => error!(
539 "ARP client {} could not build a frame for '{}': check the MAC and \
540 IP fields",
541 client_id, name
542 ),
543 }
544 }
545 Ok(ClientActionResult::Disconnect) => {
546 info!("ARP client {} stopping capture", client_id);
547 app_state
548 .update_client_status(client_id, ClientStatus::Disconnected)
549 .await;
550 let _ = status_tx.send("__UPDATE_UI__".to_string());
551 return true;
552 }
553 Ok(_) => {}
554 Err(e) => {
555 error!("ARP client {} action failed: {}", client_id, e);
556 let _ = status_tx.send(format!(
557 "[WARN] ARP client {} action failed: {}",
558 client_id, e
559 ));
560 }
561 }
562 }
563 false
564 }
565
566 pub async fn command_loop(
574 mut command_rx: mpsc::Receiver<ClientCommand>,
575 protocol: Arc<ArpClientProtocol>,
576 packet_tx: mpsc::UnboundedSender<InjectedPacket>,
577 client_id: ClientId,
578 app_state: Arc<AppState>,
579 status_tx: mpsc::UnboundedSender<String>,
580 ) {
581 use crate::llm::actions::protocol_trait::Protocol;
582
583 while let Some(command) = command_rx.recv().await {
584 let action = command.action.clone();
585 let outcome = Self::execute_injected_action(&protocol, &packet_tx, &action).await;
586
587 let outcome_json = match &outcome {
588 Ok(outcome) => serde_json::to_value(outcome).unwrap_or(serde_json::Value::Null),
589 Err(e) => serde_json::json!({"error": e.to_string()}),
590 };
591 app_state
592 .record_access_log(
593 AccessLogOwner::Client(client_id.as_u32()),
594 protocol.protocol_name(),
595 None,
596 "injected_action",
597 action,
598 vec![outcome_json],
599 )
600 .await;
601
602 let disconnect = matches!(outcome, Ok(ClientSendOutcome::Disconnected));
603 if let Err(e) = &outcome {
604 error!("ARP client {} injected action failed: {}", client_id, e);
605 let _ = status_tx.send(format!(
606 "[WARN] Client {} injected action failed: {}",
607 client_id, e
608 ));
609 }
610 let _ = status_tx.send("__UPDATE_UI__".to_string());
611 crate::client::command_support::reply(command, outcome);
612
613 if disconnect {
614 app_state.remove_client_handle(client_id).await;
615 app_state
616 .update_client_status(client_id, ClientStatus::Disconnected)
617 .await;
618 let _ = status_tx.send("__UPDATE_UI__".to_string());
619 break;
620 }
621 }
622 }
623
624 async fn execute_injected_action(
635 protocol: &Arc<ArpClientProtocol>,
636 packet_tx: &mpsc::UnboundedSender<InjectedPacket>,
637 action: &serde_json::Value,
638 ) -> Result<ClientSendOutcome> {
639 let result = match protocol.as_ref().execute_action(action.clone()) {
640 Ok(result) => result,
641 Err(e) => {
642 return Ok(ClientSendOutcome::Rejected {
643 error: e.to_string(),
644 })
645 }
646 };
647
648 let (name, data) = match result {
649 ClientActionResult::Custom { name, data } => (name, data),
650 ClientActionResult::Disconnect => return Ok(ClientSendOutcome::Disconnected),
651 ClientActionResult::WaitForMore => {
652 return Ok(ClientSendOutcome::Executed {
653 detail: "wait_for_more".to_string(),
654 })
655 }
656 other => {
657 return Ok(ClientSendOutcome::Executed {
658 detail: format!("{other:?} builds no ARP frame"),
659 })
660 }
661 };
662
663 let frame = match build_packet_for_custom_result(&name, &data) {
664 Some(frame) => frame,
665 None => {
666 return Ok(ClientSendOutcome::Rejected {
667 error: format!(
668 "could not build an ARP frame for '{name}': check the MAC and IP fields"
669 ),
670 })
671 }
672 };
673
674 let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
675 if packet_tx
676 .send(InjectedPacket {
677 frame,
678 ack: Some(ack_tx),
679 })
680 .is_err()
681 {
682 return Ok(ClientSendOutcome::Executed {
686 detail: format!(
687 "'{name}' frame built but not injected: the pcap injection thread is not \
688 running (the capture failed to open - ARP capture and injection need \
689 root / BPF access)"
690 ),
691 });
692 }
693
694 match tokio::time::timeout(INJECTION_ACK_TIMEOUT, ack_rx).await {
695 Ok(Ok(Ok(bytes_sent))) => Ok(ClientSendOutcome::Sent { bytes_sent }),
696 Ok(Ok(Err(e))) => Err(anyhow::anyhow!("pcap sendpacket failed: {e}")),
697 Ok(Err(_)) => Ok(ClientSendOutcome::Executed {
698 detail: format!(
699 "'{name}' frame handed to the pcap injection thread, which exited \
700 without reporting the result"
701 ),
702 }),
703 Err(_) => Ok(ClientSendOutcome::Executed {
704 detail: format!(
705 "'{name}' frame queued, but the pcap injection thread did not \
706 acknowledge it within {}s",
707 INJECTION_ACK_TIMEOUT.as_secs()
708 ),
709 }),
710 }
711 }
712}
713
714pub fn build_packet_for_custom_result(name: &str, data: &serde_json::Value) -> Option<Vec<u8>> {
722 match name {
723 "send_arp_request" => build_arp_request_from_action(data),
724 "send_arp_reply" => build_arp_reply_from_action(data),
725 _ => None,
726 }
727}
728
729fn operation_to_string(op: pnet::packet::arp::ArpOperation) -> &'static str {
731 match op {
732 ArpOperations::Request => "REQUEST",
733 ArpOperations::Reply => "REPLY",
734 _ => "UNKNOWN",
735 }
736}
737
738fn build_arp_request_from_action(data: &serde_json::Value) -> Option<Vec<u8>> {
740 let sender_mac = data["sender_mac"].as_str()?;
741 let sender_ip = data["sender_ip"].as_str()?;
742 let target_ip = data["target_ip"].as_str()?;
743
744 let sender_mac = MacAddr::from_str(sender_mac).ok()?;
745 let sender_ip = Ipv4Addr::from_str(sender_ip).ok()?;
746 let target_ip = Ipv4Addr::from_str(target_ip).ok()?;
747
748 Some(build_arp_request(sender_mac, sender_ip, target_ip))
749}
750
751fn build_arp_reply_from_action(data: &serde_json::Value) -> Option<Vec<u8>> {
753 let sender_mac = data["sender_mac"].as_str()?;
754 let sender_ip = data["sender_ip"].as_str()?;
755 let target_mac = data["target_mac"].as_str()?;
756 let target_ip = data["target_ip"].as_str()?;
757
758 let sender_mac = MacAddr::from_str(sender_mac).ok()?;
759 let sender_ip = Ipv4Addr::from_str(sender_ip).ok()?;
760 let target_mac = MacAddr::from_str(target_mac).ok()?;
761 let target_ip = Ipv4Addr::from_str(target_ip).ok()?;
762
763 Some(build_arp_reply(
764 sender_mac, sender_ip, target_mac, target_ip,
765 ))
766}
767
768fn build_arp_request(sender_mac: MacAddr, sender_ip: Ipv4Addr, target_ip: Ipv4Addr) -> Vec<u8> {
770 let mut eth_buffer = vec![0u8; 42];
772
773 {
775 let mut eth_packet = MutableEthernetPacket::new(&mut eth_buffer).unwrap();
776 eth_packet.set_destination(MacAddr::broadcast()); eth_packet.set_source(sender_mac);
778 eth_packet.set_ethertype(EtherTypes::Arp);
779
780 let mut arp_buffer = vec![0u8; 28];
782 {
783 let mut arp_packet = MutableArpPacket::new(&mut arp_buffer).unwrap();
784 arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet);
785 arp_packet.set_protocol_type(EtherTypes::Ipv4);
786 arp_packet.set_hw_addr_len(6);
787 arp_packet.set_proto_addr_len(4);
788 arp_packet.set_operation(ArpOperations::Request);
789 arp_packet.set_sender_hw_addr(sender_mac);
790 arp_packet.set_sender_proto_addr(sender_ip);
791 arp_packet.set_target_hw_addr(MacAddr::zero()); arp_packet.set_target_proto_addr(target_ip);
793 }
794
795 eth_packet.set_payload(&arp_buffer);
796 }
797
798 eth_buffer
799}
800
801fn build_arp_reply(
803 sender_mac: MacAddr,
804 sender_ip: Ipv4Addr,
805 target_mac: MacAddr,
806 target_ip: Ipv4Addr,
807) -> Vec<u8> {
808 let mut eth_buffer = vec![0u8; 42];
810
811 {
813 let mut eth_packet = MutableEthernetPacket::new(&mut eth_buffer).unwrap();
814 eth_packet.set_destination(target_mac);
815 eth_packet.set_source(sender_mac);
816 eth_packet.set_ethertype(EtherTypes::Arp);
817
818 let mut arp_buffer = vec![0u8; 28];
820 {
821 let mut arp_packet = MutableArpPacket::new(&mut arp_buffer).unwrap();
822 arp_packet.set_hardware_type(ArpHardwareTypes::Ethernet);
823 arp_packet.set_protocol_type(EtherTypes::Ipv4);
824 arp_packet.set_hw_addr_len(6);
825 arp_packet.set_proto_addr_len(4);
826 arp_packet.set_operation(ArpOperations::Reply);
827 arp_packet.set_sender_hw_addr(sender_mac);
828 arp_packet.set_sender_proto_addr(sender_ip);
829 arp_packet.set_target_hw_addr(target_mac);
830 arp_packet.set_target_proto_addr(target_ip);
831 }
832
833 eth_packet.set_payload(&arp_buffer);
834 }
835
836 eth_buffer
837}