use std::fmt::{self, Formatter};
use zerocopy::byteorder::{BigEndian, U16, U32};
use zerocopy::{FromBytes, IntoBytes, Unaligned};
use crate::packet::{HeaderParser, PacketHeader, PacketHeaderError};
pub const L2TP_PORT: u16 = 1701;
pub const L2TPV3_IP_PROTO: u8 = 115;
#[inline]
pub fn is_l2tp_port(port: u16) -> bool {
port == L2TP_PORT
}
#[inline]
pub fn is_l2tpv3_proto(proto: u8) -> bool {
proto == L2TPV3_IP_PROTO
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum L2tpv2MessageType {
Sccrq,
Sccrp,
Scccn,
StopCcn,
Hello,
Ocrq,
Ocrp,
Occn,
Icrq,
Icrp,
Iccn,
Cdn,
Wen,
Sli,
Zlb,
Unknown(u16),
}
impl From<u16> for L2tpv2MessageType {
fn from(value: u16) -> Self {
match value {
1 => L2tpv2MessageType::Sccrq,
2 => L2tpv2MessageType::Sccrp,
3 => L2tpv2MessageType::Scccn,
4 => L2tpv2MessageType::StopCcn,
6 => L2tpv2MessageType::Hello,
7 => L2tpv2MessageType::Ocrq,
8 => L2tpv2MessageType::Ocrp,
9 => L2tpv2MessageType::Occn,
10 => L2tpv2MessageType::Icrq,
11 => L2tpv2MessageType::Icrp,
12 => L2tpv2MessageType::Iccn,
14 => L2tpv2MessageType::Cdn,
15 => L2tpv2MessageType::Wen,
16 => L2tpv2MessageType::Sli,
0 => L2tpv2MessageType::Zlb,
v => L2tpv2MessageType::Unknown(v),
}
}
}
impl fmt::Display for L2tpv2MessageType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
L2tpv2MessageType::Sccrq => write!(f, "SCCRQ"),
L2tpv2MessageType::Sccrp => write!(f, "SCCRP"),
L2tpv2MessageType::Scccn => write!(f, "SCCCN"),
L2tpv2MessageType::StopCcn => write!(f, "StopCCN"),
L2tpv2MessageType::Hello => write!(f, "Hello"),
L2tpv2MessageType::Ocrq => write!(f, "OCRQ"),
L2tpv2MessageType::Ocrp => write!(f, "OCRP"),
L2tpv2MessageType::Occn => write!(f, "OCCN"),
L2tpv2MessageType::Icrq => write!(f, "ICRQ"),
L2tpv2MessageType::Icrp => write!(f, "ICRP"),
L2tpv2MessageType::Iccn => write!(f, "ICCN"),
L2tpv2MessageType::Cdn => write!(f, "CDN"),
L2tpv2MessageType::Wen => write!(f, "WEN"),
L2tpv2MessageType::Sli => write!(f, "SLI"),
L2tpv2MessageType::Zlb => write!(f, "ZLB"),
L2tpv2MessageType::Unknown(v) => write!(f, "Unknown({})", v),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum L2tpv3MessageType {
Sccrq,
Sccrp,
Scccn,
StopCcn,
Hello,
Icrq,
Icrp,
Iccn,
Ocrq,
Ocrp,
Occn,
Cdn,
Sli,
Ack,
Zlb,
Unknown(u16),
}
impl From<u16> for L2tpv3MessageType {
fn from(value: u16) -> Self {
match value {
1 => L2tpv3MessageType::Sccrq,
2 => L2tpv3MessageType::Sccrp,
3 => L2tpv3MessageType::Scccn,
4 => L2tpv3MessageType::StopCcn,
6 => L2tpv3MessageType::Hello,
7 => L2tpv3MessageType::Ocrq,
8 => L2tpv3MessageType::Ocrp,
9 => L2tpv3MessageType::Occn,
10 => L2tpv3MessageType::Icrq,
11 => L2tpv3MessageType::Icrp,
12 => L2tpv3MessageType::Iccn,
14 => L2tpv3MessageType::Cdn,
16 => L2tpv3MessageType::Sli,
20 => L2tpv3MessageType::Ack,
0 => L2tpv3MessageType::Zlb,
v => L2tpv3MessageType::Unknown(v),
}
}
}
impl fmt::Display for L2tpv3MessageType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
L2tpv3MessageType::Sccrq => write!(f, "SCCRQ"),
L2tpv3MessageType::Sccrp => write!(f, "SCCRP"),
L2tpv3MessageType::Scccn => write!(f, "SCCCN"),
L2tpv3MessageType::StopCcn => write!(f, "StopCCN"),
L2tpv3MessageType::Hello => write!(f, "Hello"),
L2tpv3MessageType::Icrq => write!(f, "ICRQ"),
L2tpv3MessageType::Icrp => write!(f, "ICRP"),
L2tpv3MessageType::Iccn => write!(f, "ICCN"),
L2tpv3MessageType::Ocrq => write!(f, "OCRQ"),
L2tpv3MessageType::Ocrp => write!(f, "OCRP"),
L2tpv3MessageType::Occn => write!(f, "OCCN"),
L2tpv3MessageType::Cdn => write!(f, "CDN"),
L2tpv3MessageType::Sli => write!(f, "SLI"),
L2tpv3MessageType::Ack => write!(f, "ACK"),
L2tpv3MessageType::Zlb => write!(f, "ZLB"),
L2tpv3MessageType::Unknown(v) => write!(f, "Unknown({})", v),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum L2tpAvpType {
MessageType,
ResultCode,
ProtocolVersion,
FramingCapabilities,
BearerCapabilities,
TieBreaker,
FirmwareRevision,
HostName,
VendorName,
AssignedTunnelId,
ReceiveWindowSize,
Challenge,
Q931CauseCode,
ChallengeResponse,
AssignedSessionId,
CallSerialNumber,
MinimumBps,
MaximumBps,
BearerType,
FramingType,
CalledNumber,
CallingNumber,
SubAddress,
TxConnectSpeed,
PhysicalChannelId,
InitialReceivedLcpConfreq,
LastSentLcpConfreq,
LastReceivedLcpConfreq,
ProxyAuthenType,
ProxyAuthenName,
ProxyAuthenChallenge,
ProxyAuthenId,
ProxyAuthenResponse,
CallErrors,
Accm,
RandomVector,
PrivateGroupId,
RxConnectSpeed,
SequencingRequired,
Unknown(u16),
}
impl From<u16> for L2tpAvpType {
fn from(value: u16) -> Self {
match value {
0 => L2tpAvpType::MessageType,
1 => L2tpAvpType::ResultCode,
2 => L2tpAvpType::ProtocolVersion,
3 => L2tpAvpType::FramingCapabilities,
4 => L2tpAvpType::BearerCapabilities,
5 => L2tpAvpType::TieBreaker,
6 => L2tpAvpType::FirmwareRevision,
7 => L2tpAvpType::HostName,
8 => L2tpAvpType::VendorName,
9 => L2tpAvpType::AssignedTunnelId,
10 => L2tpAvpType::ReceiveWindowSize,
11 => L2tpAvpType::Challenge,
12 => L2tpAvpType::Q931CauseCode,
13 => L2tpAvpType::ChallengeResponse,
14 => L2tpAvpType::AssignedSessionId,
15 => L2tpAvpType::CallSerialNumber,
16 => L2tpAvpType::MinimumBps,
17 => L2tpAvpType::MaximumBps,
18 => L2tpAvpType::BearerType,
19 => L2tpAvpType::FramingType,
21 => L2tpAvpType::CalledNumber,
22 => L2tpAvpType::CallingNumber,
23 => L2tpAvpType::SubAddress,
24 => L2tpAvpType::TxConnectSpeed,
25 => L2tpAvpType::PhysicalChannelId,
26 => L2tpAvpType::InitialReceivedLcpConfreq,
27 => L2tpAvpType::LastSentLcpConfreq,
28 => L2tpAvpType::LastReceivedLcpConfreq,
29 => L2tpAvpType::ProxyAuthenType,
30 => L2tpAvpType::ProxyAuthenName,
31 => L2tpAvpType::ProxyAuthenChallenge,
32 => L2tpAvpType::ProxyAuthenId,
33 => L2tpAvpType::ProxyAuthenResponse,
34 => L2tpAvpType::CallErrors,
35 => L2tpAvpType::Accm,
36 => L2tpAvpType::RandomVector,
37 => L2tpAvpType::PrivateGroupId,
38 => L2tpAvpType::RxConnectSpeed,
39 => L2tpAvpType::SequencingRequired,
v => L2tpAvpType::Unknown(v),
}
}
}
impl fmt::Display for L2tpAvpType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
L2tpAvpType::MessageType => write!(f, "Message Type"),
L2tpAvpType::ResultCode => write!(f, "Result Code"),
L2tpAvpType::ProtocolVersion => write!(f, "Protocol Version"),
L2tpAvpType::FramingCapabilities => write!(f, "Framing Capabilities"),
L2tpAvpType::BearerCapabilities => write!(f, "Bearer Capabilities"),
L2tpAvpType::TieBreaker => write!(f, "Tie Breaker"),
L2tpAvpType::FirmwareRevision => write!(f, "Firmware Revision"),
L2tpAvpType::HostName => write!(f, "Host Name"),
L2tpAvpType::VendorName => write!(f, "Vendor Name"),
L2tpAvpType::AssignedTunnelId => write!(f, "Assigned Tunnel ID"),
L2tpAvpType::ReceiveWindowSize => write!(f, "Receive Window Size"),
L2tpAvpType::Challenge => write!(f, "Challenge"),
L2tpAvpType::Q931CauseCode => write!(f, "Q.931 Cause Code"),
L2tpAvpType::ChallengeResponse => write!(f, "Challenge Response"),
L2tpAvpType::AssignedSessionId => write!(f, "Assigned Session ID"),
L2tpAvpType::CallSerialNumber => write!(f, "Call Serial Number"),
L2tpAvpType::MinimumBps => write!(f, "Minimum BPS"),
L2tpAvpType::MaximumBps => write!(f, "Maximum BPS"),
L2tpAvpType::BearerType => write!(f, "Bearer Type"),
L2tpAvpType::FramingType => write!(f, "Framing Type"),
L2tpAvpType::CalledNumber => write!(f, "Called Number"),
L2tpAvpType::CallingNumber => write!(f, "Calling Number"),
L2tpAvpType::SubAddress => write!(f, "Sub-Address"),
L2tpAvpType::TxConnectSpeed => write!(f, "Tx Connect Speed"),
L2tpAvpType::PhysicalChannelId => write!(f, "Physical Channel ID"),
L2tpAvpType::InitialReceivedLcpConfreq => write!(f, "Initial Received LCP CONFREQ"),
L2tpAvpType::LastSentLcpConfreq => write!(f, "Last Sent LCP CONFREQ"),
L2tpAvpType::LastReceivedLcpConfreq => write!(f, "Last Received LCP CONFREQ"),
L2tpAvpType::ProxyAuthenType => write!(f, "Proxy Authen Type"),
L2tpAvpType::ProxyAuthenName => write!(f, "Proxy Authen Name"),
L2tpAvpType::ProxyAuthenChallenge => write!(f, "Proxy Authen Challenge"),
L2tpAvpType::ProxyAuthenId => write!(f, "Proxy Authen ID"),
L2tpAvpType::ProxyAuthenResponse => write!(f, "Proxy Authen Response"),
L2tpAvpType::CallErrors => write!(f, "Call Errors"),
L2tpAvpType::Accm => write!(f, "ACCM"),
L2tpAvpType::RandomVector => write!(f, "Random Vector"),
L2tpAvpType::PrivateGroupId => write!(f, "Private Group ID"),
L2tpAvpType::RxConnectSpeed => write!(f, "Rx Connect Speed"),
L2tpAvpType::SequencingRequired => write!(f, "Sequencing Required"),
L2tpAvpType::Unknown(v) => write!(f, "Unknown({})", v),
}
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct L2tpv2Header {
flags_version: U16<BigEndian>,
tunnel_id: U16<BigEndian>,
session_id: U16<BigEndian>,
}
impl L2tpv2Header {
pub const FLAG_TYPE: u16 = 0x8000;
pub const FLAG_LENGTH: u16 = 0x4000;
pub const FLAG_SEQUENCE: u16 = 0x0800;
pub const FLAG_OFFSET: u16 = 0x0200;
pub const FLAG_PRIORITY: u16 = 0x0100;
pub const VERSION_MASK: u16 = 0x000F;
pub const VERSION_2: u16 = 0x0002;
pub const MIN_HEADER_LEN: usize = 6;
pub const LENGTH_FIELD_SIZE: usize = 2;
pub const SEQUENCE_FIELDS_SIZE: usize = 4;
pub const OFFSET_FIELD_SIZE: usize = 2;
#[allow(unused)]
const NAME: &'static str = "L2TPv2";
#[inline]
pub fn flags_version(&self) -> u16 {
self.flags_version.get()
}
#[inline]
pub fn version(&self) -> u16 {
self.flags_version.get() & Self::VERSION_MASK
}
#[inline]
pub fn is_control(&self) -> bool {
(self.flags_version.get() & Self::FLAG_TYPE) != 0
}
#[inline]
pub fn is_data(&self) -> bool {
!self.is_control()
}
#[inline]
pub fn has_length(&self) -> bool {
(self.flags_version.get() & Self::FLAG_LENGTH) != 0
}
#[inline]
pub fn has_sequence(&self) -> bool {
(self.flags_version.get() & Self::FLAG_SEQUENCE) != 0
}
#[inline]
pub fn has_offset(&self) -> bool {
(self.flags_version.get() & Self::FLAG_OFFSET) != 0
}
#[inline]
pub fn has_priority(&self) -> bool {
(self.flags_version.get() & Self::FLAG_PRIORITY) != 0
}
#[inline]
pub fn tunnel_id(&self) -> u16 {
self.tunnel_id.get()
}
#[inline]
pub fn session_id(&self) -> u16 {
self.session_id.get()
}
#[inline]
pub fn has_optional_fields(&self) -> bool {
self.has_length() || self.has_sequence() || self.has_offset()
}
#[inline]
pub fn header_length(&self) -> usize {
let mut len = Self::MIN_HEADER_LEN;
if self.has_length() {
len += Self::LENGTH_FIELD_SIZE;
}
if self.has_sequence() {
len += Self::SEQUENCE_FIELDS_SIZE;
}
if self.has_offset() {
len += Self::OFFSET_FIELD_SIZE;
}
len
}
fn is_valid(&self) -> bool {
if self.version() != Self::VERSION_2 {
return false;
}
if self.is_control() && (!self.has_length() || !self.has_sequence()) {
return false;
}
true
}
pub fn flags_string(&self) -> String {
let mut flags = Vec::new();
if self.is_control() {
flags.push("T");
}
if self.has_length() {
flags.push("L");
}
if self.has_sequence() {
flags.push("S");
}
if self.has_offset() {
flags.push("O");
}
if self.has_priority() {
flags.push("P");
}
if flags.is_empty() {
"none".to_string()
} else {
flags.join("|")
}
}
}
#[derive(Debug, Clone)]
pub struct L2tpv2HeaderOpt<'a> {
pub header: &'a L2tpv2Header,
pub raw_options: &'a [u8],
}
impl<'a> L2tpv2HeaderOpt<'a> {
pub fn length(&self) -> Option<u16> {
if self.header.has_length() && self.raw_options.len() >= 2 {
Some(u16::from_be_bytes([
self.raw_options[0],
self.raw_options[1],
]))
} else {
None
}
}
pub fn ns(&self) -> Option<u16> {
if !self.header.has_sequence() {
return None;
}
let offset = if self.header.has_length() { 2 } else { 0 };
if self.raw_options.len() >= offset + 2 {
Some(u16::from_be_bytes([
self.raw_options[offset],
self.raw_options[offset + 1],
]))
} else {
None
}
}
pub fn nr(&self) -> Option<u16> {
if !self.header.has_sequence() {
return None;
}
let offset = if self.header.has_length() { 4 } else { 2 };
if self.raw_options.len() >= offset + 2 {
Some(u16::from_be_bytes([
self.raw_options[offset],
self.raw_options[offset + 1],
]))
} else {
None
}
}
pub fn offset_size(&self) -> Option<u16> {
if !self.header.has_offset() {
return None;
}
let mut offset = 0;
if self.header.has_length() {
offset += 2;
}
if self.header.has_sequence() {
offset += 4;
}
if self.raw_options.len() >= offset + 2 {
Some(u16::from_be_bytes([
self.raw_options[offset],
self.raw_options[offset + 1],
]))
} else {
None
}
}
pub fn avps(&self) -> L2tpAvpIter<'a> {
if !self.header.is_control() {
return L2tpAvpIter { data: &[] };
}
L2tpAvpIter { data: &[] } }
}
impl std::ops::Deref for L2tpv2HeaderOpt<'_> {
type Target = L2tpv2Header;
#[inline]
fn deref(&self) -> &Self::Target {
self.header
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct L2tpAvpHeader {
flags_length: U16<BigEndian>,
vendor_id: U16<BigEndian>,
attribute_type: U16<BigEndian>,
}
impl L2tpAvpHeader {
pub const FLAG_MANDATORY: u16 = 0x8000;
pub const FLAG_HIDDEN: u16 = 0x4000;
pub const LENGTH_MASK: u16 = 0x03FF;
pub const HEADER_SIZE: usize = 6;
#[inline]
pub fn is_mandatory(&self) -> bool {
(self.flags_length.get() & Self::FLAG_MANDATORY) != 0
}
#[inline]
pub fn is_hidden(&self) -> bool {
(self.flags_length.get() & Self::FLAG_HIDDEN) != 0
}
#[inline]
pub fn length(&self) -> u16 {
self.flags_length.get() & Self::LENGTH_MASK
}
#[inline]
pub fn vendor_id(&self) -> u16 {
self.vendor_id.get()
}
#[inline]
pub fn attribute_type(&self) -> u16 {
self.attribute_type.get()
}
#[inline]
pub fn attribute_type_enum(&self) -> L2tpAvpType {
L2tpAvpType::from(self.attribute_type.get())
}
#[inline]
pub fn value_length(&self) -> usize {
let total = self.length() as usize;
total.saturating_sub(Self::HEADER_SIZE)
}
}
#[derive(Debug, Clone)]
pub struct L2tpAvp<'a> {
pub header: &'a L2tpAvpHeader,
pub value: &'a [u8],
}
impl<'a> L2tpAvp<'a> {
#[inline]
pub fn is_message_type(&self) -> bool {
self.header.vendor_id() == 0 && self.header.attribute_type() == 0
}
pub fn message_type(&self) -> Option<L2tpv2MessageType> {
if self.is_message_type() && self.value.len() >= 2 {
let msg_type = u16::from_be_bytes([self.value[0], self.value[1]]);
Some(L2tpv2MessageType::from(msg_type))
} else {
None
}
}
}
#[derive(Debug, Clone)]
pub struct L2tpAvpIter<'a> {
data: &'a [u8],
}
impl<'a> Iterator for L2tpAvpIter<'a> {
type Item = L2tpAvp<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.data.len() < L2tpAvpHeader::HEADER_SIZE {
return None;
}
let header = zerocopy::Ref::<_, L2tpAvpHeader>::from_prefix(self.data)
.ok()
.map(|(header_ref, _)| zerocopy::Ref::into_ref(header_ref))?;
let total_len = header.length() as usize;
if total_len < L2tpAvpHeader::HEADER_SIZE || total_len > self.data.len() {
self.data = &[];
return None;
}
let value_start = L2tpAvpHeader::HEADER_SIZE;
let value = &self.data[value_start..total_len];
self.data = &self.data[total_len..];
Some(L2tpAvp { header, value })
}
}
impl PacketHeader for L2tpv2Header {
const NAME: &'static str = "L2TPv2";
type InnerType = ();
#[inline]
fn inner_type(&self) -> Self::InnerType {}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
self.header_length()
}
#[inline]
fn is_valid(&self) -> bool {
L2tpv2Header::is_valid(self)
}
}
impl HeaderParser for L2tpv2Header {
type Output<'a> = L2tpv2HeaderOpt<'a>;
fn into_view<'a>(header: &'a Self, options: &'a [u8]) -> Self::Output<'a> {
L2tpv2HeaderOpt {
header,
raw_options: options,
}
}
}
impl fmt::Display for L2tpv2Header {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"L2TPv{} {} tunnel={} session={} flags=[{}]",
self.version(),
if self.is_control() { "CTRL" } else { "DATA" },
self.tunnel_id(),
self.session_id(),
self.flags_string()
)
}
}
impl fmt::Display for L2tpv2HeaderOpt<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"L2TPv{} {} tunnel={} session={} flags=[{}]",
self.version(),
if self.is_control() { "CTRL" } else { "DATA" },
self.tunnel_id(),
self.session_id(),
self.flags_string()
)?;
if let Some(len) = self.length() {
write!(f, " len={}", len)?;
}
if let Some(ns) = self.ns() {
write!(f, " Ns={}", ns)?;
}
if let Some(nr) = self.nr() {
write!(f, " Nr={}", nr)?;
}
if let Some(offset) = self.offset_size() {
write!(f, " offset={}", offset)?;
}
Ok(())
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct L2tpv3SessionHeader {
session_id: U32<BigEndian>,
}
impl L2tpv3SessionHeader {
pub const MIN_HEADER_LEN: usize = 4;
#[allow(unused)]
const NAME: &'static str = "L2TPv3-Session";
#[inline]
pub fn session_id(&self) -> u32 {
self.session_id.get()
}
#[inline]
fn is_valid(&self) -> bool {
self.session_id.get() != 0
}
}
#[derive(Debug, Clone)]
pub struct L2tpv3SessionHeaderCookie<'a> {
pub header: &'a L2tpv3SessionHeader,
pub cookie: &'a [u8],
}
impl<'a> L2tpv3SessionHeaderCookie<'a> {
pub fn cookie_32(&self) -> Option<u32> {
if self.cookie.len() >= 4 {
Some(u32::from_be_bytes([
self.cookie[0],
self.cookie[1],
self.cookie[2],
self.cookie[3],
]))
} else {
None
}
}
pub fn cookie_64(&self) -> Option<u64> {
if self.cookie.len() >= 8 {
Some(u64::from_be_bytes([
self.cookie[0],
self.cookie[1],
self.cookie[2],
self.cookie[3],
self.cookie[4],
self.cookie[5],
self.cookie[6],
self.cookie[7],
]))
} else {
None
}
}
}
impl std::ops::Deref for L2tpv3SessionHeaderCookie<'_> {
type Target = L2tpv3SessionHeader;
#[inline]
fn deref(&self) -> &Self::Target {
self.header
}
}
impl PacketHeader for L2tpv3SessionHeader {
const NAME: &'static str = "L2TPv3-Session";
type InnerType = ();
#[inline]
fn inner_type(&self) -> Self::InnerType {}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
Self::MIN_HEADER_LEN
}
#[inline]
fn is_valid(&self) -> bool {
L2tpv3SessionHeader::is_valid(self)
}
}
impl HeaderParser for L2tpv3SessionHeader {
type Output<'a> = L2tpv3SessionHeaderCookie<'a>;
fn into_view<'a>(header: &'a Self, options: &'a [u8]) -> Self::Output<'a> {
L2tpv3SessionHeaderCookie {
header,
cookie: options,
}
}
}
impl L2tpv3SessionHeader {
pub fn parse_with_cookie_len(
buf: &[u8],
cookie_len: usize,
) -> Result<(L2tpv3SessionHeaderCookie<'_>, &[u8]), PacketHeaderError> {
if buf.len() < Self::MIN_HEADER_LEN + cookie_len {
return Err(PacketHeaderError::TooShort("L2TPv3-Session"));
}
let (header_ref, rest) = zerocopy::Ref::<_, Self>::from_prefix(buf)
.map_err(|_| PacketHeaderError::TooShort("L2TPv3-Session"))?;
let header = zerocopy::Ref::into_ref(header_ref);
if !header.is_valid() {
return Err(PacketHeaderError::Invalid("L2TPv3-Session"));
}
let (cookie, payload) = rest.split_at(cookie_len);
Ok((L2tpv3SessionHeaderCookie { header, cookie }, payload))
}
}
impl fmt::Display for L2tpv3SessionHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "L2TPv3 Session ID={}", self.session_id())
}
}
impl fmt::Display for L2tpv3SessionHeaderCookie<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "L2TPv3 Session ID={}", self.session_id())?;
if let Some(cookie) = self.cookie_64() {
write!(f, " Cookie=0x{:016x}", cookie)?;
} else if let Some(cookie) = self.cookie_32() {
write!(f, " Cookie=0x{:08x}", cookie)?;
}
Ok(())
}
}
#[repr(C, packed)]
#[derive(
FromBytes, IntoBytes, Unaligned, Debug, Clone, Copy, zerocopy::KnownLayout, zerocopy::Immutable,
)]
pub struct L2tpv3ControlHeader {
flags_version: U16<BigEndian>,
length: U16<BigEndian>,
control_connection_id: U32<BigEndian>,
ns: U16<BigEndian>,
nr: U16<BigEndian>,
}
impl L2tpv3ControlHeader {
pub const FLAG_TYPE: u16 = 0x8000;
pub const FLAG_LENGTH: u16 = 0x4000;
pub const FLAG_SEQUENCE: u16 = 0x0800;
pub const VERSION_MASK: u16 = 0x000F;
pub const VERSION_3: u16 = 0x0003;
pub const HEADER_LEN: usize = 12;
#[allow(unused)]
const NAME: &'static str = "L2TPv3-Control";
#[inline]
pub fn flags_version(&self) -> u16 {
self.flags_version.get()
}
#[inline]
pub fn version(&self) -> u16 {
self.flags_version.get() & Self::VERSION_MASK
}
#[inline]
pub fn is_control(&self) -> bool {
(self.flags_version.get() & Self::FLAG_TYPE) != 0
}
#[inline]
pub fn has_length(&self) -> bool {
(self.flags_version.get() & Self::FLAG_LENGTH) != 0
}
#[inline]
pub fn has_sequence(&self) -> bool {
(self.flags_version.get() & Self::FLAG_SEQUENCE) != 0
}
#[inline]
pub fn length(&self) -> u16 {
self.length.get()
}
#[inline]
pub fn control_connection_id(&self) -> u32 {
self.control_connection_id.get()
}
#[inline]
pub fn ns(&self) -> u16 {
self.ns.get()
}
#[inline]
pub fn nr(&self) -> u16 {
self.nr.get()
}
fn is_valid(&self) -> bool {
if self.version() != Self::VERSION_3 {
return false;
}
if !self.is_control() || !self.has_length() || !self.has_sequence() {
return false;
}
true
}
pub fn flags_string(&self) -> String {
let mut flags = Vec::new();
if self.is_control() {
flags.push("T");
}
if self.has_length() {
flags.push("L");
}
if self.has_sequence() {
flags.push("S");
}
if flags.is_empty() {
"none".to_string()
} else {
flags.join("|")
}
}
}
impl PacketHeader for L2tpv3ControlHeader {
const NAME: &'static str = "L2TPv3-Control";
type InnerType = ();
#[inline]
fn inner_type(&self) -> Self::InnerType {}
#[inline]
fn total_len(&self, _buf: &[u8]) -> usize {
Self::HEADER_LEN
}
#[inline]
fn is_valid(&self) -> bool {
L2tpv3ControlHeader::is_valid(self)
}
}
impl HeaderParser for L2tpv3ControlHeader {
type Output<'a> = &'a L2tpv3ControlHeader;
fn into_view<'a>(header: &'a Self, _options: &'a [u8]) -> Self::Output<'a> {
header
}
}
impl fmt::Display for L2tpv3ControlHeader {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"L2TPv3 CTRL CCID={} Ns={} Nr={} len={} flags=[{}]",
self.control_connection_id(),
self.ns(),
self.nr(),
self.length(),
self.flags_string()
)
}
}
pub fn detect_l2tp_version(buf: &[u8]) -> Option<u16> {
if buf.len() < 2 {
return None;
}
let flags_version = u16::from_be_bytes([buf[0], buf[1]]);
Some(flags_version & 0x000F)
}
pub fn is_l2tpv3_data_session(buf: &[u8]) -> bool {
if buf.len() < 4 {
return false;
}
let session_id = u32::from_be_bytes([buf[0], buf[1], buf[2], buf[3]]);
session_id != 0
}
#[cfg(test)]
mod tests {
use super::*;
use crate::packet::HeaderParser;
#[test]
fn test_l2tpv2_header_size() {
assert_eq!(std::mem::size_of::<L2tpv2Header>(), 6);
assert_eq!(L2tpv2Header::MIN_HEADER_LEN, 6);
}
#[test]
fn test_l2tpv2_data_basic() {
let packet = vec![
0x00, 0x02, 0x00, 0x01, 0x00, 0x02, 0xFF, 0x03, 0x00, 0x21,
];
let (header, payload) = L2tpv2Header::from_bytes(&packet).unwrap();
assert_eq!(header.version(), 2);
assert!(!header.is_control());
assert!(header.is_data());
assert!(!header.has_length());
assert!(!header.has_sequence());
assert!(!header.has_offset());
assert!(!header.has_priority());
assert_eq!(header.tunnel_id(), 1);
assert_eq!(header.session_id(), 2);
assert_eq!(payload.len(), 4);
}
#[test]
fn test_l2tpv2_control_with_sequence() {
let packet = vec![
0xC8, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, 0x00, 0x08, 0x00, 0x00,
];
let (header, _payload) = L2tpv2Header::from_bytes(&packet).unwrap();
assert_eq!(header.version(), 2);
assert!(header.is_control());
assert!(header.has_length());
assert!(header.has_sequence());
assert_eq!(header.tunnel_id(), 1);
assert_eq!(header.session_id(), 0);
assert_eq!(header.length(), Some(16));
assert_eq!(header.ns(), Some(1));
assert_eq!(header.nr(), Some(2));
}
#[test]
fn test_l2tpv2_with_offset() {
let packet = vec![
0x02, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x04, 0xFF, 0x03,
];
let (header, _payload) = L2tpv2Header::from_bytes(&packet).unwrap();
assert!(header.has_offset());
assert_eq!(header.offset_size(), Some(4));
}
#[test]
fn test_l2tpv2_with_priority() {
let packet = vec![
0x01, 0x02, 0x00, 0x01, 0x00, 0x02, 0xFF, 0x03,
];
let (header, _payload) = L2tpv2Header::from_bytes(&packet).unwrap();
assert!(header.has_priority());
assert!(header.is_data());
}
#[test]
fn test_l2tpv2_invalid_version() {
let packet = vec![
0x00, 0x03, 0x00, 0x01, 0x00, 0x02, ];
let result = L2tpv2Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_l2tpv2_control_missing_length() {
let packet = vec![
0x88, 0x02, 0x00, 0x01, 0x00, 0x00, ];
let result = L2tpv2Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_l2tpv2_flags_string() {
let packet = vec![
0xC9, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, ];
let (header, _) = L2tpv2Header::from_bytes(&packet).unwrap();
let flags = header.flags_string();
assert!(flags.contains("T"));
assert!(flags.contains("L"));
assert!(flags.contains("P"));
}
#[test]
fn test_l2tpv2_display() {
let packet = vec![
0x00, 0x02, 0x00, 0x0A, 0x00, 0x14, 0xFF, 0x03,
];
let (header, _) = L2tpv2Header::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("L2TPv2"));
assert!(display.contains("DATA"));
assert!(display.contains("tunnel=10"));
assert!(display.contains("session=20"));
}
#[test]
fn test_l2tpv2_too_short() {
let packet = vec![0x00, 0x02, 0x00];
let result = L2tpv2Header::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_l2tpv2_message_types() {
assert_eq!(L2tpv2MessageType::from(1), L2tpv2MessageType::Sccrq);
assert_eq!(L2tpv2MessageType::from(2), L2tpv2MessageType::Sccrp);
assert_eq!(L2tpv2MessageType::from(3), L2tpv2MessageType::Scccn);
assert_eq!(L2tpv2MessageType::from(4), L2tpv2MessageType::StopCcn);
assert_eq!(L2tpv2MessageType::from(6), L2tpv2MessageType::Hello);
assert_eq!(L2tpv2MessageType::from(14), L2tpv2MessageType::Cdn);
assert_eq!(L2tpv2MessageType::from(0), L2tpv2MessageType::Zlb);
assert_eq!(
L2tpv2MessageType::from(255),
L2tpv2MessageType::Unknown(255)
);
}
#[test]
fn test_l2tpv2_message_type_display() {
assert_eq!(format!("{}", L2tpv2MessageType::Sccrq), "SCCRQ");
assert_eq!(format!("{}", L2tpv2MessageType::Hello), "Hello");
assert_eq!(format!("{}", L2tpv2MessageType::Unknown(99)), "Unknown(99)");
}
#[test]
fn test_l2tp_avp_types() {
assert_eq!(L2tpAvpType::from(0), L2tpAvpType::MessageType);
assert_eq!(L2tpAvpType::from(7), L2tpAvpType::HostName);
assert_eq!(L2tpAvpType::from(9), L2tpAvpType::AssignedTunnelId);
assert_eq!(L2tpAvpType::from(100), L2tpAvpType::Unknown(100));
}
#[test]
fn test_l2tp_port() {
assert_eq!(L2TP_PORT, 1701);
assert!(is_l2tp_port(1701));
assert!(!is_l2tp_port(1702));
}
#[test]
fn test_l2tpv3_ip_proto() {
assert_eq!(L2TPV3_IP_PROTO, 115);
assert!(is_l2tpv3_proto(115));
assert!(!is_l2tpv3_proto(114));
}
#[test]
fn test_l2tpv3_session_header_size() {
assert_eq!(std::mem::size_of::<L2tpv3SessionHeader>(), 4);
}
#[test]
fn test_l2tpv3_session_basic() {
let packet = vec![
0x00, 0x00, 0x12, 0x34, 0xFF, 0xFF, 0xFF, 0xFF,
];
let (header, payload) = L2tpv3SessionHeader::from_bytes(&packet).unwrap();
assert_eq!(header.session_id(), 0x1234);
assert_eq!(payload.len(), 4);
}
#[test]
fn test_l2tpv3_session_with_cookie_32() {
let packet = vec![
0x00, 0x00, 0x12, 0x34, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xFF,
];
let (header, payload) = L2tpv3SessionHeader::parse_with_cookie_len(&packet, 4).unwrap();
assert_eq!(header.session_id(), 0x1234);
assert_eq!(header.cookie_32(), Some(0xDEADBEEF));
assert_eq!(payload.len(), 2);
}
#[test]
fn test_l2tpv3_session_with_cookie_64() {
let packet = vec![
0x00, 0x00, 0x12, 0x34, 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE, 0xBA, 0xBE, 0xFF, 0xFF,
];
let (header, payload) = L2tpv3SessionHeader::parse_with_cookie_len(&packet, 8).unwrap();
assert_eq!(header.session_id(), 0x1234);
assert_eq!(header.cookie_64(), Some(0xDEADBEEFCAFEBABE));
assert_eq!(payload.len(), 2);
}
#[test]
fn test_l2tpv3_session_zero_id_invalid() {
let packet = vec![
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF,
];
let result = L2tpv3SessionHeader::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_l2tpv3_session_display() {
let packet = vec![
0x00, 0x00, 0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78, ];
let (header, _) = L2tpv3SessionHeader::parse_with_cookie_len(&packet, 4).unwrap();
let display = format!("{}", header);
assert!(display.contains("L2TPv3"));
assert!(display.contains("Session ID"));
assert!(display.contains("Cookie"));
}
#[test]
fn test_l2tpv3_control_header_size() {
assert_eq!(std::mem::size_of::<L2tpv3ControlHeader>(), 12);
}
#[test]
fn test_l2tpv3_control_basic() {
let packet = vec![
0xC8, 0x03, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
];
let (header, payload) = L2tpv3ControlHeader::from_bytes(&packet).unwrap();
assert_eq!(header.version(), 3);
assert!(header.is_control());
assert!(header.has_length());
assert!(header.has_sequence());
assert_eq!(header.length(), 20);
assert_eq!(header.control_connection_id(), 1);
assert_eq!(header.ns(), 5);
assert_eq!(header.nr(), 4);
assert_eq!(payload.len(), 8);
}
#[test]
fn test_l2tpv3_control_invalid_version() {
let packet = vec![
0xC8, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x01, 0x00, 0x05, 0x00, 0x04, ];
let result = L2tpv3ControlHeader::from_bytes(&packet);
assert!(result.is_err());
}
#[test]
fn test_l2tpv3_control_display() {
let packet = vec![
0xC8, 0x03, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x01, 0x00, 0x02, ];
let (header, _) = L2tpv3ControlHeader::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("L2TPv3"));
assert!(display.contains("CTRL"));
assert!(display.contains("CCID=10"));
assert!(display.contains("Ns=1"));
assert!(display.contains("Nr=2"));
}
#[test]
fn test_l2tpv3_message_types() {
assert_eq!(L2tpv3MessageType::from(1), L2tpv3MessageType::Sccrq);
assert_eq!(L2tpv3MessageType::from(20), L2tpv3MessageType::Ack);
assert_eq!(L2tpv3MessageType::from(0), L2tpv3MessageType::Zlb);
}
#[test]
fn test_detect_l2tp_version() {
let v2_packet = vec![0xC8, 0x02, 0x00, 0x10];
assert_eq!(detect_l2tp_version(&v2_packet), Some(2));
let v3_packet = vec![0xC8, 0x03, 0x00, 0x10];
assert_eq!(detect_l2tp_version(&v3_packet), Some(3));
assert_eq!(detect_l2tp_version(&[0x00]), None);
}
#[test]
fn test_is_l2tpv3_data_session() {
let data = vec![0x00, 0x00, 0x12, 0x34];
assert!(is_l2tpv3_data_session(&data));
let ctrl = vec![0x00, 0x00, 0x00, 0x00];
assert!(!is_l2tpv3_data_session(&ctrl));
assert!(!is_l2tpv3_data_session(&[0x00, 0x00]));
}
#[test]
fn test_l2tp_avp_header_size() {
assert_eq!(std::mem::size_of::<L2tpAvpHeader>(), 6);
}
#[test]
fn test_l2tp_avp_parsing() {
let avp_data = vec![
0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, ];
let mut iter = L2tpAvpIter { data: &avp_data };
let avp = iter.next().unwrap();
assert!(avp.header.is_mandatory());
assert!(!avp.header.is_hidden());
assert_eq!(avp.header.length(), 8);
assert_eq!(avp.header.vendor_id(), 0);
assert_eq!(avp.header.attribute_type(), 0);
assert!(avp.is_message_type());
assert_eq!(avp.message_type(), Some(L2tpv2MessageType::Sccrq));
assert_eq!(avp.value.len(), 2);
assert!(iter.next().is_none());
}
#[test]
fn test_l2tp_avp_multiple() {
let avp_data = vec![
0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0x80, 0x08, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00,
];
let iter = L2tpAvpIter { data: &avp_data };
let avps: Vec<_> = iter.collect();
assert_eq!(avps.len(), 2);
assert_eq!(avps[0].header.attribute_type(), 0);
assert_eq!(avps[1].header.attribute_type(), 2);
}
#[test]
fn test_l2tpv2_full_control_message() {
let packet = vec![
0xC8, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x80, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x08, 0x00, 0x00, 0x00, 0x02, 0x01, 0x00,
];
let (header, payload) = L2tpv2Header::from_bytes(&packet).unwrap();
assert!(header.is_control());
assert_eq!(header.tunnel_id(), 0);
assert_eq!(header.ns(), Some(0));
assert_eq!(header.nr(), Some(0));
let iter = L2tpAvpIter { data: payload };
let avps: Vec<_> = iter.collect();
assert_eq!(avps.len(), 2);
}
#[test]
fn test_l2tpv2_header_opt_display() {
let packet = vec![
0xC8, 0x02, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x10, 0x00, 0x05, 0x00, 0x03, ];
let (header, _) = L2tpv2Header::from_bytes(&packet).unwrap();
let display = format!("{}", header);
assert!(display.contains("CTRL"));
assert!(display.contains("tunnel=10"));
assert!(display.contains("session=20"));
assert!(display.contains("len=16"));
assert!(display.contains("Ns=5"));
assert!(display.contains("Nr=3"));
}
#[test]
fn test_l2tpv2_data_with_all_flags() {
let packet = vec![
0x4B, 0x02, 0x00, 0x01, 0x00, 0x02, 0x00, 0x14, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0xFF, 0x03,
];
let (header, _) = L2tpv2Header::from_bytes(&packet).unwrap();
assert!(header.is_data());
assert!(header.has_length());
assert!(header.has_sequence());
assert!(header.has_offset());
assert!(header.has_priority());
}
}