stochastic-routing-extended 1.0.2

SRX (Stochastic Routing eXtended) — a next-generation VPN protocol with stochastic routing, DPI evasion, post-quantum cryptography, and multi-transport channel splitting
Documentation
//! Protocol mimicry: wrapping frames to look like legitimate traffic.
//!
//! Each mode wraps SRX wire bytes inside a protocol envelope that a DPI
//! engine will classify as normal traffic:
//!
//! - **HTTPS** — TLS 1.3 Application Data record (`content_type = 0x17`,
//!   `legacy_version = 0x0303`, 2-byte length).
//! - **gRPC** — HTTP/2 DATA frame header (9 bytes: length + type 0x00 +
//!   flags + stream ID).
//! - **WebRTC** — DTLS 1.2 Application Data record (`content_type = 0x17`,
//!   `version = 0xFEFD`, 2-byte epoch, 6-byte seq, 2-byte length).

use crate::config::MimicryMode;

// ---------------------------------------------------------------------------
// TLS 1.3 constants
// ---------------------------------------------------------------------------

/// TLS content type: Application Data.
const TLS_APP_DATA: u8 = 0x17;
/// TLS legacy record version (TLS 1.2 in the record layer even for TLS 1.3).
const TLS_VERSION: [u8; 2] = [0x03, 0x03];
/// TLS record header: content_type(1) + version(2) + length(2).
const TLS_HEADER_LEN: usize = 5;

// ---------------------------------------------------------------------------
// HTTP/2 constants (gRPC)
// ---------------------------------------------------------------------------

/// HTTP/2 frame type: DATA.
const H2_DATA: u8 = 0x00;
/// HTTP/2 frame header length: length(3) + type(1) + flags(1) + stream_id(4).
const H2_HEADER_LEN: usize = 9;
/// Stream ID used for the gRPC tunnel (odd = client-initiated).
const H2_STREAM_ID: u32 = 1;

// ---------------------------------------------------------------------------
// DTLS 1.2 constants (WebRTC)
// ---------------------------------------------------------------------------

/// DTLS content type: Application Data.
const DTLS_APP_DATA: u8 = 0x17;
/// DTLS 1.2 version: 0xFEFD.
const DTLS_VERSION: [u8; 2] = [0xFE, 0xFD];
/// DTLS record header: content_type(1) + version(2) + epoch(2) + seq(6) + length(2).
const DTLS_HEADER_LEN: usize = 13;

/// Wraps raw SRX frames into protocol-mimicking envelopes.
pub struct ProtocolMimicry {
    mode: MimicryMode,
}

impl ProtocolMimicry {
    pub fn new(mode: MimicryMode) -> Self {
        Self { mode }
    }

    /// Wrap raw frame bytes into a mimicry envelope.
    pub fn wrap(&self, raw: &[u8]) -> Vec<u8> {
        match self.mode {
            MimicryMode::None => raw.to_vec(),
            MimicryMode::Https => wrap_tls(raw),
            MimicryMode::Grpc => wrap_h2_data(raw),
            MimicryMode::WebRtc => wrap_dtls(raw),
        }
    }

    /// Unwrap a mimicry envelope to extract the raw frame bytes.
    pub fn unwrap(&self, envelope: &[u8]) -> Option<Vec<u8>> {
        match self.mode {
            MimicryMode::None => Some(envelope.to_vec()),
            MimicryMode::Https => unwrap_tls(envelope),
            MimicryMode::Grpc => unwrap_h2_data(envelope),
            MimicryMode::WebRtc => unwrap_dtls(envelope),
        }
    }
}

// ---------------------------------------------------------------------------
// TLS 1.3 Application Data record
// ---------------------------------------------------------------------------
// Wire: [0x17][0x03][0x03][len_hi][len_lo][payload...]

fn wrap_tls(raw: &[u8]) -> Vec<u8> {
    let len = raw.len() as u16;
    let mut out = Vec::with_capacity(TLS_HEADER_LEN + raw.len());
    out.push(TLS_APP_DATA);
    out.extend_from_slice(&TLS_VERSION);
    out.extend_from_slice(&len.to_be_bytes());
    out.extend_from_slice(raw);
    out
}

fn unwrap_tls(envelope: &[u8]) -> Option<Vec<u8>> {
    if envelope.len() < TLS_HEADER_LEN {
        return None;
    }
    if envelope[0] != TLS_APP_DATA || envelope[1..3] != TLS_VERSION {
        return None;
    }
    let payload_len = u16::from_be_bytes([envelope[3], envelope[4]]) as usize;
    if envelope.len() != TLS_HEADER_LEN + payload_len {
        return None;
    }
    Some(envelope[TLS_HEADER_LEN..].to_vec())
}

// ---------------------------------------------------------------------------
// HTTP/2 DATA frame (gRPC)
// ---------------------------------------------------------------------------
// Wire: [len_hi][len_mid][len_lo][0x00][flags=0x00][stream_id:4BE][payload...]

fn wrap_h2_data(raw: &[u8]) -> Vec<u8> {
    let len = raw.len() as u32;
    let mut out = Vec::with_capacity(H2_HEADER_LEN + raw.len());
    // 3-byte length (big-endian, 24-bit).
    out.push((len >> 16) as u8);
    out.push((len >> 8) as u8);
    out.push(len as u8);
    out.push(H2_DATA);
    out.push(0x00); // flags
    out.extend_from_slice(&H2_STREAM_ID.to_be_bytes());
    out.extend_from_slice(raw);
    out
}

