1pub mod actions;
10
11pub use actions::OspfClientProtocol;
12
13use anyhow::{anyhow, Context, Result};
14use std::net::{IpAddr, Ipv4Addr, SocketAddr};
15use std::os::unix::io::AsRawFd;
16use std::sync::Arc;
17use tokio::io::unix::AsyncFd;
18use tokio::sync::{mpsc, Mutex};
19use tracing::{debug, error, info, trace, warn};
20
21use crate::client::llm_budget::call_llm_for_client;
22use crate::client::ospf::actions::{
23 OSPF_CLIENT_CONNECTED_EVENT, OSPF_CLIENT_DD_RECEIVED_EVENT, OSPF_CLIENT_HELLO_RECEIVED_EVENT,
24 OSPF_CLIENT_LSU_RECEIVED_EVENT,
25};
26use crate::llm::actions::client_trait::{Client, ClientActionResult};
27use crate::llm::actions::protocol_trait::Protocol;
28use crate::llm::ollama_client::OllamaClient;
29use crate::protocol::{Event, StartupParams};
30use crate::server::socket_helpers::create_ospf_raw_socket;
31use crate::state::app_state::AppState;
32use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
33use crate::state::{AccessLogOwner, ClientId, ClientStatus};
34
35const OSPF_VERSION: u8 = 2;
37const OSPF_HEADER_LEN: usize = 24;
38const IP_HEADER_MIN_LEN: usize = 20;
39
40const OSPF_TYPE_HELLO: u8 = 1;
42const OSPF_TYPE_DATABASE_DESCRIPTION: u8 = 2;
43const OSPF_TYPE_LINK_STATE_REQUEST: u8 = 3;
44const OSPF_TYPE_LINK_STATE_UPDATE: u8 = 4;
45const OSPF_TYPE_LINK_STATE_ACK: u8 = 5;
46
47const OSPF_ALL_SPF_ROUTERS: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 5);
49const OSPF_ALL_DROUTERS: Ipv4Addr = Ipv4Addr::new(224, 0, 0, 6);
50
51#[derive(Debug, Clone, PartialEq)]
53enum ConnectionState {
54 Idle,
55 Processing,
56 Accumulating,
57}
58
59struct QueuedPacket {
61 packet_type: u8,
62 ospf_data: Vec<u8>,
63 src_ip: Ipv4Addr,
64 sender_router_id: String,
65 sender_area_id: String,
66}
67
68struct ClientData {
70 state: ConnectionState,
71 queued_packets: Vec<QueuedPacket>,
72 memory: String,
73}
74
75pub struct OspfClient;
77
78impl OspfClient {
79 pub async fn connect_with_llm_actions(
81 remote_addr: String,
82 llm_client: OllamaClient,
83 app_state: Arc<AppState>,
84 status_tx: mpsc::UnboundedSender<String>,
85 client_id: ClientId,
86 startup_params: Option<StartupParams>,
87 ) -> Result<SocketAddr> {
88 let interface_ip = remote_addr
91 .split(':')
92 .next()
93 .ok_or_else(|| anyhow!("Invalid remote_addr format"))?
94 .parse::<Ipv4Addr>()
95 .context("Failed to parse interface IP")?;
96
97 let (router_id, area_id) = if let Some(ref params) = startup_params {
99 let router_id = params
100 .get_optional_string("router_id")?
101 .unwrap_or_else(|| interface_ip.to_string());
102 let area_id = params
103 .get_optional_string("area_id")?
104 .unwrap_or_else(|| "0.0.0.0".to_string());
105 (router_id, area_id)
106 } else {
107 (interface_ip.to_string(), "0.0.0.0".to_string())
108 };
109
110 let raw_socket = create_ospf_raw_socket(interface_ip, true, false)
112 .context("Failed to create OSPF raw socket (requires root)")?;
113 let socket_fd = raw_socket.as_raw_fd();
114
115 info!(
116 "OSPF client {} connected on interface {} (router_id={}, area={})",
117 client_id, interface_ip, router_id, area_id
118 );
119
120 app_state
122 .update_client_status(client_id, ClientStatus::Connected)
123 .await;
124 let _ = status_tx.send(format!(
125 "[CLIENT] OSPF client {} on {} (requires root)",
126 client_id, interface_ip
127 ));
128 let _ = status_tx.send("__UPDATE_UI__".to_string());
129
130 let local_addr = SocketAddr::new(IpAddr::V4(interface_ip), 0);
131
132 let client_data = Arc::new(Mutex::new(ClientData {
134 state: ConnectionState::Idle,
135 queued_packets: Vec::new(),
136 memory: String::new(),
137 }));
138
139 let async_socket = AsyncFd::new(raw_socket)?;
141
142 let connected_event = Event::new(
144 &OSPF_CLIENT_CONNECTED_EVENT,
145 serde_json::json!({
146 "interface_ip": interface_ip.to_string(),
147 "router_id": router_id.clone(),
148 }),
149 );
150
151 let llm_clone = llm_client.clone();
152 let app_state_clone = app_state.clone();
153 let status_tx_clone = status_tx.clone();
154 let protocol = Arc::new(OspfClientProtocol::new());
155 let protocol_for_loop = protocol.clone();
156 let protocol_for_commands = protocol.clone();
157
158 let command_rx =
162 crate::client::command_support::register_command_channel(&app_state, client_id).await;
163
164 let task_registrar = app_state.clone();
167 let task_handle = tokio::spawn(async move {
168 if let Some(instruction) = app_state_clone.get_instruction_for_client(client_id).await {
169 match call_llm_for_client(
170 &llm_clone,
171 &app_state_clone,
172 client_id.to_string(),
173 &instruction,
174 "",
175 Some(&connected_event),
176 protocol.as_ref(),
177 &status_tx_clone,
178 )
179 .await
180 {
181 Ok(result) => {
182 if let Some(memory) = result.memory_updates {
183 app_state_clone
184 .set_memory_for_client(client_id, memory)
185 .await;
186 }
187 Self::run_actions(
191 result.actions,
192 protocol.as_ref(),
193 socket_fd,
194 client_id,
195 &app_state_clone,
196 &status_tx_clone,
197 )
198 .await;
199 }
200 Err(e) => {
201 error!("OSPF client {} LLM call failed: {}", client_id, e);
202 let _ = status_tx_clone
203 .send(format!("✗ OSPF client {} LLM error: {}", client_id, e));
204 }
205 }
206 }
207 });
208 task_registrar
209 .register_client_task(client_id, task_handle)
210 .await;
211
212 let socket_fd_for_send = socket_fd;
214 let app_state_for_commands = app_state.clone();
215 let status_tx_for_commands = status_tx.clone();
216 let protocol = protocol_for_loop;
217 let task_registrar = app_state.clone();
220 let task_handle = tokio::spawn(async move {
221 let mut buffer = vec![0u8; 65535];
222
223 loop {
224 let mut guard = match async_socket.readable().await {
225 Ok(guard) => guard,
226 Err(e) => {
227 error!("OSPF client {} socket error: {}", client_id, e);
228 break;
229 }
230 };
231
232 match guard.try_io(|inner| {
233 let fd = inner.as_raw_fd();
234 unsafe {
235 let n = libc::recv(
236 fd,
237 buffer.as_mut_ptr() as *mut libc::c_void,
238 buffer.len(),
239 0,
240 );
241
242 if n < 0 {
243 Err(std::io::Error::last_os_error())
244 } else {
245 Ok(n as usize)
246 }
247 }
248 }) {
249 Ok(Ok(n)) => {
250 if n == 0 {
251 continue;
252 }
253
254 if n < IP_HEADER_MIN_LEN {
256 continue;
257 }
258
259 let ip_header_len = ((buffer[0] & 0x0F) * 4) as usize;
260 if n < ip_header_len + OSPF_HEADER_LEN {
261 continue;
262 }
263
264 let src_ip = Ipv4Addr::new(buffer[12], buffer[13], buffer[14], buffer[15]);
266
267 let ospf_data = &buffer[ip_header_len..n];
269 let version = ospf_data[0];
270 let packet_type = ospf_data[1];
271
272 if version != OSPF_VERSION {
273 continue;
274 }
275
276 let sender_router_id = format!(
278 "{}.{}.{}.{}",
279 ospf_data[4], ospf_data[5], ospf_data[6], ospf_data[7]
280 );
281
282 let sender_area_id = format!(
283 "{}.{}.{}.{}",
284 ospf_data[8], ospf_data[9], ospf_data[10], ospf_data[11]
285 );
286
287 debug!(
288 "OSPF client {} received type={} from {} ({})",
289 client_id, packet_type, src_ip, sender_router_id
290 );
291
292 let mut client_data_lock = client_data.lock().await;
294
295 match client_data_lock.state {
296 ConnectionState::Idle => {
297 client_data_lock.state = ConnectionState::Processing;
299 drop(client_data_lock);
300
301 Self::process_ospf_packet(
302 packet_type,
303 ospf_data.to_vec(),
304 src_ip,
305 sender_router_id,
306 sender_area_id,
307 llm_client.clone(),
308 app_state.clone(),
309 status_tx.clone(),
310 protocol.clone(),
311 client_id,
312 client_data.clone(),
313 socket_fd_for_send,
314 )
315 .await;
316 }
317 ConnectionState::Processing => {
318 client_data_lock.queued_packets.push(QueuedPacket {
320 packet_type,
321 ospf_data: ospf_data.to_vec(),
322 src_ip,
323 sender_router_id: sender_router_id.clone(),
324 sender_area_id: sender_area_id.clone(),
325 });
326 client_data_lock.state = ConnectionState::Accumulating;
327 trace!("OSPF client {} queued packet", client_id);
328 }
329 ConnectionState::Accumulating => {
330 client_data_lock.queued_packets.push(QueuedPacket {
332 packet_type,
333 ospf_data: ospf_data.to_vec(),
334 src_ip,
335 sender_router_id: sender_router_id.clone(),
336 sender_area_id: sender_area_id.clone(),
337 });
338 }
339 }
340 }
341 Ok(Err(e)) => {
342 error!("OSPF client {} recv error: {}", client_id, e);
343 }
344 Err(_would_block) => continue,
345 }
346 }
347
348 warn!("OSPF client {} receive loop terminated", client_id);
349 app_state
350 .update_client_status(client_id, ClientStatus::Disconnected)
351 .await;
352 app_state.remove_client_handle(client_id).await;
355 let _ = status_tx.send(format!("[CLIENT] OSPF client {} disconnected", client_id));
356 let _ = status_tx.send("__UPDATE_UI__".to_string());
357 });
358 let read_abort = task_handle.abort_handle();
361 task_registrar
362 .register_client_task(client_id, task_handle)
363 .await;
364
365 let cmd_task = tokio::spawn(Self::command_loop(
369 command_rx,
370 protocol_for_commands,
371 socket_fd_for_send,
372 client_id,
373 app_state_for_commands,
374 status_tx_for_commands,
375 read_abort,
376 ));
377 task_registrar
378 .register_client_task(client_id, cmd_task)
379 .await;
380
381 Ok(local_addr)
382 }
383
384 async fn command_loop(
392 mut command_rx: tokio::sync::mpsc::Receiver<ClientCommand>,
393 protocol: Arc<OspfClientProtocol>,
394 socket_fd: i32,
395 client_id: ClientId,
396 app_state: Arc<AppState>,
397 status_tx: mpsc::UnboundedSender<String>,
398 read_abort: tokio::task::AbortHandle,
399 ) {
400 while let Some(command) = command_rx.recv().await {
401 let action = command.action.clone();
402 let outcome = match protocol.execute_action(action.clone()) {
403 Err(e) => Ok(ClientSendOutcome::Rejected {
404 error: e.to_string(),
405 }),
406 Ok(result) => {
407 Self::apply_action(client_id, result, socket_fd).map(|applied| match applied {
408 Applied::Disconnect => ClientSendOutcome::Disconnected,
409 Applied::Sent(0) => ClientSendOutcome::Executed {
410 detail: "executed (no packet sent)".to_string(),
411 },
412 Applied::Sent(bytes_sent) => ClientSendOutcome::Sent { bytes_sent },
413 Applied::Nothing(detail) => ClientSendOutcome::Executed { detail },
414 })
415 }
416 };
417
418 let outcome_json = match &outcome {
419 Ok(outcome) => serde_json::to_value(outcome).unwrap_or(serde_json::Value::Null),
420 Err(e) => serde_json::json!({"error": e.to_string()}),
421 };
422 app_state
423 .record_access_log(
424 AccessLogOwner::Client(client_id.as_u32()),
425 protocol.protocol_name(),
426 None,
427 "injected_action",
428 action,
429 vec![outcome_json],
430 )
431 .await;
432
433 let disconnect = matches!(outcome, Ok(ClientSendOutcome::Disconnected));
434 if let Err(e) = &outcome {
435 error!("OSPF client {} injected action failed: {}", client_id, e);
436 let _ = status_tx.send(format!(
437 "[WARN] Client {} injected action failed: {}",
438 client_id, e
439 ));
440 }
441 let _ = status_tx.send("__UPDATE_UI__".to_string());
442 crate::client::command_support::reply(command, outcome);
443
444 if disconnect {
445 read_abort.abort();
446 app_state.remove_client_handle(client_id).await;
447 app_state
448 .update_client_status(client_id, ClientStatus::Disconnected)
449 .await;
450 let _ = status_tx.send("__UPDATE_UI__".to_string());
451 break;
452 }
453 }
454 }
455
456 async fn run_actions(
467 actions: Vec<serde_json::Value>,
468 protocol: &OspfClientProtocol,
469 socket_fd: i32,
470 client_id: ClientId,
471 app_state: &Arc<AppState>,
472 status_tx: &mpsc::UnboundedSender<String>,
473 ) -> bool {
474 for action in actions {
475 match protocol.execute_action(action) {
476 Ok(action_result) => {
477 match Self::apply_action(client_id, action_result, socket_fd) {
478 Ok(Applied::Disconnect) => {
479 app_state.remove_client_handle(client_id).await;
480 app_state
481 .update_client_status(client_id, ClientStatus::Disconnected)
482 .await;
483 let _ = status_tx
484 .send(format!("[CLIENT] OSPF client {} disconnected", client_id));
485 let _ = status_tx.send("__UPDATE_UI__".to_string());
486 return true;
487 }
488 Ok(Applied::Nothing(detail)) => {
489 trace!("OSPF client {}: {}", client_id, detail);
490 }
491 Ok(Applied::Sent(_)) => {}
492 Err(e) => {
493 error!("Failed to execute OSPF action: {}", e);
494 let _ = status_tx.send(format!("✗ OSPF action error: {}", e));
495 }
496 }
497 }
498 Err(e) => {
499 error!("Failed to execute action: {}", e);
500 let _ = status_tx.send(format!(
501 "✗ OSPF client {} rejected action: {}",
502 client_id, e
503 ));
504 }
505 }
506 }
507 false
508 }
509
510 fn apply_action(
511 client_id: ClientId,
512 result: ClientActionResult,
513 socket_fd: i32,
514 ) -> Result<Applied> {
515 match result {
516 ClientActionResult::Custom { name, data } if name.starts_with("ospf_") => {
517 let sent = Self::handle_ospf_action(&name, &data, socket_fd)?;
518 Ok(Applied::Sent(sent))
519 }
520 ClientActionResult::Disconnect => {
521 info!("OSPF client {} disconnecting", client_id);
522 Ok(Applied::Disconnect)
523 }
524 ClientActionResult::WaitForMore => Ok(Applied::Nothing("wait_for_more".to_string())),
525 ClientActionResult::Custom { name, .. } => Ok(Applied::Nothing(format!(
526 "custom result '{name}' is not an OSPF verb; nothing sent"
527 ))),
528 other => Ok(Applied::Nothing(format!(
529 "action result {other:?} produced no packet"
530 ))),
531 }
532 }
533
534 #[allow(clippy::too_many_arguments)]
535 async fn process_ospf_packet(
536 packet_type: u8,
537 ospf_data: Vec<u8>,
538 src_ip: Ipv4Addr,
539 sender_router_id: String,
540 sender_area_id: String,
541 llm_client: OllamaClient,
542 app_state: Arc<AppState>,
543 status_tx: mpsc::UnboundedSender<String>,
544 protocol: Arc<OspfClientProtocol>,
545 client_id: ClientId,
546 client_data: Arc<Mutex<ClientData>>,
547 socket_fd: i32,
548 ) {
549 let event = match packet_type {
550 OSPF_TYPE_HELLO => {
551 Self::parse_hello_packet(&ospf_data, &src_ip, &sender_router_id, &sender_area_id)
552 }
553 OSPF_TYPE_DATABASE_DESCRIPTION => Self::parse_dd_packet(&ospf_data, &sender_router_id),
554 OSPF_TYPE_LINK_STATE_UPDATE => Self::parse_lsu_packet(&ospf_data, &sender_router_id),
555 OSPF_TYPE_LINK_STATE_REQUEST => {
556 debug!("OSPF LSR from {}", sender_router_id);
557 None
558 }
559 OSPF_TYPE_LINK_STATE_ACK => {
560 trace!("OSPF LSAck from {}", sender_router_id);
561 None
562 }
563 _ => {
564 warn!("OSPF unknown type: {}", packet_type);
565 None
566 }
567 };
568
569 if let Some(event) = event {
570 let memory = client_data.lock().await.memory.clone();
572
573 if let Some(instruction) = app_state.get_instruction_for_client(client_id).await {
574 match call_llm_for_client(
576 &llm_client,
577 &app_state,
578 client_id.to_string(),
579 &instruction,
580 &memory,
581 Some(&event),
582 protocol.as_ref(),
583 &status_tx,
584 )
585 .await
586 {
587 Ok(result) => {
588 if let Some(new_memory) = result.memory_updates {
590 client_data.lock().await.memory = new_memory;
591 }
592
593 if Self::run_actions(
595 result.actions,
596 protocol.as_ref(),
597 socket_fd,
598 client_id,
599 &app_state,
600 &status_tx,
601 )
602 .await
603 {
604 return;
605 }
606 }
607 Err(e) => {
608 error!("OSPF client {} LLM call failed: {}", client_id, e);
609 let _ =
610 status_tx.send(format!("✗ OSPF client {} LLM error: {}", client_id, e));
611 }
612 }
613 }
614 }
615
616 let mut client_data_lock = client_data.lock().await;
618 match client_data_lock.state {
619 ConnectionState::Processing => {
620 client_data_lock.state = ConnectionState::Idle;
622 }
623 ConnectionState::Accumulating => {
624 let has_queued = !client_data_lock.queued_packets.is_empty();
626 if has_queued {
627 let queued_packet = client_data_lock.queued_packets.remove(0);
628 client_data_lock.state = ConnectionState::Processing;
629 drop(client_data_lock);
630
631 trace!("OSPF client {} processing queued packet", client_id);
633 Box::pin(Self::process_ospf_packet(
634 queued_packet.packet_type,
635 queued_packet.ospf_data,
636 queued_packet.src_ip,
637 queued_packet.sender_router_id,
638 queued_packet.sender_area_id,
639 llm_client,
640 app_state,
641 status_tx,
642 protocol,
643 client_id,
644 client_data,
645 socket_fd,
646 ))
647 .await;
648 } else {
649 client_data_lock.state = ConnectionState::Idle;
650 }
651 }
652 ConnectionState::Idle => {}
653 }
654 }
655
656 fn parse_hello_packet(
657 data: &[u8],
658 src_ip: &Ipv4Addr,
659 sender_router_id: &str,
660 sender_area_id: &str,
661 ) -> Option<Event> {
662 if data.len() < OSPF_HEADER_LEN + 20 {
663 return None;
664 }
665
666 let network_mask = format!("{}.{}.{}.{}", data[24], data[25], data[26], data[27]);
668 let hello_interval = u16::from_be_bytes([data[28], data[29]]);
669 let priority = data[31];
670 let router_dead_interval = u32::from_be_bytes([data[32], data[33], data[34], data[35]]);
671 let dr = format!("{}.{}.{}.{}", data[36], data[37], data[38], data[39]);
672 let bdr = format!("{}.{}.{}.{}", data[40], data[41], data[42], data[43]);
673
674 let mut neighbor_list = Vec::new();
676 let mut offset = 44;
677 while offset + 4 <= data.len() {
678 let neighbor_id = format!(
679 "{}.{}.{}.{}",
680 data[offset],
681 data[offset + 1],
682 data[offset + 2],
683 data[offset + 3]
684 );
685 neighbor_list.push(neighbor_id);
686 offset += 4;
687 }
688
689 Some(Event::new(
690 &OSPF_CLIENT_HELLO_RECEIVED_EVENT,
691 serde_json::json!({
692 "neighbor_id": sender_router_id,
693 "neighbor_ip": src_ip.to_string(),
694 "area_id": sender_area_id,
695 "network_mask": network_mask,
696 "hello_interval": hello_interval,
697 "router_dead_interval": router_dead_interval,
698 "router_priority": priority,
699 "dr": dr,
700 "bdr": bdr,
701 "neighbors": neighbor_list,
702 }),
703 ))
704 }
705
706 fn parse_dd_packet(data: &[u8], sender_router_id: &str) -> Option<Event> {
707 if data.len() < OSPF_HEADER_LEN + 8 {
708 return None;
709 }
710
711 let mtu = u16::from_be_bytes([data[24], data[25]]);
713 let options = data[26];
714 let flags = data[27];
715 let sequence = u32::from_be_bytes([data[28], data[29], data[30], data[31]]);
716
717 let init = (flags & 0x04) != 0;
718 let more = (flags & 0x02) != 0;
719 let master = (flags & 0x01) != 0;
720
721 debug!(
722 "OSPF DD: mtu={}, options={:02x}, seq={}, I={}, M={}, MS={}",
723 mtu, options, sequence, init, more, master
724 );
725
726 Some(Event::new(
727 &OSPF_CLIENT_DD_RECEIVED_EVENT,
728 serde_json::json!({
729 "neighbor_id": sender_router_id,
730 "sequence": sequence,
731 "init": init,
732 "more": more,
733 "master": master,
734 }),
735 ))
736 }
737
738 fn parse_lsu_packet(data: &[u8], sender_router_id: &str) -> Option<Event> {
739 if data.len() < OSPF_HEADER_LEN + 4 {
740 return None;
741 }
742
743 let advertised_count = u32::from_be_bytes([data[24], data[25], data[26], data[27]]);
745
746 let body = &data[28..];
755 let mut lsas = Vec::new();
756 let mut offset = 0usize;
757 while offset + 20 <= body.len() && lsas.len() < 64 {
758 let header = crate::server::ospf::actions::OspfProtocol::parse_lsa_headers(
759 &body[offset..offset + 20],
760 1,
761 );
762 let Some(header) = header.into_iter().next() else {
763 break;
764 };
765 let declared = header["length"].as_u64().unwrap_or(0) as usize;
766 lsas.push(header);
767 if declared < 20 {
769 break;
770 }
771 offset += declared;
772 }
773
774 debug!(
775 "OSPF LSU: {} LSAs advertised, {} parsed, from {}",
776 advertised_count,
777 lsas.len(),
778 sender_router_id
779 );
780
781 Some(Event::new(
782 &OSPF_CLIENT_LSU_RECEIVED_EVENT,
783 serde_json::json!({
784 "neighbor_id": sender_router_id,
785 "advertised_lsa_count": advertised_count,
786 "lsa_count": lsas.len(),
787 "lsa_headers": lsas,
788 }),
789 ))
790 }
791
792 fn handle_ospf_action(
795 action_name: &str,
796 data: &serde_json::Value,
797 socket_fd: i32,
798 ) -> Result<usize> {
799 let packet = match action_name {
800 "ospf_send_hello" => {
801 crate::server::ospf::actions::OspfProtocol::build_hello_packet(data)?
802 }
803 "ospf_send_dd" => {
804 crate::server::ospf::actions::OspfProtocol::build_database_description_packet(data)?
805 }
806 "ospf_send_lsr" => {
807 crate::server::ospf::actions::OspfProtocol::build_link_state_request_packet(data)?
808 }
809 "ospf_send_lsack" => {
810 crate::server::ospf::actions::OspfProtocol::build_link_state_ack_packet(data)?
811 }
812 _ => return Err(anyhow!("Unknown OSPF action: {}", action_name)),
813 };
814
815 let destination_str = data
817 .get("destination")
818 .and_then(|d| d.as_str())
819 .unwrap_or("multicast");
820
821 let dest_ip = match destination_str {
822 "multicast" => OSPF_ALL_SPF_ROUTERS,
823 "dr_multicast" => OSPF_ALL_DROUTERS,
824 ip_str => match ip_str.parse::<Ipv4Addr>() {
825 Ok(ip) => ip,
826 Err(_) => {
827 warn!(
831 "OSPF client: destination '{}' is not an IP address; sending to \
832 224.0.0.5 (all SPF routers) instead",
833 ip_str
834 );
835 OSPF_ALL_SPF_ROUTERS
836 }
837 },
838 };
839
840 let sent = Self::send_ospf_packet(socket_fd, dest_ip, &packet)?;
842
843 debug!("OSPF sent {} bytes to {}", sent, dest_ip);
844 Ok(sent)
845 }
846
847 fn send_ospf_packet(socket_fd: i32, dest_ip: Ipv4Addr, ospf_data: &[u8]) -> Result<usize> {
848 unsafe {
849 let mut dest_addr = std::mem::zeroed::<libc::sockaddr_in>();
850 #[cfg(target_os = "macos")]
851 {
852 dest_addr.sin_family = libc::AF_INET as libc::sa_family_t;
853 dest_addr.sin_len = std::mem::size_of::<libc::sockaddr_in>() as u8;
854 }
855 #[cfg(not(target_os = "macos"))]
856 {
857 dest_addr.sin_family = libc::AF_INET as u16;
858 }
859 dest_addr.sin_port = 0; dest_addr.sin_addr.s_addr = u32::from(dest_ip).to_be();
861
862 let n = libc::sendto(
863 socket_fd,
864 ospf_data.as_ptr() as *const libc::c_void,
865 ospf_data.len(),
866 0,
867 &dest_addr as *const _ as *const libc::sockaddr,
868 std::mem::size_of::<libc::sockaddr_in>() as u32,
869 );
870
871 if n < 0 {
872 return Err(std::io::Error::last_os_error().into());
873 }
874
875 Ok(n as usize)
876 }
877 }
878}
879
880#[derive(Debug)]
882enum Applied {
883 Sent(usize),
885 Nothing(String),
887 Disconnect,
889}