use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
pub const GTPC_PORT: u16 = 2123;
pub const GTPU_PORT: u16 = 2152;
pub const V1_MAX_SEQUENCE: u32 = 0xFFFF;
pub const V2_MAX_SEQUENCE: u32 = 0x00FF_FFFF;
pub const MAX_GPDU_PAYLOAD_LEN: usize = 65_495;
pub const MAX_APN_LABEL_LEN: usize = 63;
pub const V1_ECHO_REQUEST: u8 = 1;
pub const V1_ECHO_RESPONSE: u8 = 2;
pub const V1_VERSION_NOT_SUPPORTED: u8 = 3;
pub const V1_CREATE_PDP_CONTEXT_REQUEST: u8 = 16;
pub const V1_CREATE_PDP_CONTEXT_RESPONSE: u8 = 17;
pub const V1_UPDATE_PDP_CONTEXT_REQUEST: u8 = 18;
pub const V1_UPDATE_PDP_CONTEXT_RESPONSE: u8 = 19;
pub const V1_DELETE_PDP_CONTEXT_REQUEST: u8 = 20;
pub const V1_DELETE_PDP_CONTEXT_RESPONSE: u8 = 21;
pub const V1_ERROR_INDICATION: u8 = 26;
pub const V1_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION: u8 = 31;
pub const V1_END_MARKER: u8 = 254;
pub const V1_G_PDU: u8 = 255;
pub const V2_ECHO_REQUEST: u8 = 1;
pub const V2_ECHO_RESPONSE: u8 = 2;
pub const V2_CREATE_SESSION_REQUEST: u8 = 32;
pub const V2_CREATE_SESSION_RESPONSE: u8 = 33;
pub const V2_MODIFY_BEARER_REQUEST: u8 = 34;
pub const V2_MODIFY_BEARER_RESPONSE: u8 = 35;
pub const V2_DELETE_SESSION_REQUEST: u8 = 36;
pub const V2_DELETE_SESSION_RESPONSE: u8 = 37;
pub fn v1_message_name(t: u8) -> &'static str {
match t {
V1_ECHO_REQUEST => "Echo Request",
V1_ECHO_RESPONSE => "Echo Response",
V1_VERSION_NOT_SUPPORTED => "Version Not Supported",
V1_CREATE_PDP_CONTEXT_REQUEST => "Create PDP Context Request",
V1_CREATE_PDP_CONTEXT_RESPONSE => "Create PDP Context Response",
V1_UPDATE_PDP_CONTEXT_REQUEST => "Update PDP Context Request",
V1_UPDATE_PDP_CONTEXT_RESPONSE => "Update PDP Context Response",
V1_DELETE_PDP_CONTEXT_REQUEST => "Delete PDP Context Request",
V1_DELETE_PDP_CONTEXT_RESPONSE => "Delete PDP Context Response",
V1_ERROR_INDICATION => "Error Indication",
V1_SUPPORTED_EXTENSION_HEADERS_NOTIFICATION => "Supported Extension Headers Notification",
V1_END_MARKER => "End Marker",
V1_G_PDU => "G-PDU",
_ => "Unknown",
}
}
pub fn v2_message_name(t: u8) -> &'static str {
match t {
V2_ECHO_REQUEST => "Echo Request",
V2_ECHO_RESPONSE => "Echo Response",
V2_CREATE_SESSION_REQUEST => "Create Session Request",
V2_CREATE_SESSION_RESPONSE => "Create Session Response",
V2_MODIFY_BEARER_REQUEST => "Modify Bearer Request",
V2_MODIFY_BEARER_RESPONSE => "Modify Bearer Response",
V2_DELETE_SESSION_REQUEST => "Delete Session Request",
V2_DELETE_SESSION_RESPONSE => "Delete Session Response",
_ => "Unknown",
}
}
pub const V1_IE_CAUSE: u8 = 1;
pub const V1_IE_IMSI: u8 = 2;
pub const V1_IE_REORDERING_REQUIRED: u8 = 8;
pub const V1_IE_RECOVERY: u8 = 14;
pub const V1_IE_SELECTION_MODE: u8 = 15;
pub const V1_IE_TEID_DATA_I: u8 = 16;
pub const V1_IE_TEID_CONTROL_PLANE: u8 = 17;
pub const V1_IE_TEARDOWN_IND: u8 = 19;
pub const V1_IE_NSAPI: u8 = 20;
pub const V1_IE_CHARGING_ID: u8 = 127;
pub const V1_IE_END_USER_ADDRESS: u8 = 128;
pub const V1_IE_ACCESS_POINT_NAME: u8 = 131;
pub const V1_IE_PROTOCOL_CONFIG_OPTIONS: u8 = 132;
pub const V1_IE_GSN_ADDRESS: u8 = 133;
pub const V1_IE_MSISDN: u8 = 134;
pub const V1_IE_QOS_PROFILE: u8 = 135;
pub const V1_IE_RAT_TYPE: u8 = 151;
pub const V1_IE_USER_LOCATION_INFO: u8 = 152;
pub const V1_IE_MS_TIME_ZONE: u8 = 153;
pub const V1_IE_IMEISV: u8 = 154;
pub fn v1_fixed_ie_len(ie_type: u8) -> Option<usize> {
Some(match ie_type {
1 => 1, 2 => 8, 3 => 6, 4 => 4, 5 => 4, 8 => 1, 9 => 28, 11 => 1, 12 => 3, 13 => 1, 14 => 1, 15 => 1, 16 => 4, 17 => 4, 18 => 5, 19 => 1, 20 => 1, 21 => 1, 22 => 9, 23 => 1, 24 => 1, 25 => 2, 26 => 2, 27 => 2, 28 => 2, 29 => 1, 127 => 4, _ => return None,
})
}
pub fn v1_ie_name(ie_type: u8) -> &'static str {
match ie_type {
1 => "Cause",
2 => "IMSI",
3 => "Routeing Area Identity",
8 => "Reordering Required",
14 => "Recovery",
15 => "Selection Mode",
16 => "TEID Data I",
17 => "TEID Control Plane",
19 => "Teardown Ind",
20 => "NSAPI",
127 => "Charging ID",
128 => "End User Address",
129 => "MM Context",
130 => "PDP Context",
131 => "Access Point Name",
132 => "Protocol Configuration Options",
133 => "GSN Address",
134 => "MSISDN",
135 => "QoS Profile",
148 => "Common Flags",
149 => "APN Restriction",
151 => "RAT Type",
152 => "User Location Information",
153 => "MS Time Zone",
154 => "IMEI(SV)",
251 => "Charging Gateway Address",
255 => "Private Extension",
_ => "Unknown",
}
}
pub const V2_IE_IMSI: u8 = 1;
pub const V2_IE_CAUSE: u8 = 2;
pub const V2_IE_RECOVERY: u8 = 3;
pub const V2_IE_APN: u8 = 71;
pub const V2_IE_EBI: u8 = 73;
pub const V2_IE_MSISDN: u8 = 76;
pub const V2_IE_PCO: u8 = 78;
pub const V2_IE_PAA: u8 = 79;
pub const V2_IE_RAT_TYPE: u8 = 82;
pub const V2_IE_FTEID: u8 = 87;
pub const V2_IE_BEARER_CONTEXT: u8 = 93;
pub const V2_IE_ULI: u8 = 86;
pub const V2_IE_APN_RESTRICTION: u8 = 127;
pub fn v2_ie_name(ie_type: u8) -> &'static str {
match ie_type {
1 => "IMSI",
2 => "Cause",
3 => "Recovery",
71 => "APN",
72 => "AMBR",
73 => "EPS Bearer ID",
74 => "IPv4 Configuration Parameters",
75 => "MEI",
76 => "MSISDN",
77 => "Indication",
78 => "Protocol Configuration Options",
79 => "PDN Address Allocation",
80 => "Bearer QoS",
82 => "RAT Type",
83 => "Serving Network",
86 => "User Location Information",
87 => "Fully Qualified TEID",
93 => "Bearer Context",
94 => "Charging ID",
99 => "PDN Type",
127 => "APN Restriction",
255 => "Private Extension",
_ => "Unknown",
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DecodeError {
TooShort { len: usize, needed: usize },
BadVersion { version: u8 },
LengthMismatch { declared: usize, available: usize },
BadExtensionHeader { ext_type: u8 },
TruncatedIe { ie_type: u8 },
UnknownFixedIe { ie_type: u8 },
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DecodeError::TooShort { len, needed } => {
write!(f, "datagram is {len} octets, needs at least {needed}")
}
DecodeError::BadVersion { version } => {
write!(
f,
"GTP version {version} is not supported (expected 1 or 2)"
)
}
DecodeError::LengthMismatch {
declared,
available,
} => write!(
f,
"header declares {declared} octets of payload but {available} are present"
),
DecodeError::BadExtensionHeader { ext_type } => {
write!(f, "malformed extension header of type {ext_type}")
}
DecodeError::TruncatedIe { ie_type } => {
write!(f, "information element {ie_type} runs past the message")
}
DecodeError::UnknownFixedIe { ie_type } => write!(
f,
"information element {ie_type} is below 128 (fixed length) but its length is \
not known, so the rest of the message cannot be parsed"
),
}
}
}
impl std::error::Error for DecodeError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum EncodeError {
ApnLabelTooLong { label: String, len: usize },
}
impl std::fmt::Display for EncodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
EncodeError::ApnLabelTooLong { label, len } => write!(
f,
"APN label '{label}' is {len} octets, over the {MAX_APN_LABEL_LEN}-octet DNS \
label limit (TS 23.003 §9.1)"
),
}
}
}
impl std::error::Error for EncodeError {}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GtpVersion {
V1,
V2,
}
impl GtpVersion {
pub fn as_number(self) -> u8 {
match self {
GtpVersion::V1 => 1,
GtpVersion::V2 => 2,
}
}
pub fn sequence_bits(self) -> u8 {
match self {
GtpVersion::V1 => 16,
GtpVersion::V2 => 24,
}
}
pub fn max_sequence(self) -> u32 {
match self {
GtpVersion::V1 => V1_MAX_SEQUENCE,
GtpVersion::V2 => V2_MAX_SEQUENCE,
}
}
}
pub fn peek_version(data: &[u8]) -> Option<u8> {
data.first().map(|b| b >> 5)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExtensionHeader {
pub ext_type: u8,
pub content: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV1Header {
pub protocol_type: bool,
pub message_type: u8,
pub teid: u32,
pub sequence: Option<u16>,
pub npdu: Option<u8>,
pub extension_headers: Vec<ExtensionHeader>,
}
impl GtpV1Header {
pub fn new(message_type: u8, teid: u32) -> Self {
Self {
protocol_type: true,
message_type,
teid,
sequence: None,
npdu: None,
extension_headers: Vec::new(),
}
}
pub fn with_sequence(message_type: u8, teid: u32, sequence: u16) -> Self {
Self {
sequence: Some(sequence),
..Self::new(message_type, teid)
}
}
pub fn optional_present(&self) -> bool {
self.sequence.is_some() || self.npdu.is_some() || !self.extension_headers.is_empty()
}
pub fn flags(&self) -> u8 {
let mut flags = 0b0010_0000; if self.protocol_type {
flags |= 0b0001_0000;
}
if !self.extension_headers.is_empty() {
flags |= 0b0000_0100;
}
if self.sequence.is_some() {
flags |= 0b0000_0010;
}
if self.npdu.is_some() {
flags |= 0b0000_0001;
}
flags
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV1Message {
pub header: GtpV1Header,
pub body: Vec<u8>,
}
impl GtpV1Message {
pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
if data.len() < 8 {
return Err(DecodeError::TooShort {
len: data.len(),
needed: 8,
});
}
let flags = data[0];
let version = flags >> 5;
if version != 1 {
return Err(DecodeError::BadVersion { version });
}
let protocol_type = flags & 0b0001_0000 != 0;
let e = flags & 0b0000_0100 != 0;
let s = flags & 0b0000_0010 != 0;
let pn = flags & 0b0000_0001 != 0;
let message_type = data[1];
let declared = u16::from_be_bytes([data[2], data[3]]) as usize;
let teid = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
let available = data.len() - 8;
if declared > available {
return Err(DecodeError::LengthMismatch {
declared,
available,
});
}
let payload = &data[8..8 + declared];
let mut pos = 0usize;
let (sequence, npdu, mut next_ext) = if e || s || pn {
if payload.len() < 4 {
return Err(DecodeError::TooShort {
len: data.len(),
needed: 12,
});
}
let seq = u16::from_be_bytes([payload[0], payload[1]]);
let np = payload[2];
let nx = payload[3];
pos = 4;
(
if s { Some(seq) } else { None },
if pn { Some(np) } else { None },
if e { nx } else { 0 },
)
} else {
(None, None, 0)
};
let mut extension_headers = Vec::new();
while next_ext != 0 {
if pos >= payload.len() {
return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
}
let units = payload[pos] as usize;
if units == 0 {
return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
}
let total = units * 4;
if pos + total > payload.len() {
return Err(DecodeError::BadExtensionHeader { ext_type: next_ext });
}
let content = payload[pos + 1..pos + total - 1].to_vec();
extension_headers.push(ExtensionHeader {
ext_type: next_ext,
content,
});
next_ext = payload[pos + total - 1];
pos += total;
}
Ok(GtpV1Message {
header: GtpV1Header {
protocol_type,
message_type,
teid,
sequence,
npdu,
extension_headers,
},
body: payload[pos..].to_vec(),
})
}
pub fn encode(&self) -> Vec<u8> {
let h = &self.header;
let mut tail = Vec::new();
if h.optional_present() {
tail.extend_from_slice(&h.sequence.unwrap_or(0).to_be_bytes());
tail.push(h.npdu.unwrap_or(0));
tail.push(h.extension_headers.first().map(|x| x.ext_type).unwrap_or(0));
}
for (i, ext) in h.extension_headers.iter().enumerate() {
let total = ext.content.len() + 2;
debug_assert_eq!(total % 4, 0, "extension header must be a multiple of 4");
tail.push((total / 4) as u8);
tail.extend_from_slice(&ext.content);
tail.push(
h.extension_headers
.get(i + 1)
.map(|x| x.ext_type)
.unwrap_or(0),
);
}
tail.extend_from_slice(&self.body);
let mut out = Vec::with_capacity(8 + tail.len());
out.push(h.flags());
out.push(h.message_type);
out.extend_from_slice(&(tail.len() as u16).to_be_bytes());
out.extend_from_slice(&h.teid.to_be_bytes());
out.extend_from_slice(&tail);
out
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV1Ie {
pub ie_type: u8,
pub value: Vec<u8>,
}
pub fn parse_v1_ies(body: &[u8]) -> Result<Vec<GtpV1Ie>, DecodeError> {
let mut out = Vec::new();
let mut pos = 0usize;
while pos < body.len() {
let ie_type = body[pos];
if ie_type < 128 {
let len = v1_fixed_ie_len(ie_type).ok_or(DecodeError::UnknownFixedIe { ie_type })?;
if pos + 1 + len > body.len() {
return Err(DecodeError::TruncatedIe { ie_type });
}
out.push(GtpV1Ie {
ie_type,
value: body[pos + 1..pos + 1 + len].to_vec(),
});
pos += 1 + len;
} else {
if pos + 3 > body.len() {
return Err(DecodeError::TruncatedIe { ie_type });
}
let len = u16::from_be_bytes([body[pos + 1], body[pos + 2]]) as usize;
if pos + 3 + len > body.len() {
return Err(DecodeError::TruncatedIe { ie_type });
}
out.push(GtpV1Ie {
ie_type,
value: body[pos + 3..pos + 3 + len].to_vec(),
});
pos += 3 + len;
}
}
Ok(out)
}
pub fn encode_v1_ies(ies: &[GtpV1Ie]) -> Vec<u8> {
let mut out = Vec::new();
for ie in ies {
out.push(ie.ie_type);
if ie.ie_type >= 128 {
out.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
}
out.extend_from_slice(&ie.value);
}
out
}
pub fn find_v1(ies: &[GtpV1Ie], ie_type: u8) -> Option<&[u8]> {
ies.iter()
.find(|ie| ie.ie_type == ie_type)
.map(|ie| ie.value.as_slice())
}
pub fn find_all_v1(ies: &[GtpV1Ie], ie_type: u8) -> Vec<&[u8]> {
ies.iter()
.filter(|ie| ie.ie_type == ie_type)
.map(|ie| ie.value.as_slice())
.collect()
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV2Header {
pub piggyback: bool,
pub message_priority: Option<u8>,
pub message_type: u8,
pub teid: Option<u32>,
pub sequence: u32,
}
impl GtpV2Header {
pub fn new(message_type: u8, teid: u32, sequence: u32) -> Self {
Self {
piggyback: false,
message_priority: None,
message_type,
teid: Some(teid),
sequence: sequence & 0x00FF_FFFF,
}
}
pub fn without_teid(message_type: u8, sequence: u32) -> Self {
Self {
teid: None,
..Self::new(message_type, 0, sequence)
}
}
pub fn flags(&self) -> u8 {
let mut flags = 0b0100_0000; if self.piggyback {
flags |= 0b0001_0000;
}
if self.teid.is_some() {
flags |= 0b0000_1000;
}
if self.message_priority.is_some() {
flags |= 0b0000_0100;
}
flags
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV2Ie {
pub ie_type: u8,
pub instance: u8,
pub value: Vec<u8>,
}
impl GtpV2Ie {
pub fn new(ie_type: u8, instance: u8, value: Vec<u8>) -> Self {
Self {
ie_type,
instance,
value,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct GtpV2Message {
pub header: GtpV2Header,
pub ies: Vec<GtpV2Ie>,
}
impl GtpV2Message {
pub fn decode(data: &[u8]) -> Result<Self, DecodeError> {
if data.len() < 4 {
return Err(DecodeError::TooShort {
len: data.len(),
needed: 4,
});
}
let flags = data[0];
let version = flags >> 5;
if version != 2 {
return Err(DecodeError::BadVersion { version });
}
let piggyback = flags & 0b0001_0000 != 0;
let t = flags & 0b0000_1000 != 0;
let mp = flags & 0b0000_0100 != 0;
let message_type = data[1];
let declared = u16::from_be_bytes([data[2], data[3]]) as usize;
let available = data.len() - 4;
if declared > available {
return Err(DecodeError::LengthMismatch {
declared,
available,
});
}
let rest = &data[4..4 + declared];
let fixed = if t { 8 } else { 4 };
if rest.len() < fixed {
return Err(DecodeError::TooShort {
len: data.len(),
needed: 4 + fixed,
});
}
let mut pos = 0usize;
let teid = if t {
let v = u32::from_be_bytes([rest[0], rest[1], rest[2], rest[3]]);
pos = 4;
Some(v)
} else {
None
};
let sequence =
u32::from_be_bytes([0, rest[pos], rest[pos + 1], rest[pos + 2]]) & 0x00FF_FFFF;
let last = rest[pos + 3];
pos += 4;
let mut ies = Vec::new();
while pos < rest.len() {
if pos + 4 > rest.len() {
return Err(DecodeError::TruncatedIe { ie_type: rest[pos] });
}
let ie_type = rest[pos];
let len = u16::from_be_bytes([rest[pos + 1], rest[pos + 2]]) as usize;
let instance = rest[pos + 3] & 0x0F;
if pos + 4 + len > rest.len() {
return Err(DecodeError::TruncatedIe { ie_type });
}
ies.push(GtpV2Ie {
ie_type,
instance,
value: rest[pos + 4..pos + 4 + len].to_vec(),
});
pos += 4 + len;
}
Ok(GtpV2Message {
header: GtpV2Header {
piggyback,
message_priority: if mp { Some(last >> 4) } else { None },
message_type,
teid,
sequence,
},
ies,
})
}
pub fn encode(&self) -> Vec<u8> {
let mut rest = Vec::new();
if let Some(teid) = self.header.teid {
rest.extend_from_slice(&teid.to_be_bytes());
}
let seq = self.header.sequence.to_be_bytes();
rest.extend_from_slice(&seq[1..4]);
rest.push(self.header.message_priority.map(|p| p << 4).unwrap_or(0));
for ie in &self.ies {
rest.push(ie.ie_type);
rest.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
rest.push(ie.instance & 0x0F);
rest.extend_from_slice(&ie.value);
}
let mut out = Vec::with_capacity(4 + rest.len());
out.push(self.header.flags());
out.push(self.header.message_type);
out.extend_from_slice(&(rest.len() as u16).to_be_bytes());
out.extend_from_slice(&rest);
out
}
pub fn find(&self, ie_type: u8) -> Option<&[u8]> {
self.ies
.iter()
.find(|ie| ie.ie_type == ie_type)
.map(|ie| ie.value.as_slice())
}
pub fn find_instance(&self, ie_type: u8, instance: u8) -> Option<&[u8]> {
self.ies
.iter()
.find(|ie| ie.ie_type == ie_type && ie.instance == instance)
.map(|ie| ie.value.as_slice())
}
}
pub fn parse_v2_grouped(value: &[u8]) -> Result<Vec<GtpV2Ie>, DecodeError> {
let mut out = Vec::new();
let mut pos = 0usize;
while pos < value.len() {
if pos + 4 > value.len() {
return Err(DecodeError::TruncatedIe {
ie_type: value[pos],
});
}
let ie_type = value[pos];
let len = u16::from_be_bytes([value[pos + 1], value[pos + 2]]) as usize;
let instance = value[pos + 3] & 0x0F;
if pos + 4 + len > value.len() {
return Err(DecodeError::TruncatedIe { ie_type });
}
out.push(GtpV2Ie {
ie_type,
instance,
value: value[pos + 4..pos + 4 + len].to_vec(),
});
pos += 4 + len;
}
Ok(out)
}
pub fn encode_v2_grouped(ies: &[GtpV2Ie]) -> Vec<u8> {
let mut out = Vec::new();
for ie in ies {
out.push(ie.ie_type);
out.extend_from_slice(&(ie.value.len() as u16).to_be_bytes());
out.push(ie.instance & 0x0F);
out.extend_from_slice(&ie.value);
}
out
}
pub fn encode_tbcd(digits: &str) -> Vec<u8> {
let nibbles: Vec<u8> = digits
.chars()
.filter(|c| c.is_ascii_digit())
.map(|c| c as u8 - b'0')
.collect();
let mut out = Vec::with_capacity(nibbles.len().div_ceil(2));
for pair in nibbles.chunks(2) {
let low = pair[0];
let high = pair.get(1).copied().unwrap_or(0x0F);
out.push((high << 4) | low);
}
out
}
pub fn decode_tbcd(bytes: &[u8]) -> String {
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
for nibble in [b & 0x0F, b >> 4] {
if nibble == 0x0F {
return out;
}
if nibble > 9 {
return out;
}
out.push((b'0' + nibble) as char);
}
}
out
}
pub fn encode_apn(apn: &str) -> Result<Vec<u8>, EncodeError> {
let mut out = Vec::with_capacity(apn.len() + 4);
for label in apn.split('.').filter(|l| !l.is_empty()) {
let bytes = label.as_bytes();
if bytes.len() > MAX_APN_LABEL_LEN {
return Err(EncodeError::ApnLabelTooLong {
label: crate::utils::truncate_for_log(label, 32),
len: bytes.len(),
});
}
out.push(bytes.len() as u8);
out.extend_from_slice(bytes);
}
Ok(out)
}
pub fn decode_apn(value: &[u8]) -> Option<String> {
let mut parts: Vec<String> = Vec::new();
let mut pos = 0usize;
while pos < value.len() {
let len = value[pos] as usize;
if len == 0 || pos + 1 + len > value.len() {
return None;
}
parts.push(String::from_utf8_lossy(&value[pos + 1..pos + 1 + len]).to_string());
pos += 1 + len;
}
if parts.is_empty() {
None
} else {
Some(parts.join("."))
}
}
pub fn encode_end_user_address(addr: Option<IpAddr>) -> Vec<u8> {
match addr {
Some(IpAddr::V4(v4)) => {
let mut out = vec![0xF1, 0x21];
out.extend_from_slice(&v4.octets());
out
}
Some(IpAddr::V6(v6)) => {
let mut out = vec![0xF1, 0x57];
out.extend_from_slice(&v6.octets());
out
}
None => vec![0xF1, 0x21],
}
}
pub fn decode_end_user_address(value: &[u8]) -> (&'static str, Option<IpAddr>) {
if value.len() < 2 {
return ("unknown", None);
}
let (name, expected) = match value[1] {
0x21 => ("IPv4", 4usize),
0x57 => ("IPv6", 16),
0x8D => ("IPv4v6", 4),
_ => ("unknown", 0),
};
let rest = &value[2..];
if expected == 4 && rest.len() >= 4 {
(
name,
Some(IpAddr::V4(Ipv4Addr::new(
rest[0], rest[1], rest[2], rest[3],
))),
)
} else if expected == 16 && rest.len() >= 16 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&rest[..16]);
(name, Some(IpAddr::V6(Ipv6Addr::from(octets))))
} else {
(name, None)
}
}
pub fn encode_paa(addr: IpAddr) -> Vec<u8> {
match addr {
IpAddr::V4(v4) => {
let mut out = vec![1u8];
out.extend_from_slice(&v4.octets());
out
}
IpAddr::V6(v6) => {
let mut out = vec![2u8, 64];
out.extend_from_slice(&v6.octets());
out
}
}
}
pub fn decode_paa(value: &[u8]) -> (&'static str, Option<IpAddr>) {
match value.first() {
Some(1) if value.len() >= 5 => (
"IPv4",
Some(IpAddr::V4(Ipv4Addr::new(
value[1], value[2], value[3], value[4],
))),
),
Some(2) if value.len() >= 18 => {
let mut octets = [0u8; 16];
octets.copy_from_slice(&value[2..18]);
("IPv6", Some(IpAddr::V6(Ipv6Addr::from(octets))))
}
Some(3) if value.len() >= 22 => {
let mut octets = [0u8; 16];
octets.copy_from_slice(&value[2..18]);
(
"IPv4v6",
Some(IpAddr::V4(Ipv4Addr::new(
value[18], value[19], value[20], value[21],
))),
)
}
Some(1) => ("IPv4", None),
Some(2) => ("IPv6", None),
Some(3) => ("IPv4v6", None),
_ => ("unknown", None),
}
}
pub fn encode_fteid(interface_type: u8, teid: u32, addr: IpAddr) -> Vec<u8> {
let mut out = Vec::with_capacity(21);
let flag = match addr {
IpAddr::V4(_) => 0x80,
IpAddr::V6(_) => 0x40,
};
out.push(flag | (interface_type & 0x3F));
out.extend_from_slice(&teid.to_be_bytes());
match addr {
IpAddr::V4(v4) => out.extend_from_slice(&v4.octets()),
IpAddr::V6(v6) => out.extend_from_slice(&v6.octets()),
}
out
}
pub fn decode_fteid(value: &[u8]) -> Option<(u8, u32, Option<IpAddr>)> {
if value.len() < 5 {
return None;
}
let interface_type = value[0] & 0x3F;
let teid = u32::from_be_bytes([value[1], value[2], value[3], value[4]]);
let has_v4 = value[0] & 0x80 != 0;
let has_v6 = value[0] & 0x40 != 0;
let addr = if has_v4 && value.len() >= 9 {
Some(IpAddr::V4(Ipv4Addr::new(
value[5], value[6], value[7], value[8],
)))
} else if has_v6 && value.len() >= 21 {
let mut octets = [0u8; 16];
octets.copy_from_slice(&value[5..21]);
Some(IpAddr::V6(Ipv6Addr::from(octets)))
} else {
None
};
Some((interface_type, teid, addr))
}
pub fn encode_pco_dns(servers: &[IpAddr]) -> Vec<u8> {
let mut out = vec![0x80u8];
for server in servers {
match server {
IpAddr::V4(v4) => {
out.extend_from_slice(&[0x00, 0x0D, 0x04]);
out.extend_from_slice(&v4.octets());
}
IpAddr::V6(v6) => {
out.extend_from_slice(&[0x00, 0x03, 0x10]);
out.extend_from_slice(&v6.octets());
}
}
}
out
}
pub fn rat_type_name(version: GtpVersion, value: u8) -> &'static str {
match (version, value) {
(GtpVersion::V1, 1) => "UTRAN",
(GtpVersion::V1, 2) => "GERAN",
(GtpVersion::V1, 3) => "WLAN",
(GtpVersion::V1, 4) => "GAN",
(GtpVersion::V1, 5) => "HSPA Evolution",
(GtpVersion::V1, 6) => "EUTRAN",
(GtpVersion::V2, 1) => "UTRAN",
(GtpVersion::V2, 2) => "GERAN",
(GtpVersion::V2, 3) => "WLAN",
(GtpVersion::V2, 4) => "GAN",
(GtpVersion::V2, 5) => "HSPA Evolution",
(GtpVersion::V2, 6) => "EUTRAN",
(GtpVersion::V2, 8) => "EUTRAN-NB-IoT",
(GtpVersion::V2, 9) => "LTE-M",
(GtpVersion::V2, 10) => "NR",
_ => "unknown",
}
}
pub struct CauseName {
pub name: &'static str,
pub v1: u8,
pub v2: u8,
pub accepts: bool,
}
pub const CAUSES: &[CauseName] = &[
CauseName {
name: "request_accepted",
v1: 128,
v2: 16,
accepts: true,
},
CauseName {
name: "new_pdp_type_due_to_network_preference",
v1: 129,
v2: 18,
accepts: true,
},
CauseName {
name: "new_pdp_type_due_to_single_address_bearer_only",
v1: 130,
v2: 19,
accepts: true,
},
CauseName {
name: "invalid_message_format",
v1: 193,
v2: 65,
accepts: false,
},
CauseName {
name: "imsi_not_known",
v1: 194,
v2: 95,
accepts: false,
},
CauseName {
name: "version_not_supported",
v1: 198,
v2: 66,
accepts: false,
},
CauseName {
name: "no_resources_available",
v1: 199,
v2: 73,
accepts: false,
},
CauseName {
name: "service_not_supported",
v1: 200,
v2: 68,
accepts: false,
},
CauseName {
name: "mandatory_ie_incorrect",
v1: 201,
v2: 69,
accepts: false,
},
CauseName {
name: "mandatory_ie_missing",
v1: 202,
v2: 70,
accepts: false,
},
CauseName {
name: "system_failure",
v1: 204,
v2: 72,
accepts: false,
},
CauseName {
name: "user_authentication_failed",
v1: 209,
v2: 91,
accepts: false,
},
CauseName {
name: "context_not_found",
v1: 210,
v2: 64,
accepts: false,
},
CauseName {
name: "all_dynamic_addresses_are_occupied",
v1: 211,
v2: 83,
accepts: false,
},
CauseName {
name: "no_memory_available",
v1: 212,
v2: 90,
accepts: false,
},
CauseName {
name: "missing_or_unknown_apn",
v1: 219,
v2: 77,
accepts: false,
},
CauseName {
name: "apn_access_denied_no_subscription",
v1: 222,
v2: 92,
accepts: false,
},
];
pub fn cause_by_name(name: &str) -> Option<&'static CauseName> {
let normalised: String = name
.chars()
.filter_map(|c| {
if c.is_ascii_alphanumeric() {
Some(c.to_ascii_lowercase())
} else if c == ' ' || c == '-' || c == '_' {
Some('_')
} else {
None
}
})
.collect();
CAUSES.iter().find(|c| c.name == normalised)
}
pub fn cause_names() -> Vec<&'static str> {
CAUSES.iter().map(|c| c.name).collect()
}
pub fn cause_accepts(version: GtpVersion, value: u8) -> bool {
match version {
GtpVersion::V1 => (128..=191).contains(&value),
GtpVersion::V2 => (16..=63).contains(&value),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct InnerIp {
pub version: u8,
pub source: IpAddr,
pub destination: IpAddr,
pub protocol: u8,
pub protocol_name: &'static str,
pub ttl: u8,
pub length: u16,
pub source_port: Option<u16>,
pub destination_port: Option<u16>,
pub payload_offset: Option<usize>,
}
pub fn ip_protocol_name(protocol: u8) -> &'static str {
match protocol {
1 => "ICMP",
2 => "IGMP",
6 => "TCP",
17 => "UDP",
41 => "IPv6",
47 => "GRE",
50 => "ESP",
51 => "AH",
58 => "ICMPv6",
89 => "OSPF",
132 => "SCTP",
_ => "unknown",
}
}
pub fn decode_inner_ip(packet: &[u8]) -> Option<InnerIp> {
let first = *packet.first()?;
match first >> 4 {
4 => {
if packet.len() < 20 {
return None;
}
let ihl = (first & 0x0F) as usize * 4;
if ihl < 20 {
return None;
}
let protocol = packet[9];
let source = IpAddr::V4(Ipv4Addr::new(
packet[12], packet[13], packet[14], packet[15],
));
let destination = IpAddr::V4(Ipv4Addr::new(
packet[16], packet[17], packet[18], packet[19],
));
let (source_port, destination_port, payload_offset) =
transport_ports(packet, ihl, protocol);
Some(InnerIp {
version: 4,
source,
destination,
protocol,
protocol_name: ip_protocol_name(protocol),
ttl: packet[8],
length: u16::from_be_bytes([packet[2], packet[3]]),
source_port,
destination_port,
payload_offset,
})
}
6 => {
if packet.len() < 40 {
return None;
}
let protocol = packet[6];
let mut s = [0u8; 16];
s.copy_from_slice(&packet[8..24]);
let mut d = [0u8; 16];
d.copy_from_slice(&packet[24..40]);
let (source_port, destination_port, payload_offset) =
transport_ports(packet, 40, protocol);
Some(InnerIp {
version: 6,
source: IpAddr::V6(Ipv6Addr::from(s)),
destination: IpAddr::V6(Ipv6Addr::from(d)),
protocol,
protocol_name: ip_protocol_name(protocol),
ttl: packet[7],
length: u16::from_be_bytes([packet[4], packet[5]]),
source_port,
destination_port,
payload_offset,
})
}
_ => None,
}
}
fn transport_ports(
packet: &[u8],
offset: usize,
protocol: u8,
) -> (Option<u16>, Option<u16>, Option<usize>) {
if !matches!(protocol, 6 | 17 | 132) || packet.len() < offset + 4 {
return (None, None, None);
}
let sport = u16::from_be_bytes([packet[offset], packet[offset + 1]]);
let dport = u16::from_be_bytes([packet[offset + 2], packet[offset + 3]]);
let payload_offset = match protocol {
17 => Some(offset + 8),
6 if packet.len() > offset + 12 => Some(offset + ((packet[offset + 12] >> 4) as usize * 4)),
_ => None,
};
(Some(sport), Some(dport), payload_offset)
}