From 8af8978ad5c4278607e57769779b04c47fc73901 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 12 Nov 2025 15:26:20 +1100 Subject: [PATCH] chore(connlib): include "packet kind" in decapsulation errors (#10867) When looking at error logs from Gateways or Clients, it can be useful to know, what kind of packet we failed to process. --- rust/connlib/tunnel/src/gateway.rs | 4 +-- rust/connlib/tunnel/src/lib.rs | 5 +++ rust/connlib/tunnel/src/otel.rs | 12 +------ rust/connlib/tunnel/src/packet_kind.rs | 50 ++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 13 deletions(-) create mode 100644 rust/connlib/tunnel/src/packet_kind.rs diff --git a/rust/connlib/tunnel/src/gateway.rs b/rust/connlib/tunnel/src/gateway.rs index 1a5b4e180..089f3dc38 100644 --- a/rust/connlib/tunnel/src/gateway.rs +++ b/rust/connlib/tunnel/src/gateway.rs @@ -10,7 +10,7 @@ use crate::gateway::flow_tracker::FlowTracker; use crate::messages::gateway::{Client, ResourceDescription, Subject}; use crate::messages::{Answer, IceCredentials, ResolveRequest}; use crate::peer_store::PeerStore; -use crate::{GatewayEvent, IpConfig, p2p_control}; +use crate::{FailedToDecapsulate, GatewayEvent, IpConfig, p2p_control, packet_kind}; use anyhow::{Context, Result}; use boringtun::x25519::{self, PublicKey}; use chrono::{DateTime, Utc}; @@ -164,7 +164,7 @@ impl GatewayState { let Some((cid, packet)) = self .node .decapsulate(local, from, packet, now) - .context("Failed to decapsulate")? + .context(FailedToDecapsulate(packet_kind::classify(packet)))? else { return Ok(None); }; diff --git a/rust/connlib/tunnel/src/lib.rs b/rust/connlib/tunnel/src/lib.rs index ccbc9d719..139e13832 100644 --- a/rust/connlib/tunnel/src/lib.rs +++ b/rust/connlib/tunnel/src/lib.rs @@ -34,6 +34,7 @@ mod io; pub mod messages; mod otel; mod p2p_control; +mod packet_kind; mod peer_store; #[cfg(all(test, feature = "proptest"))] mod proptest; @@ -647,6 +648,10 @@ pub(crate) struct NotClientIp(IpAddr); #[error("Traffic to/from this resource IP is not allowed: {0}")] pub(crate) struct NotAllowedResource(IpAddr); +#[derive(Debug, thiserror::Error)] +#[error("Failed to decapsulate '{0}' packet")] +pub(crate) struct FailedToDecapsulate(packet_kind::Kind); + pub fn is_peer(dst: IpAddr) -> bool { match dst { IpAddr::V4(v4) => IPV4_TUNNEL.contains(v4), diff --git a/rust/connlib/tunnel/src/otel.rs b/rust/connlib/tunnel/src/otel.rs index df276e836..28cdd19df 100644 --- a/rust/connlib/tunnel/src/otel.rs +++ b/rust/connlib/tunnel/src/otel.rs @@ -6,17 +6,7 @@ pub mod attr { pub fn network_protocol_name(payload: &[u8]) -> KeyValue { const KEY: &str = "network.protocol.name"; - match payload { - [0..3, ..] => KeyValue::new(KEY, "stun"), - // Channel-data is a 4-byte header so the actual payload starts on the 5th byte - [64..=79, _, _, _, 0..3, ..] => KeyValue::new(KEY, "stun-over-turn"), - [64..=79, _, _, _, payload @ ..] if snownet::is_wireguard(payload) => { - KeyValue::new(KEY, "wireguard-over-turn") - } - [64..=79, _, _, _, ..] => KeyValue::new(KEY, "unknown-over-turn"), - payload if snownet::is_wireguard(payload) => KeyValue::new(KEY, "wireguard"), - _ => KeyValue::new(KEY, "unknown"), - } + KeyValue::new(KEY, crate::packet_kind::classify(payload)) } } diff --git a/rust/connlib/tunnel/src/packet_kind.rs b/rust/connlib/tunnel/src/packet_kind.rs new file mode 100644 index 000000000..e06542e64 --- /dev/null +++ b/rust/connlib/tunnel/src/packet_kind.rs @@ -0,0 +1,50 @@ +use std::fmt; + +#[derive(Debug)] +pub enum Kind { + Stun, + Wireguard, + Unknown, + StunOverTurn, + WireguardOverTurn, + UnknownOverTurn, +} + +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +impl Kind { + pub fn as_str(&self) -> &'static str { + match self { + Kind::Stun => "stun", + Kind::Wireguard => "wireguard", + Kind::Unknown => "unknown", + Kind::StunOverTurn => "stun-over-turn", + Kind::WireguardOverTurn => "wireguard-over-turn", + Kind::UnknownOverTurn => "unknown-over-turn", + } + } +} + +impl From for opentelemetry::Value { + fn from(val: Kind) -> Self { + opentelemetry::Value::String(opentelemetry::StringValue::from(val.as_str())) + } +} + +pub fn classify(packet: &[u8]) -> Kind { + match packet { + [0..=3, ..] => Kind::Stun, + // Channel-data is a 4-byte header so the actual payload starts on the 5th byte + [64..=79, _, _, _, 0..=3, ..] => Kind::StunOverTurn, + [64..=79, _, _, _, payload @ ..] if snownet::is_wireguard(payload) => { + Kind::WireguardOverTurn + } + [64..=79, _, _, _, ..] => Kind::UnknownOverTurn, + payload if snownet::is_wireguard(payload) => Kind::Wireguard, + _ => Kind::Unknown, + } +}