Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NAT detection for IPv4/UDP/Dublin #1216

Merged
merged 6 commits into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Added support for last icmp packet type (`T`) column ([#1105](https://github.com/fujiapple852/trippy/issues/1105))
- Added support for last icmp packet code (`C`) column ([#1109](https://github.com/fujiapple852/trippy/issues/1109))
- Added settings dialog tab hotkeys ([#1217](https://github.com/fujiapple852/trippy/issues/1217))
- Added NAT detection for `IPv4/udp/dublin` ([#1104](https://github.com/fujiapple852/trippy/issues/1104))
- Added support for NAT detection (`N`) column ([#1219](https://github.com/fujiapple852/trippy/issues/1219))

### Changed

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,7 @@ configuration file.
| `Dprt` | `P` | The destination port for the last probe for the hop |
| `Type` | `T` | The icmp packet type for the last probe for the hop:<br/>- TE: TimeExceeded<br/>- ER: EchoReply<br/>- DU: DestinationUnreachable<br/>- NA: NotApplicable |
| `Code` | `C` | The icmp packet code for the last probe for the hop |
| `Nat` | `N` | The NAT detection status for the hop |

The default columns are `holsravbwdt`.

Expand Down
2 changes: 1 addition & 1 deletion crates/trippy-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub use probe::{
Extension, Extensions, IcmpPacketType, MplsLabelStack, MplsLabelStackMember, Probe,
ProbeComplete, ProbeStatus, UnknownExtension,
};
pub use state::{Hop, State};
pub use state::{Hop, NatStatus, State};
pub use strategy::{CompletionReason, Round, Strategy};
pub use tracer::Tracer;
pub use types::{
Expand Down
1 change: 1 addition & 0 deletions crates/trippy-core/src/net/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ impl<S: Socket> Channel<S> {
&mut self.recv_socket,
self.protocol,
self.icmp_extension_mode,
self.payload_pattern,
),
IpAddr::V6(_) => ipv6::recv_icmp_probe(
&mut self.recv_socket,
Expand Down
170 changes: 143 additions & 27 deletions crates/trippy-core/src/net/ipv4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,18 @@ pub fn recv_icmp_probe<S: Socket>(
recv_socket: &mut S,
protocol: Protocol,
icmp_extension_mode: IcmpExtensionParseMode,
payload_pattern: PayloadPattern,
) -> Result<Option<Response>> {
let mut buf = [0_u8; MAX_PACKET_SIZE];
match recv_socket.read(&mut buf) {
Ok(bytes_read) => {
let ipv4 = Ipv4Packet::new_view(&buf[..bytes_read])?;
Ok(extract_probe_resp(protocol, icmp_extension_mode, &ipv4)?)
Ok(extract_probe_resp(
protocol,
icmp_extension_mode,
payload_pattern,
&ipv4,
)?)
}
Err(err) => match err.kind() {
ErrorKind::WouldBlock => Ok(None),
Expand Down Expand Up @@ -303,6 +309,28 @@ fn make_udp_packet<'a>(
Ok(udp)
}

/// Calculate the expected checksum for a UDP packet.
pub fn calc_udp_checksum(
src_addr: Ipv4Addr,
dest_addr: Ipv4Addr,
src_port: Port,
dest_port: Port,
payload_size: u16,
payload_pattern: PayloadPattern,
) -> Result<u16> {
let mut udp_buf = [0_u8; MAX_UDP_PACKET_BUF];
let payload = &[payload_pattern.0; MAX_UDP_PAYLOAD_BUF][0..usize::from(payload_size)];
let udp = make_udp_packet(
&mut udp_buf,
src_addr,
dest_addr,
src_port.0,
dest_port.0,
payload,
)?;
Ok(udp.get_checksum())
}

/// Create an `Ipv4Packet`.
#[allow(clippy::too_many_arguments)]
fn make_ipv4_packet<'a>(
Expand Down Expand Up @@ -348,6 +376,7 @@ const fn udp_payload_size(packet_size: usize) -> usize {
fn extract_probe_resp(
protocol: Protocol,
icmp_extension_mode: IcmpExtensionParseMode,
payload_pattern: PayloadPattern,
ipv4: &Ipv4Packet<'_>,
) -> Result<Option<Response>> {
let recv = SystemTime::now();
Expand All @@ -370,7 +399,7 @@ fn extract_probe_resp(
(ipv4, None)
}
};
extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| {
extract_probe_resp_seq(&nested_ipv4, protocol, payload_pattern)?.map(|resp_seq| {
Response::TimeExceeded(
ResponseData::new(recv, src, resp_seq),
IcmpPacketCode(icmp_code.0),
Expand All @@ -390,7 +419,7 @@ fn extract_probe_resp(
}
IcmpExtensionParseMode::Disabled => None,
};
extract_probe_resp_seq(&nested_ipv4, protocol)?.map(|resp_seq| {
extract_probe_resp_seq(&nested_ipv4, protocol, payload_pattern)?.map(|resp_seq| {
Response::DestinationUnreachable(
ResponseData::new(recv, src, resp_seq),
IcmpPacketCode(icmp_code.0),
Expand Down Expand Up @@ -419,6 +448,7 @@ fn extract_probe_resp(
fn extract_probe_resp_seq(
ipv4: &Ipv4Packet<'_>,
protocol: Protocol,
payload_pattern: PayloadPattern,
) -> Result<Option<ResponseSeq>> {
Ok(match (protocol, ipv4.get_protocol()) {
(Protocol::Icmp, IpProtocol::Icmp) => {
Expand All @@ -430,14 +460,23 @@ fn extract_probe_resp_seq(
)))
}
(Protocol::Udp, IpProtocol::Udp) => {
let (src_port, dest_port, checksum, identifier, payload_length) =
let (src_port, dest_port, actual_checksum, identifier, payload_length) =
extract_udp_packet(ipv4)?;
let expected_checksum = calc_udp_checksum(
ipv4.get_source(),
ipv4.get_destination(),
Port(src_port),
Port(dest_port),
payload_length,
payload_pattern,
)?;
Some(ResponseSeq::Udp(ResponseSeqUdp::new(
identifier,
IpAddr::V4(ipv4.get_destination()),
src_port,
dest_port,
checksum,
expected_checksum,
actual_checksum,
payload_length,
false,
)))
Expand Down Expand Up @@ -1036,6 +1075,7 @@ mod tests {
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

Expand Down Expand Up @@ -1086,6 +1126,7 @@ mod tests {
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

Expand Down Expand Up @@ -1135,6 +1176,7 @@ mod tests {
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

Expand Down Expand Up @@ -1180,8 +1222,13 @@ mod tests {
.expect_read()
.times(1)
.returning(mocket_read!(expected_read_buf));
let resp =
recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Disabled)?.unwrap();
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

let Response::TimeExceeded(
ResponseData {
Expand All @@ -1192,7 +1239,8 @@ mod tests {
dest_addr,
src_port,
dest_port,
checksum,
expected_udp_checksum,
actual_udp_checksum,
payload_len,
has_magic,
}),
Expand All @@ -1212,7 +1260,8 @@ mod tests {
);
assert_eq!(31829, src_port);
assert_eq!(33030, dest_port);
assert_eq!(58571, checksum);
assert_eq!(58571, expected_udp_checksum);
assert_eq!(58571, actual_udp_checksum);
assert_eq!(56, payload_len);
assert!(!has_magic);
assert_eq!(IcmpPacketCode(0), icmp_code);
Expand All @@ -1238,8 +1287,13 @@ mod tests {
.expect_read()
.times(1)
.returning(mocket_read!(expected_read_buf));
let resp =
recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Disabled)?.unwrap();
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

let Response::DestinationUnreachable(
ResponseData {
Expand All @@ -1250,7 +1304,8 @@ mod tests {
dest_addr,
src_port,
dest_port,
checksum,
expected_udp_checksum,
actual_udp_checksum,
payload_len,
has_magic,
}),
Expand All @@ -1270,7 +1325,8 @@ mod tests {
);
assert_eq!(32779, src_port);
assert_eq!(33010, dest_port);
assert_eq!(10913, checksum);
assert_eq!(10913, expected_udp_checksum);
assert_eq!(10913, actual_udp_checksum);
assert_eq!(56, payload_len);
assert!(!has_magic);
assert_eq!(IcmpPacketCode(10), icmp_code);
Expand All @@ -1295,8 +1351,13 @@ mod tests {
.expect_read()
.times(1)
.returning(mocket_read!(expected_read_buf));
let resp =
recv_icmp_probe(&mut mocket, Protocol::Tcp, IcmpExtensionParseMode::Disabled)?.unwrap();
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Tcp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

let Response::TimeExceeded(
ResponseData {
Expand Down Expand Up @@ -1347,8 +1408,13 @@ mod tests {
.expect_read()
.times(1)
.returning(mocket_read!(expected_read_buf));
let resp =
recv_icmp_probe(&mut mocket, Protocol::Tcp, IcmpExtensionParseMode::Disabled)?.unwrap();
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Tcp,
IcmpExtensionParseMode::Disabled,
PayloadPattern(0x00),
)?
.unwrap();

let Response::DestinationUnreachable(
ResponseData {
Expand Down Expand Up @@ -1397,11 +1463,26 @@ mod tests {
.expect_read()
.times(3)
.returning(mocket_read!(expected_read_buf));
let resp = recv_icmp_probe(&mut mocket, Protocol::Icmp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_some());
let resp = recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
let resp = recv_icmp_probe(&mut mocket, Protocol::Tcp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Tcp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
Ok(())
}
Expand All @@ -1424,11 +1505,26 @@ mod tests {
.expect_read()
.times(3)
.returning(mocket_read!(expected_read_buf));
let resp = recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_some());
let resp = recv_icmp_probe(&mut mocket, Protocol::Icmp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
let resp = recv_icmp_probe(&mut mocket, Protocol::Tcp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Tcp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
Ok(())
}
Expand All @@ -1450,11 +1546,26 @@ mod tests {
.expect_read()
.times(3)
.returning(mocket_read!(expected_read_buf));
let resp = recv_icmp_probe(&mut mocket, Protocol::Tcp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Tcp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_some());
let resp = recv_icmp_probe(&mut mocket, Protocol::Icmp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Icmp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
let resp = recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
Ok(())
}
Expand Down Expand Up @@ -1584,7 +1695,12 @@ mod tests {
.expect_read()
.times(1)
.returning(mocket_read!(expected_read_buf));
let resp = recv_icmp_probe(&mut mocket, Protocol::Udp, IcmpExtensionParseMode::Enabled)?;
let resp = recv_icmp_probe(
&mut mocket,
Protocol::Udp,
IcmpExtensionParseMode::Enabled,
PayloadPattern(0x00),
)?;
assert!(resp.is_none());
Ok(())
}
Expand Down
Loading