jkipsec 0.1.0

Userspace IKEv2/IPsec VPN responder for terminating iOS VPN tunnels and exposing the inner IP traffic. Pairs with jktcp for a fully userspace TCP/IP stack.
Documentation
//! IKEv2 fixed header (RFC 7296 ยง3.1).
//!
//! ```text
//!  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
//! +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
//! |                       IKE SA Initiator's SPI                  |
//! |                                                               |
//! +---------------------------------------------------------------+
//! |                       IKE SA Responder's SPI                  |
//! |                                                               |
//! +---------------------------------------------------------------+
//! |  Next Payload | MjVer | MnVer | Exchange Type |     Flags     |
//! +---------------------------------------------------------------+
//! |                          Message ID                           |
//! +---------------------------------------------------------------+
//! |                            Length                             |
//! +---------------------------------------------------------------+
//! ```

// Jackson Coxson

#![allow(missing_docs)]

use super::payload::PayloadKind;

/// Length of the fixed IKEv2 header in bytes.
pub const HEADER_LEN: usize = 28;

/// `MjVer << 4 | MnVer` for IKEv2 = `0x20`.
pub const IKE_VERSION: u8 = 0x20;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExchangeType {
    IkeSaInit,
    IkeAuth,
    CreateChildSa,
    Informational,
    Other(u8),
}

impl ExchangeType {
    pub fn from_u8(v: u8) -> Self {
        match v {
            34 => Self::IkeSaInit,
            35 => Self::IkeAuth,
            36 => Self::CreateChildSa,
            37 => Self::Informational,
            other => Self::Other(other),
        }
    }

    pub fn as_u8(self) -> u8 {
        match self {
            Self::IkeSaInit => 34,
            Self::IkeAuth => 35,
            Self::CreateChildSa => 36,
            Self::Informational => 37,
            Self::Other(v) => v,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flags(pub u8);

impl Flags {
    /// Sender is the original initiator of the SA.
    pub const INITIATOR: u8 = 0b0000_1000;
    /// Sender supports a higher major version.
    pub const VERSION: u8 = 0b0001_0000;
    /// Message is a response (vs. request).
    pub const RESPONSE: u8 = 0b0010_0000;

    pub fn is_initiator(self) -> bool {
        self.0 & Self::INITIATOR != 0
    }

    pub fn is_response(self) -> bool {
        self.0 & Self::RESPONSE != 0
    }

    pub fn higher_version(self) -> bool {
        self.0 & Self::VERSION != 0
    }
}

#[derive(Debug, Clone, Copy)]
pub struct Header {
    pub initiator_spi: u64,
    pub responder_spi: u64,
    pub next_payload: PayloadKind,
    pub version: u8,
    pub exchange_type: ExchangeType,
    pub flags: Flags,
    pub message_id: u32,
    pub length: u32,
}

impl Header {
    /// Parse the 28-byte fixed header. Caller is responsible for confirming
    /// `bytes.len() >= HEADER_LEN`.
    pub fn parse(bytes: &[u8]) -> Result<Self, super::ParseError> {
        if bytes.len() < HEADER_LEN {
            return Err(super::ParseError::Truncated {
                what: "ike header",
                need: HEADER_LEN,
                got: bytes.len(),
            });
        }

        let initiator_spi = u64::from_be_bytes(bytes[0..8].try_into().unwrap());
        let responder_spi = u64::from_be_bytes(bytes[8..16].try_into().unwrap());
        let next_payload = PayloadKind::from_u8(bytes[16]);
        let version = bytes[17];
        let exchange_type = ExchangeType::from_u8(bytes[18]);
        let flags = Flags(bytes[19]);
        let message_id = u32::from_be_bytes(bytes[20..24].try_into().unwrap());
        let length = u32::from_be_bytes(bytes[24..28].try_into().unwrap());

        Ok(Self {
            initiator_spi,
            responder_spi,
            next_payload,
            version,
            exchange_type,
            flags,
            message_id,
            length,
        })
    }

    pub fn write_into(&self, out: &mut [u8; HEADER_LEN]) {
        out[0..8].copy_from_slice(&self.initiator_spi.to_be_bytes());
        out[8..16].copy_from_slice(&self.responder_spi.to_be_bytes());
        out[16] = self.next_payload.as_u8();
        out[17] = self.version;
        out[18] = self.exchange_type.as_u8();
        out[19] = self.flags.0;
        out[20..24].copy_from_slice(&self.message_id.to_be_bytes());
        out[24..28].copy_from_slice(&self.length.to_be_bytes());
    }
}