diff options
Diffstat (limited to 'src/protocol/pppoe.rs')
| -rw-r--r-- | src/protocol/pppoe.rs | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/src/protocol/pppoe.rs b/src/protocol/pppoe.rs new file mode 100644 index 0000000..fbd8448 --- /dev/null +++ b/src/protocol/pppoe.rs @@ -0,0 +1,252 @@ +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(Clone, Copy, Debug, PartialEq, Eq)] +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(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PPPoETagType { + EndOfList, + ServiceName, + ACName, + HostUniq, + ACcookie, + VendorSpecific, + RelaySessionId, + ServiceNameError, + ACSystemError, + GenericError, + Other(u16), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PPPoETag { + pub tag_type: PPPoETagType, + pub tag_length: u16, + pub tag_value: Vec<u8>, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum PPPoEStage { + Discovery(Vec<PPPoETag>), + Session(PPPProtocol), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +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<u8> 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<u16> 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)?; + let (input, tag_length) = number::complete::be_u16(input)?; + let (input, tag_value) = nom::bytes::complete::take(tag_length)(input)?; + Ok(( + input, + PPPoETag { + tag_type: tag_type.into(), + tag_length, + tag_value: tag_value.to_vec(), + }, + )) +} + +fn pppoe_tags_decode(input: &[u8]) -> IResult<&[u8], Vec<PPPoETag>> { + let mut tags = Vec::new(); + let mut remain = input; + loop { + let (input, tag) = pppoe_tag_decode(remain)?; + let tag_type = tag.tag_type; + remain = input; + tags.push(tag); + if remain.is_empty() || tag_type == PPPoETagType::EndOfList { + 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)?; + let (input, session_id) = number::complete::be_u16(input)?; + let (input, payload_length) = number::complete::be_u16(input)?; + let (input, stage) = match code.into() { + 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: code.into(), + 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); + } +} |
