1use anyhow::{anyhow, Result};
35use std::net::SocketAddr;
36use std::pin::Pin;
37use std::sync::Arc;
38use std::task::{Context, Poll};
39use std::time::Duration;
40use tokio::io::AsyncWrite;
41use tokio::net::{TcpListener, TcpStream};
42
43use super::codec;
44
45pub const IPPROTO_SCTP: i32 = 132;
47pub const LAB_HEADER_LEN: usize = 4;
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
52pub enum Transport {
53 Sctp,
55 Tcp,
57}
58
59impl Transport {
60 pub fn parse(value: &str) -> Result<Self> {
61 match value.trim().to_ascii_lowercase().as_str() {
62 "sctp" => Ok(Self::Sctp),
63 "tcp" => Ok(Self::Tcp),
64 other => Err(anyhow!(
65 "transport must be \"sctp\" (what TS 38.412 defines for N2) or \"tcp\" \
66 (non-standard length-prefixed framing, for local protocol work only), got {other:?}"
67 )),
68 }
69 }
70
71 pub fn from_params(params: Option<&crate::protocol::StartupParams>) -> Result<Self> {
72 match params
73 .map(|p| p.get_optional_string("transport"))
74 .transpose()?
75 .flatten()
76 {
77 Some(s) => Self::parse(&s),
78 None => Ok(Self::Sctp),
79 }
80 }
81
82 pub fn as_str(self) -> &'static str {
83 match self {
84 Self::Sctp => "sctp",
85 Self::Tcp => "tcp",
86 }
87 }
88
89 pub fn label(self) -> &'static str {
92 match self {
93 Self::Sctp => "sctp (TS 38.412, PPID 60)",
94 Self::Tcp => "tcp (NON-STANDARD length-prefixed lab transport, not N2)",
95 }
96 }
97}
98
99fn no_sctp(role: &str, e: std::io::Error) -> anyhow::Error {
100 anyhow!(
101 "NGAP {role} cannot open an SCTP socket on this host: {e}. NGAP's transport is SCTP \
102 (TS 38.412 section 7, port 38412, PPID 60) and this operating system has no SCTP stack \
103 available to this process — macOS ships neither kernel support nor headers for SCTP, and \
104 a Linux kernel without the sctp module fails the same way (`modprobe sctp`). NetGet \
105 refuses rather than pretend it is speaking N2. Either run on a host with SCTP, or, for \
106 local protocol work only, pass the startup parameter transport=\"tcp\" — a NON-STANDARD \
107 length-prefixed framing that no real gNB or AMF speaks."
108 )
109}
110
111fn sctp_socket(addr: &SocketAddr, role: &str) -> Result<socket2::Socket> {
112 use socket2::{Domain, Protocol, Socket, Type};
113 let domain = if addr.is_ipv4() {
114 Domain::IPV4
115 } else {
116 Domain::IPV6
117 };
118 Socket::new(domain, Type::STREAM, Some(Protocol::from(IPPROTO_SCTP)))
119 .map_err(|e| no_sctp(role, e))
120}
121
122pub async fn bind_listener(addr: SocketAddr, transport: Transport) -> Result<TcpListener> {
127 match transport {
128 Transport::Tcp => crate::server::socket_helpers::create_reusable_tcp_listener(addr).await,
129 Transport::Sctp => {
130 let socket = sctp_socket(&addr, "AMF")?;
131 socket
132 .set_reuse_address(true)
133 .map_err(|e| anyhow!("NGAP could not set SO_REUSEADDR on the SCTP socket: {e}"))?;
134 socket
135 .bind(&addr.into())
136 .map_err(|e| anyhow!("NGAP could not bind the SCTP socket to {addr}: {e}"))?;
137 socket
138 .listen(128)
139 .map_err(|e| anyhow!("NGAP could not listen on the SCTP socket at {addr}: {e}"))?;
140 socket
141 .set_nonblocking(true)
142 .map_err(|e| anyhow!("NGAP could not make the SCTP socket non-blocking: {e}"))?;
143 let std_listener: std::net::TcpListener = socket.into();
144 TcpListener::from_std(std_listener)
145 .map_err(|e| anyhow!("NGAP could not register the SCTP listener with tokio: {e}"))
146 }
147 }
148}
149
150pub async fn connect(addr: SocketAddr, transport: Transport, deadline: Duration) -> Result<Conn> {
152 let stream = match transport {
153 Transport::Tcp => tokio::time::timeout(deadline, TcpStream::connect(addr))
154 .await
155 .map_err(|_| anyhow!("no answer from {addr} within {}s", deadline.as_secs()))??,
156 Transport::Sctp => {
157 let socket = sctp_socket(&addr, "gNB")?;
158 let std_stream = tokio::task::spawn_blocking(move || -> Result<std::net::TcpStream> {
159 socket
160 .connect_timeout(&addr.into(), deadline)
161 .map_err(|e| anyhow!("SCTP association to {addr} failed: {e}"))?;
162 socket.set_nonblocking(true)?;
163 Ok(socket.into())
164 })
165 .await
166 .map_err(|e| anyhow!("SCTP connect task failed: {e}"))??;
167 TcpStream::from_std(std_stream)?
168 }
169 };
170 Ok(Conn::new(stream, transport))
171}
172
173pub struct Conn {
175 stream: Arc<TcpStream>,
176 transport: Transport,
177}
178
179impl Conn {
180 pub fn new(stream: TcpStream, transport: Transport) -> Self {
181 Conn {
182 stream: Arc::new(stream),
183 transport,
184 }
185 }
186
187 pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
188 self.stream.local_addr()
189 }
190
191 #[doc(hidden)]
193 pub fn socket(&self) -> socket2::SockRef<'_> {
194 socket2::SockRef::from(&*self.stream)
195 }
196
197 pub fn reader(&self) -> MessageReader {
198 MessageReader {
199 stream: self.stream.clone(),
200 transport: self.transport,
201 }
202 }
203
204 pub fn writer(&self) -> MessageWriter {
205 MessageWriter {
206 stream: self.stream.clone(),
207 transport: self.transport,
208 pending: None,
209 }
210 }
211}
212
213#[derive(Debug)]
215pub enum ReadOutcome {
216 Message(Vec<u8>),
217 TooLarge(usize),
220 Eof,
221 TimedOut,
222 Error(String),
223}
224
225pub struct MessageReader {
227 stream: Arc<TcpStream>,
228 transport: Transport,
229}
230
231impl MessageReader {
232 pub async fn next(&mut self, deadline: Duration) -> ReadOutcome {
235 match tokio::time::timeout(deadline, self.next_inner(deadline)).await {
236 Ok(o) => o,
237 Err(_) => ReadOutcome::TimedOut,
238 }
239 }
240
241 async fn next_inner(&mut self, _deadline: Duration) -> ReadOutcome {
242 match self.transport {
243 Transport::Tcp => {
244 let mut header = [0u8; LAB_HEADER_LEN];
245 match read_exact(&self.stream, &mut header).await {
246 Ok(true) => {}
247 Ok(false) => return ReadOutcome::Eof,
248 Err(e) => return ReadOutcome::Error(e.to_string()),
249 }
250 let len = u32::from_be_bytes(header) as usize;
251 if len > codec::MAX_PDU_LEN {
252 return ReadOutcome::TooLarge(len);
253 }
254 let mut body = vec![0u8; len];
255 match read_exact(&self.stream, &mut body).await {
256 Ok(true) => ReadOutcome::Message(body),
257 Ok(false) => ReadOutcome::Eof,
258 Err(e) => ReadOutcome::Error(e.to_string()),
259 }
260 }
261 Transport::Sctp => sctp_recv(&self.stream).await,
262 }
263 }
264}
265
266async fn read_exact(stream: &TcpStream, buf: &mut [u8]) -> std::io::Result<bool> {
268 let mut got = 0;
269 while got < buf.len() {
270 stream.readable().await?;
271 match stream.try_read(&mut buf[got..]) {
272 Ok(0) if got == 0 => return Ok(false),
273 Ok(0) => {
274 return Err(std::io::Error::new(
275 std::io::ErrorKind::UnexpectedEof,
276 "truncated frame",
277 ))
278 }
279 Ok(n) => got += n,
280 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
281 Err(e) => return Err(e),
282 }
283 }
284 Ok(true)
285}
286
287#[cfg(unix)]
288async fn sctp_recv(stream: &TcpStream) -> ReadOutcome {
289 use std::os::fd::AsRawFd;
290 let fd = stream.as_raw_fd();
291 let mut message: Vec<u8> = Vec::new();
292 let mut chunk = vec![0u8; codec::MAX_PDU_LEN + 1];
293 let mut oversized = false;
294 loop {
295 if let Err(e) = stream.readable().await {
296 return ReadOutcome::Error(e.to_string());
297 }
298 let r = stream.try_io(tokio::io::Interest::READABLE, || {
299 let mut iov = libc::iovec {
300 iov_base: chunk.as_mut_ptr() as *mut libc::c_void,
301 iov_len: chunk.len(),
302 };
303 let mut msg: libc::msghdr = unsafe { std::mem::zeroed() };
306 msg.msg_iov = &mut iov;
307 msg.msg_iovlen = 1;
308 let n = unsafe { libc::recvmsg(fd, &mut msg, 0) };
310 if n < 0 {
311 Err(std::io::Error::last_os_error())
312 } else {
313 Ok((n as usize, msg.msg_flags))
314 }
315 });
316 let (n, flags) = match r {
317 Ok(v) => v,
318 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
319 Err(e) => return ReadOutcome::Error(e.to_string()),
320 };
321 if n == 0 {
322 return ReadOutcome::Eof;
323 }
324 if flags & 0x8000 != 0 {
327 continue;
328 }
329 if !oversized {
330 if message.len() + n > codec::MAX_PDU_LEN {
331 oversized = true;
332 } else {
333 message.extend_from_slice(&chunk[..n]);
334 }
335 }
336 let total = message.len() + if oversized { n } else { 0 };
337 if flags & libc::MSG_EOR != 0 {
338 return if oversized {
339 ReadOutcome::TooLarge(total.max(codec::MAX_PDU_LEN + 1))
340 } else {
341 ReadOutcome::Message(message)
342 };
343 }
344 }
345}
346
347#[cfg(not(unix))]
348async fn sctp_recv(_stream: &TcpStream) -> ReadOutcome {
349 ReadOutcome::Error("SCTP is not available on this platform".into())
350}
351
352pub struct MessageWriter {
358 stream: Arc<TcpStream>,
359 transport: Transport,
360 pending: Option<(Vec<u8>, usize)>,
362}
363
364impl MessageWriter {
365 pub async fn send(&mut self, pdu: &[u8]) -> std::io::Result<()> {
367 use tokio::io::AsyncWriteExt;
368 self.write_all(pdu).await?;
369 self.flush().await
370 }
371
372 pub fn close(&self) {
374 let _ = socket2::SockRef::from(&*self.stream).shutdown(std::net::Shutdown::Write);
375 }
376}
377
378#[cfg(unix)]
379fn sctp_send(stream: &TcpStream, pdu: &[u8], sid: u16) -> std::io::Result<usize> {
380 use std::os::fd::AsRawFd;
381 #[repr(C)]
384 struct SndRcvInfo {
385 stream: u16,
386 ssn: u16,
387 flags: u16,
388 _pad: u16,
389 ppid: u32,
390 context: u32,
391 timetolive: u32,
392 tsn: u32,
393 cumtsn: u32,
394 assoc_id: i32,
395 }
396 const SCTP_SNDRCV: i32 = 1;
397 let info = SndRcvInfo {
398 stream: sid,
399 ssn: 0,
400 flags: 0,
401 _pad: 0,
402 ppid: codec::PPID.to_be(),
404 context: 0,
405 timetolive: 0,
406 tsn: 0,
407 cumtsn: 0,
408 assoc_id: 0,
409 };
410 let space = unsafe { libc::CMSG_SPACE(std::mem::size_of::<SndRcvInfo>() as u32) } as usize;
412 let mut control = vec![0u8; space];
413 let mut iov = libc::iovec {
414 iov_base: pdu.as_ptr() as *mut libc::c_void,
415 iov_len: pdu.len(),
416 };
417 unsafe {
421 let mut msg: libc::msghdr = std::mem::zeroed();
422 msg.msg_iov = &mut iov;
423 msg.msg_iovlen = 1;
424 msg.msg_control = control.as_mut_ptr() as *mut libc::c_void;
425 msg.msg_controllen = space as _;
426 let cmsg = libc::CMSG_FIRSTHDR(&msg);
427 (*cmsg).cmsg_level = IPPROTO_SCTP;
428 (*cmsg).cmsg_type = SCTP_SNDRCV;
429 (*cmsg).cmsg_len = libc::CMSG_LEN(std::mem::size_of::<SndRcvInfo>() as u32) as _;
430 std::ptr::copy_nonoverlapping(
431 &info as *const SndRcvInfo as *const u8,
432 libc::CMSG_DATA(cmsg),
433 std::mem::size_of::<SndRcvInfo>(),
434 );
435 let n = libc::sendmsg(stream.as_raw_fd(), &msg, 0);
436 if n < 0 {
437 Err(std::io::Error::last_os_error())
438 } else {
439 Ok(n as usize)
440 }
441 }
442}
443
444#[cfg(not(unix))]
445fn sctp_send(_stream: &TcpStream, _pdu: &[u8], _sid: u16) -> std::io::Result<usize> {
446 Err(std::io::Error::other(
447 "SCTP is not available on this platform",
448 ))
449}
450
451impl AsyncWrite for MessageWriter {
452 fn poll_write(
453 mut self: Pin<&mut Self>,
454 cx: &mut Context<'_>,
455 buf: &[u8],
456 ) -> Poll<std::io::Result<usize>> {
457 if buf.len() > codec::MAX_PDU_LEN {
458 return Poll::Ready(Err(std::io::Error::new(
459 std::io::ErrorKind::InvalidInput,
460 format!(
461 "NGAP PDU of {} octets exceeds {}",
462 buf.len(),
463 codec::MAX_PDU_LEN
464 ),
465 )));
466 }
467 match self.transport {
468 Transport::Sctp => {
469 let preferred: u16 = if codec::is_ue_associated(buf) { 1 } else { 0 };
471 loop {
472 if let Err(e) = std::task::ready!(self.stream.poll_write_ready(cx)) {
473 return Poll::Ready(Err(e));
474 }
475 let stream = self.stream.clone();
476 let mut result = stream.try_io(tokio::io::Interest::WRITABLE, || {
477 sctp_send(&stream, buf, preferred)
478 });
479 if preferred != 0
480 && matches!(&result, Err(e) if e.raw_os_error() == Some(libc::EINVAL))
481 {
482 result = stream
484 .try_io(tokio::io::Interest::WRITABLE, || sctp_send(&stream, buf, 0));
485 }
486 match result {
487 Ok(_) => return Poll::Ready(Ok(buf.len())),
488 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
489 Err(e) => return Poll::Ready(Err(e)),
490 }
491 }
492 }
493 Transport::Tcp => {
494 if self.pending.is_none() {
495 let mut framed = Vec::with_capacity(LAB_HEADER_LEN + buf.len());
496 framed.extend_from_slice(&(buf.len() as u32).to_be_bytes());
497 framed.extend_from_slice(buf);
498 self.pending = Some((framed, 0));
499 }
500 loop {
501 let (data, done) = self.pending.as_ref().expect("set above");
502 if *done == data.len() {
503 self.pending = None;
504 return Poll::Ready(Ok(buf.len()));
505 }
506 if let Err(e) = std::task::ready!(self.stream.poll_write_ready(cx)) {
507 self.pending = None;
508 return Poll::Ready(Err(e));
509 }
510 let remaining = data[*done..].to_vec();
511 match self.stream.try_write(&remaining) {
512 Ok(n) => {
513 if let Some((_, d)) = self.pending.as_mut() {
514 *d += n;
515 }
516 }
517 Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => continue,
518 Err(e) => {
519 self.pending = None;
520 return Poll::Ready(Err(e));
521 }
522 }
523 }
524 }
525 }
526 }
527
528 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
529 Poll::Ready(Ok(()))
530 }
531
532 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
533 self.close();
534 Poll::Ready(Ok(()))
535 }
536}