diff --git a/src/lib.rs b/src/lib.rs index edbc093..45f2dfd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -use crc32fast::hash; +use crc32fast::{hash, Hasher}; use log::warn; use nom::bytes::complete::{tag, take}; use nom::error::ParseError; @@ -144,7 +144,7 @@ pub enum StunAttribute { ChangedAddress(AddrPort), Username(String), MessageIntegrity([u8; 20]), - Fingerprint(u32), + Fingerprint((u32, bool)), ErrorCode((u16, String)), Realm(String), Nonce(String), @@ -202,22 +202,27 @@ impl fmt::Display for StunAttribute { a.port ) } - StunAttribute::Username(username) => writeln!(f, " Username {}", username), + StunAttribute::Username(username) => write!(f, " Username {}", username), StunAttribute::MessageIntegrity(msg_integrity) => { write!(f, " MessageIntegrity {:?}", msg_integrity) } - StunAttribute::Fingerprint(fingerprint) => { - write!(f, " Fingerprint 0x{:08x}", fingerprint) + StunAttribute::Fingerprint((crc, ok)) => { + write!( + f, + " Fingerprint 0x{:08x} ({})", + crc, + if *ok { "OK" } else { "FAIL" } + ) } StunAttribute::ErrorCode((err_num, error)) => { write!(f, " ErrorCode {} ({})", err_num, error) } - StunAttribute::Realm(realm) => writeln!(f, " Realm {}", realm), - StunAttribute::Nonce(nonce) => writeln!(f, " Nonce {}", nonce), + StunAttribute::Realm(realm) => write!(f, " Realm {}", realm), + StunAttribute::Nonce(nonce) => write!(f, " Nonce {}", nonce), StunAttribute::UnknownAttributes(unknown_attrs) => { write!(f, " UnknownAttributes {:?}", unknown_attrs) } - StunAttribute::Software(software) => writeln!(f, " Software {}", software), + StunAttribute::Software(software) => write!(f, " Software {}", software), StunAttribute::AlternateServer(a) => { write!( f, @@ -235,7 +240,7 @@ impl fmt::Display for StunAttribute { a.address, a.port ) - }, + } StunAttribute::OtherAddress(a) => { write!( f, @@ -244,7 +249,7 @@ impl fmt::Display for StunAttribute { a.address, a.port ) - }, + } StunAttribute::Unknown((attr_type, data)) => { write!(f, " Unknown ({:04x}) {:?}", attr_type, data) } @@ -334,16 +339,59 @@ where fn parse_stun_message<'a, I, E: ParseError>(input: I) -> IResult where - I: nom::Input + nom::Compare + nom::Compare<&'a [u8]> + AsBytes + Debug, + I: nom::Input + + nom::Compare + + nom::Compare<&'a [u8]> + + nom::Offset + + AsBytes + + Debug, { + let mut hasher = Some(Hasher::new()); + let input_start = input.clone(); + let (input, h) = parse_stun_header(input)?; + hasher + .as_mut() + .unwrap() + .update(input_start.take(input_start.offset(&input)).as_bytes()); + let (residual, input) = take(h.msg_length)(input)?; if residual.input_len() != 0 { warn!("Trailing bytes in STUN message: {:?}", residual); } - let (input, attributes) = many0(parse_stun_attribute(&h.tx_id)).parse(input)?; - if !input.input_len() != 0 { + let mut input = input; + let mut attributes = Vec::new(); + + while let Ok((new_input, attr)) = parse_stun_attribute::(&h.tx_id)(input.clone()) { + let attr = if let Some(StunAttribute::Fingerprint(fingerprint)) = attr { + let crc = hasher.unwrap().finalize() ^ 0x5354554e; + hasher = None; + if crc == fingerprint.0 { + Some(StunAttribute::Fingerprint((crc, true))) + } else { + warn!( + "Fingerprint mismatch: expected 0x{:08x}, got 0x{:08x}", + crc, fingerprint.0 + ); + attr + } + } else { + if hasher.is_some() { + hasher + .as_mut() + .unwrap() + .update(input.take(input.offset(&new_input)).as_bytes()); + } else { + warn!("Received attributes after FINGERPRINT"); + } + attr + }; + + attributes.push(attr); + input = new_input; + } + if input.input_len() != 0 { warn!("Trailing bytes in STUN message attributes: {:?}", input); } let attributes = StunAttributes(attributes.iter().filter_map(|i| i.clone()).collect()); @@ -492,7 +540,7 @@ where } ATTR_NUM_FINGERPRINT => { let (_residual, fingerprint) = be_u32(attr_data)?; - StunAttribute::Fingerprint(fingerprint) + StunAttribute::Fingerprint((fingerprint, false)) } ATTR_REALM => { let realm = String::from_iter(attr_data.iter_elements().map(|b| b as char)); diff --git a/src/main.rs b/src/main.rs index 4098ab0..9b75699 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ impl OutputFormat { #[command(about = "Test a Tailscale derp node's stun service")] struct Cli { host: String, - #[clap(short, long, default_value = "3478")] + #[clap(default_value = "3478")] port: u16, #[clap(short = '4', conflicts_with = "v6_only", default_value = "false")] v4_only: bool, @@ -100,22 +100,27 @@ fn main() { let mut buf = [0u8; 1500]; - if let Ok(received) = socket.recv(&mut buf) { - debug!("Received response: {:?}", &buf[..received]); + match socket.recv(&mut buf) { + Ok(received) => { + let buf = &buf[..received]; + debug!("Received response: {:?}", buf); - let msg = StunMessage::parse(&buf).unwrap(); - info!("Parsed message from {}:", socket.peer_addr().unwrap()); - if cli.address_only { - if let Some(addr) = msg.attributes.mapped_address() { - println!("{}", cli.format.format_address(addr)); + let msg = StunMessage::parse(buf).unwrap(); + info!("Parsed message from {}:", socket.peer_addr().unwrap()); + if cli.address_only { + match msg.attributes.mapped_address() { + Some(addr) => println!("{}", cli.format.format_address(addr)), + None => { + // No mapped address + std::process::exit(1); + } + } } else { - // No mapped address - std::process::exit(1); + println!("{}", cli.format.format_stun(&msg)); } - } else { - println!("{}", cli.format.format_stun(&msg)); } - } else if let Err(e) = socket.recv(&mut buf) { - println!("recv function failed: {e:?}"); + Err(e) => { + println!("recv function failed: {e:?}"); + } } }