netget/server/bfd/
runner.rs1use super::packet::{self, ControlPacket, State};
6use super::session::{Session, Timers};
7use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
8use anyhow::{bail, Context, Result};
9use serde_json::{json, Value};
10use std::net::SocketAddr;
11use std::sync::Arc;
12use std::time::Duration;
13use tokio::net::UdpSocket;
14use tokio::sync::mpsc;
15use tokio::time::Instant;
16
17pub enum Input {
18 Packet {
19 bytes: Vec<u8>,
20 packet: ControlPacket,
21 },
22 Action {
25 action: Value,
26 depth: u32,
27 command: Option<ClientCommand>,
28 },
29}
30
31pub enum Note {
32 Changed {
33 previous: State,
34 snapshot: Value,
35 depth: u32,
36 },
37 Ended { local_discr: u32 },
39}
40
41pub fn check_action(v: &Value) -> Result<()> {
43 match v["type"].as_str().unwrap_or_default() {
44 "bfd_set_timers" => {
45 Timers::from_json(v, Timers::default())?;
46 }
47 "bfd_admin_down" => {
48 diag_of(v)?;
49 }
50 "bfd_admin_up" => {}
51 other => bail!("{other:?} is not a BFD session action"),
52 }
53 Ok(())
54}
55
56fn diag_of(v: &Value) -> Result<u8> {
57 match v["diag"].as_str() {
58 None => Ok(7),
59 Some(name) => packet::diag_code(name),
60 }
61}
62
63fn apply(s: &mut Session, v: &Value) -> Result<Option<(State, State)>> {
64 check_action(v)?;
65 Ok(match v["type"].as_str().unwrap_or_default() {
66 "bfd_set_timers" => {
67 s.set_timers(Timers::from_json(v, s.configured)?);
68 None
69 }
70 "bfd_admin_down" => s.admin_down(diag_of(v)?),
71 _ => s.admin_up(),
72 })
73}
74
75pub struct Link {
76 pub socket: Arc<UdpSocket>,
77 pub dest: SocketAddr,
78 pub label: String,
79}
80
81impl Link {
82 async fn send(&self, s: &mut Session, final_: bool) {
83 let bytes = s.packet(final_);
84 if let Err(e) = self.socket.send_to(&bytes, self.dest).await {
85 tracing::warn!("{}: sending to {} failed: {e}", self.label, self.dest);
86 }
87 }
88}
89
90pub async fn run(
93 mut s: Session,
94 link: Link,
95 mut inputs: mpsc::Receiver<Input>,
96 notes: mpsc::Sender<Note>,
97 idle: Option<Duration>,
98) {
99 let started = Instant::now();
100 let mut next_tx = Instant::now();
101 loop {
102 let detect = s.detection_deadline();
103 let idle_at = idle.and_then(|d| {
104 matches!(s.state, State::Down | State::AdminDown)
105 .then(|| s.last_rx.unwrap_or(started) + d)
106 });
107 let (transition, depth) = tokio::select! {
108 input = inputs.recv() => match input {
109 None => break,
110 Some(Input::Packet { bytes, packet }) => {
111 let could_send = s.may_transmit();
112 match s.receive(&bytes, &packet, Instant::now()) {
113 Ok(r) => {
114 if r.send_final {
115 link.send(&mut s, true).await;
116 }
117 if r.transition.is_some() || !could_send {
118 next_tx = Instant::now();
119 }
120 (r.transition, 0)
121 }
122 Err(e) => {
123 tracing::warn!("{} dropped a packet decision=dropped: {e:#}", link.label);
124 continue;
125 }
126 }
127 }
128 Some(Input::Action { action, depth, command }) => match apply(&mut s, &action) {
129 Ok(t) => {
130 if s.may_transmit() {
132 link.send(&mut s, false).await;
133 }
134 next_tx = Instant::now() + s.next_interval();
135 if let Some(c) = command {
136 crate::client::command_support::reply(c, Ok(ClientSendOutcome::Executed {
137 detail: s.snapshot().to_string(),
138 }));
139 }
140 (t, depth)
141 }
142 Err(e) => {
143 tracing::warn!("{} refused {}: {e:#}", link.label, action["type"]);
144 if let Some(c) = command {
145 crate::client::command_support::reply(c, Ok(ClientSendOutcome::Rejected {
146 error: e.to_string(),
147 }));
148 }
149 continue;
150 }
151 },
152 },
153 _ = tokio::time::sleep_until(next_tx) => {
154 if s.may_transmit() {
155 link.send(&mut s, false).await;
156 }
157 next_tx = Instant::now() + s.next_interval();
158 continue;
159 }
160 _ = sleep_until_opt(detect) => (s.detection_expired(), 0),
161 _ = sleep_until_opt(idle_at) => {
162 tracing::info!("{}: Down with nothing heard for {:?}; ending the session", link.label, idle.unwrap_or_default());
163 break;
164 }
165 };
166 if let Some((previous, _)) = transition {
167 let note = Note::Changed {
168 previous,
169 snapshot: s.snapshot(),
170 depth,
171 };
172 if !notes.is_closed() && notes.try_send(note).is_err() {
173 tracing::warn!(
174 "{}: a state change was not reported: the model is behind decision=note_queue_full",
175 link.label
176 );
177 }
178 }
179 }
180 let _ = notes.try_send(Note::Ended {
181 local_discr: s.local_discr,
182 });
183}
184
185async fn sleep_until_opt(at: Option<Instant>) {
186 match at {
187 Some(t) => tokio::time::sleep_until(t).await,
188 None => std::future::pending().await,
189 }
190}
191
192pub async fn tx_socket(ip: std::net::IpAddr) -> Result<UdpSocket> {
194 let mut last = None;
195 for _ in 0..64 {
196 let port = packet::SOURCE_PORTS.start()
197 + (rand::random::<u16>() % (packet::SOURCE_PORTS.end() - packet::SOURCE_PORTS.start()));
198 match UdpSocket::bind(SocketAddr::new(ip, port)).await {
199 Ok(s) => {
200 if ip.is_ipv4() {
201 s.set_ttl(255).context("setting TTL 255")?;
202 }
203 return Ok(s);
204 }
205 Err(e) => last = Some(e),
206 }
207 }
208 Err(last
209 .map(anyhow::Error::from)
210 .unwrap_or_else(|| anyhow::anyhow!("no source port")))
211 .context("no free BFD source port in 49152-65535")
212}
213
214pub fn state_event(peer: &str, previous: State, snapshot: &Value) -> Value {
216 let mut data = snapshot.clone();
217 data["peer"] = json!(peer);
218 data["previous_state"] = json!(previous.name());
219 data
220}