summaryrefslogtreecommitdiff
path: root/src/protocol/pppoe.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/protocol/pppoe.rs')
-rw-r--r--src/protocol/pppoe.rs252
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);
+ }
+}