use anyhow::{bail, Result};
use bytes::{BufMut, BytesMut};
pub const MAX_PACKET_SIZE: usize = 65535;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Opcode {
ControlHardResetClientV1 = 1,
ControlHardResetServerV1 = 2,
ControlSoftResetV1 = 3,
ControlV1 = 4,
AckV1 = 5,
DataV1 = 6,
ControlHardResetClientV2 = 7,
ControlHardResetServerV2 = 8,
DataV2 = 9,
ControlHardResetClientV3 = 10,
ControlWkcV1 = 11,
}
impl Opcode {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
1 => Some(Opcode::ControlHardResetClientV1),
2 => Some(Opcode::ControlHardResetServerV1),
3 => Some(Opcode::ControlSoftResetV1),
4 => Some(Opcode::ControlV1),
5 => Some(Opcode::AckV1),
6 => Some(Opcode::DataV1),
7 => Some(Opcode::ControlHardResetClientV2),
8 => Some(Opcode::ControlHardResetServerV2),
9 => Some(Opcode::DataV2),
10 => Some(Opcode::ControlHardResetClientV3),
11 => Some(Opcode::ControlWkcV1),
_ => None,
}
}
pub fn is_control(&self) -> bool {
matches!(
self,
Opcode::ControlHardResetClientV1
| Opcode::ControlHardResetServerV1
| Opcode::ControlHardResetClientV2
| Opcode::ControlHardResetServerV2
| Opcode::ControlSoftResetV1
| Opcode::ControlV1
)
}
pub fn is_data(&self) -> bool {
matches!(self, Opcode::DataV1 | Opcode::DataV2)
}
pub fn is_ack(&self) -> bool {
matches!(self, Opcode::AckV1)
}
pub fn is_tls_crypt_v2(&self) -> bool {
matches!(
self,
Opcode::ControlHardResetClientV3 | Opcode::ControlWkcV1
)
}
pub fn is_client_reset(&self) -> bool {
matches!(
self,
Opcode::ControlHardResetClientV1 | Opcode::ControlHardResetClientV2
)
}
}
pub fn parse_opcode_byte(data: &[u8]) -> Result<(Opcode, u8)> {
let first = *data
.first()
.ok_or_else(|| anyhow::anyhow!("Empty packet"))?;
let raw = (first >> 3) & 0x1F;
let opcode =
Opcode::from_u8(raw).ok_or_else(|| anyhow::anyhow!("Unknown OpenVPN opcode: {}", raw))?;
Ok((opcode, first & 0x07))
}
struct Reader<'a> {
buf: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(buf: &'a [u8]) -> Self {
Reader { buf, pos: 0 }
}
fn take(&mut self, n: usize, what: &str) -> Result<&'a [u8]> {
let end = self
.pos
.checked_add(n)
.ok_or_else(|| anyhow::anyhow!("Length overflow reading {}", what))?;
if end > self.buf.len() {
bail!(
"Packet too short for {}: need {} more bytes at offset {}, have {}",
what,
n,
self.pos,
self.buf.len().saturating_sub(self.pos)
);
}
let out = &self.buf[self.pos..end];
self.pos = end;
Ok(out)
}
fn u8(&mut self, what: &str) -> Result<u8> {
Ok(self.take(1, what)?[0])
}
fn u32(&mut self, what: &str) -> Result<u32> {
let b = self.take(4, what)?;
Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]]))
}
fn u64(&mut self, what: &str) -> Result<u64> {
let b = self.take(8, what)?;
Ok(u64::from_be_bytes([
b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
]))
}
fn rest(&mut self) -> &'a [u8] {
let out = &self.buf[self.pos..];
self.pos = self.buf.len();
out
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ControlFrame {
pub opcode: Opcode,
pub key_id: u8,
pub session_id: u64,
pub ack_packet_ids: Vec<u32>,
pub remote_session_id: Option<u64>,
pub packet_id: Option<u32>,
pub payload: Vec<u8>,
}
impl ControlFrame {
pub fn hard_reset_server_v2(
key_id: u8,
server_session_id: u64,
client_session_id: u64,
client_packet_id: u32,
our_packet_id: u32,
) -> Self {
ControlFrame {
opcode: Opcode::ControlHardResetServerV2,
key_id,
session_id: server_session_id,
ack_packet_ids: vec![client_packet_id],
remote_session_id: Some(client_session_id),
packet_id: Some(our_packet_id),
payload: Vec::new(),
}
}
pub fn ack(
key_id: u8,
server_session_id: u64,
client_session_id: u64,
ack_packet_ids: Vec<u32>,
) -> Self {
ControlFrame {
opcode: Opcode::AckV1,
key_id,
session_id: server_session_id,
ack_packet_ids,
remote_session_id: Some(client_session_id),
packet_id: None,
payload: Vec::new(),
}
}
pub fn parse(data: &[u8]) -> Result<Self> {
let (opcode, key_id) = parse_opcode_byte(data)?;
if opcode.is_tls_crypt_v2() {
bail!(
"{:?} is a tls-crypt-v2 frame: its reliability fields are encrypted and \
this server has no tls-crypt-v2 key, so it cannot be parsed",
opcode
);
}
if !opcode.is_control() && !opcode.is_ack() {
bail!("Not a control or ACK packet: {:?}", opcode);
}
let mut r = Reader::new(data);
r.take(1, "opcode byte")?;
let session_id = r.u64("session id")?;
let ack_count = r.u8("ACK array length")? as usize;
let mut ack_packet_ids = Vec::with_capacity(ack_count.min(64));
for i in 0..ack_count {
ack_packet_ids.push(r.u32(&format!("ACK id #{}", i))?);
}
let remote_session_id = if ack_count > 0 {
Some(r.u64("remote session id")?)
} else {
None
};
let packet_id = if opcode.is_ack() {
None
} else {
Some(r.u32("message packet id")?)
};
Ok(ControlFrame {
opcode,
key_id,
session_id,
ack_packet_ids,
remote_session_id,
packet_id,
payload: r.rest().to_vec(),
})
}
pub fn serialize(&self) -> BytesMut {
let mut buf = BytesMut::with_capacity(32 + self.payload.len());
buf.put_u8(((self.opcode as u8) << 3) | (self.key_id & 0x07));
buf.put_u64(self.session_id);
let acks = &self.ack_packet_ids[..self.ack_packet_ids.len().min(u8::MAX as usize)];
buf.put_u8(acks.len() as u8);
for id in acks {
buf.put_u32(*id);
}
if !acks.is_empty() {
buf.put_u64(self.remote_session_id.unwrap_or(0));
}
if !self.opcode.is_ack() {
buf.put_u32(self.packet_id.unwrap_or(0));
}
buf.put_slice(&self.payload);
buf
}
pub fn is_plain_reset(&self) -> bool {
self.opcode.is_client_reset() && self.payload.is_empty() && self.ack_packet_ids.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DataFrame {
pub opcode: Opcode,
pub key_id: u8,
pub peer_id: Option<u32>,
pub payload: Vec<u8>,
}
impl DataFrame {
pub fn parse(data: &[u8]) -> Result<Self> {
let (opcode, key_id) = parse_opcode_byte(data)?;
if !opcode.is_data() {
bail!("Not a data packet: {:?}", opcode);
}
let mut r = Reader::new(data);
r.take(1, "opcode byte")?;
let peer_id = if opcode == Opcode::DataV2 {
let b = r.take(3, "peer id")?;
Some(u32::from_be_bytes([0, b[0], b[1], b[2]]))
} else {
None
};
Ok(DataFrame {
opcode,
key_id,
peer_id,
payload: r.rest().to_vec(),
})
}
pub fn serialize(&self) -> BytesMut {
let mut buf = BytesMut::with_capacity(4 + self.payload.len());
buf.put_u8(((self.opcode as u8) << 3) | (self.key_id & 0x07));
if self.opcode == Opcode::DataV2 {
let id = self.peer_id.unwrap_or(0);
buf.put_slice(&[(id >> 16) as u8, (id >> 8) as u8, id as u8]);
}
buf.put_slice(&self.payload);
buf
}
}