1pub mod actions;
5pub mod wire;
6
7use crate::llm::action_helper::call_llm;
8use crate::llm::actions::protocol_trait::ActionResult;
9use crate::logging::emit::Log;
10use crate::protocol::{Event, EventType, SpawnContext};
11use crate::server::{
12 accept_bounded::{accept_bounded, ConnectionLimiter, DEFAULT_MAX_CONNECTIONS},
13 connection::ConnectionId,
14};
15use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
16use anyhow::Result;
17use serde_json::{json, Value};
18use std::net::{IpAddr, SocketAddr};
19use std::sync::Arc;
20use std::time::Duration;
21use tokio::io::AsyncWriteExt;
22use tokio::net::{TcpStream, UdpSocket};
23use wire::{Accepted, Mapping, Reader, Writer};
24
25pub const IDLE_TIMEOUT: Duration = Duration::from_secs(120);
27pub const MAX_UDP_IN_FLIGHT: usize = 64;
29
30pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
31 let listener = tokio::net::TcpListener::bind(ctx.legacy_listen_addr()).await?;
32 let local = listener.local_addr()?;
33 let udp = Arc::new(UdpSocket::bind(local).await?);
35 Log::new(Some(&ctx.status_tx)).info(format!(
36 "Portmapper (PMAP 2, RPCBIND 3-4) listening on {local} tcp and udp"
37 ));
38 let server_id = ctx.server_id;
39 let state = ctx.state.clone();
40
41 let udp_ctx = ctx.clone();
42 let udp_task = tokio::spawn(async move {
43 let permits = Arc::new(tokio::sync::Semaphore::new(MAX_UDP_IN_FLIGHT));
44 let mut buf = vec![0u8; 65_536];
45 loop {
46 let Ok((n, peer)) = udp.recv_from(&mut buf).await else {
47 break;
48 };
49 let Ok(permit) = permits.clone().acquire_owned().await else {
50 break;
51 };
52 let msg = buf[..n].to_vec();
53 let ctx = udp_ctx.clone();
54 let udp = udp.clone();
55 udp_ctx
56 .state
57 .spawn_server_task(server_id, async move {
58 let _permit = permit;
59 if let Some(reply) = handle(&ctx, None, &msg, "udp", local.ip()).await {
60 let _ = udp.send_to(&reply, peer).await;
61 }
62 })
63 .await;
64 }
65 });
66 state.register_server_task(server_id, udp_task).await;
67
68 let accept =
69 tokio::spawn(async move {
70 let limiter = ConnectionLimiter::new(DEFAULT_MAX_CONNECTIONS);
71 loop {
72 let (stream, peer, permit) =
73 match accept_bounded(&listener, &limiter, b"", "SunRPC", Some(&ctx.status_tx))
74 .await
75 {
76 Ok(v) => v,
77 Err(_) => break,
78 };
79 let child = ctx.clone();
80 ctx.state
81 .spawn_server_task(server_id, async move {
82 let _permit = permit;
83 connection(child, stream, peer, local).await;
84 })
85 .await;
86 }
87 });
88 state.register_server_task(server_id, accept).await;
89 Ok(local)
90}
91
92async fn connection(ctx: SpawnContext, stream: TcpStream, remote: SocketAddr, local: SocketAddr) {
93 let id = ConnectionId::new(ctx.state.get_next_unified_id().await);
94 let now = crate::utils::clock::Instant::now();
95 ctx.state
96 .add_connection_to_server(
97 ctx.server_id,
98 ConnectionState {
99 id,
100 remote_addr: remote,
101 local_addr: local,
102 bytes_sent: 0,
103 bytes_received: 0,
104 packets_sent: 0,
105 packets_received: 0,
106 last_activity: now,
107 status: ConnectionStatus::Active,
108 status_changed_at: now,
109 protocol_info: ProtocolConnectionInfo::empty(),
110 },
111 )
112 .await;
113 let local_ip = stream.local_addr().map(|a| a.ip()).unwrap_or(local.ip());
114 let (mut r, mut w) = stream.into_split();
115 let reason = loop {
116 let msg = match tokio::time::timeout(IDLE_TIMEOUT, wire::read_record(&mut r)).await {
117 Err(_) => break "idle".to_string(),
118 Ok(Ok(None)) => break "closed by the peer".to_string(),
119 Ok(Err(e)) => break format!("refused: {e:#}"),
120 Ok(Ok(Some(m))) => m,
121 };
122 ctx.state
123 .update_connection_stats(
124 ctx.server_id,
125 id,
126 Some(msg.len() as u64),
127 None,
128 Some(1),
129 None,
130 )
131 .await;
132 if let Some(reply) = handle(&ctx, Some(id), &msg, "tcp", local_ip).await {
133 if w.write_all(&wire::record(&reply)).await.is_err() {
134 break "write failed".into();
135 }
136 ctx.state
137 .update_connection_stats(
138 ctx.server_id,
139 id,
140 None,
141 Some(reply.len() as u64),
142 None,
143 Some(1),
144 )
145 .await;
146 }
147 };
148 let _ = w.shutdown().await;
149 ctx.state
150 .update_connection_status(ctx.server_id, id, ConnectionStatus::Closed)
151 .await;
152 Log::new(Some(&ctx.status_tx)).debug(format!("SunRPC connection {id} ended: {reason}"));
153 let _ = ctx.status_tx.send("__UPDATE_UI__".into());
154}
155
156fn outcome(ctx: &SpawnContext, op: &str, decision: &str) {
157 let line = format!("SunRPC operation={op} decision={decision}");
158 let log = Log::new(Some(&ctx.status_tx));
159 if decision.starts_with("fail_closed") || decision == "model_silent" {
160 log.error(line)
161 } else {
162 log.info(line)
163 }
164}
165
166async fn ask(
168 ctx: &SpawnContext,
169 conn: Option<ConnectionId>,
170 event_type: &'static EventType,
171 data: Value,
172 op: &str,
173) -> Option<(String, Value)> {
174 let event = Event::new(event_type, data);
175 let result = match call_llm(
176 &ctx.llm_client,
177 &ctx.state,
178 ctx.server_id,
179 conn,
180 &event,
181 &actions::SunRpcProtocol,
182 )
183 .await
184 {
185 Ok(r) if r.failures.is_empty() => r,
186 Ok(_) => {
187 outcome(ctx, op, "fail_closed_invalid_reply");
188 return None;
189 }
190 Err(_) => {
191 outcome(ctx, op, "fail_closed_llm_error");
192 return None;
193 }
194 };
195 let mut answers = Vec::new();
196 let mut stack = result.protocol_results;
197 while let Some(r) = stack.pop() {
198 match r {
199 ActionResult::Custom { name, data } if name.starts_with("sunrpc_") => {
200 answers.push((name, data))
201 }
202 ActionResult::Multiple(items) => stack.extend(items),
203 _ => {}
204 }
205 }
206 match answers.len() {
207 1 => {
208 outcome(ctx, op, "model_answer");
209 answers.pop()
210 }
211 0 => {
212 outcome(ctx, op, "model_silent");
213 None
214 }
215 _ => {
216 outcome(ctx, op, "fail_closed_invalid_reply");
217 None
218 }
219 }
220}
221
222async fn mappings(
224 ctx: &SpawnContext,
225 conn: Option<ConnectionId>,
226 data: Value,
227 op: &str,
228) -> Option<Vec<Mapping>> {
229 let (name, answer) = ask(ctx, conn, &actions::QUERY_EVENT, data, op).await?;
230 if name != actions::MAPPINGS {
231 outcome(ctx, op, "fail_closed_invalid_reply");
232 return None;
233 }
234 answer["mappings"]
235 .as_array()?
236 .iter()
237 .map(wire::mapping_from_json)
238 .collect::<Result<Vec<_>>>()
239 .ok()
240}
241
242fn pick(ms: &[Mapping], program: u32, version: u32, netid: &str, exact: bool) -> Vec<Mapping> {
245 let of = |v: Option<u32>| -> Vec<Mapping> {
246 ms.iter()
247 .filter(|m| {
248 m.program == program
249 && v.is_none_or(|v| m.version == v)
250 && (netid.is_empty() || m.netid == netid)
251 })
252 .cloned()
253 .collect()
254 };
255 let hit = of(Some(version));
256 if hit.is_empty() && !exact {
257 of(None)
258 } else {
259 hit
260 }
261}
262
263async fn register(
264 ctx: &SpawnContext,
265 conn: Option<ConnectionId>,
266 data: Value,
267 op: &str,
268) -> Accepted {
269 match ask(ctx, conn, &actions::REGISTER_EVENT, data, op).await {
270 Some((name, _)) if name == actions::ACCEPT => {
271 Accepted::Success(Writer::default().u32(1).0.clone())
272 }
273 Some((name, _)) if name == actions::REJECT => {
274 Accepted::Success(Writer::default().u32(0).0.clone())
275 }
276 Some(_) => {
277 outcome(ctx, op, "fail_closed_invalid_reply");
278 Accepted::SystemErr
279 }
280 None => Accepted::SystemErr,
281 }
282}
283
284pub async fn handle(
286 ctx: &SpawnContext,
287 conn: Option<ConnectionId>,
288 msg: &[u8],
289 transport: &str,
290 local: IpAddr,
291) -> Option<Vec<u8>> {
292 let call = match wire::parse_call(msg) {
293 Ok(Some(c)) => c,
294 Ok(None) => return None,
295 Err(_) => {
296 return wire::xid_of(msg).map(|x| wire::accepted_reply(x, &Accepted::GarbageArgs))
297 }
298 };
299 if call.rpc_version != 2 {
300 return Some(wire::rpc_mismatch_reply(call.xid));
301 }
302 let result = if call.program != wire::PROGRAM {
303 Accepted::ProgUnavail
304 } else if !(wire::LOW_VERSION..=wire::HIGH_VERSION).contains(&call.version) {
305 Accepted::ProgMismatch(wire::LOW_VERSION, wire::HIGH_VERSION)
306 } else {
307 let credentials = wire::credential_json(call.cred_flavor, call.cred);
308 match procedure(ctx, conn, &call, transport, local, credentials).await {
309 Ok(a) => a,
310 Err(_) => Accepted::GarbageArgs,
311 }
312 };
313 Some(wire::accepted_reply(call.xid, &result))
314}
315
316async fn procedure(
317 ctx: &SpawnContext,
318 conn: Option<ConnectionId>,
319 call: &wire::Call<'_>,
320 transport: &str,
321 local: IpAddr,
322 credentials: Value,
323) -> Result<Accepted> {
324 let v = call.version;
325 let mut r = Reader::new(call.args);
326 let base =
327 |procedure: &str| json!({"transport": transport, "rpc_version": v, "procedure": procedure});
328 Ok(match (v, call.procedure) {
329 (_, 0) => Accepted::Success(Vec::new()),
330 (2, 1 | 2) => {
332 let (program, version, prot, port) = wire::read_pmap(&mut r)?;
333 let set = call.procedure == 1;
334 let Some(netid) = wire::netid_of(prot) else {
335 return Ok(Accepted::Success(Writer::default().u32(0).0.clone()));
337 };
338 let mut data = json!({"transport": transport, "rpc_version": v,
339 "operation": if set { "set" } else { "unset" }, "program": program,
340 "program_name": wire::program_name(program), "program_version": version,
341 "protocol": netid, "credentials": credentials});
342 if set {
343 data["port"] = json!(port);
344 }
345 register(ctx, conn, data, if set { "set" } else { "unset" }).await
346 }
347 (2, 3) => {
348 let (program, version, prot, _) = wire::read_pmap(&mut r)?;
349 let netid = wire::netid_of(prot).unwrap_or("");
350 let mut data = base("getport");
351 data["program"] = json!(program);
352 data["program_name"] = json!(wire::program_name(program));
353 data["program_version"] = json!(version);
354 data["protocol"] = json!(netid);
355 match mappings(ctx, conn, data, "getport").await {
356 None => Accepted::SystemErr,
357 Some(ms) => {
358 let port = if netid.is_empty() {
359 0
360 } else {
361 pick(&ms, program, version, netid, false)
362 .first()
363 .map(|m| u32::from(m.port))
364 .unwrap_or(0)
365 };
366 Accepted::Success(Writer::default().u32(port).0.clone())
367 }
368 }
369 }
370 (2, 4) => match mappings(ctx, conn, base("dump"), "dump").await {
371 None => Accepted::SystemErr,
372 Some(ms) => Accepted::Success(wire::pmaplist(&ms)),
373 },
374 (3 | 4, 1 | 2) => {
376 let b = wire::read_rpcb(&mut r)?;
377 let set = call.procedure == 1;
378 let mut data = json!({"transport": transport, "rpc_version": v,
379 "operation": if set { "set" } else { "unset" }, "program": b.program,
380 "program_name": wire::program_name(b.program), "program_version": b.version,
381 "protocol": b.netid, "owner": b.owner, "credentials": credentials});
382 if set {
383 if wire::protocol_of(&b.netid).is_none() {
384 return Ok(Accepted::Success(Writer::default().u32(0).0.clone()));
385 }
386 let addr = wire::parse_uaddr(&b.addr)?;
387 data["port"] = json!(addr.port());
388 data["address"] = json!(b.addr);
389 }
390 register(ctx, conn, data, if set { "set" } else { "unset" }).await
391 }
392 (3 | 4, 3) | (4, 9) => {
393 let b = wire::read_rpcb(&mut r)?;
394 let exact = call.procedure == 9;
395 let procedure = if exact { "getversaddr" } else { "getaddr" };
396 let mut data = base(procedure);
397 data["program"] = json!(b.program);
398 data["program_name"] = json!(wire::program_name(b.program));
399 data["program_version"] = json!(b.version);
400 data["protocol"] = json!(b.netid);
401 match mappings(ctx, conn, data, procedure).await {
402 None => Accepted::SystemErr,
403 Some(ms) => {
404 let netid = if b.netid.is_empty() {
405 transport
406 } else {
407 b.netid.as_str()
408 };
409 let addr = pick(&ms, b.program, b.version, netid, exact)
410 .first()
411 .map(|m| wire::uaddr(wire::host_for(&m.netid, local), m.port))
412 .unwrap_or_default();
413 Accepted::Success(Writer::default().string(&addr).0.clone())
414 }
415 }
416 }
417 (3 | 4, 4) => match mappings(ctx, conn, base("dump"), "dump").await {
418 None => Accepted::SystemErr,
419 Some(ms) => Accepted::Success(wire::rpcblist(&ms, local)),
420 },
421 (3 | 4, 6) => {
422 let now = crate::utils::clock::SystemTime::now()
423 .duration_since(crate::utils::clock::UNIX_EPOCH)
424 .map(|d| d.as_secs() as u32)
425 .unwrap_or(0);
426 Accepted::Success(Writer::default().u32(now).0.clone())
427 }
428 (4, 11) => {
429 let b = wire::read_rpcb(&mut r)?;
430 let mut data = base("getaddrlist");
431 data["program"] = json!(b.program);
432 data["program_name"] = json!(wire::program_name(b.program));
433 data["program_version"] = json!(b.version);
434 data["protocol"] = json!(b.netid);
435 match mappings(ctx, conn, data, "getaddrlist").await {
436 None => Accepted::SystemErr,
437 Some(ms) => Accepted::Success(wire::entry_list(
438 &pick(&ms, b.program, b.version, "", true),
439 local,
440 )),
441 }
442 }
443 _ => Accepted::ProcUnavail,
444 })
445}