mirror of https://github.com/telemt/telemt.git
Allow Shadowsocks upstreams in middle-proxy mode
Co-authored-by: Maxim Myalin <maxim@myalin.ru>
This commit is contained in:
parent
2e8bfa1101
commit
140bf0e180
|
|
@ -122,11 +122,11 @@ enabled = true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Shadowsocks as Upstream
|
#### Shadowsocks as Upstream
|
||||||
Requires `use_middle_proxy = false`.
|
Works with both `use_middle_proxy = false` and `use_middle_proxy = true`.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[general]
|
[general]
|
||||||
use_middle_proxy = false
|
use_middle_proxy = true
|
||||||
|
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
type = "shadowsocks"
|
type = "shadowsocks"
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ Defaults below are code defaults (used when a key is omitted), not necessarily v
|
||||||
8. In ME mode, the selected upstream is also used for ME TCP dial path.
|
8. In ME mode, the selected upstream is also used for ME TCP dial path.
|
||||||
9. In ME mode for `direct` upstream with bind/interface, STUN reflection logic is bind-aware for KDF source material.
|
9. In ME mode for `direct` upstream with bind/interface, STUN reflection logic is bind-aware for KDF source material.
|
||||||
10. In ME mode for SOCKS upstream, SOCKS `BND.ADDR/BND.PORT` is used for KDF when it is valid/public for the same family.
|
10. In ME mode for SOCKS upstream, SOCKS `BND.ADDR/BND.PORT` is used for KDF when it is valid/public for the same family.
|
||||||
11. `shadowsocks` upstreams require `general.use_middle_proxy = false`. Config load fails fast if ME mode is enabled.
|
11. `shadowsocks` upstreams work in both Direct and ME modes. In ME mode, the connected local Shadowsocks address is reused for bind-aware STUN reflection when available.
|
||||||
|
|
||||||
## Upstream Configuration Examples
|
## Upstream Configuration Examples
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ enabled = true
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[general]
|
[general]
|
||||||
use_middle_proxy = false
|
use_middle_proxy = true
|
||||||
|
|
||||||
[[upstreams]]
|
[[upstreams]]
|
||||||
type = "shadowsocks"
|
type = "shadowsocks"
|
||||||
|
|
|
||||||
|
|
@ -129,16 +129,6 @@ fn sanitize_ad_tag(ad_tag: &mut Option<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_upstreams(config: &ProxyConfig) -> Result<()> {
|
fn validate_upstreams(config: &ProxyConfig) -> Result<()> {
|
||||||
let has_enabled_shadowsocks = config.upstreams.iter().any(|upstream| {
|
|
||||||
upstream.enabled && matches!(upstream.upstream_type, UpstreamType::Shadowsocks { .. })
|
|
||||||
});
|
|
||||||
|
|
||||||
if has_enabled_shadowsocks && config.general.use_middle_proxy {
|
|
||||||
return Err(ProxyError::Config(
|
|
||||||
"shadowsocks upstreams require general.use_middle_proxy = false".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
for upstream in &config.upstreams {
|
for upstream in &config.upstreams {
|
||||||
if let UpstreamType::Shadowsocks { url, .. } = &upstream.upstream_type {
|
if let UpstreamType::Shadowsocks { url, .. } = &upstream.upstream_type {
|
||||||
let parsed = ShadowsocksServerConfig::from_url(url)
|
let parsed = ShadowsocksServerConfig::from_url(url)
|
||||||
|
|
@ -2275,7 +2265,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn shadowsocks_requires_direct_mode() {
|
fn shadowsocks_is_allowed_with_middle_proxy() {
|
||||||
let toml = format!(
|
let toml = format!(
|
||||||
r#"
|
r#"
|
||||||
[general]
|
[general]
|
||||||
|
|
@ -2294,11 +2284,11 @@ mod tests {
|
||||||
url = TEST_SHADOWSOCKS_URL,
|
url = TEST_SHADOWSOCKS_URL,
|
||||||
);
|
);
|
||||||
let dir = std::env::temp_dir();
|
let dir = std::env::temp_dir();
|
||||||
let path = dir.join("telemt_shadowsocks_me_reject_test.toml");
|
let path = dir.join("telemt_shadowsocks_me_allow_test.toml");
|
||||||
std::fs::write(&path, toml).unwrap();
|
std::fs::write(&path, toml).unwrap();
|
||||||
let err = ProxyConfig::load(&path).unwrap_err().to_string();
|
let loaded = ProxyConfig::load(&path);
|
||||||
|
|
||||||
assert!(err.contains("shadowsocks upstreams require general.use_middle_proxy = false"));
|
assert!(loaded.is_ok());
|
||||||
|
|
||||||
let _ = std::fs::remove_file(path);
|
let _ = std::fs::remove_file(path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ use bytes::Bytes;
|
||||||
use crate::crypto::{AesCbc, crc32, crc32c};
|
use crate::crypto::{AesCbc, crc32, crc32c};
|
||||||
use crate::error::{ProxyError, Result};
|
use crate::error::{ProxyError, Result};
|
||||||
use crate::protocol::constants::*;
|
use crate::protocol::constants::*;
|
||||||
|
use crate::transport::UpstreamStream;
|
||||||
|
|
||||||
/// Commands sent to dedicated writer tasks to avoid mutex contention on TCP writes.
|
/// Commands sent to dedicated writer tasks to avoid mutex contention on upstream writes.
|
||||||
pub(crate) enum WriterCommand {
|
pub(crate) enum WriterCommand {
|
||||||
Data(Bytes),
|
Data(Bytes),
|
||||||
DataAndFlush(Bytes),
|
DataAndFlush(Bytes),
|
||||||
|
|
@ -213,7 +214,7 @@ pub(crate) fn cbc_decrypt_inplace(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct RpcWriter {
|
pub(crate) struct RpcWriter {
|
||||||
pub(crate) writer: tokio::io::WriteHalf<tokio::net::TcpStream>,
|
pub(crate) writer: tokio::io::WriteHalf<UpstreamStream>,
|
||||||
pub(crate) key: [u8; 32],
|
pub(crate) key: [u8; 32],
|
||||||
pub(crate) iv: [u8; 16],
|
pub(crate) iv: [u8; 16],
|
||||||
pub(crate) seq_no: i32,
|
pub(crate) seq_no: i32,
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use socket2::{SockRef, TcpKeepalive};
|
use socket2::{SockRef, TcpKeepalive};
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use libc;
|
use libc;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::fd::BorrowedFd;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use std::os::fd::{AsRawFd, RawFd};
|
use std::os::fd::RawFd;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use std::os::raw::c_int;
|
use std::os::raw::c_int;
|
||||||
|
|
||||||
|
|
@ -26,7 +28,7 @@ use crate::protocol::constants::{
|
||||||
ME_CONNECT_TIMEOUT_SECS, ME_HANDSHAKE_TIMEOUT_SECS, RPC_CRYPTO_AES_U32,
|
ME_CONNECT_TIMEOUT_SECS, ME_HANDSHAKE_TIMEOUT_SECS, RPC_CRYPTO_AES_U32,
|
||||||
RPC_HANDSHAKE_ERROR_U32, rpc_crypto_flags,
|
RPC_HANDSHAKE_ERROR_U32, rpc_crypto_flags,
|
||||||
};
|
};
|
||||||
use crate::transport::{UpstreamEgressInfo, UpstreamRouteKind};
|
use crate::transport::{UpstreamEgressInfo, UpstreamRouteKind, UpstreamStream};
|
||||||
|
|
||||||
use super::codec::{
|
use super::codec::{
|
||||||
RpcChecksumMode, build_handshake_payload, build_nonce_payload, build_rpc_frame,
|
RpcChecksumMode, build_handshake_payload, build_nonce_payload, build_rpc_frame,
|
||||||
|
|
@ -57,8 +59,8 @@ impl KdfClientPortSource {
|
||||||
|
|
||||||
/// Result of a successful ME handshake with timings.
|
/// Result of a successful ME handshake with timings.
|
||||||
pub(crate) struct HandshakeOutput {
|
pub(crate) struct HandshakeOutput {
|
||||||
pub rd: ReadHalf<TcpStream>,
|
pub rd: ReadHalf<UpstreamStream>,
|
||||||
pub wr: WriteHalf<TcpStream>,
|
pub wr: WriteHalf<UpstreamStream>,
|
||||||
pub source_ip: IpAddr,
|
pub source_ip: IpAddr,
|
||||||
pub read_key: [u8; 32],
|
pub read_key: [u8; 32],
|
||||||
pub read_iv: [u8; 16],
|
pub read_iv: [u8; 16],
|
||||||
|
|
@ -89,15 +91,19 @@ impl MePool {
|
||||||
i16::try_from(self.resolve_dc_for_endpoint(addr).await).ok()
|
i16::try_from(self.resolve_dc_for_endpoint(addr).await).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn direct_bind_ip_for_stun(
|
fn non_socks_bind_ip_for_stun(
|
||||||
family: IpFamily,
|
family: IpFamily,
|
||||||
upstream_egress: Option<UpstreamEgressInfo>,
|
upstream_egress: Option<UpstreamEgressInfo>,
|
||||||
) -> Option<IpAddr> {
|
) -> Option<IpAddr> {
|
||||||
let info = upstream_egress?;
|
let info = upstream_egress?;
|
||||||
if info.route_kind != UpstreamRouteKind::Direct {
|
let bind_ip = match info.route_kind {
|
||||||
return None;
|
UpstreamRouteKind::Direct => info
|
||||||
}
|
.direct_bind_ip
|
||||||
match (family, info.direct_bind_ip) {
|
.or_else(|| info.local_addr.map(|addr| addr.ip())),
|
||||||
|
UpstreamRouteKind::Shadowsocks => info.local_addr.map(|addr| addr.ip()),
|
||||||
|
UpstreamRouteKind::Socks4 | UpstreamRouteKind::Socks5 => None,
|
||||||
|
};
|
||||||
|
match (family, bind_ip) {
|
||||||
(IpFamily::V4, Some(IpAddr::V4(ip))) => Some(IpAddr::V4(ip)),
|
(IpFamily::V4, Some(IpAddr::V4(ip))) => Some(IpAddr::V4(ip)),
|
||||||
(IpFamily::V6, Some(IpAddr::V6(ip))) => Some(IpAddr::V6(ip)),
|
(IpFamily::V6, Some(IpAddr::V6(ip))) => Some(IpAddr::V6(ip)),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -141,12 +147,12 @@ impl MePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TCP connect with timeout + return RTT in milliseconds.
|
/// Connect to a middle-proxy endpoint and return RTT in milliseconds.
|
||||||
pub(crate) async fn connect_tcp(
|
pub(crate) async fn connect_tcp(
|
||||||
&self,
|
&self,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
dc_idx_override: Option<i16>,
|
dc_idx_override: Option<i16>,
|
||||||
) -> Result<(TcpStream, f64, Option<UpstreamEgressInfo>)> {
|
) -> Result<(UpstreamStream, f64, Option<UpstreamEgressInfo>)> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let (stream, upstream_egress) = if let Some(upstream) = &self.upstream {
|
let (stream, upstream_egress) = if let Some(upstream) = &self.upstream {
|
||||||
let dc_idx = if let Some(dc_idx) = dc_idx_override {
|
let dc_idx = if let Some(dc_idx) = dc_idx_override {
|
||||||
|
|
@ -154,7 +160,9 @@ impl MePool {
|
||||||
} else {
|
} else {
|
||||||
self.resolve_dc_idx_for_endpoint(addr).await
|
self.resolve_dc_idx_for_endpoint(addr).await
|
||||||
};
|
};
|
||||||
let (stream, egress) = upstream.connect_with_details(addr, dc_idx, None).await?;
|
let (stream, egress) = upstream
|
||||||
|
.connect_stream_with_details(addr, dc_idx, None)
|
||||||
|
.await?;
|
||||||
(stream, Some(egress))
|
(stream, Some(egress))
|
||||||
} else {
|
} else {
|
||||||
let connect_fut = async {
|
let connect_fut = async {
|
||||||
|
|
@ -183,7 +191,7 @@ impl MePool {
|
||||||
.map_err(|_| ProxyError::ConnectionTimeout {
|
.map_err(|_| ProxyError::ConnectionTimeout {
|
||||||
addr: addr.to_string(),
|
addr: addr.to_string(),
|
||||||
})??;
|
})??;
|
||||||
(stream, None)
|
(UpstreamStream::Tcp(stream), None)
|
||||||
};
|
};
|
||||||
|
|
||||||
let connect_ms = start.elapsed().as_secs_f64() * 1000.0;
|
let connect_ms = start.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
|
@ -192,14 +200,22 @@ impl MePool {
|
||||||
warn!(error = %e, "ME keepalive setup failed");
|
warn!(error = %e, "ME keepalive setup failed");
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
if let Err(e) = Self::configure_user_timeout(stream.as_raw_fd()) {
|
if let Err(e) = Self::configure_user_timeout(stream.raw_fd()) {
|
||||||
warn!(error = %e, "ME TCP_USER_TIMEOUT setup failed");
|
warn!(error = %e, "ME TCP_USER_TIMEOUT setup failed");
|
||||||
}
|
}
|
||||||
Ok((stream, connect_ms, upstream_egress))
|
Ok((stream, connect_ms, upstream_egress))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn configure_keepalive(stream: &TcpStream) -> std::io::Result<()> {
|
fn configure_keepalive(stream: &UpstreamStream) -> std::io::Result<()> {
|
||||||
let sock = SockRef::from(stream);
|
#[cfg(unix)]
|
||||||
|
let borrowed = unsafe { BorrowedFd::borrow_raw(stream.raw_fd()) };
|
||||||
|
#[cfg(unix)]
|
||||||
|
let sock = SockRef::from(&borrowed);
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
let sock = match stream {
|
||||||
|
UpstreamStream::Tcp(stream) => SockRef::from(stream),
|
||||||
|
UpstreamStream::Shadowsocks(_) => return Ok(()),
|
||||||
|
};
|
||||||
let ka = TcpKeepalive::new().with_time(Duration::from_secs(30));
|
let ka = TcpKeepalive::new().with_time(Duration::from_secs(30));
|
||||||
|
|
||||||
// Mirror socket2 v0.5.10 target gate for with_retries(), the stricter method.
|
// Mirror socket2 v0.5.10 target gate for with_retries(), the stricter method.
|
||||||
|
|
@ -243,11 +259,11 @@ impl MePool {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform full ME RPC handshake on an established TCP stream.
|
/// Perform full ME RPC handshake on an established upstream stream.
|
||||||
/// Returns cipher keys/ivs and split halves; does not register writer.
|
/// Returns cipher keys/ivs and split halves; does not register writer.
|
||||||
pub(crate) async fn handshake_only(
|
pub(crate) async fn handshake_only(
|
||||||
&self,
|
&self,
|
||||||
stream: TcpStream,
|
stream: UpstreamStream,
|
||||||
addr: SocketAddr,
|
addr: SocketAddr,
|
||||||
upstream_egress: Option<UpstreamEgressInfo>,
|
upstream_egress: Option<UpstreamEgressInfo>,
|
||||||
rng: &SecureRandom,
|
rng: &SecureRandom,
|
||||||
|
|
@ -300,7 +316,7 @@ impl MePool {
|
||||||
MeSocksKdfPolicy::Compat => {
|
MeSocksKdfPolicy::Compat => {
|
||||||
self.stats.increment_me_socks_kdf_compat_fallback();
|
self.stats.increment_me_socks_kdf_compat_fallback();
|
||||||
if self.nat_probe {
|
if self.nat_probe {
|
||||||
let bind_ip = Self::direct_bind_ip_for_stun(family, upstream_egress);
|
let bind_ip = Self::non_socks_bind_ip_for_stun(family, upstream_egress);
|
||||||
self.maybe_reflect_public_addr(family, bind_ip).await
|
self.maybe_reflect_public_addr(family, bind_ip).await
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -308,7 +324,7 @@ impl MePool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.nat_probe {
|
} else if self.nat_probe {
|
||||||
let bind_ip = Self::direct_bind_ip_for_stun(family, upstream_egress);
|
let bind_ip = Self::non_socks_bind_ip_for_stun(family, upstream_egress);
|
||||||
self.maybe_reflect_public_addr(family, bind_ip).await
|
self.maybe_reflect_public_addr(family, bind_ip).await
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -722,6 +738,8 @@ mod tests {
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
use crate::transport::{UpstreamEgressInfo, UpstreamRouteKind, UpstreamStream};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_configure_keepalive_loopback() {
|
async fn test_configure_keepalive_loopback() {
|
||||||
let listener = match TcpListener::bind("127.0.0.1:0").await {
|
let listener = match TcpListener::bind("127.0.0.1:0").await {
|
||||||
|
|
@ -740,6 +758,7 @@ mod tests {
|
||||||
Err(error) if error.kind() == ErrorKind::PermissionDenied => return,
|
Err(error) if error.kind() == ErrorKind::PermissionDenied => return,
|
||||||
Err(error) => panic!("connect failed: {error}"),
|
Err(error) => panic!("connect failed: {error}"),
|
||||||
};
|
};
|
||||||
|
let stream = UpstreamStream::Tcp(stream);
|
||||||
|
|
||||||
if let Err(error) = MePool::configure_keepalive(&stream) {
|
if let Err(error) = MePool::configure_keepalive(&stream) {
|
||||||
if error.kind() == ErrorKind::PermissionDenied {
|
if error.kind() == ErrorKind::PermissionDenied {
|
||||||
|
|
@ -777,4 +796,56 @@ mod tests {
|
||||||
.with_interval(Duration::from_secs(10))
|
.with_interval(Duration::from_secs(10))
|
||||||
.with_retries(3);
|
.with_retries(3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn direct_route_prefers_explicit_bind_ip_for_stun() {
|
||||||
|
let bind_ip: IpAddr = "198.51.100.10".parse().unwrap();
|
||||||
|
let local_addr: SocketAddr = "198.51.100.20:40000".parse().unwrap();
|
||||||
|
let egress = UpstreamEgressInfo {
|
||||||
|
upstream_id: 0,
|
||||||
|
route_kind: UpstreamRouteKind::Direct,
|
||||||
|
local_addr: Some(local_addr),
|
||||||
|
direct_bind_ip: Some(bind_ip),
|
||||||
|
socks_bound_addr: None,
|
||||||
|
socks_proxy_addr: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected = MePool::non_socks_bind_ip_for_stun(IpFamily::V4, Some(egress));
|
||||||
|
|
||||||
|
assert_eq!(selected, Some(bind_ip));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shadowsocks_route_uses_local_addr_for_stun() {
|
||||||
|
let local_addr: SocketAddr = "198.51.100.30:40001".parse().unwrap();
|
||||||
|
let egress = UpstreamEgressInfo {
|
||||||
|
upstream_id: 1,
|
||||||
|
route_kind: UpstreamRouteKind::Shadowsocks,
|
||||||
|
local_addr: Some(local_addr),
|
||||||
|
direct_bind_ip: None,
|
||||||
|
socks_bound_addr: None,
|
||||||
|
socks_proxy_addr: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected = MePool::non_socks_bind_ip_for_stun(IpFamily::V4, Some(egress));
|
||||||
|
|
||||||
|
assert_eq!(selected, Some(local_addr.ip()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn socks_route_keeps_compat_fallback_unbound() {
|
||||||
|
let local_addr: SocketAddr = "198.51.100.40:40002".parse().unwrap();
|
||||||
|
let egress = UpstreamEgressInfo {
|
||||||
|
upstream_id: 2,
|
||||||
|
route_kind: UpstreamRouteKind::Socks5,
|
||||||
|
local_addr: Some(local_addr),
|
||||||
|
direct_bind_ip: None,
|
||||||
|
socks_bound_addr: None,
|
||||||
|
socks_proxy_addr: Some("198.51.100.50:1080".parse().unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let selected = MePool::non_socks_bind_ip_for_stun(IpFamily::V4, Some(egress));
|
||||||
|
|
||||||
|
assert_eq!(selected, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use std::time::Instant;
|
||||||
|
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
use tokio::sync::mpsc::error::TrySendError;
|
use tokio::sync::mpsc::error::TrySendError;
|
||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
@ -16,13 +15,14 @@ use crate::crypto::AesCbc;
|
||||||
use crate::error::{ProxyError, Result};
|
use crate::error::{ProxyError, Result};
|
||||||
use crate::protocol::constants::*;
|
use crate::protocol::constants::*;
|
||||||
use crate::stats::Stats;
|
use crate::stats::Stats;
|
||||||
|
use crate::transport::UpstreamStream;
|
||||||
|
|
||||||
use super::codec::{RpcChecksumMode, WriterCommand, rpc_crc};
|
use super::codec::{RpcChecksumMode, WriterCommand, rpc_crc};
|
||||||
use super::registry::RouteResult;
|
use super::registry::RouteResult;
|
||||||
use super::{ConnRegistry, MeResponse};
|
use super::{ConnRegistry, MeResponse};
|
||||||
|
|
||||||
pub(crate) async fn reader_loop(
|
pub(crate) async fn reader_loop(
|
||||||
mut rd: tokio::io::ReadHalf<TcpStream>,
|
mut rd: tokio::io::ReadHalf<UpstreamStream>,
|
||||||
dk: [u8; 32],
|
dk: [u8; 32],
|
||||||
mut div: [u8; 16],
|
mut div: [u8; 16],
|
||||||
crc_mode: RpcChecksumMode,
|
crc_mode: RpcChecksumMode,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ use std::sync::Arc;
|
||||||
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::fd::{AsRawFd, RawFd};
|
||||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
@ -173,6 +175,7 @@ pub struct StartupPingResult {
|
||||||
pub both_available: bool,
|
pub both_available: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transport stream returned by an upstream connection attempt.
|
||||||
pub enum UpstreamStream {
|
pub enum UpstreamStream {
|
||||||
Tcp(TcpStream),
|
Tcp(TcpStream),
|
||||||
Shadowsocks(Box<ShadowsocksStream>),
|
Shadowsocks(Box<ShadowsocksStream>),
|
||||||
|
|
@ -188,6 +191,35 @@ impl std::fmt::Debug for UpstreamStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpstreamStream {
|
impl UpstreamStream {
|
||||||
|
pub(crate) fn local_addr(&self) -> std::io::Result<SocketAddr> {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(stream) => stream.local_addr(),
|
||||||
|
Self::Shadowsocks(stream) => stream.get_ref().local_addr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn peer_addr(&self) -> std::io::Result<SocketAddr> {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(stream) => stream.peer_addr(),
|
||||||
|
Self::Shadowsocks(stream) => stream.get_ref().peer_addr(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_nodelay(&self, nodelay: bool) -> std::io::Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(stream) => stream.set_nodelay(nodelay),
|
||||||
|
Self::Shadowsocks(stream) => stream.get_ref().set_nodelay(nodelay),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub(crate) fn raw_fd(&self) -> RawFd {
|
||||||
|
match self {
|
||||||
|
Self::Tcp(stream) => stream.as_raw_fd(),
|
||||||
|
Self::Shadowsocks(stream) => stream.get_ref().as_raw_fd(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_tcp(self) -> Result<TcpStream> {
|
pub fn into_tcp(self) -> Result<TcpStream> {
|
||||||
match self {
|
match self {
|
||||||
Self::Tcp(stream) => Ok(stream),
|
Self::Tcp(stream) => Ok(stream),
|
||||||
|
|
@ -744,13 +776,13 @@ impl UpstreamManager {
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connect to target through a selected upstream and return egress details.
|
/// Connect to target through a selected upstream and return transport egress details.
|
||||||
pub async fn connect_with_details(
|
pub(crate) async fn connect_stream_with_details(
|
||||||
&self,
|
&self,
|
||||||
target: SocketAddr,
|
target: SocketAddr,
|
||||||
dc_idx: Option<i16>,
|
dc_idx: Option<i16>,
|
||||||
scope: Option<&str>,
|
scope: Option<&str>,
|
||||||
) -> Result<(TcpStream, UpstreamEgressInfo)> {
|
) -> Result<(UpstreamStream, UpstreamEgressInfo)> {
|
||||||
let idx = self
|
let idx = self
|
||||||
.select_upstream(dc_idx, scope)
|
.select_upstream(dc_idx, scope)
|
||||||
.await
|
.await
|
||||||
|
|
@ -774,6 +806,19 @@ impl UpstreamManager {
|
||||||
let (stream, egress) = self
|
let (stream, egress) = self
|
||||||
.connect_selected_upstream(idx, upstream, target, dc_idx, bind_rr)
|
.connect_selected_upstream(idx, upstream, target, dc_idx, bind_rr)
|
||||||
.await?;
|
.await?;
|
||||||
|
Ok((stream, egress))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Connect to target through a selected upstream and return egress details.
|
||||||
|
pub async fn connect_with_details(
|
||||||
|
&self,
|
||||||
|
target: SocketAddr,
|
||||||
|
dc_idx: Option<i16>,
|
||||||
|
scope: Option<&str>,
|
||||||
|
) -> Result<(TcpStream, UpstreamEgressInfo)> {
|
||||||
|
let (stream, egress) = self
|
||||||
|
.connect_stream_with_details(target, dc_idx, scope)
|
||||||
|
.await?;
|
||||||
Ok((stream.into_tcp()?, egress))
|
Ok((stream.into_tcp()?, egress))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue