Skip to content

Commit

Permalink
hysteria2 hop
Browse files Browse the repository at this point in the history
  • Loading branch information
eauxxs committed Oct 21, 2024
1 parent d22ff7c commit ec4ecf2
Show file tree
Hide file tree
Showing 9 changed files with 341 additions and 433 deletions.
115 changes: 56 additions & 59 deletions Cargo.lock

Large diffs are not rendered by default.

16 changes: 13 additions & 3 deletions clash/tests/data/config/hysteria2.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ port: 8891
socks-port: 8889
mixed-port: 8888


dns:
enable: true
listen: 127.0.0.1:53533
Expand Down Expand Up @@ -50,8 +49,19 @@ proxies:
password: passwd
sni: example.com
skip-cert-verify: true
obfs: salamander
obfs-password: "passwd"

# fingerprint: "DF:F8:47:B5:6A:A3:8D:E8:A8:09:76:70:DD:1D:BB:DF:EB:3D:FB:07:FB:12:EE:C5:A9:BC:9B:07:26:7A:75:33"
# or
# fingerprint: "dff847b56aa38de8a8097670dd1dbbdfeb3dfb07fb12eec5a9bc9b07267a7533"

# obfs: salamander
# obfs-password: "passwd"

# server port hop range
# if hysteria2 server is deployed on `localhost` for testing, you can use this iptables commands to redirect the traffic, otherwise https://v2.hysteria.network/docs/advanced/Port-Hopping/#server
# sudo iptables -t nat -I PREROUTING -p udp --dport 15000:16000 -j REDIRECT --to-ports 10086
# sudo iptables -t nat -I OUTPUT -o lo -p udp --dport 15000:16000 -j REDIRECT --to-ports 10086
ports: "15000-16000"

rules:
- MATCH, local
2 changes: 1 addition & 1 deletion clash_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ tokio-test = "0.4.4"
axum-macros = "0.4.2"
bollard = "0.17"
serial_test = "3.1"
env_logger = "0.11"
env_logger = "*"

[build-dependencies]
prost-build = "0.13"
Expand Down
2 changes: 1 addition & 1 deletion clash_lib/src/app/profile/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, sync::Arc};

use serde::{Deserialize, Serialize};
use tracing::{error, trace, warn};
use tracing::{error, warn};

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Db {
Expand Down
31 changes: 27 additions & 4 deletions clash_lib/src/config/internal/proxy.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::{common::utils::default_bool_true, config::utils, Error};
use crate::{common::utils::{decode_hex, default_bool_true}, config::utils, Error};
use serde::{de::value::MapDeserializer, Deserialize};
use serde_yaml::Value;
use std::{
collections::HashMap,
fmt::{Display, Formatter},
fmt::{Display, Formatter}, path::PathBuf,
};
use uuid::Uuid;

Expand Down Expand Up @@ -499,8 +499,11 @@ pub struct OutboundHysteria2 {
pub sni: Option<String>,
pub skip_cert_verify: bool,
pub ca: Option<String>,
pub ca_str: Option<String>,
pub fingerprint: Option<String>,
pub ca_str: Option<PathBuf>,
// a hex_encoded sha256 fingerprint of the server certificate
#[serde(deserialize_with = "deserialize_sha256")]
#[serde(default)]
pub fingerprint: Option<[u8; 32]>,
/// bbr congestion control window
pub cwnd: Option<u64>,
}
Expand All @@ -510,3 +513,23 @@ pub struct OutboundHysteria2 {
pub enum Hysteria2Obfs {
Salamander,
}

fn deserialize_sha256<'de, D>(deserializer: D) -> Result<Option<[u8; 32]>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = Option::<String>::deserialize(deserializer)?.map(|x| x.replace(':', ""));
if let Some(s) = s {
let s = decode_hex(&s).map_err(serde::de::Error::custom)?;
if s.len() != 32 {
return Err(serde::de::Error::custom(
"fingerprint must be 32 bytes long",
));
}
let mut arr = [0; 32];
arr.copy_from_slice(&s);
Ok(Some(arr))
} else {
Ok(None)
}
}
45 changes: 39 additions & 6 deletions clash_lib/src/proxy/converters/hysteria2.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::{
num::{NonZeroU16, ParseIntError},
ops::RangeInclusive,
path::PathBuf,
sync::Arc,
};

use rand::Rng;
use rustls::pki_types::{pem::PemObject, CertificateDer};

use crate::{
config::internal::proxy::{Hysteria2Obfs, OutboundHysteria2},
Expand Down Expand Up @@ -40,10 +42,8 @@ impl PortGenrateor {
}

pub fn get(&self) -> u16 {
let mut rng = rand::thread_rng();
let len =
1 + self.ports.len() + self.range.iter().map(|r| r.len()).sum::<usize>();
let idx = rng.gen_range(0..len);
let len = 1 /* default_port */ + self.ports.len() + self.range.iter().map(|r| r.len()).sum::<usize>();
let idx = rand::thread_rng().gen_range(0..len);
match idx {
0 => self.default,
idx if idx <= self.ports.len() => self.ports[idx - 1],
Expand Down Expand Up @@ -122,14 +122,13 @@ impl TryFrom<OutboundHysteria2> for AnyOutboundHandler {
sni: value.sni.or(addr.domain().map(|s| s.to_owned())),
addr,
alpn: value.alpn.unwrap_or_default(),
ca: value.ca.map(|s| s.into()),
ca: parse_certificate(value.ca.as_deref(), value.ca_str.as_ref())?,
fingerprint: value.fingerprint,
skip_cert_verify: value.skip_cert_verify,
passwd: value.password,
ports: ports_gen,
obfs,
up_down: value.up.zip(value.down),
ca_str: value.ca_str,
cwnd: value.cwnd,
};

Expand All @@ -138,6 +137,40 @@ impl TryFrom<OutboundHysteria2> for AnyOutboundHandler {
}
}

fn parse_certificate(
ca_str: Option<&str>,
ca_path: Option<&PathBuf>,
) -> Result<Option<CertificateDer<'static>>, crate::Error> {
let ca_from_str = ca_str
.map(|s| CertificateDer::from_pem_slice(s.as_bytes()).map(|c| c.to_owned()))
.transpose()
.map_err(|e| {
crate::Error::InvalidConfig(format!("parse ca from str error: {:?}", e))
})?;

let ca_from_path = ca_path
.map(|p| CertificateDer::from_pem_file(p).map(|c| c.to_owned()))
.transpose()
.map_err(|e| {
crate::Error::InvalidConfig(format!("parse ca from path error: {:?}", e))
})?;

match (ca_from_str, ca_from_path) {
(Some(ca), None) => Ok(Some(ca)),
(None, Some(ca)) => Ok(Some(ca)),
(Some(a), Some(b)) => {
if a == b {
Ok(a.into())
} else {
Err(crate::Error::InvalidConfig(
"ca from str and path not equal".to_owned(),
))
}
}
(None, None) => Ok(None),
}
}

#[test]
fn test_port_gen() {
let p = PortGenrateor::new(1000).parse_ports_str("").unwrap();
Expand Down
Loading

0 comments on commit ec4ecf2

Please sign in to comment.