fn unwrap_h2_data(envelope: &[u8]) -> Option<Vec<u8>> {
    if envelope.len() < H2_HEADER_LEN {
        return None;
    }
    if envelope[3] != H2_DATA {
        return None;
    }
    let payload_len =
        ((envelope[0] as usize) << 16) | ((envelope[1] as usize) << 8) | (envelope[2] as usize);
    if envelope.len() != H2_HEADER_LEN + payload_len {
        return None;
    }
    Some(envelope[H2_HEADER_LEN..].to_vec())
}

// ---------------------------------------------------------------------------
// DTLS 1.2 Application Data record (WebRTC)
// ---------------------------------------------------------------------------
// Wire: [0x17][0xFE][0xFD][epoch:2][seq:6][len_hi][len_lo][payload...]

fn wrap_dtls(raw: &[u8]) -> Vec<u8> {
    let len = raw.len() as u16;
    let mut out = Vec::with_capacity(DTLS_HEADER_LEN + raw.len());
    out.push(DTLS_APP_DATA);
    out.extend_from_slice(&DTLS_VERSION);
    out.extend_from_slice(&[0x00, 0x01]); // epoch = 1 (after handshake)
    out.extend_from_slice(&[0x00; 6]); // sequence number placeholder
    out.extend_from_slice(&len.to_be_bytes());
    out.extend_from_slice(raw);
    out
}

fn unwrap_dtls(envelope: &[u8]) -> Option<Vec<u8>> {
    if envelope.len() < DTLS_HEADER_LEN {
        return None;
    }
    if envelope[0] != DTLS_APP_DATA || envelope[1..3] != DTLS_VERSION {
        return None;
    }
    let payload_len = u16::from_be_bytes([envelope[11], envelope[12]]) as usize;
    if envelope.len() != DTLS_HEADER_LEN + payload_len {
        return None;
    }
    Some(envelope[DTLS_HEADER_LEN..].to_vec())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn none_passthrough() {
        let m = ProtocolMimicry::new(MimicryMode::None);
        let raw = b"transparent";
        let w = m.wrap(raw);
        assert_eq!(w, raw);
        assert_eq!(m.unwrap(&w).unwrap(), raw);
    }

    #[test]
    fn tls_roundtrip() {
        let m = ProtocolMimicry::new(MimicryMode::Https);
        let raw = b"encrypted-payload";
        let w = m.wrap(raw);
        // Verify TLS record structure.
        assert_eq!(w[0], TLS_APP_DATA);
        assert_eq!(&w[1..3], &TLS_VERSION);
        let len = u16::from_be_bytes([w[3], w[4]]) as usize;
        assert_eq!(len, raw.len());
        assert_eq!(w.len(), TLS_HEADER_LEN + raw.len());
        // Roundtrip.
        assert_eq!(m.unwrap(&w).unwrap(), raw);
    }

    #[test]
    fn tls_rejects_wrong_type() {
        let m = ProtocolMimicry::new(MimicryMode::Https);
        let mut bad = m.wrap(b"data");
        bad[0] = 0x16; // Change type (should be handshake).
        assert!(m.unwrap(&bad).is_none());
    }

    #[test]
    fn grpc_roundtrip() {
        let m = ProtocolMimicry::new(MimicryMode::Grpc);
        let raw = b"grpc-stream-data";
        let w = m.wrap(raw);
        // Verify HTTP/2 DATA frame structure.
        assert_eq!(w[3], H2_DATA);
        let len = ((w[0] as usize) << 16) | ((w[1] as usize) << 8) | (w[2] as usize);
        assert_eq!(len, raw.len());
        assert_eq!(w.len(), H2_HEADER_LEN + raw.len());
        // Stream ID check.
        let sid = u32::from_be_bytes([w[5], w[6], w[7], w[8]]);
        assert_eq!(sid, H2_STREAM_ID);
        // Roundtrip.
        assert_eq!(m.unwrap(&w).unwrap(), raw);
    }

    #[test]
    fn grpc_rejects_wrong_frame_type() {
        let m = ProtocolMimicry::new(MimicryMode::Grpc);
        let mut bad = m.wrap(b"data");
        bad[3] = 0x01; // HEADERS instead of DATA.
        assert!(m.unwrap(&bad).is_none());
    }

    #[test]
    fn dtls_roundtrip() {
        let m = ProtocolMimicry::new(MimicryMode::WebRtc);
        let raw = b"webrtc-datachannel";
        let w = m.wrap(raw);
        // Verify DTLS record structure.
        assert_eq!(w[0], DTLS_APP_DATA);
        assert_eq!(&w[1..3], &DTLS_VERSION);
        assert_eq!(w.len(), DTLS_HEADER_LEN + raw.len());
        // Roundtrip.
        assert_eq!(m.unwrap(&w).unwrap(), raw);
    }

    #[test]
    fn dtls_rejects_wrong_version() {
        let m = ProtocolMimicry::new(MimicryMode::WebRtc);
        let mut bad = m.wrap(b"data");
        bad[1] = 0x03; // TLS version instead of DTLS.
        assert!(m.unwrap(&bad).is_none());
    }

    #[test]
    fn empty_payload_roundtrip() {
        for mode in [
            MimicryMode::None,
            MimicryMode::Https,
            MimicryMode::Grpc,
            MimicryMode::WebRtc,
        ] {
            let m = ProtocolMimicry::new(mode);
            let w = m.wrap(b"");
            assert_eq!(m.unwrap(&w).unwrap(), b"");
        }
    }

    #[test]
    fn truncated_envelope_rejected() {
        let m = ProtocolMimicry::new(MimicryMode::Https);
        assert!(m.unwrap(&[0x17, 0x03]).is_none()); // Too short.
        // Length mismatch.
        assert!(m.unwrap(&[0x17, 0x03, 0x03, 0x00, 0x05, 0x01]).is_none());
    }
}