Skip to content

Commit

Permalink
implement grpc for trojan and vmess (#203)
Browse files Browse the repository at this point in the history
* grpc

* up

* up

* up

* up

* up

* WIP

* up

* WIP

* WIP

* WIP

* WIP

* test with trojan + grpc

* up

* up

* up

* up

* up

* up

* up

* trojan grpc working

* it worked
  • Loading branch information
ibigbug authored Dec 6, 2023
1 parent 7c965cc commit f99e92a
Show file tree
Hide file tree
Showing 21 changed files with 475 additions and 208 deletions.
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions clash/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,15 @@ fn main() {
}
}
}
clash::start(clash::Options {
match clash::start(clash::Options {
config: clash::Config::File(file),
cwd: cli.directory.map(|x| x.to_string_lossy().to_string()),
rt: Some(TokioRuntime::MultiThread),
log_file: None,
})
.unwrap();
}) {
Ok(_) => {}
Err(_) => {
exit(1);
}
}
}
36 changes: 33 additions & 3 deletions clash/tests/data/config/rules.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ dns:
- 114.114.114.114 # default value
- 1.1.1.1 # default value
- tls://1.1.1.1:853 # DNS over TLS
- https://1.1.1.1/dns-query # DNS over HTTPS
# - dhcp://en0 # dns from dhcp

allow-lan: true
Expand Down Expand Up @@ -167,6 +166,20 @@ proxies:
h2-opts:
path: /ray

- name: grpc-vmess
type: vmess
server: 10.0.0.13
port: 19443
uuid: b831381d-6324-4d53-ad4f-8cda48b30811
alterId: 0
cipher: auto
udp: true
skip-cert-verify: true
tls: true
network: grpc
grpc-opts:
grpc-service-name: abc

- name: vmess-altid
type: vmess
server: tw-1.ac.laowanxiang.com
Expand All @@ -187,6 +200,7 @@ proxies:
cipher: aes-256-gcm
password: "password"
udp: true

- name: "trojan"
type: trojan
server: 10.0.0.13
Expand All @@ -199,6 +213,20 @@ proxies:
- http/1.1
skip-cert-verify: true

- name: "trojan-grpc"
type: trojan
server: 10.0.0.13
port: 19443
password: password1
udp: true
# sni: example.com # aka server name
alpn:
- h2
skip-cert-verify: true
network: grpc
grpc-opts:
grpc-service-name: def

proxy-providers:
file-provider:
type: file
Expand All @@ -217,11 +245,13 @@ rule-providers:
behavior: domain

rules:
- DOMAIN,ipinfo.io,relay
- DOMAIN,google.com,grpc-vmess
- DOMAIN-KEYWORD,httpbin,trojan-grpc
- DOMAIN,ipinfo.io,trojan-grpc
# - RULE-SET,file-provider,trojan
- GEOIP,CN,relay
- DOMAIN-SUFFIX,facebook.com,REJECT
- DOMAIN-KEYWORD,google,select
- DOMAIN-KEYWORD,google,grpc-vmess
- DOMAIN,google.com,select
- SRC-IP-CIDR,192.168.1.1/24,DIRECT
- GEOIP,CN,DIRECT
Expand Down
6 changes: 3 additions & 3 deletions clash_lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ bench = ["criterion"]

