1#![deny(missing_docs)]
31
32use packet_dissector_core::packet::DissectBuffer;
33
34macro_rules! ppp_header_descriptors {
35 ($dfn:expr) => {
36 &[
37 FieldDescriptor::new("code", "Code", FieldType::U8).with_display_fn($dfn),
38 FieldDescriptor::new("identifier", "Identifier", FieldType::U8),
39 FieldDescriptor::new("length", "Length", FieldType::U16),
40 ]
41 };
42}
43
44macro_rules! ppp_option_descriptors {
45 ($dfn:expr) => {
46 &[
47 FieldDescriptor::new("type", "Type", FieldType::U8).with_display_fn($dfn),
48 FieldDescriptor::new("length", "Length", FieldType::U8),
49 FieldDescriptor::new("value", "Value", FieldType::Bytes),
50 ]
51 };
52}
53
54pub mod chap;
55pub mod ipcp;
56pub mod lcp;
57pub mod pap;
58
59use packet_dissector_core::dissector::{DispatchHint, DissectResult, Dissector};
60use packet_dissector_core::error::PacketError;
61use packet_dissector_core::field::{FieldDescriptor, FieldType, FieldValue};
62use packet_dissector_core::util::read_be_u16;
63
64pub const PPP_HEADER_SIZE: usize = 4;
71
72const FD_HDR_CODE: usize = 0;
73const FD_HDR_IDENTIFIER: usize = 1;
74const FD_HDR_LENGTH: usize = 2;
75
76pub(crate) static HEADER_DESCRIPTORS: &[FieldDescriptor] =
80 ppp_header_descriptors!(|v, _| match v {
81 FieldValue::U8(c) => Some(code_name(*c)),
82 _ => None,
83 });
84pub(crate) static LCP_HEADER_DESCRIPTORS: &[FieldDescriptor] =
85 ppp_header_descriptors!(|v, _| match v {
86 FieldValue::U8(c) => Some(lcp_code_name(*c)),
87 _ => None,
88 });
89pub(crate) static PAP_HEADER_DESCRIPTORS: &[FieldDescriptor] =
90 ppp_header_descriptors!(|v, _| match v {
91 FieldValue::U8(c) => Some(pap_code_name(*c)),
92 _ => None,
93 });
94pub(crate) static CHAP_HEADER_DESCRIPTORS: &[FieldDescriptor] =
95 ppp_header_descriptors!(|v, _| match v {
96 FieldValue::U8(c) => Some(chap_code_name(*c)),
97 _ => None,
98 });
99
100const FD_OPT_TYPE: usize = 0;
101const FD_OPT_LENGTH: usize = 1;
102const FD_OPT_VALUE: usize = 2;
103
104pub fn parse_header(
110 data: &[u8],
111 offset: usize,
112 descriptors: &'static [FieldDescriptor],
113 buf: &mut DissectBuffer<'_>,
114) -> Option<(u8, u16)> {
115 if data.len() < PPP_HEADER_SIZE {
116 return None;
117 }
118 let code = data[0];
119 let length = read_be_u16(data, 2).unwrap_or_default();
120 buf.push_field(
121 &descriptors[FD_HDR_CODE],
122 FieldValue::U8(code),
123 offset..offset + 1,
124 );
125 buf.push_field(
126 &descriptors[FD_HDR_IDENTIFIER],
127 FieldValue::U8(data[1]),
128 offset + 1..offset + 2,
129 );
130 buf.push_field(
131 &descriptors[FD_HDR_LENGTH],
132 FieldValue::U16(length),
133 offset + 2..offset + 4,
134 );
135 Some((code, length))
136}
137
138pub fn code_name(code: u8) -> &'static str {
145 match code {
146 1 => "Configure-Request",
147 2 => "Configure-Ack",
148 3 => "Configure-Nak",
149 4 => "Configure-Reject",
150 5 => "Terminate-Request",
151 6 => "Terminate-Ack",
152 7 => "Code-Reject",
153 _ => "Unknown",
154 }
155}
156
157pub fn lcp_code_name(code: u8) -> &'static str {
163 match code {
164 1 => "Configure-Request",
165 2 => "Configure-Ack",
166 3 => "Configure-Nak",
167 4 => "Configure-Reject",
168 5 => "Terminate-Request",
169 6 => "Terminate-Ack",
170 7 => "Code-Reject",
171 8 => "Protocol-Reject",
172 9 => "Echo-Request",
173 10 => "Echo-Reply",
174 11 => "Discard-Request",
175 _ => "Unknown",
176 }
177}
178
179pub fn pap_code_name(code: u8) -> &'static str {
183 match code {
184 1 => "Authenticate-Request",
185 2 => "Authenticate-Ack",
186 3 => "Authenticate-Nak",
187 _ => "Unknown",
188 }
189}
190
191pub fn chap_code_name(code: u8) -> &'static str {
195 match code {
196 1 => "Challenge",
197 2 => "Response",
198 3 => "Success",
199 4 => "Failure",
200 _ => "Unknown",
201 }
202}
203
204pub fn parse_protocol<'pkt>(
209 protocol_id: u16,
210 data: &'pkt [u8],
211 offset: usize,
212 buf: &mut DissectBuffer<'pkt>,
213) {
214 match protocol_id {
215 PPP_PROTO_IPCP => ipcp::parse(data, offset, buf),
216 PPP_PROTO_LCP => lcp::parse(data, offset, buf),
217 PPP_PROTO_PAP => pap::parse(data, offset, buf),
218 PPP_PROTO_CHAP => chap::parse(data, offset, buf),
219 _ => {
220 static FD_RAW: FieldDescriptor = FieldDescriptor::new("data", "Data", FieldType::Bytes);
221 buf.push_field(
222 &FD_RAW,
223 FieldValue::Bytes(data),
224 offset..offset + data.len(),
225 );
226 }
227 }
228}
229
230pub(crate) fn parse_options<'pkt>(
235 options_data: &'pkt [u8],
236 base_offset: usize,
237 container_descriptor: &'static FieldDescriptor,
238 descriptors: &'static [FieldDescriptor],
239 value_parser: fn(u8, &[u8]) -> FieldValue,
240 buf: &mut DissectBuffer<'pkt>,
241) -> bool {
242 let mut pos: usize = 0;
243 let mut count = 0;
244 while pos + 2 <= options_data.len() {
245 let opt_type = options_data[pos];
246 let opt_len = options_data[pos + 1] as usize;
247 if opt_len < 2 {
248 break;
249 }
250 if pos
251 .checked_add(opt_len)
252 .is_none_or(|end| end > options_data.len())
253 {
254 break;
255 }
256 let opt_start = base_offset + pos;
257 let opt_value = value_parser(opt_type, &options_data[pos + 2..pos + opt_len]);
258 let obj_idx = buf.begin_container(
259 container_descriptor,
260 FieldValue::Object(0..0),
261 opt_start..opt_start + opt_len,
262 );
263 buf.push_field(
264 &descriptors[FD_OPT_TYPE],
265 FieldValue::U8(opt_type),
266 opt_start..opt_start + 1,
267 );
268 buf.push_field(
269 &descriptors[FD_OPT_LENGTH],
270 FieldValue::U8(opt_len as u8),
271 opt_start + 1..opt_start + 2,
272 );
273 buf.push_field(
274 &descriptors[FD_OPT_VALUE],
275 opt_value,
276 opt_start + 2..opt_start + opt_len,
277 );
278 buf.end_container(obj_idx);
279 count += 1;
280 pos += opt_len;
281 }
282 count > 0
283}
284
285const HDLC_ADDRESS: u8 = 0xFF;
288const HDLC_CONTROL: u8 = 0x03;
289const MIN_FRAME_SIZE: usize = 2;
292const PPP_PROTO_IPV4: u16 = 0x0021;
296const PPP_PROTO_IPV6: u16 = 0x0057;
297const PPP_PROTO_IPCP: u16 = 0x8021;
298const PPP_PROTO_LCP: u16 = 0xC021;
299const PPP_PROTO_PAP: u16 = 0xC023;
300const PPP_PROTO_CHAP: u16 = 0xC223;
301
302const FD_ADDRESS: usize = 0;
303const FD_CONTROL: usize = 1;
304const FD_PROTOCOL: usize = 2;
305const FD_PAYLOAD: usize = 3;
306
307static FIELD_DESCRIPTORS: &[FieldDescriptor] = &[
308 FieldDescriptor::new("address", "Address", FieldType::U8).optional(),
309 FieldDescriptor::new("control", "Control", FieldType::U8).optional(),
310 FieldDescriptor {
311 name: "protocol",
312 display_name: "Protocol",
313 field_type: FieldType::U16,
314 optional: false,
315 children: None,
316 display_fn: Some(|v, _| match v {
317 FieldValue::U16(p) => protocol_name(*p),
318 _ => None,
319 }),
320 format_fn: None,
321 },
322 FieldDescriptor::new("payload", "Payload", FieldType::Object).optional(),
323];
324
325fn protocol_name(proto: u16) -> Option<&'static str> {
326 match proto {
327 0x0021 => Some("IPv4"),
328 0x0057 => Some("IPv6"),
329 0x8021 => Some("IPCP"),
330 0x8057 => Some("IPv6CP"),
331 0xC021 => Some("LCP"),
332 0xC023 => Some("PAP"),
333 0xC223 => Some("CHAP"),
334 0x0031 => Some("Bridging PDU"),
335 0x003D => Some("Multi-Link"),
336 0x00FD => Some("MPPC/MPPE"),
337 0x8031 => Some("Bridging NCP"),
338 0x80FD => Some("CCP"),
339 _ => None,
340 }
341}
342
343pub struct PppDissector;
345
346impl Dissector for PppDissector {
347 fn name(&self) -> &'static str {
348 "Point-to-Point Protocol"
349 }
350 fn short_name(&self) -> &'static str {
351 "PPP"
352 }
353 fn field_descriptors(&self) -> &'static [FieldDescriptor] {
354 FIELD_DESCRIPTORS
355 }
356
357 fn dissect<'pkt>(
358 &self,
359 data: &'pkt [u8],
360 buf: &mut DissectBuffer<'pkt>,
361 offset: usize,
362 ) -> Result<DissectResult, PacketError> {
363 if data.len() < MIN_FRAME_SIZE {
364 return Err(PacketError::Truncated {
365 expected: MIN_FRAME_SIZE,
366 actual: data.len(),
367 });
368 }
369 let mut pos = 0;
370 let has_hdlc = data.len() >= 4 && data[0] == HDLC_ADDRESS && data[1] == HDLC_CONTROL;
371 if has_hdlc {
372 pos = 2;
373 }
374 if data.len() < pos + 2 {
375 return Err(PacketError::Truncated {
376 expected: pos + 2,
377 actual: data.len(),
378 });
379 }
380 let proto = read_be_u16(data, pos)?;
381 pos += 2;
382 let header_len = pos;
383 let payload = &data[pos..];
384 let dispatch = match proto {
385 PPP_PROTO_IPCP | PPP_PROTO_LCP | PPP_PROTO_PAP | PPP_PROTO_CHAP => DispatchHint::End,
386 PPP_PROTO_IPV4 => DispatchHint::ByEtherType(0x0800),
387 PPP_PROTO_IPV6 => DispatchHint::ByEtherType(0x86DD),
388 _ => DispatchHint::End,
389 };
390 buf.begin_layer("PPP", None, FIELD_DESCRIPTORS, offset..offset + header_len);
391 if has_hdlc {
392 buf.push_field(
393 &FIELD_DESCRIPTORS[FD_ADDRESS],
394 FieldValue::U8(HDLC_ADDRESS),
395 offset..offset + 1,
396 );
397 buf.push_field(
398 &FIELD_DESCRIPTORS[FD_CONTROL],
399 FieldValue::U8(HDLC_CONTROL),
400 offset + 1..offset + 2,
401 );
402 }
403 buf.push_field(
404 &FIELD_DESCRIPTORS[FD_PROTOCOL],
405 FieldValue::U16(proto),
406 offset + header_len - 2..offset + header_len,
407 );
408 match proto {
409 PPP_PROTO_IPCP | PPP_PROTO_LCP | PPP_PROTO_PAP | PPP_PROTO_CHAP
410 if !payload.is_empty() =>
411 {
412 let obj_idx = buf.begin_container(
413 &FIELD_DESCRIPTORS[FD_PAYLOAD],
414 FieldValue::Object(0..0),
415 offset + pos..offset + data.len(),
416 );
417 parse_protocol(proto, payload, offset + pos, buf);
418 buf.end_container(obj_idx);
419 }
420 _ => {}
421 }
422 buf.end_layer();
423 Ok(DissectResult::new(header_len, dispatch))
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
446
447 #[test]
448 fn parse_header_valid() {
449 let data = [0x01, 0x42, 0x00, 0x04];
450 let mut buf = DissectBuffer::new();
451 buf.begin_layer("test", None, &[], 0..4);
452 let result = parse_header(&data, 10, HEADER_DESCRIPTORS, &mut buf);
453 buf.end_layer();
454 let (code, length) = result.unwrap();
455 assert_eq!(code, 1);
456 assert_eq!(length, 4);
457 let layer = &buf.layers()[0];
458 let fields = buf.layer_fields(layer);
459 assert_eq!(fields.len(), 3);
460 assert_eq!(fields[0].value, FieldValue::U8(1));
461 assert_eq!(fields[0].range, 10..11);
462 assert_eq!(
463 fields[0].descriptor.display_fn.unwrap()(&fields[0].value, &[]),
464 Some("Configure-Request")
465 );
466 assert_eq!(fields[1].value, FieldValue::U8(0x42));
467 assert_eq!(fields[2].value, FieldValue::U16(4));
468 }
469
470 #[test]
471 fn parse_header_truncated() {
472 let mut buf = DissectBuffer::new();
473 assert!(parse_header(&[0x01, 0x02], 0, HEADER_DESCRIPTORS, &mut buf).is_none());
474 }
475
476 #[test]
477 fn dispatch_ipcp() {
478 let data = [0x01, 0x01, 0x00, 0x04];
479 let mut buf = DissectBuffer::new();
480 buf.begin_layer("test", None, &[], 0..4);
481 let idx = buf.begin_container(
482 &FIELD_DESCRIPTORS[FD_PAYLOAD],
483 FieldValue::Object(0..0),
484 0..4,
485 );
486 parse_protocol(0x8021, &data, 0, &mut buf);
487 buf.end_container(idx);
488 buf.end_layer();
489 let layer = &buf.layers()[0];
490 let fields = buf.layer_fields(layer);
491 assert!(matches!(fields[0].value, FieldValue::Object(_)));
492 }
493
494 #[test]
495 fn dispatch_unknown() {
496 let data = [0x01, 0x02, 0x03];
497 let mut buf = DissectBuffer::new();
498 buf.begin_layer("test", None, &[], 0..3);
499 parse_protocol(0xFFFF, &data, 0, &mut buf);
500 buf.end_layer();
501 let layer = &buf.layers()[0];
502 let fields = buf.layer_fields(layer);
503 assert_eq!(fields[0].value, FieldValue::Bytes(&[0x01, 0x02, 0x03]));
504 }
505
506 #[test]
507 fn dissect_hdlc_ipv4() {
508 let data = [0xFF, 0x03, 0x00, 0x21, 0x45, 0x00, 0x00];
509 let mut buf = DissectBuffer::new();
510 let result = PppDissector.dissect(&data, &mut buf, 0).unwrap();
511 assert_eq!(result.bytes_consumed, 4);
512 assert_eq!(result.next, DispatchHint::ByEtherType(0x0800));
513 let layer = buf.layer_by_name("PPP").unwrap();
514 let fields = buf.layer_fields(layer);
515 assert_eq!(fields.len(), 3);
516 assert_eq!(fields[0].value, FieldValue::U8(0xFF));
517 assert_eq!(fields[1].value, FieldValue::U8(0x03));
518 assert_eq!(fields[2].value, FieldValue::U16(0x0021));
519 let display = fields[2].descriptor.display_fn.unwrap()(&fields[2].value, fields);
520 assert_eq!(display, Some("IPv4"));
521 }
522
523 #[test]
524 fn dissect_no_hdlc_ipv4() {
525 let data = [0x00, 0x21, 0x45, 0x00, 0x00];
526 let mut buf = DissectBuffer::new();
527 let result = PppDissector.dissect(&data, &mut buf, 10).unwrap();
528 assert_eq!(result.bytes_consumed, 2);
529 let layer = buf.layer_by_name("PPP").unwrap();
530 let fields = buf.layer_fields(layer);
531 assert_eq!(fields.len(), 1);
532 assert_eq!(fields[0].value, FieldValue::U16(0x0021));
533 assert_eq!(fields[0].range, 10..12);
534 }
535
536 #[test]
537 fn dissect_ipv6() {
538 let data = [0x00, 0x57, 0x60, 0x00];
539 let mut buf = DissectBuffer::new();
540 let result = PppDissector.dissect(&data, &mut buf, 0).unwrap();
541 assert_eq!(result.next, DispatchHint::ByEtherType(0x86DD));
542 let layer = buf.layer_by_name("PPP").unwrap();
543 let fields = buf.layer_fields(layer);
544 let display = fields[0].descriptor.display_fn.unwrap()(&fields[0].value, fields);
545 assert_eq!(display, Some("IPv6"));
546 }
547
548 #[test]
549 fn dissect_lcp_inline() {
550 let data = [0xC0, 0x21, 0x01, 0x01, 0x00, 0x08, 1, 4, 0x05, 0xDC];
551 let mut buf = DissectBuffer::new();
552 let result = PppDissector.dissect(&data, &mut buf, 0).unwrap();
553 assert_eq!(result.bytes_consumed, 2);
554 assert_eq!(result.next, DispatchHint::End);
555 let layer = buf.layer_by_name("PPP").unwrap();
556 let fields = buf.layer_fields(layer);
557 assert!(fields.len() >= 2);
559 let display = fields[0].descriptor.display_fn.unwrap()(&fields[0].value, fields);
560 assert_eq!(display, Some("LCP"));
561 assert!(matches!(fields[1].value, FieldValue::Object(_)));
562 }
563
564 #[test]
565 fn dissect_unknown_protocol() {
566 let data = [0x00, 0x99, 0xAA, 0xBB];
567 let mut buf = DissectBuffer::new();
568 let result = PppDissector.dissect(&data, &mut buf, 0).unwrap();
569 assert_eq!(result.next, DispatchHint::End);
570 let layer = buf.layer_by_name("PPP").unwrap();
571 let fields = buf.layer_fields(layer);
572 assert_eq!(fields.len(), 1);
573 }
574
575 #[test]
576 fn dissect_truncated() {
577 let mut buf = DissectBuffer::new();
578 let result = PppDissector.dissect(&[0x00], &mut buf, 0);
579 assert!(matches!(result, Err(PacketError::Truncated { .. })));
580 }
581}