support for checking fingerprint, misc

This commit is contained in:
Keenan Tims 2025-02-14 18:55:57 -08:00
parent 6985ed6614
commit a66a6a263e
No known key found for this signature in database
GPG Key ID: B8FDD4AD6B193F06
2 changed files with 81 additions and 28 deletions

View File

@ -1,4 +1,4 @@
use crc32fast::hash; use crc32fast::{hash, Hasher};
use log::warn; use log::warn;
use nom::bytes::complete::{tag, take}; use nom::bytes::complete::{tag, take};
use nom::error::ParseError; use nom::error::ParseError;
@ -144,7 +144,7 @@ pub enum StunAttribute {
ChangedAddress(AddrPort), ChangedAddress(AddrPort),
Username(String), Username(String),
MessageIntegrity([u8; 20]), MessageIntegrity([u8; 20]),
Fingerprint(u32), Fingerprint((u32, bool)),
ErrorCode((u16, String)), ErrorCode((u16, String)),
Realm(String), Realm(String),
Nonce(String), Nonce(String),
@ -202,22 +202,27 @@ impl fmt::Display for StunAttribute {
a.port a.port
) )
} }
StunAttribute::Username(username) => writeln!(f, " Username {}", username), StunAttribute::Username(username) => write!(f, " Username {}", username),
StunAttribute::MessageIntegrity(msg_integrity) => { StunAttribute::MessageIntegrity(msg_integrity) => {
write!(f, " MessageIntegrity {:?}", msg_integrity) write!(f, " MessageIntegrity {:?}", msg_integrity)
} }
StunAttribute::Fingerprint(fingerprint) => { StunAttribute::Fingerprint((crc, ok)) => {
write!(f, " Fingerprint 0x{:08x}", fingerprint) write!(
f,
" Fingerprint 0x{:08x} ({})",
crc,
if *ok { "OK" } else { "FAIL" }
)
} }
StunAttribute::ErrorCode((err_num, error)) => { StunAttribute::ErrorCode((err_num, error)) => {
write!(f, " ErrorCode {} ({})", err_num, error) write!(f, " ErrorCode {} ({})", err_num, error)
} }
StunAttribute::Realm(realm) => writeln!(f, " Realm {}", realm), StunAttribute::Realm(realm) => write!(f, " Realm {}", realm),
StunAttribute::Nonce(nonce) => writeln!(f, " Nonce {}", nonce), StunAttribute::Nonce(nonce) => write!(f, " Nonce {}", nonce),
StunAttribute::UnknownAttributes(unknown_attrs) => { StunAttribute::UnknownAttributes(unknown_attrs) => {
write!(f, " 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) => { StunAttribute::AlternateServer(a) => {
write!( write!(
f, f,
@ -235,7 +240,7 @@ impl fmt::Display for StunAttribute {
a.address, a.address,
a.port a.port
) )
}, }
StunAttribute::OtherAddress(a) => { StunAttribute::OtherAddress(a) => {
write!( write!(
f, f,
@ -244,7 +249,7 @@ impl fmt::Display for StunAttribute {
a.address, a.address,
a.port a.port
) )
}, }
StunAttribute::Unknown((attr_type, data)) => { StunAttribute::Unknown((attr_type, data)) => {
write!(f, " Unknown ({:04x}) {:?}", attr_type, data) write!(f, " Unknown ({:04x}) {:?}", attr_type, data)
} }
@ -334,16 +339,59 @@ where
fn parse_stun_message<'a, I, E: ParseError<I>>(input: I) -> IResult<I, StunMessage, E> fn parse_stun_message<'a, I, E: ParseError<I>>(input: I) -> IResult<I, StunMessage, E>
where where
I: nom::Input<Item = u8> + nom::Compare<I> + nom::Compare<&'a [u8]> + AsBytes + Debug, I: nom::Input<Item = u8>
+ nom::Compare<I>
+ 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)?; 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)?; let (residual, input) = take(h.msg_length)(input)?;
if residual.input_len() != 0 { if residual.input_len() != 0 {
warn!("Trailing bytes in STUN message: {:?}", residual); warn!("Trailing bytes in STUN message: {:?}", residual);
} }
let (input, attributes) = many0(parse_stun_attribute(&h.tx_id)).parse(input)?; let mut input = input;
if !input.input_len() != 0 { let mut attributes = Vec::new();
while let Ok((new_input, attr)) = parse_stun_attribute::<I, E>(&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); warn!("Trailing bytes in STUN message attributes: {:?}", input);
} }
let attributes = StunAttributes(attributes.iter().filter_map(|i| i.clone()).collect()); let attributes = StunAttributes(attributes.iter().filter_map(|i| i.clone()).collect());
@ -492,7 +540,7 @@ where
} }
ATTR_NUM_FINGERPRINT => { ATTR_NUM_FINGERPRINT => {
let (_residual, fingerprint) = be_u32(attr_data)?; let (_residual, fingerprint) = be_u32(attr_data)?;
StunAttribute::Fingerprint(fingerprint) StunAttribute::Fingerprint((fingerprint, false))
} }
ATTR_REALM => { ATTR_REALM => {
let realm = String::from_iter(attr_data.iter_elements().map(|b| b as char)); let realm = String::from_iter(attr_data.iter_elements().map(|b| b as char));

View File

@ -42,7 +42,7 @@ impl OutputFormat {
#[command(about = "Test a Tailscale derp node's stun service")] #[command(about = "Test a Tailscale derp node's stun service")]
struct Cli { struct Cli {
host: String, host: String,
#[clap(short, long, default_value = "3478")] #[clap(default_value = "3478")]
port: u16, port: u16,
#[clap(short = '4', conflicts_with = "v6_only", default_value = "false")] #[clap(short = '4', conflicts_with = "v6_only", default_value = "false")]
v4_only: bool, v4_only: bool,
@ -100,22 +100,27 @@ fn main() {
let mut buf = [0u8; 1500]; let mut buf = [0u8; 1500];
if let Ok(received) = socket.recv(&mut buf) { match socket.recv(&mut buf) {
debug!("Received response: {:?}", &buf[..received]); Ok(received) => {
let buf = &buf[..received];
debug!("Received response: {:?}", buf);
let msg = StunMessage::parse(&buf).unwrap(); let msg = StunMessage::parse(buf).unwrap();
info!("Parsed message from {}:", socket.peer_addr().unwrap()); info!("Parsed message from {}:", socket.peer_addr().unwrap());
if cli.address_only { if cli.address_only {
if let Some(addr) = msg.attributes.mapped_address() { match msg.attributes.mapped_address() {
println!("{}", cli.format.format_address(addr)); Some(addr) => println!("{}", cli.format.format_address(addr)),
None => {
// No mapped address
std::process::exit(1);
}
}
} else { } else {
// No mapped address println!("{}", cli.format.format_stun(&msg));
std::process::exit(1);
} }
} else {
println!("{}", cli.format.format_stun(&msg));
} }
} else if let Err(e) = socket.recv(&mut buf) { Err(e) => {
println!("recv function failed: {e:?}"); println!("recv function failed: {e:?}");
}
} }
} }