Skip to content

Commit

Permalink
DynOutboundV1Proxy analyzer
Browse files Browse the repository at this point in the history
  • Loading branch information
bdbai committed Mar 11, 2024
1 parent c7bbef9 commit 5866a40
Show file tree
Hide file tree
Showing 9 changed files with 1,279 additions and 317 deletions.
1 change: 1 addition & 0 deletions ytflow-app-util/src/cbor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

1 change: 1 addition & 0 deletions ytflow-app-util/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![cfg_attr(feature = "ffi", feature(ptr_metadata))]

pub mod cbor;
#[cfg(feature = "ffi")]
pub mod ffi;
pub mod proxy;
Expand Down
1 change: 1 addition & 0 deletions ytflow-app-util/src/proxy/data.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod analyze;
mod compose_v1;
mod v1;

pub use analyze::{analyze_data_proxy, AnalyzeError, AnalyzeResult};
pub use compose_v1::{compose_data_proxy as compose_data_proxy_v1, ComposeError, ComposeResult};
31 changes: 28 additions & 3 deletions ytflow-app-util/src/proxy/data/analyze.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
use thiserror::Error;

use crate::proxy::Proxy;

pub enum AnalyzeError {}
#[derive(Debug, Clone, Error, PartialEq, Eq)]
pub enum AnalyzeError {
#[error("unknown proxy version")]
UnknownVersion,
#[error("invalid JSON, proxy or plugin format")]
InvalidEncoding,
#[error(r#"duplicated plugin name "{0}""#)]
DuplicateName(String),
#[error(r#"plugin "{0}" required by "{1}" not found"#)]
PluginNotFound(String, String),
#[error(r#"unknown access point: "{0}""#)]
UnknownAccessPoint(String),
#[error(r#"expect plugin "{0}" to have a UDP access point = {1}, but it does not"#)]
UnexpectedUdpAccessPoint(String, bool),
#[error("too complicated")]
TooComplicated,
#[error(r#"invalid plugin: "{0}""#)]
InvalidPlugin(String),
#[error(r#"unused plugin: "{0}""#)]
UnusedPlugin(String),
}

pub type AnalyzeResult<T> = Result<T, AnalyzeError>;

