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 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<I>>(input: I) -> IResult<I, StunMessage, E>
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)?;
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::<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);
}
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));

View File

@ -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:?}");
}
}
}