Accept as unknown_sni_action

This commit is contained in:
Alexey 2026-04-06 12:03:06 +03:00
parent b88457b9bc
commit 13dc1f70bf
No known key found for this signature in database
4 changed files with 112 additions and 23 deletions

View File

@ -1635,6 +1635,22 @@ mod tests {
cfg_mask.censorship.unknown_sni_action, cfg_mask.censorship.unknown_sni_action,
UnknownSniAction::Mask UnknownSniAction::Mask
); );
let cfg_accept: ProxyConfig = toml::from_str(
r#"
[server]
[general]
[network]
[access]
[censorship]
unknown_sni_action = "accept"
"#,
)
.unwrap();
assert_eq!(
cfg_accept.censorship.unknown_sni_action,
UnknownSniAction::Accept
);
} }
#[test] #[test]

View File

@ -1502,6 +1502,7 @@ pub enum UnknownSniAction {
#[default] #[default]
Drop, Drop,
Mask, Mask,
Accept,
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]

View File

@ -813,31 +813,45 @@ where
}; };
if client_sni.is_some() && matched_tls_domain.is_none() && preferred_user_hint.is_none() { if client_sni.is_some() && matched_tls_domain.is_none() && preferred_user_hint.is_none() {
auth_probe_record_failure_in(shared, peer.ip(), Instant::now());
maybe_apply_server_hello_delay(config).await;
let sni = client_sni.as_deref().unwrap_or_default(); let sni = client_sni.as_deref().unwrap_or_default();
let log_now = Instant::now(); match config.censorship.unknown_sni_action {
if should_emit_unknown_sni_warn_in(shared, log_now) { UnknownSniAction::Accept => {
warn!( debug!(
peer = %peer, peer = %peer,
sni = %sni, sni = %sni,
unknown_sni = true, unknown_sni = true,
unknown_sni_action = ?config.censorship.unknown_sni_action, unknown_sni_action = ?config.censorship.unknown_sni_action,
"TLS handshake rejected by unknown SNI policy" "TLS handshake accepted by unknown SNI policy"
); );
} else { }
info!( action @ (UnknownSniAction::Drop | UnknownSniAction::Mask) => {
peer = %peer, auth_probe_record_failure_in(shared, peer.ip(), Instant::now());
sni = %sni, maybe_apply_server_hello_delay(config).await;
unknown_sni = true, let log_now = Instant::now();
unknown_sni_action = ?config.censorship.unknown_sni_action, if should_emit_unknown_sni_warn_in(shared, log_now) {
"TLS handshake rejected by unknown SNI policy" warn!(
); peer = %peer,
sni = %sni,
unknown_sni = true,
unknown_sni_action = ?action,
"TLS handshake rejected by unknown SNI policy"
);
} else {
info!(
peer = %peer,
sni = %sni,
unknown_sni = true,
unknown_sni_action = ?action,
"TLS handshake rejected by unknown SNI policy"
);
}
return match action {
UnknownSniAction::Drop => HandshakeResult::Error(ProxyError::UnknownTlsSni),
UnknownSniAction::Mask => HandshakeResult::BadClient { reader, writer },
UnknownSniAction::Accept => unreachable!(),
};
}
} }
return match config.censorship.unknown_sni_action {
UnknownSniAction::Drop => HandshakeResult::Error(ProxyError::UnknownTlsSni),
UnknownSniAction::Mask => HandshakeResult::BadClient { reader, writer },
};
} }
let secrets = decode_user_secrets_in(shared, config, preferred_user_hint); let secrets = decode_user_secrets_in(shared, config, preferred_user_hint);

View File

@ -1006,6 +1006,64 @@ async fn tls_unknown_sni_mask_policy_falls_back_to_bad_client() {
assert!(matches!(result, HandshakeResult::BadClient { .. })); assert!(matches!(result, HandshakeResult::BadClient { .. }));
} }
#[tokio::test]
async fn tls_unknown_sni_accept_policy_continues_auth_path() {
let secret = [0x4Bu8; 16];
let mut config = test_config_with_secret_hex("4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b4b");
config.censorship.unknown_sni_action = UnknownSniAction::Accept;
let replay_checker = ReplayChecker::new(128, Duration::from_secs(60));
let rng = SecureRandom::new();
let peer: SocketAddr = "198.51.100.210:44326".parse().unwrap();
let handshake =
make_valid_tls_client_hello_with_sni_and_alpn(&secret, 0, "unknown.example", &[b"h2"]);
let result = handle_tls_handshake(
&handshake,
tokio::io::empty(),
tokio::io::sink(),
peer,
&config,
&replay_checker,
&rng,
None,
)
.await;
assert!(matches!(result, HandshakeResult::Success(_)));
}
#[tokio::test]
async fn tls_unknown_sni_accept_policy_still_requires_valid_secret() {
let mut config = test_config_with_secret_hex("4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c4c");
config.censorship.unknown_sni_action = UnknownSniAction::Accept;
let replay_checker = ReplayChecker::new(128, Duration::from_secs(60));
let rng = SecureRandom::new();
let peer: SocketAddr = "198.51.100.211:44326".parse().unwrap();
let attacker_secret = [0x4Du8; 16];
let handshake = make_valid_tls_client_hello_with_sni_and_alpn(
&attacker_secret,
0,
"unknown.example",
&[b"h2"],
);
let result = handle_tls_handshake(
&handshake,
tokio::io::empty(),
tokio::io::sink(),
peer,
&config,
&replay_checker,
&rng,
None,
)
.await;
assert!(matches!(result, HandshakeResult::BadClient { .. }));
}
#[tokio::test] #[tokio::test]
async fn tls_missing_sni_keeps_legacy_auth_path() { async fn tls_missing_sni_keeps_legacy_auth_path() {
let secret = [0x4Au8; 16]; let secret = [0x4Au8; 16];