pub fn analyze_data_proxy(name: String, proxy: &[u8]) -> AnalyzeResult<Proxy> {
todo!()
pub fn analyze_data_proxy(name: String, proxy: &[u8], version: u16) -> AnalyzeResult<Proxy> {
if version != 0 {
return Err(AnalyzeError::UnknownVersion);
}
super::v1::analyzer::analyze(name, proxy)
}
313 changes: 5 additions & 308 deletions ytflow-app-util/src/proxy/data/compose_v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ pub enum ComposeError {

pub type ComposeResult<T> = Result<T, ComposeError>;

fn to_cbor(value: Result<ciborium::Value, ciborium::value::Error>) -> ByteBuf {
let buf = Vec::with_capacity(128);
let value = value.expect("cannot encode cbor");
let buf = cbor4ii::serde::to_vec(buf, &value).expect("cannot serialize cbor");
pub(crate) fn to_cbor(value: Result<ciborium::Value, ciborium::value::Error>) -> ByteBuf {
let mut buf = Vec::with_capacity(128);
ciborium::ser::into_writer(&value.expect("cannot encode cbor"), &mut buf)
.expect("Cannot serialize proxy");
ByteBuf::from(buf)
}

Expand Down Expand Up @@ -155,7 +155,7 @@ fn encode_protocol(
plugin: "vmess-client".into(),
plugin_version: 0,
param: to_cbor(cbor!({
"user_id" => vmess.user_id,
"user_id" => vmess.user_id.to_string(),
"alter_id" => vmess.alter_id,
"security" => vmess.security,
"tcp_next" => tcp_next,
Expand Down Expand Up @@ -254,306 +254,3 @@ pub fn compose_data_proxy(proxy: &Proxy) -> ComposeResult<Vec<u8>> {
cbor4ii::serde::to_vec(Vec::with_capacity(512), &composed).expect("Cannot serialize proxy");
Ok(buf)
}

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;

use ciborium::Value;
use ytflow::{flow::HostName, plugin::shadowsocks::SupportedCipher};

use crate::proxy::{obfs::HttpObfsObfs, protocol::ShadowsocksProxy};

use super::*;

fn deserialize_plugin_param(data: &[u8]) -> BTreeMap<String, Value> {
cbor4ii::serde::from_slice(data).unwrap()
}
fn find_plugin_param(proxy: &DynOutboundV1Proxy, plugin_name: &str) -> BTreeMap<String, Value> {
let plugin = proxy
.plugins
.iter()
.find(|p| p.name == plugin_name)
.expect(&format!("{} not found", plugin_name));
deserialize_plugin_param(&plugin.param)
}
fn assert_plugin(
actual_plugin: &DynOutboundV1Plugin,
expected_plugin: &DynOutboundV1Plugin,
expected_data: Result<Value, ciborium::value::Error>,
desc: &str,
) {
assert_eq!(actual_plugin.name, expected_plugin.name);
assert_eq!(
actual_plugin.plugin, expected_plugin.plugin,
"{} {}",
&expected_plugin.name, desc
);
assert_eq!(
actual_plugin.plugin_version, expected_plugin.plugin_version,
"{} {}",
&expected_plugin.name, desc
);
assert_eq!(
cbor4ii::serde::from_slice::<Value>(&actual_plugin.param).unwrap(),
expected_data.unwrap(),
"{} {}",
&expected_plugin.name,
desc
);
}

#[test]
fn test_compose_data_proxy_no_leg() {
let proxy = Proxy {
name: "test".into(),
legs: vec![],
udp_supported: false,
};
assert_eq!(compose_data_proxy(&proxy), Err(ComposeError::NoLeg));
}

#[test]
fn test_compose_data_proxy_one_leg_only_protocol() {
let proxy = Proxy {
name: "test".into(),
legs: vec![ProxyLeg {
protocol: ProxyProtocolType::Http(Default::default()),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: None,
tls: None,
}],
udp_supported: true,
};
let data = compose_data_proxy(&proxy).unwrap();
let proxy: DynOutboundV1Proxy = cbor4ii::serde::from_slice(&data).unwrap();
assert_eq!(proxy.tcp_entry, "p.tcp");
assert_eq!(proxy.udp_entry, None);
assert_eq!(proxy.plugins.len(), 2);
let redir = proxy.plugins.iter().find(|p| p.name == "r").unwrap();
let protocol = proxy.plugins.iter().find(|p| p.name == "p").unwrap();
assert_plugin(
redir,
&DynOutboundV1Plugin {
name: "r".into(),
plugin: "redirect".into(),
plugin_version: 0,
param: Default::default(),
},
cbor!({
"dest" => DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
"tcp_next" => "$out.tcp",
"udp_next" => "$out.udp",
}),
"redir",
);
assert_plugin(
protocol,
&DynOutboundV1Plugin {
name: "p".into(),
plugin: "http-proxy-client".into(),
plugin_version: 0,
param: Default::default(),
},
cbor!({
"user" => ByteBuf::default(),
"pass" => ByteBuf::default(),
"tcp_next" => "r.tcp",
}),
"protocol",
);
}

#[test]
fn test_compose_data_proxy_one_leg_protocol_tls() {
let proxy = Proxy {
name: "test".into(),
legs: vec![ProxyLeg {
protocol: ProxyProtocolType::Http(Default::default()),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: None,
tls: Some(Default::default()),
}],
udp_supported: false,
};
let data = compose_data_proxy(&proxy).unwrap();
let proxy: DynOutboundV1Proxy = cbor4ii::serde::from_slice(&data).unwrap();
assert_eq!(proxy.plugins.len(), 3);
let tls = find_plugin_param(&proxy, "t");
let redir = find_plugin_param(&proxy, "r");
let protocol = find_plugin_param(&proxy, "p");

assert_eq!(tls["next"], cbor!("$out.tcp").unwrap(), "tls");
assert_eq!(redir["tcp_next"], cbor!("t.tcp").unwrap(), "redir");
assert_eq!(protocol["tcp_next"], cbor!("r.tcp").unwrap(), "protocol");
}
#[test]
fn test_compose_data_proxy_one_leg_protocol_obfs() {
let proxy = Proxy {
name: "test".into(),
legs: vec![ProxyLeg {
protocol: ProxyProtocolType::Shadowsocks(ShadowsocksProxy {
cipher: SupportedCipher::Aes128Gcm,
password: ByteBuf::from("password"),
}),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: Some(ProxyObfsType::HttpObfs(HttpObfsObfs {
host: "obfs.example.com".into(),
path: "/obfs".into(),
})),
tls: None,
}],
udp_supported: false,
};
let data = compose_data_proxy(&proxy).unwrap();
let proxy: DynOutboundV1Proxy = cbor4ii::serde::from_slice(&data).unwrap();
assert_eq!(proxy.plugins.len(), 3);
assert_eq!(proxy.udp_entry, None);
let obfs = find_plugin_param(&proxy, "o");
let redir = find_plugin_param(&proxy, "r");
let protocol = find_plugin_param(&proxy, "p");

assert_eq!(obfs["next"], cbor!("$out.tcp").unwrap(), "obfs");
assert_eq!(redir["tcp_next"], cbor!("o.tcp").unwrap(), "redir");
assert_eq!(redir["udp_next"], cbor!("$out.udp").unwrap(), "redir");
assert_eq!(protocol["tcp_next"], cbor!("r.tcp").unwrap(), "protocol");
assert_eq!(protocol["udp_next"], cbor!("r.udp").unwrap(), "protocol");
}
#[test]
fn test_compose_data_proxy_one_leg_protocol_obfs_tls() {
let proxy = Proxy {
name: "test".into(),
legs: vec![ProxyLeg {
protocol: ProxyProtocolType::Shadowsocks(ShadowsocksProxy {
cipher: SupportedCipher::Aes128Gcm,
password: ByteBuf::from("password"),
}),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: Some(ProxyObfsType::HttpObfs(HttpObfsObfs {
host: "obfs.example.com".into(),
path: "/obfs".into(),
})),
tls: Some(Default::default()),
}],
udp_supported: false,
};
let data = compose_data_proxy(&proxy).unwrap();
let proxy: DynOutboundV1Proxy = cbor4ii::serde::from_slice(&data).unwrap();
assert_eq!(proxy.plugins.len(), 4);
let tls = find_plugin_param(&proxy, "t");
let obfs = find_plugin_param(&proxy, "o");
let redir = find_plugin_param(&proxy, "r");
let protocol = find_plugin_param(&proxy, "p");

assert_eq!(tls["next"], cbor!("$out.tcp").unwrap(), "tls");
assert_eq!(obfs["next"], cbor!("t.tcp").unwrap(), "obfs");
assert_eq!(redir["tcp_next"], cbor!("o.tcp").unwrap(), "redir");
assert_eq!(redir["udp_next"], cbor!("$out.udp").unwrap(), "redir");
assert_eq!(protocol["tcp_next"], cbor!("r.tcp").unwrap(), "protocol");
assert_eq!(protocol["udp_next"], cbor!("r.udp").unwrap(), "protocol");
}
#[test]
fn test_compose_data_proxy_4legs() {
let proxy = Proxy {
name: "test".into(),
legs: vec![
ProxyLeg {
protocol: ProxyProtocolType::Shadowsocks(ShadowsocksProxy {
cipher: SupportedCipher::Aes128Gcm,
password: ByteBuf::from("password"),
}),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: Some(ProxyObfsType::HttpObfs(HttpObfsObfs {
host: "obfs.example.com".into(),
path: "/obfs".into(),
})),
tls: Some(Default::default()),
},
ProxyLeg {
protocol: ProxyProtocolType::Http(Default::default()),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: None,
tls: Some(Default::default()),
},
ProxyLeg {
protocol: ProxyProtocolType::Http(Default::default()),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: Some(ProxyObfsType::HttpObfs(HttpObfsObfs {
host: "obfs.example.com".into(),
path: "/obfs".into(),
})),
tls: None,
},
ProxyLeg {
protocol: ProxyProtocolType::Shadowsocks(ShadowsocksProxy {
cipher: SupportedCipher::Aes128Gcm,
password: ByteBuf::from("password"),
}),
dest: DestinationAddr {
host: HostName::from_domain_name("example.com".into()).unwrap(),
port: 443,
},
obfs: None,
tls: None,
},
],
udp_supported: true,
};
let data = compose_data_proxy(&proxy).unwrap();
let proxy: DynOutboundV1Proxy = cbor4ii::serde::from_slice(&data).unwrap();
assert_eq!(proxy.tcp_entry, "p4.tcp");
assert_eq!(proxy.udp_entry, Some("p4.udp".into()));
assert_eq!(proxy.plugins.len(), 12);
let tls1 = find_plugin_param(&proxy, "t1");
let obfs1 = find_plugin_param(&proxy, "o1");
let redir1 = find_plugin_param(&proxy, "r1");
let protocol1 = find_plugin_param(&proxy, "p1");
assert_eq!(tls1["next"], cbor!("$out.tcp").unwrap(), "tls1");
assert_eq!(obfs1["next"], cbor!("t1.tcp").unwrap(), "obfs1");
assert_eq!(redir1["tcp_next"], cbor!("o1.tcp").unwrap(), "redir1");
assert_eq!(redir1["udp_next"], cbor!("$out.udp").unwrap(), "redir1");
assert_eq!(protocol1["tcp_next"], cbor!("r1.tcp").unwrap(), "protocol1");
assert_eq!(protocol1["udp_next"], cbor!("r1.udp").unwrap(), "protocol1");
let tls2 = find_plugin_param(&proxy, "t2");
let redir2 = find_plugin_param(&proxy, "r2");
let protocol2 = find_plugin_param(&proxy, "p2");
assert_eq!(tls2["next"], cbor!("p1.tcp").unwrap(), "tls2");
assert_eq!(redir2["tcp_next"], cbor!("t2.tcp").unwrap(), "redir2");
assert_eq!(protocol2["tcp_next"], cbor!("r2.tcp").unwrap(), "protocol2");
let obfs3 = find_plugin_param(&proxy, "o3");
let redir3 = find_plugin_param(&proxy, "r3");
let protocol3 = find_plugin_param(&proxy, "p3");
assert_eq!(obfs3["next"], cbor!("p2.tcp").unwrap(), "obfs3");
assert_eq!(redir3["tcp_next"], cbor!("o3.tcp").unwrap(), "redir3");
assert_eq!(protocol3["tcp_next"], cbor!("r3.tcp").unwrap(), "protocol3");
let redir4 = find_plugin_param(&proxy, "r4");
let protocol4 = find_plugin_param(&proxy, "p4");
assert_eq!(redir4["tcp_next"], cbor!("p3.tcp").unwrap(), "redir4");
assert_eq!(redir4["udp_next"], cbor!("$null.udp").unwrap(), "redir4");
assert_eq!(protocol4["tcp_next"], cbor!("r4.tcp").unwrap(), "protocol4");
}
}
Loading

0 comments on commit 5866a40

Please sign in to comment.