use crate::protocol::codec::Decode; use crate::protocol::ppp::PPPProtocol; use nom::bits; use nom::error::Error; use nom::number; use nom::sequence; use nom::IResult; /****************************************************************************** * Struct ******************************************************************************/ /* * RFC 2516 - A Method for Transmitting PPP Over Ethernet (PPPoE) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | VER | TYPE | CODE | SESSION_ID | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | LENGTH | payload ~ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * https://datatracker.ietf.org/doc/html/rfc2516 * https://info.support.huawei.com/info-finder/encyclopedia/zh/PPPoE.html */ #[derive(Debug, PartialEq)] pub enum PPPoECode { SessionData, // Session Data PADI, // Active Discovery Initiation PADO, // Active Discovery Offer PADR, // Active Discovery Request PADS, // Active Discovery Session-confirmation PADT, // Active Discovery Terminate Other(u8), } #[derive(Debug, PartialEq)] pub enum PPPoETagType { EndOfList, ServiceName, ACName, HostUniq, ACcookie, VendorSpecific, RelaySessionId, ServiceNameError, ACSystemError, GenericError, Other(u16), } #[derive(Debug, PartialEq)] pub struct PPPoETag { pub tag_type: PPPoETagType, pub tag_length: u16, pub tag_value: Vec, } #[derive(Debug, PartialEq)] pub enum PPPoEStage { Discovery(Vec), Session(PPPProtocol), } #[derive(Debug, PartialEq)] pub struct PPPoEHeader { pub version: u8, // 4 bits pub type_: u8, // 4 bits pub code: PPPoECode, // 8 bits pub session_id: u16, // 16 bits pub payload_length: u16, // 16 bits pub stage: PPPoEStage, } /****************************************************************************** * API ******************************************************************************/ impl From for PPPoECode { fn from(raw: u8) -> Self { match raw { 0x00 => PPPoECode::SessionData, 0x09 => PPPoECode::PADI, 0x07 => PPPoECode::PADO, 0x19 => PPPoECode::PADR, 0x65 => PPPoECode::PADS, 0xa7 => PPPoECode::PADT, other => PPPoECode::Other(other), } } } impl From for PPPoETagType { fn from(raw: u16) -> Self { match raw { 0x0000 => PPPoETagType::EndOfList, 0x0101 => PPPoETagType::ServiceName, 0x0102 => PPPoETagType::ACName, 0x0103 => PPPoETagType::HostUniq, 0x0104 => PPPoETagType::ACcookie, 0x0105 => PPPoETagType::VendorSpecific, 0x0110 => PPPoETagType::RelaySessionId, 0x0201 => PPPoETagType::ServiceNameError, 0x0202 => PPPoETagType::ACSystemError, 0x0203 => PPPoETagType::GenericError, other => PPPoETagType::Other(other), } } } fn version_type_decode(input: &[u8]) -> IResult<&[u8], (u8, u8)> { bits::bits::<_, _, Error<_>, _, _>(sequence::tuple(( bits::streaming::take(4u8), bits::streaming::take(4u8), )))(input) } fn pppoe_tag_decode(input: &[u8]) -> IResult<&[u8], PPPoETag> { let (input, tag_type) = number::complete::be_u16(input).map(|(i, l)| (i, l.into()))?; let (input, tag_length) = number::complete::be_u16(input)?; let (input, tag_value) = nom::bytes::complete::take(tag_length)(input).map(|(i, l)| (i, l.to_vec()))?; Ok(( input, PPPoETag { tag_type, tag_length, tag_value, }, )) } fn pppoe_tags_decode(input: &[u8]) -> IResult<&[u8], Vec> { let mut tags = Vec::new(); let mut remain = input; loop { let (input, tag) = pppoe_tag_decode(remain)?; let is_end_of_list = tag.tag_type == PPPoETagType::EndOfList; remain = input; tags.push(tag); if remain.is_empty() || is_end_of_list { break; } } Ok((remain, tags)) } impl Decode for PPPoEHeader { type Iterm = PPPoEHeader; fn decode(input: &[u8]) -> IResult<&[u8], PPPoEHeader> { let (input, (version, type_)) = version_type_decode(input)?; /* * https://datatracker.ietf.org/doc/html/rfc2516 * * The VER field is four bits and MUST be set to 0x1 for this version of the PPPoE specification. * The TYPE field is four bits and MUST be set to 0x1 for this version of the PPPoE specification. */ match (version, type_) { (0x1, 0x1) => {} _ => { return Err(nom::Err::Error(Error::new( input, nom::error::ErrorKind::Verify, ))) } } let (input, code) = number::complete::be_u8(input).map(|(i, l)| (i, l.into()))?; let (input, session_id) = number::complete::be_u16(input)?; let (input, payload_length) = number::complete::be_u16(input)?; let (input, stage) = match code { PPPoECode::SessionData => { let (input, ppp_protocol) = PPPProtocol::decode(input)?; (input, PPPoEStage::Session(ppp_protocol)) } _ => { let (remain, tags) = pppoe_tags_decode(input)?; (remain, PPPoEStage::Discovery(tags)) } }; Ok(( input, PPPoEHeader { version, type_, code, session_id, payload_length, stage, }, )) } } /****************************************************************************** * TEST ******************************************************************************/ #[cfg(test)] mod tests { use super::PPPoECode; use super::PPPoEHeader; use super::PPPoEStage; use crate::protocol::codec::Decode; use crate::protocol::ppp::PPPProtocol; const LAST_SLICE: &'static [u8] = &[0xff]; #[test] fn pppoe_header_decode() { /* * PPP-over-Ethernet Session * 0001 .... = Version: 1 * .... 0001 = Type: 1 * Code: Session Data (0x00) * Session ID: 0xb4bc * Payload Length: 544 * Point-to-Point Protocol * Protocol: Internet Protocol version 4 (0x0021) */ let bytes = [ 0x11, 0x00, 0xb4, 0xbc, 0x02, 0x20, /* PPPoE */ 0x00, 0x21, /* PPP */ 0xff, /* Payload */ ]; let expectation = PPPoEHeader { version: 1, type_: 1, code: PPPoECode::SessionData, session_id: 0xb4bc, payload_length: 544, stage: PPPoEStage::Session(PPPProtocol::IPv4), }; assert_eq!(PPPoEHeader::decode(&bytes), Ok((LAST_SLICE, expectation))); // example let result = PPPoEHeader::decode(&bytes); match result { Ok((payload, header)) => { println!("OK: {:?}, payload: {}", header, payload.len()); } Err(e) => { println!("ERR: {:?}", e); } } // assert_eq!(1, 0); } }