Skip to content

Commit d949fdb

Browse files
committed
WIP
1 parent 2f1ea75 commit d949fdb

14 files changed

Lines changed: 443 additions & 152 deletions

File tree

crates/tuic-server/src/acl.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -729,9 +729,7 @@ fn clone_rule_type(rt: &wrule::RuleType) -> wrule::RuleType {
729729
// Round-trip through Display → parse. This works for all types that are
730730
// produced by the conversion above (no Regex types are generated).
731731
let s = format!("{rt},__CLONE");
732-
wrule::Rule::parse(&s)
733-
.map(|r| r.rule_type)
734-
.unwrap_or(wrule::RuleType::Match)
732+
wrule::Rule::parse(&s).map(|r| r.rule_type).unwrap_or(wrule::RuleType::Match)
735733
}
736734

737735
#[cfg(test)]

crates/tuic-server/src/config.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,6 @@ impl Config {
469469
self.quic.pmtu = pmtu;
470470
}
471471
}
472-
473472
}
474473

475474
pub fn full_example() -> Self {
@@ -1715,12 +1714,21 @@ mod tests {
17151714

17161715
// Serialize to TOML string and verify rules appear as strings
17171716
let serialized = toml::to_string_pretty(&result).unwrap();
1718-
assert!(serialized.contains("DOMAIN,ad.example.com,REJECT"), "serialized:\n{serialized}");
1717+
assert!(
1718+
serialized.contains("DOMAIN,ad.example.com,REJECT"),
1719+
"serialized:\n{serialized}"
1720+
);
17191721
assert!(serialized.contains("MATCH,proxy"), "serialized:\n{serialized}");
1720-
assert!(serialized.contains("IP-CIDR,10.0.0.0/8,direct,no-resolve"), "serialized:\n{serialized}");
1722+
assert!(
1723+
serialized.contains("IP-CIDR,10.0.0.0/8,direct,no-resolve"),
1724+
"serialized:\n{serialized}"
1725+
);
17211726

17221727
// Verify the serialized form is a string array
1723-
assert!(serialized.contains(r#""DOMAIN,ad.example.com,REJECT""#), "rules should be serialized as quoted strings");
1728+
assert!(
1729+
serialized.contains(r#""DOMAIN,ad.example.com,REJECT""#),
1730+
"rules should be serialized as quoted strings"
1731+
);
17241732
}
17251733

17261734
#[tokio::test]
@@ -1740,12 +1748,7 @@ rules = ["INVALID_TYPE,value,target"]
17401748

17411749
fs::write(&config_path, bad_config).unwrap();
17421750

1743-
let cli = Cli::try_parse_from(vec![
1744-
"test_binary",
1745-
"--config",
1746-
&config_path.to_string_lossy(),
1747-
])
1748-
.unwrap();
1751+
let cli = Cli::try_parse_from(vec!["test_binary", "--config", &config_path.to_string_lossy()]).unwrap();
17491752

17501753
let result = parse_config(cli, EnvState::default()).await;
17511754
assert!(result.is_err());

crates/tuic-server/src/wind_adapter.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ use crate::{AppContext as TuicAppContext, acl::acl_to_rules, config::OutboundRul
5252
///
5353
/// Evaluation order:
5454
/// 1. **Experimental guards** – reject loopback / private addresses.
55-
/// 2. **Unified Metacubex-style rules** – legacy ACL rules are converted
56-
/// to Metacubex format and prepended to explicit `rules`, then
57-
/// delegated to [`AclRouter`] from wind-core.
55+
/// 2. **Unified Metacubex-style rules** – legacy ACL rules are converted to
56+
/// Metacubex format and prepended to explicit `rules`, then delegated to
57+
/// [`AclRouter`] from wind-core.
5858
/// 3. If nothing matched, forward to `"default"` outbound.
5959
pub struct TuicRouter {
6060
ctx: Arc<TuicAppContext>,
@@ -391,8 +391,8 @@ pub async fn create_inbound(ctx: Arc<TuicAppContext>) -> eyre::Result<(TuicInbou
391391
// through the standalone Server path which uses a resolver.
392392
// Here we attempt to load existing cert files, or fall back to self-signed.
393393
tracing::warn!(
394-
"auto_ssl with wind-tuic adapter is not fully supported; \
395-
attempting to load existing certs or falling back to self-signed for: {}",
394+
"auto_ssl with wind-tuic adapter is not fully supported; attempting to load existing certs or falling back to \
395+
self-signed for: {}",
396396
cfg.tls.hostname
397397
);
398398
let cert_path = &cfg.tls.certificate;
@@ -472,7 +472,6 @@ fn load_cert_from_files(
472472
let cert_data = std::fs::read(cert_path)?;
473473
let key_data = std::fs::read(key_path)?;
474474
let certs = rustls_pemfile::certs(&mut cert_data.as_slice()).collect::<Result<Vec<_>, _>>()?;
475-
let key = rustls_pemfile::private_key(&mut key_data.as_slice())?
476-
.ok_or_else(|| eyre::eyre!("No private key found"))?;
475+
let key = rustls_pemfile::private_key(&mut key_data.as_slice())?.ok_or_else(|| eyre::eyre!("No private key found"))?;
477476
Ok((certs, key))
478477
}

crates/tuic-tests/tests/integration_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,9 @@ fn test_fragmented_udp_packets() {
175175
fn test_edge_case_values() {
176176
// Test edge case values for Packet command
177177
let test_cases: Vec<(u16, u16, u8, u8, u16)> = vec![
178-
(0, 0, 1, 0, 0), // Minimum values
178+
(0, 0, 1, 0, 0), // Minimum values
179179
(u16::MAX, u16::MAX, u8::MAX, u8::MAX - 1, u16::MAX), // Maximum values
180-
(32768, 16384, 128, 64, 8192), // Mid-range values
180+
(32768, 16384, 128, 64, 8192), // Mid-range values
181181
];
182182

183183
for (assoc_id, pkt_id, frag_total, frag_id, size) in test_cases {

crates/wind-acme/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ pub fn is_valid_domain(hostname: &str) -> bool {
4040
/// started **only** when the ACME state machine actually needs to answer a
4141
/// challenge, and is shut down once the new certificate has been deployed.
4242
///
43-
/// Returns a certificate resolver that can be used with a `rustls::ServerConfig`.
43+
/// Returns a certificate resolver that can be used with a
44+
/// `rustls::ServerConfig`.
4445
pub async fn start_acme(
4546
cancel: CancellationToken,
4647
hostname: &str,

crates/wind-core/src/dispatcher.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,8 @@ impl<R: Router> InboundCallback for Dispatcher<R> {
222222
///
223223
/// Rule targets are mapped to [`RouteAction`] as follows:
224224
///
225-
/// * `"reject"` / `"block"` / `"deny"` (case-insensitive) → [`RouteAction::Reject`]
225+
/// * `"reject"` / `"block"` / `"deny"` (case-insensitive) →
226+
/// [`RouteAction::Reject`]
226227
/// * anything else → [`RouteAction::Forward`] with the target name
227228
///
228229
/// # Example

crates/wind-core/src/inbound.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
use crate::{tcp::AbstractTcpStream, types::TargetAddr, udp::UdpStream};
21
use std::future::Future;
32

3+
use crate::{tcp::AbstractTcpStream, types::TargetAddr, udp::UdpStream};
4+
45
pub trait FutResult<T> = Future<Output = eyre::Result<T>> + Send;
56

67
pub trait AbstractInbound {

crates/wind-core/src/interface.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub enum Network {
4141
ICMPv6,
4242
}
4343

44-
#[derive(Debug, Clone, Copy)]
44+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4545
pub enum StackPrefer {
4646
V4,
4747
V6,
@@ -54,3 +54,113 @@ impl StackPrefer {
5454
!matches!(self, StackPrefer::V4)
5555
}
5656
}
57+
58+
impl Serialize for StackPrefer {
59+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
60+
where
61+
S: serde::Serializer,
62+
{
63+
let s = match self {
64+
StackPrefer::V4 => "v4",
65+
StackPrefer::V6 => "v6",
66+
StackPrefer::V4V6 => "v4v6",
67+
StackPrefer::V6V4 => "v6v4",
68+
};
69+
serializer.serialize_str(s)
70+
}
71+
}
72+
73+
impl<'de> Deserialize<'de> for StackPrefer {
74+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
75+
where
76+
D: serde::Deserializer<'de>,
77+
{
78+
let s = String::deserialize(deserializer)?;
79+
match s.to_ascii_lowercase().as_str() {
80+
"v4" | "v4only" | "only_v4" => Ok(StackPrefer::V4),
81+
"v6" | "v6only" | "only_v6" => Ok(StackPrefer::V6),
82+
"v4v6" | "v4_v6" | "v4first" | "prefer_v4" => Ok(StackPrefer::V4V6),
83+
"v6v4" | "v6_v4" | "v6first" | "prefer_v6" => Ok(StackPrefer::V6V4),
84+
_ => Err(serde::de::Error::custom(format!("invalid stack preference: '{s}'"))),
85+
}
86+
}
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::*;
92+
93+
#[test]
94+
fn test_stack_prefer_serialize() {
95+
assert_eq!(serde_json::to_string(&StackPrefer::V4).unwrap(), r#""v4""#);
96+
assert_eq!(serde_json::to_string(&StackPrefer::V6).unwrap(), r#""v6""#);
97+
assert_eq!(serde_json::to_string(&StackPrefer::V4V6).unwrap(), r#""v4v6""#);
98+
assert_eq!(serde_json::to_string(&StackPrefer::V6V4).unwrap(), r#""v6v4""#);
99+
}
100+
101+
#[test]
102+
fn test_stack_prefer_deserialize_canonical() {
103+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v4""#).unwrap(), StackPrefer::V4);
104+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v6""#).unwrap(), StackPrefer::V6);
105+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v4v6""#).unwrap(), StackPrefer::V4V6);
106+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v6v4""#).unwrap(), StackPrefer::V6V4);
107+
}
108+
109+
#[test]
110+
fn test_stack_prefer_deserialize_aliases() {
111+
// V4 aliases
112+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v4only""#).unwrap(), StackPrefer::V4);
113+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""only_v4""#).unwrap(), StackPrefer::V4);
114+
115+
// V6 aliases
116+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v6only""#).unwrap(), StackPrefer::V6);
117+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""only_v6""#).unwrap(), StackPrefer::V6);
118+
119+
// V4V6 aliases
120+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v4_v6""#).unwrap(), StackPrefer::V4V6);
121+
assert_eq!(
122+
serde_json::from_str::<StackPrefer>(r#""v4first""#).unwrap(),
123+
StackPrefer::V4V6
124+
);
125+
assert_eq!(
126+
serde_json::from_str::<StackPrefer>(r#""prefer_v4""#).unwrap(),
127+
StackPrefer::V4V6
128+
);
129+
130+
// V6V4 aliases
131+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""v6_v4""#).unwrap(), StackPrefer::V6V4);
132+
assert_eq!(
133+
serde_json::from_str::<StackPrefer>(r#""v6first""#).unwrap(),
134+
StackPrefer::V6V4
135+
);
136+
assert_eq!(
137+
serde_json::from_str::<StackPrefer>(r#""prefer_v6""#).unwrap(),
138+
StackPrefer::V6V4
139+
);
140+
}
141+
142+
#[test]
143+
fn test_stack_prefer_deserialize_case_insensitive() {
144+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""V4""#).unwrap(), StackPrefer::V4);
145+
assert_eq!(serde_json::from_str::<StackPrefer>(r#""V4V6""#).unwrap(), StackPrefer::V4V6);
146+
assert_eq!(
147+
serde_json::from_str::<StackPrefer>(r#""V6First""#).unwrap(),
148+
StackPrefer::V6V4
149+
);
150+
}
151+
152+
#[test]
153+
fn test_stack_prefer_deserialize_invalid() {
154+
assert!(serde_json::from_str::<StackPrefer>(r#""invalid""#).is_err());
155+
assert!(serde_json::from_str::<StackPrefer>(r#""v5""#).is_err());
156+
}
157+
158+
#[test]
159+
fn test_stack_prefer_roundtrip() {
160+
for variant in [StackPrefer::V4, StackPrefer::V6, StackPrefer::V4V6, StackPrefer::V6V4] {
161+
let serialized = serde_json::to_string(&variant).unwrap();
162+
let deserialized: StackPrefer = serde_json::from_str(&serialized).unwrap();
163+
assert_eq!(variant, deserialized);
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)