Skip to main content

netget/client/gtp/
msg.rs

1//! What the GTP client puts on the wire and how it reads the answers. Pure: no sockets, no
2//! state, no model — `mod.rs` owns those — so `tests/client/gtp/msg_test.rs` can pin the
3//! octets and the bounds without a peer.
4//!
5//! Every octet is built on the server's codec (`crate::server::gtp::codec`): the GTPv1
6//! all-or-nothing optional block, the fixed-below-128 IE split, GTPv2 TLIV and the grouped
7//! Bearer Context all come from there. This file adds the *request* side — Create Session /
8//! Create PDP Context, Modify Bearer / Update PDP Context, Delete Session / Delete PDP Context,
9//! Echo — and the inner IPv4 packet a G-PDU carries, which the model describes as fields and
10//! never as bytes.
11
12use crate::server::gtp::codec::{
13    self, GtpV1Header, GtpV1Ie, GtpV1Message, GtpV2Header, GtpV2Ie, GtpV2Message, GtpVersion,
14};
15use anyhow::{bail, ensure, Context, Result};
16use serde_json::{json, Value};
17use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
18
19/// The largest datagram this client reads. A read that fills the buffer past it is refused,
20/// because it means the datagram was cut short.
21pub const MAX_DATAGRAM: usize = 65_535;
22/// Information elements one message may carry before it is refused unread. A real Create
23/// Session Response has about fifteen; 64 KiB of four-octet IEs would be sixteen thousand.
24pub const MAX_IES: usize = 256;
25/// Payload octets of a received G-PDU that reach the model; the rest is counted, not shown.
26pub const MAX_EVENT_PAYLOAD: usize = 1024;
27/// Octets of transport payload one G-PDU may carry. An IPv4 + UDP inner packet inside a G-PDU
28/// with a sequence number must fit one UDP datagram, which is what
29/// [`codec::MAX_GPDU_PAYLOAD_LEN`] measures.
30pub const MAX_INNER_PAYLOAD: usize = codec::MAX_GPDU_PAYLOAD_LEN - 28;
31
32/// GTPv2 F-TEID interface types (TS 29.274 §8.22, Table 8.22-1) this client uses or names.
33pub const IF_S1U_ENB_GTPU: u8 = 0;
34pub const IF_S1U_SGW_GTPU: u8 = 1;
35pub const IF_S5S8_SGW_GTPU: u8 = 4;
36pub const IF_S5S8_PGW_GTPU: u8 = 5;
37pub const IF_S5S8_SGW_GTPC: u8 = 6;
38pub const IF_S5S8_PGW_GTPC: u8 = 7;
39pub const IF_S11_MME_GTPC: u8 = 10;
40pub const IF_S11S4_SGW_GTPC: u8 = 11;
41
42pub const V2_IE_MEI: u8 = 75;
43pub const V2_IE_AMBR: u8 = 72;
44pub const V2_IE_BEARER_QOS: u8 = 80;
45pub const V2_IE_SERVING_NETWORK: u8 = 83;
46pub const V2_IE_PDN_TYPE: u8 = 99;
47pub const V2_IE_SELECTION_MODE: u8 = 128;
48pub const V2_IE_CHARGING_ID: u8 = 94;
49pub const V1_IE_CHARGING_GATEWAY: u8 = 251;
50/// GTP-U IE "GTP-U Peer Address" in an Error Indication (TS 29.281 §8.4) is IE 133.
51pub const V1_IE_PEER_ADDRESS: u8 = 133;
52
53pub const V1_ERROR_INDICATION: u8 = codec::V1_ERROR_INDICATION;
54pub const V1_END_MARKER: u8 = codec::V1_END_MARKER;
55pub const V1_G_PDU: u8 = codec::V1_G_PDU;
56/// GTPv2 Version Not Supported Indication (TS 29.274 Table 6.1-1).
57pub const V2_VERSION_NOT_SUPPORTED: u8 = 3;
58
59pub fn interface_name(t: u8) -> &'static str {
60    match t {
61        IF_S1U_ENB_GTPU => "S1-U eNodeB GTP-U",
62        IF_S1U_SGW_GTPU => "S1-U SGW GTP-U",
63        2 => "S12 RNC GTP-U",
64        3 => "S12 SGW GTP-U",
65        IF_S5S8_SGW_GTPU => "S5/S8 SGW GTP-U",
66        IF_S5S8_PGW_GTPU => "S5/S8 PGW GTP-U",
67        IF_S5S8_SGW_GTPC => "S5/S8 SGW GTP-C",
68        IF_S5S8_PGW_GTPC => "S5/S8 PGW GTP-C",
69        IF_S11_MME_GTPC => "S11 MME GTP-C",
70        IF_S11S4_SGW_GTPC => "S11/S4 SGW GTP-C",
71        _ => "other",
72    }
73}
74
75/// Which node this client is, which decides the interface types it advertises.
76#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub enum Role {
78    /// GTPv1 Gn: an SGSN towards a GGSN.
79    Gn,
80    /// GTPv2 S5/S8: an SGW towards a PGW.
81    S5,
82    /// GTPv2 S11: an MME towards an SGW.
83    S11,
84}
85
86impl Role {
87    pub fn version(self) -> GtpVersion {
88        match self {
89            Role::Gn => GtpVersion::V1,
90            _ => GtpVersion::V2,
91        }
92    }
93    pub fn as_str(self) -> &'static str {
94        match self {
95            Role::Gn => "gn",
96            Role::S5 => "s5",
97            Role::S11 => "s11",
98        }
99    }
100}
101
102/// The request kinds this client issues; each one's answer is matched by sequence **and**
103/// by message type.
104#[derive(Clone, Copy, Debug, PartialEq, Eq)]
105pub enum Request {
106    Echo,
107    Create,
108    Modify,
109    Delete,
110}
111
112impl Request {
113    pub fn message_type(self, version: GtpVersion) -> u8 {
114        match (version, self) {
115            (_, Request::Echo) => 1,
116            (GtpVersion::V1, Request::Create) => codec::V1_CREATE_PDP_CONTEXT_REQUEST,
117            (GtpVersion::V1, Request::Modify) => codec::V1_UPDATE_PDP_CONTEXT_REQUEST,
118            (GtpVersion::V1, Request::Delete) => codec::V1_DELETE_PDP_CONTEXT_REQUEST,
119            (GtpVersion::V2, Request::Create) => codec::V2_CREATE_SESSION_REQUEST,
120            (GtpVersion::V2, Request::Modify) => codec::V2_MODIFY_BEARER_REQUEST,
121            (GtpVersion::V2, Request::Delete) => codec::V2_DELETE_SESSION_REQUEST,
122        }
123    }
124    /// The message that answers it.
125    pub fn response_type(self, version: GtpVersion) -> u8 {
126        self.message_type(version) + 1
127    }
128    pub fn name(self, version: GtpVersion) -> &'static str {
129        match version {
130            GtpVersion::V1 => codec::v1_message_name(self.message_type(version)),
131            GtpVersion::V2 => codec::v2_message_name(self.message_type(version)),
132        }
133    }
134}
135
136/// QoS for the default bearer, as the model gives it.
137#[derive(Clone, Debug, PartialEq, Eq)]
138pub struct BearerQos {
139    pub qci: u8,
140    pub arp_priority_level: u8,
141    pub pre_emption_capability: bool,
142    pub pre_emption_vulnerability: bool,
143    pub mbr_uplink_kbps: u64,
144    pub mbr_downlink_kbps: u64,
145    pub gbr_uplink_kbps: u64,
146    pub gbr_downlink_kbps: u64,
147}
148
149impl Default for BearerQos {
150    fn default() -> Self {
151        Self {
152            qci: 9,
153            arp_priority_level: 8,
154            pre_emption_capability: false,
155            pre_emption_vulnerability: true,
156            mbr_uplink_kbps: 0,
157            mbr_downlink_kbps: 0,
158            gbr_uplink_kbps: 0,
159            gbr_downlink_kbps: 0,
160        }
161    }
162}
163
164/// The 40-bit bit-rate fields of a Bearer QoS IE hold kilobits per second.
165const MAX_BITRATE_KBPS: u64 = 0xFF_FFFF_FFFF;
166
167impl BearerQos {
168    pub fn from_json(v: Option<&Value>) -> Result<Self> {
169        let mut q = Self::default();
170        let Some(v) = v else { return Ok(q) };
171        ensure!(v.is_object(), "qos is an object");
172        if let Some(x) = v.get("qci") {
173            q.qci = small(x, "qos.qci", 1, 254)? as u8;
174        }
175        if let Some(x) = v.get("arp_priority_level") {
176            q.arp_priority_level = small(x, "qos.arp_priority_level", 1, 15)? as u8;
177        }
178        if let Some(x) = v.get("pre_emption_capability") {
179            q.pre_emption_capability = x
180                .as_bool()
181                .context("qos.pre_emption_capability is true or false")?;
182        }
183        if let Some(x) = v.get("pre_emption_vulnerability") {
184            q.pre_emption_vulnerability = x
185                .as_bool()
186                .context("qos.pre_emption_vulnerability is true or false")?;
187        }
188        for (key, slot) in [
189            ("mbr_uplink_kbps", &mut q.mbr_uplink_kbps),
190            ("mbr_downlink_kbps", &mut q.mbr_downlink_kbps),
191            ("gbr_uplink_kbps", &mut q.gbr_uplink_kbps),
192            ("gbr_downlink_kbps", &mut q.gbr_downlink_kbps),
193        ] {
194            if let Some(x) = v.get(key) {
195                *slot = small(x, &format!("qos.{key}"), 0, MAX_BITRATE_KBPS)?;
196            }
197        }
198        Ok(q)
199    }
200
201    /// TS 29.274 §8.15: `spare|PCI|PL(4)|spare|PVI`, QCI, then four 40-bit rates.
202    /// PCI and PVI are *disable* bits: 0 means the capability/vulnerability is on.
203    pub fn encode_v2(&self) -> Vec<u8> {
204        let mut out = Vec::with_capacity(22);
205        let pci = u8::from(!self.pre_emption_capability);
206        let pvi = u8::from(!self.pre_emption_vulnerability);
207        out.push((pci << 6) | ((self.arp_priority_level & 0x0F) << 2) | pvi);
208        out.push(self.qci);
209        for rate in [
210            self.mbr_uplink_kbps,
211            self.mbr_downlink_kbps,
212            self.gbr_uplink_kbps,
213            self.gbr_downlink_kbps,
214        ] {
215            out.extend_from_slice(&rate.to_be_bytes()[3..8]);
216        }
217        out
218    }
219
220    /// TS 29.060 §7.7.34: the Allocation/Retention Priority octet, then a TS 24.008 QoS
221    /// profile. The profile is the R97 best-effort one (delay class 1, reliability class 3,
222    /// peak throughput class 9, precedence 2, best-effort mean throughput) — the octets
223    /// Osmocom's `sgsnemu` sends by default. Only the ARP comes from the model.
224    pub fn encode_v1(&self) -> Vec<u8> {
225        vec![self.arp_priority_level.clamp(1, 3), 0x0B, 0x92, 0x1F]
226    }
227}
228
229fn small(v: &Value, name: &str, min: u64, max: u64) -> Result<u64> {
230    let n = v
231        .as_u64()
232        .with_context(|| format!("{name} is a whole number"))?;
233    ensure!(
234        (min..=max).contains(&n),
235        "{name} is {n}, outside {min}..={max}"
236    );
237    Ok(n)
238}
239
240/// Everything a Create Session / Create PDP Context Request needs, already validated.
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub struct CreateParams {
243    pub imsi: String,
244    pub msisdn: Option<String>,
245    pub mei: Option<String>,
246    pub apn: String,
247    pub pdn_type: u8,
248    pub ebi: u8,
249    pub rat_type: u8,
250    pub mcc: String,
251    pub mnc: String,
252    pub selection_mode: u8,
253    pub qos: BearerQos,
254    pub ambr_uplink_kbps: u32,
255    pub ambr_downlink_kbps: u32,
256    pub request_dns: bool,
257    pub pgw_address: Option<IpAddr>,
258}
259
260/// Bounds on what the model may name. Each is a format limit, not a policy.
261fn digits(v: &Value, name: &str, min: usize, max: usize) -> Result<String> {
262    let s = v
263        .as_str()
264        .with_context(|| format!("{name} is a string of digits"))?;
265    ensure!(
266        s.chars().all(|c| c.is_ascii_digit()),
267        "{name} must be decimal digits only"
268    );
269    ensure!(
270        (min..=max).contains(&s.len()),
271        "{name} has {} digits, outside {min}..={max}",
272        s.len()
273    );
274    Ok(s.to_string())
275}
276
277/// Longest APN (TS 23.003 §9.1).
278pub const MAX_APN_LEN: usize = 100;
279
280pub fn rat_value(version: GtpVersion, name: &str) -> Result<u8> {
281    let n = name
282        .trim()
283        .to_ascii_uppercase()
284        .replace(['-', ' ', '_'], "");
285    let pairs: &[(&str, u8)] = &[
286        ("UTRAN", 1),
287        ("GERAN", 2),
288        ("WLAN", 3),
289        ("GAN", 4),
290        ("HSPAEVOLUTION", 5),
291        ("EUTRAN", 6),
292        ("EUTRANNBIOT", 8),
293        ("LTEM", 9),
294        ("NR", 10),
295    ];
296    let v = pairs
297        .iter()
298        .find(|(k, _)| *k == n)
299        .map(|(_, v)| *v)
300        .with_context(|| {
301            format!("rat_type {name:?} is not one of UTRAN, GERAN, WLAN, GAN, HSPA Evolution, EUTRAN, EUTRAN-NB-IoT, LTE-M, NR")
302        })?;
303    ensure!(
304        version == GtpVersion::V2 || v <= 6,
305        "rat_type {name:?} has no GTPv1 code"
306    );
307    Ok(v)
308}
309
310impl CreateParams {
311    pub fn from_action(role: Role, a: &Value) -> Result<Self> {
312        let version = role.version();
313        let imsi = digits(a.get("imsi").context("imsi is required")?, "imsi", 5, 15)?;
314        let msisdn = a
315            .get("msisdn")
316            .map(|v| digits(v, "msisdn", 1, 15))
317            .transpose()?;
318        let mei = a.get("mei").map(|v| digits(v, "mei", 14, 16)).transpose()?;
319        let apn = a
320            .get("apn")
321            .and_then(Value::as_str)
322            .context("apn is required (a dotted name such as \"internet\")")?
323            .to_string();
324        ensure!(
325            !apn.is_empty() && apn.len() <= MAX_APN_LEN,
326            "apn is 1..={MAX_APN_LEN} characters"
327        );
328        codec::encode_apn(&apn)?;
329        let pdn_type = match a
330            .get("pdn_type")
331            .and_then(Value::as_str)
332            .unwrap_or("ipv4")
333            .to_ascii_lowercase()
334            .as_str()
335        {
336            "ipv4" => 1,
337            "ipv6" => 2,
338            "ipv4v6" => 3,
339            other => bail!("pdn_type {other:?} is ipv4, ipv6 or ipv4v6"),
340        };
341        let ebi = match a.get("ebi") {
342            Some(v) => small(v, "ebi", 5, 15)? as u8,
343            None => 5,
344        };
345        let rat_type = match a.get("rat_type").and_then(Value::as_str) {
346            Some(r) => rat_value(version, r)?,
347            None if version == GtpVersion::V1 => 1,
348            None => 6,
349        };
350        let mcc = match a.get("mcc") {
351            Some(v) => digits(v, "mcc", 3, 3)?,
352            None => "001".into(),
353        };
354        let mnc = match a.get("mnc") {
355            Some(v) => digits(v, "mnc", 2, 3)?,
356            None => "01".into(),
357        };
358        let selection_mode = match a.get("selection_mode") {
359            Some(v) => small(v, "selection_mode", 0, 3)? as u8,
360            None => 0,
361        };
362        let qos = BearerQos::from_json(a.get("qos"))?;
363        let ambr = |k: &str| -> Result<u32> {
364            match a.get(k) {
365                Some(v) => Ok(small(v, k, 0, u32::MAX as u64)? as u32),
366                None => Ok(DEFAULT_AMBR_KBPS),
367            }
368        };
369        let ambr_uplink_kbps = ambr("ambr_uplink_kbps")?;
370        let ambr_downlink_kbps = ambr("ambr_downlink_kbps")?;
371        let request_dns = match a.get("request_dns") {
372            Some(v) => v.as_bool().context("request_dns is true or false")?,
373            None => true,
374        };
375        let pgw_address = match a.get("pgw_address").and_then(Value::as_str) {
376            Some(s) => Some(
377                s.parse::<IpAddr>()
378                    .with_context(|| format!("pgw_address {s:?} is not an IP address"))?,
379            ),
380            None => None,
381        };
382        if role == Role::S11 {
383            ensure!(
384                pgw_address.is_some(),
385                "on S11 the SGW needs the PGW to forward to: give pgw_address"
386            );
387        }
388        Ok(Self {
389            imsi,
390            msisdn,
391            mei,
392            apn,
393            pdn_type,
394            ebi,
395            rat_type,
396            mcc,
397            mnc,
398            selection_mode,
399            qos,
400            ambr_uplink_kbps,
401            ambr_downlink_kbps,
402            request_dns,
403            pgw_address,
404        })
405    }
406}
407
408pub const DEFAULT_AMBR_KBPS: u32 = 100_000;
409
410/// The addresses and tunnel ids this client advertises for one session.
411#[derive(Clone, Copy, Debug)]
412pub struct Local {
413    pub control_ip: IpAddr,
414    pub user_ip: IpAddr,
415    pub control_teid: u32,
416    pub data_teid: u32,
417}
418
419/// TS 24.008 §10.5.1.13 PLMN: MCC2|MCC1, MNC3|MCC3, MNC2|MNC1; a two-digit MNC puts F in
420/// MNC3.
421pub fn encode_plmn(mcc: &str, mnc: &str) -> Vec<u8> {
422    let d = |s: &str, i: usize| s.as_bytes().get(i).map(|b| b - b'0').unwrap_or(0x0F);
423    vec![
424        (d(mcc, 1) << 4) | d(mcc, 0),
425        (d(mnc, 2) << 4) | d(mcc, 2),
426        (d(mnc, 1) << 4) | d(mnc, 0),
427    ]
428}
429
430/// A TBCD field padded with filler to a fixed length (GTPv1 IMSI and IMEI(SV) are TV, 8
431/// octets).
432fn tbcd_fixed(digits: &str, len: usize) -> Vec<u8> {
433    let mut v = codec::encode_tbcd(digits);
434    v.resize(len, 0xFF);
435    v
436}
437
438/// PCO asking for DNS servers: container 0x000D (DNS Server IPv4 Address Request), and
439/// 0x0003 for IPv6 when the PDN is v6.
440fn pco_dns_request(pdn_type: u8) -> Vec<u8> {
441    let mut out = vec![0x80];
442    if pdn_type != 2 {
443        out.extend_from_slice(&[0x00, 0x0D, 0x00]);
444    }
445    if pdn_type != 1 {
446        out.extend_from_slice(&[0x00, 0x03, 0x00]);
447    }
448    out
449}
450
451fn ip_octets(ip: IpAddr) -> Vec<u8> {
452    match ip {
453        IpAddr::V4(a) => a.octets().to_vec(),
454        IpAddr::V6(a) => a.octets().to_vec(),
455    }
456}
457
458fn v1ie(ie_type: u8, value: Vec<u8>) -> GtpV1Ie {
459    GtpV1Ie { ie_type, value }
460}
461
462fn v1_encode(message_type: u8, teid: u32, sequence: u32, ies: &[GtpV1Ie]) -> Vec<u8> {
463    GtpV1Message {
464        header: GtpV1Header::with_sequence(message_type, teid, sequence as u16),
465        body: codec::encode_v1_ies(ies),
466    }
467    .encode()
468}
469
470fn v2_encode(header: GtpV2Header, ies: Vec<GtpV2Ie>) -> Vec<u8> {
471    GtpV2Message { header, ies }.encode()
472}
473
474/// Echo Request on either plane. GTPv1 carries no IE (Recovery belongs to the response);
475/// GTPv2 carries Recovery (TS 29.274 Table 7.1.1-1).
476pub fn echo_request(version: GtpVersion, sequence: u32, recovery: u8) -> Vec<u8> {
477    match version {
478        GtpVersion::V1 => v1_encode(codec::V1_ECHO_REQUEST, 0, sequence, &[]),
479        GtpVersion::V2 => v2_encode(
480            GtpV2Header::without_teid(codec::V2_ECHO_REQUEST, sequence),
481            vec![GtpV2Ie::new(codec::V2_IE_RECOVERY, 0, vec![recovery])],
482        ),
483    }
484}
485
486/// Echo Response to the peer's Echo Request, with this node's Recovery counter.
487pub fn echo_response(version: GtpVersion, sequence: u32, recovery: u8) -> Vec<u8> {
488    match version {
489        GtpVersion::V1 => v1_encode(
490            codec::V1_ECHO_RESPONSE,
491            0,
492            sequence,
493            &[v1ie(codec::V1_IE_RECOVERY, vec![recovery])],
494        ),
495        GtpVersion::V2 => v2_encode(
496            GtpV2Header::without_teid(codec::V2_ECHO_RESPONSE, sequence),
497            vec![GtpV2Ie::new(codec::V2_IE_RECOVERY, 0, vec![recovery])],
498        ),
499    }
500}
501
502/// Create Session Request (GTPv2, TS 29.274 §7.2.1) or Create PDP Context Request (GTPv1,
503/// TS 29.060 §7.3.1).
504pub fn create_request(
505    role: Role,
506    p: &CreateParams,
507    local: &Local,
508    sequence: u32,
509    recovery: u8,
510) -> Result<Vec<u8>> {
511    let apn = codec::encode_apn(&p.apn)?;
512    Ok(match role {
513        Role::Gn => {
514            // TV IEs must appear in ascending type order; the TLV ones follow.
515            let mut ies = vec![
516                v1ie(codec::V1_IE_IMSI, tbcd_fixed(&p.imsi, 8)),
517                v1ie(codec::V1_IE_RECOVERY, vec![recovery]),
518                v1ie(codec::V1_IE_SELECTION_MODE, vec![0xFC | p.selection_mode]),
519                v1ie(
520                    codec::V1_IE_TEID_DATA_I,
521                    local.data_teid.to_be_bytes().to_vec(),
522                ),
523                v1ie(
524                    codec::V1_IE_TEID_CONTROL_PLANE,
525                    local.control_teid.to_be_bytes().to_vec(),
526                ),
527                v1ie(codec::V1_IE_NSAPI, vec![p.ebi & 0x0F]),
528                v1ie(
529                    codec::V1_IE_END_USER_ADDRESS,
530                    vec![
531                        0xF1,
532                        match p.pdn_type {
533                            1 => 0x21,
534                            2 => 0x57,
535                            _ => 0x8D,
536                        },
537                    ],
538                ),
539                v1ie(codec::V1_IE_ACCESS_POINT_NAME, apn),
540            ];
541            if p.request_dns {
542                ies.push(v1ie(
543                    codec::V1_IE_PROTOCOL_CONFIG_OPTIONS,
544                    pco_dns_request(p.pdn_type),
545                ));
546            }
547            ies.push(v1ie(codec::V1_IE_GSN_ADDRESS, ip_octets(local.control_ip)));
548            ies.push(v1ie(codec::V1_IE_GSN_ADDRESS, ip_octets(local.user_ip)));
549            if let Some(m) = &p.msisdn {
550                // Numbering plan octet: international number, ISDN/E.164.
551                let mut v = vec![0x91];
552                v.extend(codec::encode_tbcd(m));
553                ies.push(v1ie(codec::V1_IE_MSISDN, v));
554            }
555            ies.push(v1ie(codec::V1_IE_QOS_PROFILE, p.qos.encode_v1()));
556            ies.push(v1ie(codec::V1_IE_RAT_TYPE, vec![p.rat_type]));
557            if let Some(m) = &p.mei {
558                ies.push(v1ie(codec::V1_IE_IMEISV, tbcd_fixed(m, 8)));
559            }
560            v1_encode(codec::V1_CREATE_PDP_CONTEXT_REQUEST, 0, sequence, &ies)
561        }
562        Role::S5 | Role::S11 => {
563            let sender_if = if role == Role::S5 {
564                IF_S5S8_SGW_GTPC
565            } else {
566                IF_S11_MME_GTPC
567            };
568            let mut ies = vec![GtpV2Ie::new(
569                codec::V2_IE_IMSI,
570                0,
571                codec::encode_tbcd(&p.imsi),
572            )];
573            if let Some(m) = &p.msisdn {
574                ies.push(GtpV2Ie::new(codec::V2_IE_MSISDN, 0, codec::encode_tbcd(m)));
575            }
576            if let Some(m) = &p.mei {
577                ies.push(GtpV2Ie::new(V2_IE_MEI, 0, codec::encode_tbcd(m)));
578            }
579            ies.push(GtpV2Ie::new(
580                V2_IE_SERVING_NETWORK,
581                0,
582                encode_plmn(&p.mcc, &p.mnc),
583            ));
584            ies.push(GtpV2Ie::new(codec::V2_IE_RAT_TYPE, 0, vec![p.rat_type]));
585            ies.push(GtpV2Ie::new(
586                codec::V2_IE_FTEID,
587                0,
588                codec::encode_fteid(sender_if, local.control_teid, local.control_ip),
589            ));
590            if let (Role::S11, Some(pgw)) = (role, p.pgw_address) {
591                ies.push(GtpV2Ie::new(
592                    codec::V2_IE_FTEID,
593                    1,
594                    codec::encode_fteid(IF_S5S8_PGW_GTPC, 0, pgw),
595                ));
596            }
597            ies.push(GtpV2Ie::new(codec::V2_IE_APN, 0, apn));
598            // GTPv2 spare bits are zero (TS 29.274 §8.1); GTPv1's are one (TS 29.060
599            // §7.7.12), which is why the two Selection Modes differ by 0xFC.
600            ies.push(GtpV2Ie::new(
601                V2_IE_SELECTION_MODE,
602                0,
603                vec![p.selection_mode],
604            ));
605            ies.push(GtpV2Ie::new(V2_IE_PDN_TYPE, 0, vec![p.pdn_type]));
606            ies.push(GtpV2Ie::new(
607                codec::V2_IE_PAA,
608                0,
609                match p.pdn_type {
610                    1 => vec![1, 0, 0, 0, 0],
611                    2 => {
612                        let mut v = vec![2, 64];
613                        v.extend([0u8; 16]);
614                        v
615                    }
616                    _ => {
617                        let mut v = vec![3, 64];
618                        v.extend([0u8; 20]);
619                        v
620                    }
621                },
622            ));
623            ies.push(GtpV2Ie::new(codec::V2_IE_APN_RESTRICTION, 0, vec![0]));
624            let mut ambr = p.ambr_uplink_kbps.to_be_bytes().to_vec();
625            ambr.extend(p.ambr_downlink_kbps.to_be_bytes());
626            ies.push(GtpV2Ie::new(V2_IE_AMBR, 0, ambr));
627            if p.request_dns {
628                ies.push(GtpV2Ie::new(
629                    codec::V2_IE_PCO,
630                    0,
631                    pco_dns_request(p.pdn_type),
632                ));
633            }
634            let mut bearer = vec![GtpV2Ie::new(codec::V2_IE_EBI, 0, vec![p.ebi & 0x0F])];
635            if role == Role::S5 {
636                bearer.push(GtpV2Ie::new(
637                    codec::V2_IE_FTEID,
638                    2,
639                    codec::encode_fteid(IF_S5S8_SGW_GTPU, local.data_teid, local.user_ip),
640                ));
641            }
642            bearer.push(GtpV2Ie::new(V2_IE_BEARER_QOS, 0, p.qos.encode_v2()));
643            ies.push(GtpV2Ie::new(
644                codec::V2_IE_BEARER_CONTEXT,
645                0,
646                codec::encode_v2_grouped(&bearer),
647            ));
648            ies.push(GtpV2Ie::new(codec::V2_IE_RECOVERY, 0, vec![recovery]));
649            v2_encode(
650                GtpV2Header::new(codec::V2_CREATE_SESSION_REQUEST, 0, sequence),
651                ies,
652            )
653        }
654    })
655}
656
657/// Modify Bearer Request (GTPv2 §7.2.7) or Update PDP Context Request (GTPv1 §7.3.3),
658/// addressed to the peer's control TEID. It tells the peer where this node's end of the user
659/// plane is: on S11 as the eNodeB's S1-U F-TEID, on S5 as the SGW's S5/S8-U F-TEID.
660pub fn modify_request(
661    role: Role,
662    peer_control_teid: u32,
663    ebi: u8,
664    qos: &BearerQos,
665    local: &Local,
666    sequence: u32,
667) -> Vec<u8> {
668    match role {
669        Role::Gn => {
670            let ies = vec![
671                v1ie(
672                    codec::V1_IE_TEID_DATA_I,
673                    local.data_teid.to_be_bytes().to_vec(),
674                ),
675                v1ie(
676                    codec::V1_IE_TEID_CONTROL_PLANE,
677                    local.control_teid.to_be_bytes().to_vec(),
678                ),
679                v1ie(codec::V1_IE_NSAPI, vec![ebi & 0x0F]),
680                v1ie(codec::V1_IE_GSN_ADDRESS, ip_octets(local.control_ip)),
681                v1ie(codec::V1_IE_GSN_ADDRESS, ip_octets(local.user_ip)),
682                v1ie(codec::V1_IE_QOS_PROFILE, qos.encode_v1()),
683            ];
684            v1_encode(
685                codec::V1_UPDATE_PDP_CONTEXT_REQUEST,
686                peer_control_teid,
687                sequence,
688                &ies,
689            )
690        }
691        Role::S5 | Role::S11 => {
692            let (instance, iface) = if role == Role::S11 {
693                (0, IF_S1U_ENB_GTPU)
694            } else {
695                (1, IF_S5S8_SGW_GTPU)
696            };
697            let bearer = vec![
698                GtpV2Ie::new(codec::V2_IE_EBI, 0, vec![ebi & 0x0F]),
699                GtpV2Ie::new(
700                    codec::V2_IE_FTEID,
701                    instance,
702                    codec::encode_fteid(iface, local.data_teid, local.user_ip),
703                ),
704            ];
705            v2_encode(
706                GtpV2Header::new(codec::V2_MODIFY_BEARER_REQUEST, peer_control_teid, sequence),
707                vec![GtpV2Ie::new(
708                    codec::V2_IE_BEARER_CONTEXT,
709                    0,
710                    codec::encode_v2_grouped(&bearer),
711                )],
712            )
713        }
714    }
715}
716
717/// Delete Session Request (GTPv2 §7.2.9, with the Linked EPS Bearer ID) or Delete PDP Context
718/// Request (GTPv1 §7.3.5, with Teardown Ind set so the whole PDP address goes).
719pub fn delete_request(role: Role, peer_control_teid: u32, ebi: u8, sequence: u32) -> Vec<u8> {
720    match role {
721        Role::Gn => v1_encode(
722            codec::V1_DELETE_PDP_CONTEXT_REQUEST,
723            peer_control_teid,
724            sequence,
725            &[
726                v1ie(codec::V1_IE_TEARDOWN_IND, vec![0xFF]),
727                v1ie(codec::V1_IE_NSAPI, vec![ebi & 0x0F]),
728            ],
729        ),
730        Role::S5 | Role::S11 => v2_encode(
731            GtpV2Header::new(
732                codec::V2_DELETE_SESSION_REQUEST,
733                peer_control_teid,
734                sequence,
735            ),
736            vec![GtpV2Ie::new(codec::V2_IE_EBI, 0, vec![ebi & 0x0F])],
737        ),
738    }
739}
740
741/// A response to a request the peer sent, carrying only a Cause.
742pub fn cause_response(
743    version: GtpVersion,
744    message_type: u8,
745    teid: u32,
746    sequence: u32,
747    cause: u8,
748) -> Vec<u8> {
749    match version {
750        GtpVersion::V1 => v1_encode(
751            message_type,
752            teid,
753            sequence,
754            &[v1ie(codec::V1_IE_CAUSE, vec![cause])],
755        ),
756        GtpVersion::V2 => v2_encode(
757            GtpV2Header::new(message_type, teid, sequence),
758            vec![GtpV2Ie::new(codec::V2_IE_CAUSE, 0, vec![cause, 0])],
759        ),
760    }
761}
762
763/// The cause's symbolic name in this version's numbering, or `cause_<n>`.
764pub fn cause_name(version: GtpVersion, value: u8) -> String {
765    codec::CAUSES
766        .iter()
767        .find(|c| match version {
768            GtpVersion::V1 => c.v1 == value,
769            GtpVersion::V2 => c.v2 == value,
770        })
771        .map(|c| c.name.to_string())
772        .unwrap_or_else(|| match (version, value) {
773            (GtpVersion::V2, 64) => "context_not_found".into(),
774            (GtpVersion::V2, 96) => "imsi_imei_not_known".into(),
775            _ => format!("cause_{value}"),
776        })
777}
778
779/// Resolve a cause the model named (`request_accepted`) or numbered.
780pub fn cause_value(version: GtpVersion, v: &Value) -> Result<u8> {
781    if let Some(n) = v.as_u64() {
782        ensure!(n <= 255, "cause {n} is one octet");
783        return Ok(n as u8);
784    }
785    let name = v.as_str().context("cause is a name or a number")?;
786    let c = codec::cause_by_name(name).with_context(|| {
787        format!(
788            "cause {name:?} is not one of {}",
789            codec::cause_names().join(", ")
790        )
791    })?;
792    Ok(match version {
793        GtpVersion::V1 => c.v1,
794        GtpVersion::V2 => c.v2,
795    })
796}
797
798// ===========================================================================
799// Reading answers
800// ===========================================================================
801
802/// One decoded control-plane datagram, version-neutral.
803#[derive(Debug)]
804pub struct Control {
805    pub version: GtpVersion,
806    pub message_type: u8,
807    pub sequence: u32,
808    pub teid: u32,
809    pub v1: Vec<GtpV1Ie>,
810    pub v2: Vec<GtpV2Ie>,
811}
812
813impl Control {
814    pub fn name(&self) -> &'static str {
815        message_name(self.version, self.message_type)
816    }
817    /// A request the peer may send this node that it answers with `type + 1` — other than
818    /// Echo, which Rust answers. GTPv1 (TS 29.060 Table 1): Create/Update/Delete PDP Context,
819    /// Initiate PDP Context Activation, PDU Notification. GTPv2 (TS 29.274 Table 6.1-1):
820    /// Create/Modify/Delete Session, Create/Update/Delete Bearer, Delete PDN Connection Set.
821    /// Parity is no guide: GTPv2's bearer requests are odd and GTPv1's PDU Notification is.
822    pub fn is_peer_request(&self) -> bool {
823        match self.version {
824            GtpVersion::V1 => matches!(self.message_type, 16 | 18 | 20 | 22 | 27),
825            GtpVersion::V2 => matches!(self.message_type, 32 | 34 | 36 | 95 | 97 | 99 | 101),
826        }
827    }
828}
829
830/// Message names, including the requests a peer may send that the server codec never needed.
831pub fn message_name(version: GtpVersion, t: u8) -> &'static str {
832    match (version, t) {
833        (GtpVersion::V1, 22) => "Initiate PDP Context Activation Request",
834        (GtpVersion::V1, 23) => "Initiate PDP Context Activation Response",
835        (GtpVersion::V1, 27) => "PDU Notification Request",
836        (GtpVersion::V1, 28) => "PDU Notification Response",
837        (GtpVersion::V1, _) => codec::v1_message_name(t),
838        (GtpVersion::V2, 3) => "Version Not Supported Indication",
839        (GtpVersion::V2, 95) => "Create Bearer Request",
840        (GtpVersion::V2, 96) => "Create Bearer Response",
841        (GtpVersion::V2, 97) => "Update Bearer Request",
842        (GtpVersion::V2, 98) => "Update Bearer Response",
843        (GtpVersion::V2, 99) => "Delete Bearer Request",
844        (GtpVersion::V2, 100) => "Delete Bearer Response",
845        (GtpVersion::V2, 101) => "Delete PDN Connection Set Request",
846        (GtpVersion::V2, 102) => "Delete PDN Connection Set Response",
847        (GtpVersion::V2, _) => codec::v2_message_name(t),
848    }
849}
850
851/// Decode a control-plane datagram, refusing anything malformed or past the IE bound.
852pub fn decode_control(data: &[u8]) -> Result<Control> {
853    ensure!(
854        data.len() <= MAX_DATAGRAM,
855        "datagram of {} octets is past {MAX_DATAGRAM}",
856        data.len()
857    );
858    match codec::peek_version(data) {
859        Some(1) => {
860            let m = GtpV1Message::decode(data)?;
861            ensure!(m.header.protocol_type, "GTP' (charging) is not spoken here");
862            let ies = codec::parse_v1_ies(&m.body)?;
863            ensure!(ies.len() <= MAX_IES, "{} IEs is past {MAX_IES}", ies.len());
864            Ok(Control {
865                version: GtpVersion::V1,
866                message_type: m.header.message_type,
867                sequence: m
868                    .header
869                    .sequence
870                    .map(u32::from)
871                    .context("a GTPv1-C message without a sequence number cannot be matched")?,
872                teid: m.header.teid,
873                v1: ies,
874                v2: vec![],
875            })
876        }
877        Some(2) => {
878            let m = GtpV2Message::decode(data)?;
879            ensure!(
880                m.ies.len() <= MAX_IES,
881                "{} IEs is past {MAX_IES}",
882                m.ies.len()
883            );
884            Ok(Control {
885                version: GtpVersion::V2,
886                message_type: m.header.message_type,
887                sequence: m.header.sequence,
888                teid: m.header.teid.unwrap_or(0),
889                v1: vec![],
890                v2: m.ies,
891            })
892        }
893        Some(v) => bail!("GTP version {v} is not spoken here"),
894        None => bail!("empty datagram"),
895    }
896}
897
898/// A bare 4- or 16-octet address.
899pub fn addr(value: &[u8]) -> Option<IpAddr> {
900    match value.len() {
901        4 => Some(IpAddr::V4(Ipv4Addr::new(
902            value[0], value[1], value[2], value[3],
903        ))),
904        16 => {
905            let mut o = [0u8; 16];
906            o.copy_from_slice(value);
907            Some(IpAddr::V6(Ipv6Addr::from(o)))
908        }
909        _ => None,
910    }
911}
912
913/// DNS servers out of a PCO: containers 0x000D / 0x0003, and the primary/secondary DNS
914/// options inside an IPCP (0x8021) Configure-Nak/Ack, which is how many GGSNs answer.
915pub fn pco_dns(value: &[u8]) -> Vec<String> {
916    let mut out = Vec::new();
917    let mut pos = 1usize;
918    while pos + 3 <= value.len() {
919        let id = u16::from_be_bytes([value[pos], value[pos + 1]]);
920        let len = value[pos + 2] as usize;
921        let Some(c) = value.get(pos + 3..pos + 3 + len) else {
922            break;
923        };
924        match id {
925            0x000D | 0x0003 => {
926                if let Some(a) = addr(c) {
927                    out.push(a.to_string());
928                }
929            }
930            0x8021 if c.len() >= 4 => {
931                let total = (u16::from_be_bytes([c[2], c[3]]) as usize).min(c.len());
932                let mut o = 4usize;
933                while o + 2 <= total {
934                    let (t, l) = (c[o], c[o + 1] as usize);
935                    if l < 2 || o + l > total {
936                        break;
937                    }
938                    if (t == 129 || t == 131) && l == 6 {
939                        out.push(Ipv4Addr::new(c[o + 2], c[o + 3], c[o + 4], c[o + 5]).to_string());
940                    }
941                    o += l;
942                }
943            }
944            _ => {}
945        }
946        pos += 3 + len;
947    }
948    out
949}
950
951fn fteid_json(instance: u8, value: &[u8]) -> Option<Value> {
952    let (iface, teid, address) = codec::decode_fteid(value)?;
953    Some(json!({
954        "instance": instance,
955        "interface": interface_name(iface),
956        "interface_type": iface,
957        "teid": teid,
958        "address": address.map(|a| a.to_string()),
959    }))
960}
961
962/// What the peer told this client in a response: the event data the model sees, plus the
963/// tunnel endpoints Rust keeps for the session.
964#[derive(Debug, Default)]
965pub struct Answer {
966    pub cause: Option<u8>,
967    pub peer_control_teid: Option<u32>,
968    pub peer_control_address: Option<IpAddr>,
969    pub peer_data_teid: Option<u32>,
970    pub peer_data_address: Option<IpAddr>,
971    pub ue_address: Option<IpAddr>,
972    pub data: Value,
973}
974
975/// Read a response into structured fields. `role` decides which F-TEID is the peer's
976/// control plane and which bearer F-TEID is its user plane.
977pub fn read_answer(role: Role, c: &Control) -> Answer {
978    let mut a = Answer {
979        data: json!({}),
980        ..Default::default()
981    };
982    match c.version {
983        GtpVersion::V1 => {
984            let find = |t| codec::find_v1(&c.v1, t);
985            a.cause = find(codec::V1_IE_CAUSE).and_then(|v| v.first().copied());
986            if let Some(r) = find(codec::V1_IE_RECOVERY).and_then(|v| v.first()) {
987                a.data["recovery"] = json!(r);
988            }
989            a.peer_data_teid = find(codec::V1_IE_TEID_DATA_I)
990                .map(|v| u32::from_be_bytes([v[0], v[1], v[2], v[3]]));
991            a.peer_control_teid = find(codec::V1_IE_TEID_CONTROL_PLANE)
992                .map(|v| u32::from_be_bytes([v[0], v[1], v[2], v[3]]));
993            if let Some(v) = find(codec::V1_IE_CHARGING_ID) {
994                a.data["charging_id"] = json!(u32::from_be_bytes([v[0], v[1], v[2], v[3]]));
995            }
996            if let Some(v) = find(codec::V1_IE_END_USER_ADDRESS) {
997                let (t, ip) = codec::decode_end_user_address(v);
998                a.data["pdn_type"] = json!(t);
999                a.ue_address = ip;
1000            }
1001            if let Some(v) = find(codec::V1_IE_PROTOCOL_CONFIG_OPTIONS) {
1002                a.data["dns_servers"] = json!(pco_dns(v));
1003            }
1004            let gsn = codec::find_all_v1(&c.v1, codec::V1_IE_GSN_ADDRESS);
1005            a.peer_control_address = gsn.first().and_then(|v| addr(v));
1006            a.peer_data_address = gsn.get(1).and_then(|v| addr(v));
1007        }
1008        GtpVersion::V2 => {
1009            let top = |t, i| {
1010                c.v2.iter()
1011                    .find(|ie| ie.ie_type == t && ie.instance == i)
1012                    .map(|ie| ie.value.as_slice())
1013            };
1014            a.cause = top(codec::V2_IE_CAUSE, 0).and_then(|v| v.first().copied());
1015            if let Some(r) = top(codec::V2_IE_RECOVERY, 0).and_then(|v| v.first()) {
1016                a.data["recovery"] = json!(r);
1017            }
1018            if let Some(v) = top(codec::V2_IE_PAA, 0) {
1019                let (t, ip) = codec::decode_paa(v);
1020                a.data["pdn_type"] = json!(t);
1021                a.ue_address = ip;
1022            }
1023            if let Some(v) = top(codec::V2_IE_PCO, 0) {
1024                a.data["dns_servers"] = json!(pco_dns(v));
1025            }
1026            if let Some(v) = top(V2_IE_AMBR, 0).filter(|v| v.len() == 8) {
1027                a.data["ambr_uplink_kbps"] = json!(u32::from_be_bytes([v[0], v[1], v[2], v[3]]));
1028                a.data["ambr_downlink_kbps"] = json!(u32::from_be_bytes([v[4], v[5], v[6], v[7]]));
1029            }
1030            if let Some(v) = top(codec::V2_IE_APN_RESTRICTION, 0).and_then(|v| v.first()) {
1031                a.data["apn_restriction"] = json!(v);
1032            }
1033            // Control F-TEIDs at the top level: the sender's own (instance 0) is the peer's
1034            // control plane; on S11 instance 1 is the PGW behind the SGW.
1035            let mut fteids = Vec::new();
1036            for ie in c.v2.iter().filter(|ie| ie.ie_type == codec::V2_IE_FTEID) {
1037                if let Some(j) = fteid_json(ie.instance, &ie.value) {
1038                    fteids.push(j);
1039                }
1040            }
1041            let ctl = top(codec::V2_IE_FTEID, 0)
1042                .or_else(|| top(codec::V2_IE_FTEID, 1))
1043                .and_then(codec::decode_fteid);
1044            if let Some((_, teid, address)) = ctl {
1045                a.peer_control_teid = Some(teid);
1046                a.peer_control_address = address;
1047            }
1048            a.data["control_fteids"] = Value::Array(fteids);
1049            // Bearer Context created / modified (instance 0).
1050            if let Some(inner) =
1051                top(codec::V2_IE_BEARER_CONTEXT, 0).and_then(|v| codec::parse_v2_grouped(v).ok())
1052            {
1053                let mut bearer = json!({});
1054                let mut data_fteids = Vec::new();
1055                for ie in &inner {
1056                    match ie.ie_type {
1057                        codec::V2_IE_EBI => {
1058                            bearer["ebi"] = json!(ie.value.first().map(|v| v & 0x0F))
1059                        }
1060                        codec::V2_IE_CAUSE => {
1061                            if let Some(v) = ie.value.first() {
1062                                bearer["cause"] = json!(cause_name(GtpVersion::V2, *v));
1063                                bearer["cause_value"] = json!(v);
1064                            }
1065                        }
1066                        V2_IE_CHARGING_ID if ie.value.len() == 4 => {
1067                            bearer["charging_id"] = json!(u32::from_be_bytes([
1068                                ie.value[0],
1069                                ie.value[1],
1070                                ie.value[2],
1071                                ie.value[3]
1072                            ]))
1073                        }
1074                        codec::V2_IE_FTEID => {
1075                            if let Some(j) = fteid_json(ie.instance, &ie.value) {
1076                                data_fteids.push(j);
1077                            }
1078                        }
1079                        _ => {}
1080                    }
1081                }
1082                let wanted = if role == Role::S11 {
1083                    IF_S1U_SGW_GTPU
1084                } else {
1085                    IF_S5S8_PGW_GTPU
1086                };
1087                let user = inner
1088                    .iter()
1089                    .filter(|ie| ie.ie_type == codec::V2_IE_FTEID)
1090                    .filter_map(|ie| codec::decode_fteid(&ie.value))
1091                    .find(|f| f.0 == wanted)
1092                    .or_else(|| {
1093                        inner
1094                            .iter()
1095                            .filter(|ie| ie.ie_type == codec::V2_IE_FTEID)
1096                            .find_map(|ie| codec::decode_fteid(&ie.value))
1097                    });
1098                if let Some((_, teid, address)) = user {
1099                    a.peer_data_teid = Some(teid);
1100                    a.peer_data_address = address;
1101                }
1102                bearer["fteids"] = Value::Array(data_fteids);
1103                a.data["bearer"] = bearer;
1104            }
1105        }
1106    }
1107    if let Some(c2) = a.cause {
1108        a.data["cause"] = json!(cause_name(c.version, c2));
1109        a.data["cause_value"] = json!(c2);
1110        a.data["accepted"] = json!(codec::cause_accepts(c.version, c2));
1111    }
1112    if let Some(ip) = a.ue_address {
1113        a.data["ue_address"] = json!(ip.to_string());
1114    }
1115    if let Some(t) = a.peer_control_teid {
1116        a.data["peer_control_teid"] = json!(t);
1117    }
1118    if let Some(t) = a.peer_data_teid {
1119        a.data["peer_data_teid"] = json!(t);
1120    }
1121    if let Some(ip) = a.peer_data_address {
1122        a.data["peer_data_address"] = json!(ip.to_string());
1123    }
1124    a
1125}
1126
1127// ===========================================================================
1128// The user packet inside a G-PDU
1129// ===========================================================================
1130
1131/// RFC 1071 one's-complement sum.
1132pub fn checksum(chunks: &[&[u8]]) -> u16 {
1133    let mut sum: u32 = 0;
1134    for chunk in chunks {
1135        let mut it = chunk.chunks_exact(2);
1136        for w in &mut it {
1137            sum += u16::from_be_bytes([w[0], w[1]]) as u32;
1138        }
1139        if let [b] = it.remainder() {
1140            sum += (*b as u32) << 8;
1141        }
1142    }
1143    while sum >> 16 != 0 {
1144        sum = (sum & 0xFFFF) + (sum >> 16);
1145    }
1146    !(sum as u16)
1147}
1148
1149/// The inner packet as the model describes it.
1150#[derive(Clone, Debug, PartialEq, Eq)]
1151pub enum Inner {
1152    IcmpEcho {
1153        identifier: u16,
1154        sequence: u16,
1155    },
1156    Udp {
1157        source_port: u16,
1158        destination_port: u16,
1159    },
1160}
1161
1162/// Build an IPv4 packet with correct header and transport checksums. Only IPv4: an IPv6
1163/// inner packet is refused by the action check before it gets here.
1164pub fn ipv4_packet(
1165    source: Ipv4Addr,
1166    destination: Ipv4Addr,
1167    ttl: u8,
1168    identification: u16,
1169    inner: &Inner,
1170    payload: &[u8],
1171) -> Result<Vec<u8>> {
1172    ensure!(
1173        payload.len() <= MAX_INNER_PAYLOAD,
1174        "payload of {} octets is past {MAX_INNER_PAYLOAD}",
1175        payload.len()
1176    );
1177    let (protocol, transport) = match inner {
1178        Inner::IcmpEcho {
1179            identifier,
1180            sequence,
1181        } => {
1182            let mut t = vec![8, 0, 0, 0];
1183            t.extend(identifier.to_be_bytes());
1184            t.extend(sequence.to_be_bytes());
1185            t.extend_from_slice(payload);
1186            let c = checksum(&[&t]);
1187            t[2..4].copy_from_slice(&c.to_be_bytes());
1188            (1u8, t)
1189        }
1190        Inner::Udp {
1191            source_port,
1192            destination_port,
1193        } => {
1194            let len = (8 + payload.len()) as u16;
1195            let mut t = Vec::with_capacity(len as usize);
1196            t.extend(source_port.to_be_bytes());
1197            t.extend(destination_port.to_be_bytes());
1198            t.extend(len.to_be_bytes());
1199            t.extend([0, 0]);
1200            t.extend_from_slice(payload);
1201            let mut pseudo = Vec::with_capacity(12);
1202            pseudo.extend(source.octets());
1203            pseudo.extend(destination.octets());
1204            pseudo.extend([0, 17]);
1205            pseudo.extend(len.to_be_bytes());
1206            let mut c = checksum(&[&pseudo, &t]);
1207            if c == 0 {
1208                c = 0xFFFF;
1209            }
1210            t[6..8].copy_from_slice(&c.to_be_bytes());
1211            (17u8, t)
1212        }
1213    };
1214    let total = (20 + transport.len()) as u16;
1215    let mut ip = vec![0x45, 0];
1216    ip.extend(total.to_be_bytes());
1217    ip.extend(identification.to_be_bytes());
1218    ip.extend([0x40, 0]); // Don't Fragment
1219    ip.push(ttl);
1220    ip.push(protocol);
1221    ip.extend([0, 0]);
1222    ip.extend(source.octets());
1223    ip.extend(destination.octets());
1224    let c = checksum(&[&ip]);
1225    ip[10..12].copy_from_slice(&c.to_be_bytes());
1226    ip.extend(transport);
1227    Ok(ip)
1228}
1229
1230/// A G-PDU (TS 29.281 §5.1) carrying `packet` to `teid`, with a sequence number.
1231pub fn gpdu(teid: u32, sequence: u16, packet: &[u8]) -> Vec<u8> {
1232    GtpV1Message {
1233        header: GtpV1Header::with_sequence(codec::V1_G_PDU, teid, sequence),
1234        body: packet.to_vec(),
1235    }
1236    .encode()
1237}
1238
1239/// End Marker (TS 29.281 §7.3.2): no payload, the tunnel's TEID.
1240pub fn end_marker(teid: u32) -> Vec<u8> {
1241    GtpV1Message {
1242        header: GtpV1Header::new(codec::V1_END_MARKER, teid),
1243        body: vec![],
1244    }
1245    .encode()
1246}
1247
1248/// Error Indication (TS 29.281 §7.3.1): TEID Data I is the TEID the offending G-PDU carried,
1249/// GTP-U Peer Address is this node's user-plane address. Header TEID 0, sequence present.
1250pub fn error_indication(teid: u32, sequence: u16, own_user_ip: IpAddr) -> Vec<u8> {
1251    GtpV1Message {
1252        header: GtpV1Header::with_sequence(codec::V1_ERROR_INDICATION, 0, sequence),
1253        body: codec::encode_v1_ies(&[
1254            v1ie(codec::V1_IE_TEID_DATA_I, teid.to_be_bytes().to_vec()),
1255            v1ie(V1_IE_PEER_ADDRESS, ip_octets(own_user_ip)),
1256        ]),
1257    }
1258    .encode()
1259}
1260
1261/// The inner packet of a received G-PDU, for the model: addresses, protocol, ports, ICMP
1262/// type, checksum verdicts and the payload as text (or hex when it is not text).
1263pub fn inner_json(packet: &[u8]) -> Value {
1264    let Some(ip) = codec::decode_inner_ip(packet) else {
1265        return json!({"decoded": false, "length": packet.len()});
1266    };
1267    let mut j = json!({
1268        "decoded": true,
1269        "version": ip.version,
1270        "source": ip.source.to_string(),
1271        "destination": ip.destination.to_string(),
1272        "protocol": ip.protocol_name,
1273        "protocol_number": ip.protocol,
1274        "ttl": ip.ttl,
1275        "length": ip.length,
1276    });
1277    let mut payload_at = ip.payload_offset;
1278    if ip.version == 4 {
1279        let ihl = (packet[0] & 0x0F) as usize * 4;
1280        if packet.len() >= ihl {
1281            j["ipv4_header_checksum_ok"] = json!(checksum(&[&packet[..ihl]]) == 0);
1282            let end = (ip.length as usize).clamp(ihl, packet.len());
1283            let transport = &packet[ihl..end];
1284            if ip.protocol == 1 && transport.len() >= 8 {
1285                j["icmp"] = json!({
1286                    "type": transport[0],
1287                    "type_name": match transport[0] { 0 => "echo_reply", 8 => "echo_request", 3 => "destination_unreachable", 11 => "time_exceeded", _ => "other" },
1288                    "code": transport[1],
1289                    "identifier": u16::from_be_bytes([transport[4], transport[5]]),
1290                    "sequence": u16::from_be_bytes([transport[6], transport[7]]),
1291                    "checksum_ok": checksum(&[transport]) == 0,
1292                });
1293                payload_at = Some(ihl + 8);
1294            }
1295            if ip.protocol == 17 && transport.len() >= 8 {
1296                j["source_port"] = json!(ip.source_port);
1297                j["destination_port"] = json!(ip.destination_port);
1298                let stored = u16::from_be_bytes([transport[6], transport[7]]);
1299                if stored != 0 {
1300                    let mut pseudo = Vec::with_capacity(12);
1301                    pseudo.extend(ip_octets(ip.source));
1302                    pseudo.extend(ip_octets(ip.destination));
1303                    pseudo.extend([0, 17]);
1304                    pseudo.extend((transport.len() as u16).to_be_bytes());
1305                    j["udp_checksum_ok"] = json!(checksum(&[&pseudo, transport]) == 0);
1306                }
1307            }
1308        }
1309    }
1310    if let Some(at) = payload_at.filter(|at| *at <= packet.len()) {
1311        let end = if ip.version == 4 {
1312            (ip.length as usize).clamp(at, packet.len())
1313        } else {
1314            packet.len()
1315        };
1316        let payload = &packet[at..end];
1317        let shown = &payload[..payload.len().min(MAX_EVENT_PAYLOAD)];
1318        let text = std::str::from_utf8(shown)
1319            .ok()
1320            .filter(|s| !crate::utils::sanitize::has_controls(&s.replace(char::is_whitespace, "")));
1321        match text {
1322            Some(t) => {
1323                j["payload"] = json!(t);
1324                j["payload_encoding"] = json!("utf8");
1325            }
1326            None => {
1327                j["payload"] = json!(hex::encode(shown));
1328                j["payload_encoding"] = json!("hex");
1329            }
1330        }
1331        j["payload_length"] = json!(payload.len());
1332        if payload.len() > shown.len() {
1333            j["payload_truncated"] = json!(true);
1334        }
1335    }
1336    j
1337}