[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-util = { version = "0.7", features = ["net", "codec", "io"] }
tokio-util = { version = "0.7", features = ["net", "codec", "io", "compat"] }
tokio-rustls = "0.24"
thiserror = "1.0"
async-trait = "0.1"
Expand All @@ -26,9 +26,9 @@ byteorder = "1.5"
state = "0.6"
lru_time_cache = "0.11"
hyper = { version = "0.14", features = ["http1","http2","client", "server", "tcp"] }
http = { version = "0.2" }
http = { version = "0.2.11" }
httparse = "1.8.0"
h2 = "0.3"
h2 = "0.3.22"
prost = "0.12"
tower = { version = "0.4", features = ["util"] }
libc = "0.2"
Expand Down
4 changes: 4 additions & 0 deletions clash_lib/src/app/outbound/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{Mutex, RwLock};
use tracing::debug;
use tracing::error;

use tracing::info;
Expand Down Expand Up @@ -65,6 +66,7 @@ impl OutboundManager {
let mut selector_control = HashMap::new();
let proxy_manager = ProxyManager::new(dns_resolver.clone());

debug!("initializing proxy providers");
Self::load_proxy_providers(
cwd,
proxy_providers,
Expand All @@ -74,6 +76,7 @@ impl OutboundManager {
)
.await?;

debug!("initializing handlers");
Self::load_handlers(
outbounds,
outbound_groups,
Expand Down Expand Up @@ -628,6 +631,7 @@ impl OutboundManager {
error!("failed to initialize proxy provider {}: {}", p.name(), err);
}
}
info!("initialized provider {}", p.name());
}

Ok(())
Expand Down
16 changes: 9 additions & 7 deletions clash_lib/src/app/remote_content_manager/healthcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ impl HealthCheck {
let lazy = self.lazy;
let proxies = self.inner.read().await.proxies.clone();

let url = self.url.clone();
tokio::spawn(async move {
proxy_manager.check(&proxies, &url, None).await;
});
{
let url = self.url.clone();
let proxies = proxies.clone();
tokio::spawn(async move {
proxy_manager.check(&proxies, &url, None).await;
});
}

let inner = self.inner.clone();
let proxies = self.inner.read().await.proxies.clone();
let proxy_manager = self.proxy_manager.clone();
let url = self.url.clone();
let task_handle = tokio::spawn(async move {
Expand All @@ -65,8 +67,8 @@ impl HealthCheck {
_ = ticker.tick() => {
pm_debug!("healthcheck ticking: {}, lazy: {}", url, lazy);
let now = tokio::time::Instant::now();
let r = inner.read().await;
if !lazy || now.duration_since(r.last_check).as_secs() >= interval {
let last_check = inner.read().await.last_check;
if !lazy || now.duration_since(last_check).as_secs() >= interval {
proxy_manager.check(&proxies, &url, None).await;
let mut w = inner.write().await;
w.last_check = now;
Expand Down
1 change: 1 addition & 0 deletions clash_lib/src/config/internal/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ pub struct OutboundVmess {
pub network: Option<String>,
pub ws_opts: Option<WsOpt>,
pub h2_opts: Option<H2Opt>,
pub grpc_opts: Option<GrpcOpt>,
}

#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
Expand Down
19 changes: 14 additions & 5 deletions clash_lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ use common::http::new_http_client;
use common::mmdb;
use config::def::LogLevel;
use proxy::tun::get_tun_runner;

use state::InitCell;
use std::io;
use std::path::PathBuf;
use tokio::task::JoinHandle;
use tracing::error;
use tracing::info;

use std::sync::Arc;
use thiserror::Error;
use tokio::sync::{broadcast, mpsc, Mutex};
use tokio::task::JoinHandle;
use tracing::debug;
use tracing::error;
use tracing::info;

mod app;
mod common;
Expand Down Expand Up @@ -161,9 +162,12 @@ async fn start_async(opts: Options) -> Result<(), Error> {
let mut tasks = Vec::<Runner>::new();
let mut runners = Vec::new();

debug!("initializing dns resolver");
let system_resolver =
Arc::new(SystemResolver::new().map_err(|x| Error::DNSError(x.to_string()))?);
let client = new_http_client(system_resolver).map_err(|x| Error::DNSError(x.to_string()))?;

debug!("initializing mmdb");
let mmdb = Arc::new(
mmdb::MMDB::new(
cwd.join(&config.general.mmdb),
Expand All @@ -173,13 +177,15 @@ async fn start_async(opts: Options) -> Result<(), Error> {
.await?,
);

debug!("initializing cache store");
let cache_store = profile::ThreadSafeCacheFile::new(
cwd.join("cache.db").as_path().to_str().unwrap(),
config.profile.store_selected,
);

let dns_resolver = dns::Resolver::new(&config.dns, cache_store.clone(), mmdb.clone()).await;

debug!("initializing outbound manager");
let outbound_manager = Arc::new(
OutboundManager::new(
config
Expand Down Expand Up @@ -207,6 +213,7 @@ async fn start_async(opts: Options) -> Result<(), Error> {
.await?,
);

debug!("initializing router");
let router = Arc::new(
Router::new(
config.rules,
Expand All @@ -230,6 +237,7 @@ async fn start_async(opts: Options) -> Result<(), Error> {

let authenticator = Arc::new(auth::PlainAuthenticator::new(config.users));

debug!("initializing inbound manager");
let inbound_manager = Arc::new(Mutex::new(InboundManager::new(
config.general.inbound,
dispatcher.clone(),
Expand All @@ -244,6 +252,7 @@ async fn start_async(opts: Options) -> Result<(), Error> {
runners.push(tun_runner);
}

debug!("initializing dns listener");
let dns_listener_handle = dns::get_dns_listener(config.dns, dns_resolver.clone())
.await
.map(|l| tokio::spawn(l));
Expand Down Expand Up @@ -272,7 +281,7 @@ async fn start_async(opts: Options) -> Result<(), Error> {
}

runners.push(Box::pin(async move {
info!("receive shutdown signal");
info!("receiving shutdown signal");
shutdown_rx.recv().await;
Ok(())
}));
Expand Down
7 changes: 6 additions & 1 deletion clash_lib/src/proxy/converters/trojan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,12 @@ impl TryFrom<&OutboundTrojan> for AnyOutboundHandler {
.ok_or(Error::InvalidConfig(
"grpc_opts is required for grpc".to_owned(),
)),
_ => return Err(Error::InvalidConfig(format!("unsupported network: {}", x))),
_ => {
return Err(Error::InvalidConfig(format!(
"unsupported trojan network: {}",
x
)))
}
})
.transpose()?,
});
Expand Down
20 changes: 18 additions & 2 deletions clash_lib/src/proxy/converters/vmess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use tracing::warn;
use crate::{
config::internal::proxy::OutboundVmess,
proxy::{
options::{Http2Option, WsOption},
options::{GrpcOption, Http2Option, WsOption},
transport::TLSOptions,
vmess::{Handler, HandlerOptions, VmessTransport},
AnyOutboundHandler, CommonOption,
Expand Down Expand Up @@ -75,6 +75,22 @@ impl TryFrom<&OutboundVmess> for AnyOutboundHandler {
.ok_or(Error::InvalidConfig(
"h2_opts is required for h2".to_owned(),
)),
"grpc" => s
.grpc_opts
.as_ref()
.map(|x| {
VmessTransport::Grpc(GrpcOption {
service_name: x
.grpc_service_name
.as_ref()
.to_owned()
.unwrap_or(&"GunService".to_owned())
.to_owned(),
})
})
.ok_or(Error::InvalidConfig(
"grpc_opts is required for grpc".to_owned(),
)),
_ => {
return Err(Error::InvalidConfig(format!("unsupported network: {}", x)));
}
Expand Down Expand Up @@ -106,7 +122,7 @@ impl TryFrom<&OutboundVmess> for AnyOutboundHandler {
.map(|x| match x.as_str() {
"ws" => Ok(vec!["http/1.1".to_owned()]),
"http" => Ok(vec![]),
"h2" => Ok(vec!["h2".to_owned()]),
"h2" | "grpc" => Ok(vec!["h2".to_owned()]),
_ => Err(Error::InvalidConfig(format!("unsupported network: {}", x))),
})
.transpose()?,
Expand Down
Loading

0 comments on commit f99e92a

Please sign in to comment.