mirror of
https://github.com/fafhrd91/actix-net
synced 2024-12-04 19:31:54 +01:00
add Tlv trait
This commit is contained in:
parent
b9877392ab
commit
69d9afd39e
@ -21,6 +21,7 @@ crc32fast = "1"
|
|||||||
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
|
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||||
itoa = "1"
|
itoa = "1"
|
||||||
|
smallvec = "1"
|
||||||
tokio = { version = "1.13.1", features = ["sync", "io-util"] }
|
tokio = { version = "1.13.1", features = ["sync", "io-util"] }
|
||||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ use std::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_proxy_protocol::{v1, v2, AddressFamily, Command, Tlv, TransportProtocol};
|
use actix_proxy_protocol::{tlv, v1, v2, AddressFamily, Command, TransportProtocol};
|
||||||
use actix_rt::net::TcpStream;
|
use actix_rt::net::TcpStream;
|
||||||
use actix_server::Server;
|
use actix_server::Server;
|
||||||
use actix_service::{fn_service, ServiceFactoryExt as _};
|
use actix_service::{fn_service, ServiceFactoryExt as _};
|
||||||
@ -31,6 +31,7 @@ TLV = type-length-value
|
|||||||
TO DO:
|
TO DO:
|
||||||
handle UNKNOWN transport
|
handle UNKNOWN transport
|
||||||
v2 UNSPEC mode
|
v2 UNSPEC mode
|
||||||
|
AF_UNIX socket
|
||||||
*/
|
*/
|
||||||
|
|
||||||
fn extend_with_ip_bytes(buf: &mut Vec<u8>, ip: IpAddr) {
|
fn extend_with_ip_bytes(buf: &mut Vec<u8>, ip: IpAddr) {
|
||||||
@ -71,18 +72,10 @@ async fn wrap_with_proxy_protocol_v2(mut stream: TcpStream) -> io::Result<()> {
|
|||||||
UPSTREAM.to_string()
|
UPSTREAM.to_string()
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut proxy_header = v2::Header::new(
|
let mut proxy_header = v2::Header::new_tcp_ipv4_proxy(([127, 0, 0, 1], 8082), *UPSTREAM);
|
||||||
Command::Proxy,
|
|
||||||
TransportProtocol::Stream,
|
|
||||||
AddressFamily::Inet,
|
|
||||||
SocketAddr::from(([127, 0, 0, 1], 8082)),
|
|
||||||
*UPSTREAM,
|
|
||||||
vec![
|
|
||||||
Tlv::new(0x05, [0x34, 0x32, 0x36, 0x39]), // UNIQUE_ID
|
|
||||||
Tlv::new(0x04, "NOOP m9"), // NOOP
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
|
proxy_header.add_tlv(0x05, [0x34, 0x32, 0x36, 0x39]); // UNIQUE_ID
|
||||||
|
proxy_header.add_tlv(0x04, "NOOP m9"); // NOOP
|
||||||
proxy_header.add_crc23c_checksum_tlv();
|
proxy_header.add_crc23c_checksum_tlv();
|
||||||
|
|
||||||
proxy_header.write_to_tokio(&mut upstream).await?;
|
proxy_header.write_to_tokio(&mut upstream).await?;
|
||||||
|
@ -16,6 +16,7 @@ use std::{
|
|||||||
use arrayvec::{ArrayString, ArrayVec};
|
use arrayvec::{ArrayString, ArrayVec};
|
||||||
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
||||||
|
|
||||||
|
pub mod tlv;
|
||||||
pub mod v1;
|
pub mod v1;
|
||||||
pub mod v2;
|
pub mod v2;
|
||||||
|
|
||||||
@ -165,80 +166,3 @@ enum ProxyProtocolHeader {
|
|||||||
V1(v1::Header),
|
V1(v1::Header),
|
||||||
V2(v2::Header),
|
V2(v2::Header),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
||||||
pub struct Pp2Crc32c {
|
|
||||||
checksum: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Pp2Crc32c {
|
|
||||||
fn to_tlv(&self) -> Tlv {
|
|
||||||
Tlv {
|
|
||||||
r#type: 0x03,
|
|
||||||
value: self.checksum.to_be_bytes().to_vec(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Tlv {
|
|
||||||
r#type: u8,
|
|
||||||
value: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tlv {
|
|
||||||
pub fn new(r#type: u8, value: impl Into<Vec<u8>>) -> Self {
|
|
||||||
let value = value.into();
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!value.is_empty(),
|
|
||||||
"TLV values must have length greater than 1",
|
|
||||||
);
|
|
||||||
|
|
||||||
Self { r#type, value }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len(&self) -> u16 {
|
|
||||||
// 1b type + 2b len
|
|
||||||
// + [len]b value
|
|
||||||
1 + 2 + (self.value.len() as u16)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_to(&self, wrt: &mut impl io::Write) -> io::Result<()> {
|
|
||||||
wrt.write_all(&[self.r#type])?;
|
|
||||||
wrt.write_all(&(self.value.len() as u16).to_be_bytes())?;
|
|
||||||
wrt.write_all(&self.value)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_crc32c(&self) -> Option<Pp2Crc32c> {
|
|
||||||
if self.r#type != 0x03 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let checksum_bytes = <[u8; 4]>::try_from(self.value.as_slice()).ok()?;
|
|
||||||
|
|
||||||
Some(Pp2Crc32c {
|
|
||||||
checksum: u32::from_be_bytes(checksum_bytes),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tlv_as_crc32c() {
|
|
||||||
let noop = Tlv::new(0x04, vec![0x00]);
|
|
||||||
assert_eq!(noop.as_crc32c(), None);
|
|
||||||
|
|
||||||
let noop = Tlv::new(0x03, vec![0x08, 0x70, 0x17, 0x7b]);
|
|
||||||
assert_eq!(
|
|
||||||
noop.as_crc32c(),
|
|
||||||
Some(Pp2Crc32c {
|
|
||||||
checksum: 141563771
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
58
actix-proxy-protocol/src/tlv.rs
Normal file
58
actix-proxy-protocol/src/tlv.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
|
pub trait Tlv: Sized {
|
||||||
|
const TYPE: u8;
|
||||||
|
|
||||||
|
fn try_from_parts(typ: u8, value: &[u8]) -> Option<Self>;
|
||||||
|
|
||||||
|
fn value_bytes(&self) -> Vec<u8>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||||
|
pub struct Crc32c {
|
||||||
|
pub(crate) checksum: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tlv for Crc32c {
|
||||||
|
const TYPE: u8 = 0x03;
|
||||||
|
|
||||||
|
fn try_from_parts(typ: u8, value: &[u8]) -> Option<Self> {
|
||||||
|
if typ != Self::TYPE {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let checksum_bytes = <[u8; 4]>::try_from(value).ok()?;
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
checksum: u32::from_be_bytes(checksum_bytes),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_bytes(&self) -> Vec<u8> {
|
||||||
|
self.checksum.to_be_bytes().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// #[should_panic]
|
||||||
|
// fn tlv_zero_len() {
|
||||||
|
// Tlv::new(0x00, vec![]);
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tlv_as_crc32c() {
|
||||||
|
// noop
|
||||||
|
assert_eq!(Crc32c::try_from_parts(0x04, &[0x00]), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Crc32c::try_from_parts(0x03, &[0x08, 0x70, 0x17, 0x7b]),
|
||||||
|
Some(Crc32c {
|
||||||
|
checksum: 141563771
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,9 +3,13 @@ use std::{
|
|||||||
net::{IpAddr, SocketAddr},
|
net::{IpAddr, SocketAddr},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use smallvec::{smallvec, SmallVec, ToSmallVec as _};
|
||||||
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
use tokio::io::{AsyncWrite, AsyncWriteExt as _};
|
||||||
|
|
||||||
use crate::{AddressFamily, Command, Pp2Crc32c, Tlv, TransportProtocol, Version};
|
use crate::{
|
||||||
|
tlv::{Crc32c, Tlv},
|
||||||
|
AddressFamily, Command, TransportProtocol, Version,
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) const SIGNATURE: [u8; 12] = [
|
pub(crate) const SIGNATURE: [u8; 12] = [
|
||||||
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
|
0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A,
|
||||||
@ -18,26 +22,39 @@ pub struct Header {
|
|||||||
address_family: AddressFamily,
|
address_family: AddressFamily,
|
||||||
src: SocketAddr,
|
src: SocketAddr,
|
||||||
dst: SocketAddr,
|
dst: SocketAddr,
|
||||||
tlvs: Vec<Tlv>,
|
tlvs: SmallVec<[(u8, SmallVec<[u8; 16]>); 4]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Header {
|
impl Header {
|
||||||
pub const fn new(
|
pub fn new(
|
||||||
command: Command,
|
command: Command,
|
||||||
transport_protocol: TransportProtocol,
|
transport_protocol: TransportProtocol,
|
||||||
address_family: AddressFamily,
|
address_family: AddressFamily,
|
||||||
src: SocketAddr,
|
src: impl Into<SocketAddr>,
|
||||||
dst: SocketAddr,
|
dst: impl Into<SocketAddr>,
|
||||||
tlvs: Vec<Tlv>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
command,
|
command,
|
||||||
transport_protocol,
|
transport_protocol,
|
||||||
address_family,
|
address_family,
|
||||||
|
src: src.into(),
|
||||||
|
dst: dst.into(),
|
||||||
|
tlvs: SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_tcp_ipv4_proxy(src: impl Into<SocketAddr>, dst: impl Into<SocketAddr>) -> Self {
|
||||||
|
Self::new(
|
||||||
|
Command::Proxy,
|
||||||
|
TransportProtocol::Stream,
|
||||||
|
AddressFamily::Inet,
|
||||||
src,
|
src,
|
||||||
dst,
|
dst,
|
||||||
tlvs,
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_tlv(&mut self, typ: u8, value: impl AsRef<[u8]>) {
|
||||||
|
self.tlvs.push((typ, SmallVec::from_slice(value.as_ref())));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn v2_len(&self) -> u16 {
|
fn v2_len(&self) -> u16 {
|
||||||
@ -47,7 +64,12 @@ impl Header {
|
|||||||
16 + 2 // 16b IPv6 + 2b port number
|
16 + 2 // 16b IPv6 + 2b port number
|
||||||
};
|
};
|
||||||
|
|
||||||
(addr_len * 2) + self.tlvs.iter().map(|tlv| tlv.len()).sum::<u16>()
|
(addr_len * 2)
|
||||||
|
+ self
|
||||||
|
.tlvs
|
||||||
|
.iter()
|
||||||
|
.map(|(_, value)| 1 + 2 + value.len() as u16)
|
||||||
|
.sum::<u16>()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_to(&self, wrt: &mut impl io::Write) -> io::Result<()> {
|
pub fn write_to(&self, wrt: &mut impl io::Write) -> io::Result<()> {
|
||||||
@ -81,8 +103,10 @@ impl Header {
|
|||||||
wrt.write_all(&self.dst.port().to_be_bytes())?;
|
wrt.write_all(&self.dst.port().to_be_bytes())?;
|
||||||
|
|
||||||
// TLVs
|
// TLVs
|
||||||
for tlv in &self.tlvs {
|
for (typ, value) in &self.tlvs {
|
||||||
tlv.write_to(wrt)?;
|
wrt.write_all(&[*typ])?;
|
||||||
|
wrt.write_all(&(value.len() as u16).to_be_bytes())?;
|
||||||
|
wrt.write_all(&value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -100,8 +124,14 @@ impl Header {
|
|||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn has_tlv<T: Tlv>(&self) -> bool {
|
||||||
|
self.tlvs.iter().any(|&(typ, _)| typ == T::TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is not called last thing it will be wrong.
|
||||||
pub fn add_crc23c_checksum_tlv(&mut self) {
|
pub fn add_crc23c_checksum_tlv(&mut self) {
|
||||||
if self.tlvs.iter().any(|tlv| tlv.as_crc32c().is_some()) {
|
// don't add a checksum if it is already set
|
||||||
|
if self.has_tlv::<Crc32c>() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,7 +144,7 @@ impl Header {
|
|||||||
// the bits unchanged.
|
// the bits unchanged.
|
||||||
|
|
||||||
// add zeroed checksum field to TLVs
|
// add zeroed checksum field to TLVs
|
||||||
let crc = Pp2Crc32c::default().to_tlv();
|
let crc = (Crc32c::TYPE, Crc32c::default().value_bytes().to_smallvec());
|
||||||
self.tlvs.push(crc);
|
self.tlvs.push(crc);
|
||||||
|
|
||||||
// write PROXY header to buffer
|
// write PROXY header to buffer
|
||||||
@ -123,16 +153,18 @@ impl Header {
|
|||||||
|
|
||||||
// calculate CRC on buffer and update CRC TLV
|
// calculate CRC on buffer and update CRC TLV
|
||||||
let crc_calc = crc32fast::hash(&buf);
|
let crc_calc = crc32fast::hash(&buf);
|
||||||
self.tlvs.last_mut().unwrap().value = crc_calc.to_be_bytes().to_vec();
|
self.tlvs.last_mut().unwrap().1 = crc_calc.to_be_bytes().to_smallvec();
|
||||||
|
|
||||||
tracing::debug!("checksum is {}", crc_calc);
|
tracing::debug!("checksum is {}", crc_calc);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_crc32c_tlv(&self) -> Option<bool> {
|
pub fn validate_crc32c_tlv(&self) -> Option<bool> {
|
||||||
dbg!(&self.tlvs);
|
// extract crc32c TLV or exit early if none is present
|
||||||
|
let crc_sent = self
|
||||||
// exit early if no crc32c TLV is present
|
.tlvs
|
||||||
let crc_sent = self.tlvs.iter().filter_map(|tlv| tlv.as_crc32c()).next()?;
|
.iter()
|
||||||
|
.filter_map(|(typ, value)| Crc32c::try_from_parts(*typ, &value))
|
||||||
|
.next()?;
|
||||||
|
|
||||||
// If the checksum is provided as part of the PROXY header and the checksum
|
// If the checksum is provided as part of the PROXY header and the checksum
|
||||||
// functionality is supported by the receiver, the receiver MUST:
|
// functionality is supported by the receiver, the receiver MUST:
|
||||||
@ -145,9 +177,9 @@ impl Header {
|
|||||||
// The default procedure for handling an invalid TCP connection is to abort it.
|
// The default procedure for handling an invalid TCP connection is to abort it.
|
||||||
|
|
||||||
let mut this = self.clone();
|
let mut this = self.clone();
|
||||||
for tlv in this.tlvs.iter_mut() {
|
for (typ, value) in this.tlvs.iter_mut() {
|
||||||
if tlv.as_crc32c().is_some() {
|
if Crc32c::try_from_parts(*typ, &value).is_some() {
|
||||||
tlv.value.fill(0);
|
value.fill(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +200,6 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn tlv_zero_len() {
|
|
||||||
Tlv::new(0x00, vec![]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn write_v2_no_tlvs() {
|
fn write_v2_no_tlvs() {
|
||||||
let mut exp = Vec::new();
|
let mut exp = Vec::new();
|
||||||
@ -189,7 +215,6 @@ mod tests {
|
|||||||
AddressFamily::Inet,
|
AddressFamily::Inet,
|
||||||
SocketAddr::from(([127, 0, 0, 1], 1234)),
|
SocketAddr::from(([127, 0, 0, 1], 1234)),
|
||||||
SocketAddr::from(([127, 0, 0, 2], 80)),
|
SocketAddr::from(([127, 0, 0, 2], 80)),
|
||||||
vec![],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(header.v2_len(), 12);
|
assert_eq!(header.v2_len(), 12);
|
||||||
@ -207,7 +232,7 @@ mod tests {
|
|||||||
exp.extend_from_slice(&[0x00, 80, 0xff, 0xff]); // 45-49
|
exp.extend_from_slice(&[0x00, 80, 0xff, 0xff]); // 45-49
|
||||||
exp.extend_from_slice(&[0x04, 0x00, 0x01, 0x00]); // 50-53 NOOP TLV
|
exp.extend_from_slice(&[0x04, 0x00, 0x01, 0x00]); // 50-53 NOOP TLV
|
||||||
|
|
||||||
let header = Header::new(
|
let mut header = Header::new(
|
||||||
Command::Local,
|
Command::Local,
|
||||||
TransportProtocol::Stream,
|
TransportProtocol::Stream,
|
||||||
AddressFamily::Inet,
|
AddressFamily::Inet,
|
||||||
@ -216,9 +241,10 @@ mod tests {
|
|||||||
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
|
Ipv6Addr::from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]),
|
||||||
65535,
|
65535,
|
||||||
)),
|
)),
|
||||||
vec![Tlv::new(0x04, [0])],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
header.add_tlv(0x04, [0]);
|
||||||
|
|
||||||
assert_eq!(header.v2_len(), 36 + 4);
|
assert_eq!(header.v2_len(), 36 + 4);
|
||||||
assert_eq!(header.to_vec(), exp);
|
assert_eq!(header.to_vec(), exp);
|
||||||
}
|
}
|
||||||
@ -248,7 +274,6 @@ mod tests {
|
|||||||
AddressFamily::Inet,
|
AddressFamily::Inet,
|
||||||
SocketAddr::from(([127, 0, 0, 1], 80)),
|
SocketAddr::from(([127, 0, 0, 1], 80)),
|
||||||
SocketAddr::from(([127, 0, 0, 1], 80)),
|
SocketAddr::from(([127, 0, 0, 1], 80)),
|
||||||
vec![],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
@ -266,7 +291,7 @@ mod tests {
|
|||||||
assert_eq!(header.validate_crc32c_tlv().unwrap(), true);
|
assert_eq!(header.validate_crc32c_tlv().unwrap(), true);
|
||||||
|
|
||||||
// mangle crc32c TLV and assert that validate now fails
|
// mangle crc32c TLV and assert that validate now fails
|
||||||
*header.tlvs.last_mut().unwrap().value.last_mut().unwrap() = 0x00;
|
*header.tlvs.last_mut().unwrap().1.last_mut().unwrap() = 0x00;
|
||||||
assert_eq!(header.validate_crc32c_tlv().unwrap(), false);
|
assert_eq!(header.validate_crc32c_tlv().unwrap(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user