mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-11-06 08:14:07 +00:00
feat(local-http): support HTTP Basic auth (#1994)
This commit is contained in:
Generated
+1
@@ -3120,6 +3120,7 @@ name = "shadowsocks-service"
|
||||
version = "1.24.0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base64",
|
||||
"brotli",
|
||||
"bson",
|
||||
"byte_string",
|
||||
|
||||
@@ -614,7 +614,10 @@ Example configuration:
|
||||
"local_address": "127.0.0.1",
|
||||
"local_port": 3128,
|
||||
// OPTIONAL. macOS launchd activate socket
|
||||
"launchd_tcp_socket_name": "TCPListener"
|
||||
"launchd_tcp_socket_name": "TCPListener",
|
||||
// OPTIONAL. Authentication configuration file
|
||||
// Configuration file document could be found in the next section.
|
||||
"http_auth_config_path": "/path/to/auth.json",
|
||||
},
|
||||
{
|
||||
// DNS local server (feature = "local-dns")
|
||||
@@ -955,6 +958,24 @@ The configuration file is set by `socks5_auth_config_path` in `locals`.
|
||||
}
|
||||
```
|
||||
|
||||
### HTTP Authentication Configuration
|
||||
|
||||
The configuration file is set by `http_auth_config_path` in `locals`.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Basic Authentication (RFC9110)
|
||||
"basic": {
|
||||
"users": [
|
||||
{
|
||||
"user_name": "USERNAME in UTF-8",
|
||||
"password": "PASSWORD in UTF-8"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `SS_SERVER_PASSWORD`: A default password for servers that created from command line argument (`--server-addr`)
|
||||
|
||||
+8
-7
@@ -19,14 +19,15 @@ fn main() -> ExitCode {
|
||||
|
||||
// Allow running `ssservice` as symlink of `sslocal`, `ssserver` and `ssmanager`
|
||||
if let Some(program_path) = env::args().next()
|
||||
&& let Some(program_name) = Path::new(&program_path).file_name() {
|
||||
match program_name.to_str() {
|
||||
Some("sslocal") => return local::main(&local::define_command_line_options(app).get_matches()),
|
||||
Some("ssserver") => return server::main(&server::define_command_line_options(app).get_matches()),
|
||||
Some("ssmanager") => return manager::main(&manager::define_command_line_options(app).get_matches()),
|
||||
_ => {}
|
||||
}
|
||||
&& let Some(program_name) = Path::new(&program_path).file_name()
|
||||
{
|
||||
match program_name.to_str() {
|
||||
Some("sslocal") => return local::main(&local::define_command_line_options(app).get_matches()),
|
||||
Some("ssserver") => return server::main(&server::define_command_line_options(app).get_matches()),
|
||||
Some("ssmanager") => return manager::main(&manager::define_command_line_options(app).get_matches()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let matches = app
|
||||
.subcommand_required(true)
|
||||
|
||||
@@ -60,7 +60,7 @@ local-dns-relay = ["local-dns"]
|
||||
# Currently is only used in Android
|
||||
local-flow-stat = ["local"]
|
||||
# Enable HTTP protocol for sslocal
|
||||
local-http = ["local", "hyper", "http", "http-body-util"]
|
||||
local-http = ["local", "hyper", "http", "http-body-util", "base64"]
|
||||
local-http-native-tls = ["local-http", "tokio-native-tls", "native-tls"]
|
||||
local-http-native-tls-vendored = [
|
||||
"local-http-native-tls",
|
||||
@@ -179,6 +179,7 @@ mime = { version = "0.3", optional = true }
|
||||
flate2 = { version = "1.0", optional = true }
|
||||
brotli = { version = "8.0", optional = true }
|
||||
zstd = { version = "0.13", optional = true }
|
||||
base64 = { version = "0.22", optional = true }
|
||||
|
||||
etherparse = { version = "0.19", optional = true }
|
||||
smoltcp = { version = "0.12", optional = true, default-features = false, features = [
|
||||
|
||||
@@ -209,13 +209,14 @@ impl ParsingRules {
|
||||
}
|
||||
}
|
||||
} else if let Some(set_rule) = caps.get(2)
|
||||
&& let Ok(set_rule) = str::from_utf8(set_rule.as_bytes()) {
|
||||
let set_rule = set_rule.replace("\\.", ".");
|
||||
if self.add_set_rule_inner(&set_rule).is_ok() {
|
||||
trace!("REGEX-RULE {} => SET-RULE {}", rule, set_rule);
|
||||
return;
|
||||
}
|
||||
&& let Ok(set_rule) = str::from_utf8(set_rule.as_bytes())
|
||||
{
|
||||
let set_rule = set_rule.replace("\\.", ".");
|
||||
if self.add_set_rule_inner(&set_rule).is_ok() {
|
||||
trace!("REGEX-RULE {} => SET-RULE {}", rule, set_rule);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trace!("REGEX-RULE {}", rule);
|
||||
|
||||
@@ -80,6 +80,8 @@ use shadowsocks::{
|
||||
use crate::acl::AccessControl;
|
||||
#[cfg(feature = "local-dns")]
|
||||
use crate::local::dns::NameServerAddr;
|
||||
#[cfg(feature = "local-http")]
|
||||
use crate::local::http::config::HttpAuthConfig;
|
||||
#[cfg(feature = "local")]
|
||||
use crate::local::socks::config::Socks5AuthConfig;
|
||||
|
||||
@@ -323,6 +325,11 @@ struct SSLocalExtConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
socks5_auth_config_path: Option<String>,
|
||||
|
||||
/// HTTP
|
||||
#[cfg(feature = "local")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
http_auth_config_path: Option<String>,
|
||||
|
||||
/// Fake DNS
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -1038,6 +1045,10 @@ pub struct LocalConfig {
|
||||
#[cfg(feature = "local")]
|
||||
pub socks5_auth: Socks5AuthConfig,
|
||||
|
||||
/// HTTP Authentication configuration
|
||||
#[cfg(feature = "local")]
|
||||
pub http_auth: HttpAuthConfig,
|
||||
|
||||
/// Fake DNS record expire seconds
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
pub fake_dns_record_expire_duration: Option<Duration>,
|
||||
@@ -1108,6 +1119,9 @@ impl LocalConfig {
|
||||
#[cfg(feature = "local")]
|
||||
socks5_auth: Socks5AuthConfig::default(),
|
||||
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: HttpAuthConfig::default(),
|
||||
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
fake_dns_record_expire_duration: None,
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
@@ -1832,6 +1846,11 @@ impl Config {
|
||||
local_config.socks5_auth = Socks5AuthConfig::load_from_file(&socks5_auth_config_path)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-http")]
|
||||
if let Some(http_auth_config_path) = local.http_auth_config_path {
|
||||
local_config.http_auth = HttpAuthConfig::load_from_file(&http_auth_config_path)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
{
|
||||
if let Some(d) = local.fake_dns_record_expire_duration {
|
||||
@@ -2390,15 +2409,16 @@ impl Config {
|
||||
// Security
|
||||
if let Some(sec) = config.security
|
||||
&& let Some(replay_attack) = sec.replay_attack
|
||||
&& let Some(policy) = replay_attack.policy {
|
||||
match policy.parse::<ReplayAttackPolicy>() {
|
||||
Ok(p) => nconfig.security.replay_attack.policy = p,
|
||||
Err(..) => {
|
||||
let err = Error::new(ErrorKind::Invalid, "invalid replay attack policy", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
&& let Some(policy) = replay_attack.policy
|
||||
{
|
||||
match policy.parse::<ReplayAttackPolicy>() {
|
||||
Ok(p) => nconfig.security.replay_attack.policy = p,
|
||||
Err(..) => {
|
||||
let err = Error::new(ErrorKind::Invalid, "invalid replay attack policy", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(balancer) = config.balancer {
|
||||
nconfig.balancer = BalancerConfig {
|
||||
@@ -2619,16 +2639,18 @@ impl Config {
|
||||
|
||||
// Balancer related checks
|
||||
if let Some(rtt) = self.balancer.max_server_rtt
|
||||
&& rtt.as_secs() == 0 {
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.max_server_rtt must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
&& rtt.as_secs() == 0
|
||||
{
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.max_server_rtt must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
if let Some(intv) = self.balancer.check_interval
|
||||
&& intv.as_secs() == 0 {
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.check_interval must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
&& intv.as_secs() == 0
|
||||
{
|
||||
let err = Error::new(ErrorKind::Invalid, "balancer.check_interval must be > 0", None);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
if self.config_type.is_server() && self.server.is_empty() {
|
||||
@@ -2664,10 +2686,11 @@ impl Config {
|
||||
|
||||
// Plugin shouldn't be an empty string
|
||||
if let Some(plugin) = server.plugin()
|
||||
&& plugin.plugin.trim().is_empty() {
|
||||
let err = Error::new(ErrorKind::Malformed, "`plugin` shouldn't be an empty string", None);
|
||||
return Err(err);
|
||||
}
|
||||
&& plugin.plugin.trim().is_empty()
|
||||
{
|
||||
let err = Error::new(ErrorKind::Malformed, "`plugin` shouldn't be an empty string", None);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Server's domain name shouldn't be an empty string
|
||||
match server.addr() {
|
||||
@@ -2899,6 +2922,9 @@ impl fmt::Display for Config {
|
||||
#[cfg(feature = "local")]
|
||||
socks5_auth_config_path: None,
|
||||
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth_config_path: None,
|
||||
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
fake_dns_record_expire_duration: local.fake_dns_record_expire_duration.map(|d| d.as_secs()),
|
||||
#[cfg(feature = "local-fake-dns")]
|
||||
@@ -3068,20 +3094,22 @@ impl fmt::Display for Config {
|
||||
}
|
||||
|
||||
if jconf.method.is_none()
|
||||
&& let Some(ref m) = m.method {
|
||||
jconf.method = Some(m.to_string());
|
||||
}
|
||||
&& let Some(ref m) = m.method
|
||||
{
|
||||
jconf.method = Some(m.to_string());
|
||||
}
|
||||
|
||||
if jconf.plugin.is_none()
|
||||
&& let Some(ref p) = m.plugin {
|
||||
jconf.plugin = Some(p.plugin.clone());
|
||||
if let Some(ref o) = p.plugin_opts {
|
||||
jconf.plugin_opts = Some(o.clone());
|
||||
}
|
||||
if !p.plugin_args.is_empty() {
|
||||
jconf.plugin_args = Some(p.plugin_args.clone());
|
||||
}
|
||||
&& let Some(ref p) = m.plugin
|
||||
{
|
||||
jconf.plugin = Some(p.plugin.clone());
|
||||
if let Some(ref o) = p.plugin_opts {
|
||||
jconf.plugin_opts = Some(o.clone());
|
||||
}
|
||||
if !p.plugin_args.is_empty() {
|
||||
jconf.plugin_args = Some(p.plugin_args.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.no_delay {
|
||||
@@ -3188,17 +3216,18 @@ impl fmt::Display for Config {
|
||||
/// It will return the original value if fails to read `${VAR_NAME}`.
|
||||
pub fn read_variable_field_value(value: &str) -> Cow<'_, str> {
|
||||
if let Some(left_over) = value.strip_prefix("${")
|
||||
&& let Some(var_name) = left_over.strip_suffix('}') {
|
||||
match env::var(var_name) {
|
||||
Ok(value) => return value.into(),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"couldn't read password from environment variable {}, error: {}",
|
||||
var_name, err
|
||||
);
|
||||
}
|
||||
&& let Some(var_name) = left_over.strip_suffix('}')
|
||||
{
|
||||
match env::var(var_name) {
|
||||
Ok(value) => return value.into(),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"couldn't read password from environment variable {}, error: {}",
|
||||
var_name, err
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
value.into()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
//! HTTP protocol configuration
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs::OpenOptions,
|
||||
io::{self, Read},
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use base64::Engine as _;
|
||||
use log::trace;
|
||||
use serde::Deserialize;
|
||||
|
||||
const BASIC_AUTH_BASE64_ENGINE: base64::engine::GeneralPurpose = base64::engine::GeneralPurpose::new(
|
||||
&base64::alphabet::STANDARD,
|
||||
base64::engine::GeneralPurposeConfig::new()
|
||||
.with_encode_padding(true)
|
||||
.with_decode_padding_mode(base64::engine::DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SSHttpAuthBasicUserConfig {
|
||||
user_name: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SSHttpAuthBasicConfig {
|
||||
users: Vec<SSHttpAuthBasicUserConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SSHttpAuthConfig {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
basic: Option<SSHttpAuthBasicConfig>,
|
||||
}
|
||||
|
||||
/// HTTP Authentication method
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HttpAuthConfig {
|
||||
pub basic: HttpAuthBasicConfig,
|
||||
}
|
||||
|
||||
impl HttpAuthConfig {
|
||||
/// Create a new HTTP Authentication configuration
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
basic: HttpAuthBasicConfig::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load from configuration file
|
||||
///
|
||||
/// ```json
|
||||
/// {
|
||||
/// "basic": {
|
||||
/// "users": [
|
||||
/// {
|
||||
/// "user_name": "USER_NAME",
|
||||
/// "password": "PASSWORD"
|
||||
/// }
|
||||
/// ]
|
||||
/// }
|
||||
/// }
|
||||
pub fn load_from_file<P: AsRef<Path> + ?Sized>(filename: &P) -> io::Result<Self> {
|
||||
let filename = filename.as_ref();
|
||||
|
||||
trace!(
|
||||
"loading socks5 authentication configuration from {}",
|
||||
filename.display()
|
||||
);
|
||||
|
||||
let mut reader = OpenOptions::new().read(true).open(filename)?;
|
||||
let mut content = String::new();
|
||||
reader.read_to_string(&mut content)?;
|
||||
|
||||
let jconf: SSHttpAuthConfig = match json5::from_str(&content) {
|
||||
Ok(c) => c,
|
||||
Err(err) => return Err(io::Error::other(err)),
|
||||
};
|
||||
|
||||
let mut basic = HttpAuthBasicConfig::new();
|
||||
if let Some(p) = jconf.basic {
|
||||
for user in p.users {
|
||||
basic.add_user(user.user_name, user.password);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self { basic })
|
||||
}
|
||||
|
||||
/// Check if authentication is required
|
||||
pub fn auth_required(&self) -> bool {
|
||||
self.basic.total_users() > 0
|
||||
}
|
||||
|
||||
/// Check Basic Authentication
|
||||
pub fn verify_basic_auth(&self, header_value: &str) -> bool {
|
||||
const BASIC_PREFIX: &str = "Basic ";
|
||||
|
||||
if !header_value.starts_with(BASIC_PREFIX) {
|
||||
return false;
|
||||
}
|
||||
let b64_encoded = &header_value[BASIC_PREFIX.len()..];
|
||||
let decoded_bytes = match BASIC_AUTH_BASE64_ENGINE.decode(b64_encoded) {
|
||||
Ok(b) => b,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let decoded_str = match String::from_utf8(decoded_bytes) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let (user_name, password) = match decoded_str.split_once(':') {
|
||||
Some((u, p)) => (u, p),
|
||||
None => return false,
|
||||
};
|
||||
self.basic.check_user(user_name, password)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpAuthConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP server User/Password Authentication configuration
|
||||
///
|
||||
/// RFC9110 https://httpwg.org/specs/rfc9110.html#auth.client.proxy
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HttpAuthBasicConfig {
|
||||
users: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl HttpAuthBasicConfig {
|
||||
/// Create an empty `Passwd` configuration
|
||||
pub fn new() -> Self {
|
||||
Self { users: HashMap::new() }
|
||||
}
|
||||
|
||||
/// Add a user with password
|
||||
pub fn add_user<U, P>(&mut self, user_name: U, password: P)
|
||||
where
|
||||
U: Into<String>,
|
||||
P: Into<String>,
|
||||
{
|
||||
self.users.insert(user_name.into(), password.into());
|
||||
}
|
||||
|
||||
/// Check if `user_name` exists and validate `password`
|
||||
pub fn check_user<U, P>(&self, user_name: U, password: P) -> bool
|
||||
where
|
||||
U: AsRef<str>,
|
||||
P: AsRef<str>,
|
||||
{
|
||||
match self.users.get(user_name.as_ref()) {
|
||||
Some(pwd) => pwd == password.as_ref(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Total users
|
||||
pub fn total_users(&self) -> usize {
|
||||
self.users.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpAuthBasicConfig {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use crate::local::{
|
||||
};
|
||||
|
||||
use super::{
|
||||
config::HttpAuthConfig,
|
||||
http_client::HttpClient,
|
||||
utils::{authority_addr, check_keep_alive, connect_host, host_addr},
|
||||
};
|
||||
@@ -30,6 +31,7 @@ pub struct HttpService {
|
||||
peer_addr: SocketAddr,
|
||||
http_client: HttpClient<body::Incoming>,
|
||||
balancer: PingBalancer,
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
}
|
||||
|
||||
impl HttpService {
|
||||
@@ -38,12 +40,14 @@ impl HttpService {
|
||||
peer_addr: SocketAddr,
|
||||
http_client: HttpClient<body::Incoming>,
|
||||
balancer: PingBalancer,
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
peer_addr,
|
||||
http_client,
|
||||
balancer,
|
||||
http_auth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +57,31 @@ impl HttpService {
|
||||
) -> hyper::Result<Response<BoxBody<Bytes, hyper::Error>>> {
|
||||
trace!("request {} {:?}", self.peer_addr, req);
|
||||
|
||||
if self.http_auth.auth_required() {
|
||||
// Only support Basic authentication for now
|
||||
match req.headers().get(header::PROXY_AUTHORIZATION) {
|
||||
Some(header_value) => {
|
||||
if let Ok(header_str) = header_value.to_str() {
|
||||
if !self.http_auth.verify_basic_auth(header_str) {
|
||||
debug!("HTTP {} invalid proxy authorization: {}", req.method(), header_str);
|
||||
return make_proxy_authentication_required();
|
||||
}
|
||||
} else {
|
||||
debug!(
|
||||
"HTTP {} invalid proxy authorization header encoding: {:?}",
|
||||
req.method(),
|
||||
header_value
|
||||
);
|
||||
return make_proxy_authentication_required();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
debug!("HTTP {} missing proxy authorization", req.method());
|
||||
return make_proxy_authentication_required();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse URI
|
||||
//
|
||||
// Proxy request URI must contains a host
|
||||
@@ -204,6 +233,14 @@ fn make_bad_request() -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::E
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn make_proxy_authentication_required() -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::PROXY_AUTHENTICATION_REQUIRED)
|
||||
.header(header::PROXY_AUTHENTICATE, "Basic charset=\"UTF-8\"")
|
||||
.body(empty_body())
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn make_internal_server_error() -> Result<Response<BoxBody<Bytes, hyper::Error>>, hyper::Error> {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
|
||||
@@ -7,6 +7,7 @@ pub use self::{
|
||||
server::{Http, HttpBuilder, HttpConnectionHandler},
|
||||
};
|
||||
|
||||
pub mod config;
|
||||
mod http_client;
|
||||
mod http_service;
|
||||
mod http_stream;
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::local::{
|
||||
context::ServiceContext, loadbalancing::PingBalancer, net::tcp::listener::create_standard_tcp_listener,
|
||||
};
|
||||
|
||||
use super::{http_client::HttpClient, http_service::HttpService, tokio_rt::TokioIo};
|
||||
use super::{config::HttpAuthConfig, http_client::HttpClient, http_service::HttpService, tokio_rt::TokioIo};
|
||||
|
||||
/// HTTP Local server builder
|
||||
pub struct HttpBuilder {
|
||||
@@ -25,6 +25,7 @@ pub struct HttpBuilder {
|
||||
balancer: PingBalancer,
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_tcp_socket_name: Option<String>,
|
||||
http_auth: HttpAuthConfig,
|
||||
}
|
||||
|
||||
impl HttpBuilder {
|
||||
@@ -42,6 +43,7 @@ impl HttpBuilder {
|
||||
balancer,
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_tcp_socket_name: None,
|
||||
http_auth: HttpAuthConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +52,10 @@ impl HttpBuilder {
|
||||
self.launchd_tcp_socket_name = Some(n);
|
||||
}
|
||||
|
||||
pub fn set_http_auth(&mut self, auth: HttpAuthConfig) {
|
||||
self.http_auth = auth;
|
||||
}
|
||||
|
||||
/// Build HTTP server instance
|
||||
pub async fn build(self) -> io::Result<Http> {
|
||||
cfg_if::cfg_if! {
|
||||
@@ -77,6 +83,7 @@ impl HttpBuilder {
|
||||
context: self.context,
|
||||
listener,
|
||||
balancer: self.balancer,
|
||||
http_auth: Arc::new(self.http_auth),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -86,6 +93,7 @@ pub struct Http {
|
||||
context: Arc<ServiceContext>,
|
||||
listener: TcpListener,
|
||||
balancer: PingBalancer,
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
}
|
||||
|
||||
impl Http {
|
||||
@@ -104,7 +112,7 @@ impl Http {
|
||||
self.listener.local_addr().expect("http local_addr")
|
||||
);
|
||||
|
||||
let handler = HttpConnectionHandler::new(self.context, self.balancer);
|
||||
let handler = HttpConnectionHandler::new(self.context, self.balancer, self.http_auth);
|
||||
|
||||
loop {
|
||||
let (stream, peer_addr) = match self.listener.accept().await {
|
||||
@@ -135,15 +143,17 @@ pub struct HttpConnectionHandler {
|
||||
context: Arc<ServiceContext>,
|
||||
balancer: PingBalancer,
|
||||
http_client: HttpClient<body::Incoming>,
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
}
|
||||
|
||||
impl HttpConnectionHandler {
|
||||
/// Create a new Handler
|
||||
pub fn new(context: Arc<ServiceContext>, balancer: PingBalancer) -> Self {
|
||||
pub fn new(context: Arc<ServiceContext>, balancer: PingBalancer, http_auth: Arc<HttpAuthConfig>) -> Self {
|
||||
Self {
|
||||
context,
|
||||
balancer,
|
||||
http_client: HttpClient::new(),
|
||||
http_auth,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,6 +166,7 @@ impl HttpConnectionHandler {
|
||||
context,
|
||||
balancer,
|
||||
http_client,
|
||||
http_auth,
|
||||
} = self;
|
||||
|
||||
let io = TokioIo::new(stream);
|
||||
@@ -169,8 +180,14 @@ impl HttpConnectionHandler {
|
||||
.serve_connection(
|
||||
io,
|
||||
service::service_fn(move |req| {
|
||||
HttpService::new(context.clone(), peer_addr, http_client.clone(), balancer.clone())
|
||||
.serve_connection(req)
|
||||
HttpService::new(
|
||||
context.clone(),
|
||||
peer_addr,
|
||||
http_client.clone(),
|
||||
balancer.clone(),
|
||||
http_auth.clone(),
|
||||
)
|
||||
.serve_connection(req)
|
||||
}),
|
||||
)
|
||||
.with_upgrades()
|
||||
|
||||
@@ -164,9 +164,10 @@ impl PingBalancerBuilder {
|
||||
|
||||
pub async fn build(self) -> io::Result<PingBalancer> {
|
||||
if let Some(intv) = self.check_best_interval
|
||||
&& intv > self.check_interval {
|
||||
return Err(io::Error::other("check_interval must be >= check_best_interval"));
|
||||
}
|
||||
&& intv > self.check_interval
|
||||
{
|
||||
return Err(io::Error::other("check_interval must be >= check_best_interval"));
|
||||
}
|
||||
|
||||
let (shared_context, task_abortable) = PingBalancerContext::new(
|
||||
self.servers,
|
||||
|
||||
@@ -279,6 +279,8 @@ impl Server {
|
||||
let mut server_builder = SocksBuilder::with_context(context.clone(), client_addr, balancer);
|
||||
server_builder.set_mode(local_config.mode);
|
||||
server_builder.set_socks5_auth(local_config.socks5_auth);
|
||||
#[cfg(feature = "local-http")]
|
||||
server_builder.set_http_auth(local_config.http_auth);
|
||||
|
||||
if let Some(c) = config.udp_max_associations {
|
||||
server_builder.set_udp_capacity(c);
|
||||
@@ -349,6 +351,7 @@ impl Server {
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut builder = HttpBuilder::with_context(context.clone(), client_addr, balancer);
|
||||
builder.set_http_auth(local_config.http_auth);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
if let Some(n) = local_config.launchd_tcp_socket_name {
|
||||
|
||||
@@ -515,11 +515,10 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
if UDP_SOCKET_SUPPORT_DUAL_STACK
|
||||
&& let SocketAddr::V4(saddr) = target_addr {
|
||||
let mapped_ip = saddr.ip().to_ipv6_mapped();
|
||||
target_addr = SocketAddr::V6(SocketAddrV6::new(mapped_ip, saddr.port(), 0, 0));
|
||||
}
|
||||
if UDP_SOCKET_SUPPORT_DUAL_STACK && let SocketAddr::V4(saddr) = target_addr {
|
||||
let mapped_ip = saddr.ip().to_ipv6_mapped();
|
||||
target_addr = SocketAddr::V6(SocketAddrV6::new(mapped_ip, saddr.port(), 0, 0));
|
||||
}
|
||||
|
||||
let n = socket.send_to(data, target_addr).await?;
|
||||
if n != data.len() {
|
||||
|
||||
@@ -215,13 +215,14 @@ impl OnlineConfigService {
|
||||
if let Some(ref allowed_plugins) = self.allowed_plugins {
|
||||
for server in &online_config.server {
|
||||
if let Some(plugin) = server.config.plugin()
|
||||
&& !allowed_plugins.contains(&plugin.plugin) {
|
||||
error!(
|
||||
"server-loader task found not allowed plugin: {}, url: {}",
|
||||
plugin.plugin, self.config_url
|
||||
);
|
||||
return Err(io::Error::other(format!("not allowed plugin: {}", plugin.plugin)));
|
||||
}
|
||||
&& !allowed_plugins.contains(&plugin.plugin)
|
||||
{
|
||||
error!(
|
||||
"server-loader task found not allowed plugin: {}, url: {}",
|
||||
plugin.plugin, self.config_url
|
||||
);
|
||||
return Err(io::Error::other(format!("not allowed plugin: {}", plugin.plugin)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -64,9 +64,10 @@ async fn handle_redir_client(
|
||||
//
|
||||
// Try to convert IPv4 mapped IPv6 address for dual-stack mode.
|
||||
if let SocketAddr::V6(ref a) = daddr
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip())
|
||||
{
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
let target_addr = Address::from(daddr);
|
||||
establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await
|
||||
}
|
||||
|
||||
@@ -71,16 +71,15 @@ impl UdpRedirSocket {
|
||||
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
if reuse_port
|
||||
&& let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
// SO_REUSEPORT is supported after 3.9
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
} else {
|
||||
error!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
if reuse_port && let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
// SO_REUSEPORT is supported after 3.9
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
} else {
|
||||
error!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SockAddr::from(addr);
|
||||
|
||||
|
||||
@@ -52,15 +52,14 @@ impl UdpRedirSocket {
|
||||
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
if reuse_port
|
||||
&& let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
} else {
|
||||
error!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
if reuse_port && let Err(err) = socket.set_reuse_port(true) {
|
||||
if let Some(libc::ENOPROTOOPT) = err.raw_os_error() {
|
||||
trace!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
} else {
|
||||
error!("failed to set SO_REUSEPORT, error: {}", err);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
|
||||
let sock_addr = SockAddr::from(addr);
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ pub use self::server::{SocksTcpServer, SocksTcpServerBuilder, SocksUdpServer};
|
||||
use self::socks5::Socks5UdpServerBuilder;
|
||||
|
||||
use super::config::Socks5AuthConfig;
|
||||
#[cfg(feature = "local-http")]
|
||||
use crate::local::http::config::HttpAuthConfig;
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
mod server;
|
||||
@@ -33,6 +35,8 @@ pub struct SocksBuilder {
|
||||
launchd_tcp_socket_name: Option<String>,
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_udp_socket_name: Option<String>,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: HttpAuthConfig,
|
||||
}
|
||||
|
||||
impl SocksBuilder {
|
||||
@@ -58,6 +62,8 @@ impl SocksBuilder {
|
||||
launchd_tcp_socket_name: None,
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_udp_socket_name: None,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: HttpAuthConfig::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,6 +99,12 @@ impl SocksBuilder {
|
||||
self.socks5_auth = p;
|
||||
}
|
||||
|
||||
/// Set HTTP Authentication configuration
|
||||
#[cfg(feature = "local-http")]
|
||||
pub fn set_http_auth(&mut self, p: HttpAuthConfig) {
|
||||
self.http_auth = p;
|
||||
}
|
||||
|
||||
/// macOS launchd activate socket
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn set_launchd_tcp_socket_name(&mut self, n: String) {
|
||||
@@ -144,6 +156,8 @@ impl SocksBuilder {
|
||||
self.balancer.clone(),
|
||||
self.mode,
|
||||
self.socks5_auth,
|
||||
#[cfg(feature = "local-http")]
|
||||
self.http_auth,
|
||||
);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -5,7 +5,7 @@ use shadowsocks::{ServerAddr, config::Mode, net::TcpListener as ShadowTcpListene
|
||||
use tokio::{net::TcpStream, time};
|
||||
|
||||
#[cfg(feature = "local-http")]
|
||||
use crate::local::http::HttpConnectionHandler;
|
||||
use crate::local::http::{HttpConnectionHandler, config::HttpAuthConfig};
|
||||
use crate::local::{
|
||||
context::ServiceContext, loadbalancing::PingBalancer, net::tcp::listener::create_standard_tcp_listener,
|
||||
socks::config::Socks5AuthConfig,
|
||||
@@ -24,6 +24,8 @@ pub struct SocksTcpServerBuilder {
|
||||
socks5_auth: Arc<Socks5AuthConfig>,
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_socket_name: Option<String>,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
}
|
||||
|
||||
impl SocksTcpServerBuilder {
|
||||
@@ -34,6 +36,7 @@ impl SocksTcpServerBuilder {
|
||||
balancer: PingBalancer,
|
||||
mode: Mode,
|
||||
socks5_auth: Socks5AuthConfig,
|
||||
#[cfg(feature = "local-http")] http_auth: HttpAuthConfig,
|
||||
) -> Self {
|
||||
Self {
|
||||
context,
|
||||
@@ -44,6 +47,8 @@ impl SocksTcpServerBuilder {
|
||||
socks5_auth: Arc::new(socks5_auth),
|
||||
#[cfg(target_os = "macos")]
|
||||
launchd_socket_name: None,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: Arc::new(http_auth),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +85,8 @@ impl SocksTcpServerBuilder {
|
||||
balancer: self.balancer,
|
||||
mode: self.mode,
|
||||
socks5_auth: self.socks5_auth,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: self.http_auth,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -92,6 +99,8 @@ pub struct SocksTcpServer {
|
||||
balancer: PingBalancer,
|
||||
mode: Mode,
|
||||
socks5_auth: Arc<Socks5AuthConfig>,
|
||||
#[cfg(feature = "local-http")]
|
||||
http_auth: Arc<HttpAuthConfig>,
|
||||
}
|
||||
|
||||
impl SocksTcpServer {
|
||||
@@ -107,7 +116,8 @@ impl SocksTcpServer {
|
||||
// If UDP is enabled, SOCK5 UDP_ASSOCIATE command will let client to send requests to this address
|
||||
let udp_associate_addr = Arc::new(self.udp_associate_addr);
|
||||
#[cfg(feature = "local-http")]
|
||||
let http_handler = HttpConnectionHandler::new(self.context.clone(), self.balancer.clone());
|
||||
let http_handler =
|
||||
HttpConnectionHandler::new(self.context.clone(), self.balancer.clone(), self.http_auth.clone());
|
||||
|
||||
loop {
|
||||
let (stream, peer_addr) = match self.listener.accept().await {
|
||||
|
||||
@@ -160,9 +160,10 @@ impl AsyncRead for TcpConnection {
|
||||
|
||||
// Nothing could be read. Wait for notify.
|
||||
if let Some(old_waker) = control.recv_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
&& !old_waker.will_wake(cx.waker())
|
||||
{
|
||||
old_waker.wake();
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
@@ -191,9 +192,10 @@ impl AsyncWrite for TcpConnection {
|
||||
|
||||
if control.send_buffer.is_full() {
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
&& !old_waker.will_wake(cx.waker())
|
||||
{
|
||||
old_waker.wake();
|
||||
}
|
||||
|
||||
return Poll::Pending;
|
||||
}
|
||||
@@ -223,9 +225,10 @@ impl AsyncWrite for TcpConnection {
|
||||
}
|
||||
|
||||
if let Some(old_waker) = control.send_waker.replace(cx.waker().clone())
|
||||
&& !old_waker.will_wake(cx.waker()) {
|
||||
old_waker.wake();
|
||||
}
|
||||
&& !old_waker.will_wake(cx.waker())
|
||||
{
|
||||
old_waker.wake();
|
||||
}
|
||||
|
||||
self.manager_notify.notify();
|
||||
Poll::Pending
|
||||
@@ -418,10 +421,12 @@ impl TcpTun {
|
||||
wake_receiver = true;
|
||||
}
|
||||
|
||||
if wake_receiver && control.recv_waker.is_some()
|
||||
&& let Some(waker) = control.recv_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
if wake_receiver
|
||||
&& control.recv_waker.is_some()
|
||||
&& let Some(waker) = control.recv_waker.take()
|
||||
{
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
// Check if writable
|
||||
let mut wake_sender = false;
|
||||
@@ -452,10 +457,12 @@ impl TcpTun {
|
||||
}
|
||||
}
|
||||
|
||||
if wake_sender && control.send_waker.is_some()
|
||||
&& let Some(waker) = control.send_waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
if wake_sender
|
||||
&& control.send_waker.is_some()
|
||||
&& let Some(waker) = control.send_waker.take()
|
||||
{
|
||||
waker.wake();
|
||||
}
|
||||
}
|
||||
|
||||
for socket_handle in sockets_to_remove {
|
||||
@@ -597,9 +604,10 @@ async fn handle_redir_client(
|
||||
//
|
||||
// Try to convert IPv4 mapped IPv6 address for dual-stack mode.
|
||||
if let SocketAddr::V6(ref a) = daddr
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip()) {
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
&& let Some(v4) = to_ipv4_mapped(a.ip())
|
||||
{
|
||||
daddr = SocketAddr::new(IpAddr::from(v4), a.port());
|
||||
}
|
||||
let target_addr = Address::from(daddr);
|
||||
establish_client_tcp_redir(context, balancer, s, peer_addr, &target_addr).await
|
||||
}
|
||||
|
||||
@@ -330,22 +330,23 @@ impl Manager {
|
||||
|
||||
let pid_path = self.server_pid_path(port);
|
||||
if pid_path.exists()
|
||||
&& let Ok(mut pid_file) = File::open(&pid_path) {
|
||||
let mut pid_content = String::new();
|
||||
if pid_file.read_to_string(&mut pid_content).is_ok() {
|
||||
let pid_content = pid_content.trim();
|
||||
&& let Ok(mut pid_file) = File::open(&pid_path)
|
||||
{
|
||||
let mut pid_content = String::new();
|
||||
if pid_file.read_to_string(&mut pid_content).is_ok() {
|
||||
let pid_content = pid_content.trim();
|
||||
|
||||
match pid_content.parse::<libc::pid_t>() {
|
||||
Ok(pid) => {
|
||||
let _ = unsafe { libc::kill(pid, libc::SIGTERM) };
|
||||
debug!("killed standalone server port {}, pid: {}", port, pid);
|
||||
}
|
||||
Err(..) => {
|
||||
warn!("failed to read pid from {}", pid_path.display());
|
||||
}
|
||||
match pid_content.parse::<libc::pid_t>() {
|
||||
Ok(pid) => {
|
||||
let _ = unsafe { libc::kill(pid, libc::SIGTERM) };
|
||||
debug!("killed standalone server port {}, pid: {}", port, pid);
|
||||
}
|
||||
Err(..) => {
|
||||
warn!("failed to read pid from {}", pid_path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let server_config_path = self.server_config_path(port);
|
||||
|
||||
|
||||
@@ -594,13 +594,14 @@ impl UdpAssociationContext {
|
||||
control: &Option<UdpSocketControlData>,
|
||||
) {
|
||||
if let Some(ref mut session) = self.client_session
|
||||
&& peer_addr != self.peer_addr {
|
||||
debug!(
|
||||
"udp relay for {} changed to {}, session: {:?}",
|
||||
self.peer_addr, peer_addr, session.client_session_id
|
||||
);
|
||||
self.peer_addr = peer_addr;
|
||||
}
|
||||
&& peer_addr != self.peer_addr
|
||||
{
|
||||
debug!(
|
||||
"udp relay for {} changed to {}, session: {:?}",
|
||||
self.peer_addr, peer_addr, session.client_session_id
|
||||
);
|
||||
self.peer_addr = peer_addr;
|
||||
}
|
||||
|
||||
trace!(
|
||||
"udp relay {} -> {} with {} bytes, control: {:?}",
|
||||
@@ -737,9 +738,10 @@ impl UdpAssociationContext {
|
||||
// But for some implementations, they may expect the target address to be IPv4, because
|
||||
// the peer address is IPv4 when calling `sendto`.
|
||||
if let Address::SocketAddress(SocketAddr::V6(ref v6)) = addr
|
||||
&& let Some(v4) = to_ipv4_mapped(v6.ip()) {
|
||||
addr = Address::SocketAddress(SocketAddr::new(v4.into(), v6.port()));
|
||||
}
|
||||
&& let Some(v4) = to_ipv4_mapped(v6.ip())
|
||||
{
|
||||
addr = Address::SocketAddress(SocketAddr::new(v4.into(), v6.port()));
|
||||
}
|
||||
|
||||
match self.client_session {
|
||||
None => {
|
||||
|
||||
@@ -696,18 +696,20 @@ impl ServerConfig {
|
||||
/// Get server's TCP external address
|
||||
pub fn tcp_external_addr(&self) -> &ServerAddr {
|
||||
if let Some(plugin) = self.plugin()
|
||||
&& plugin.plugin_mode.enable_tcp() {
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
&& plugin.plugin_mode.enable_tcp()
|
||||
{
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
&self.addr
|
||||
}
|
||||
|
||||
/// Get server's UDP external address
|
||||
pub fn udp_external_addr(&self) -> &ServerAddr {
|
||||
if let Some(plugin) = self.plugin()
|
||||
&& plugin.plugin_mode.enable_udp() {
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
&& plugin.plugin_mode.enable_udp()
|
||||
{
|
||||
return self.plugin_addr.as_ref().unwrap_or(&self.addr);
|
||||
}
|
||||
&self.addr
|
||||
}
|
||||
|
||||
|
||||
@@ -168,13 +168,14 @@ static IP_STACK_CAPABILITIES: LazyLock<IpStackCapabilities> = LazyLock::new(|| {
|
||||
|
||||
// Check IPv6 (::1)
|
||||
if let Ok(ipv6_socket) = Socket::new(Domain::IPV6, Type::STREAM, Some(Protocol::TCP))
|
||||
&& ipv6_socket.set_only_v6(true).is_ok() {
|
||||
let local_host = SockAddr::from(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0));
|
||||
if ipv6_socket.bind(&local_host).is_ok() {
|
||||
caps.support_ipv6 = true;
|
||||
debug!("IpStackCapability support_ipv6=true");
|
||||
}
|
||||
&& ipv6_socket.set_only_v6(true).is_ok()
|
||||
{
|
||||
let local_host = SockAddr::from(SocketAddr::new(Ipv6Addr::LOCALHOST.into(), 0));
|
||||
if ipv6_socket.bind(&local_host).is_ok() {
|
||||
caps.support_ipv6 = true;
|
||||
debug!("IpStackCapability support_ipv6=true");
|
||||
}
|
||||
}
|
||||
|
||||
// Check IPv4-mapped-IPv6 (127.0.0.1)
|
||||
if check_ipv4_mapped_ipv6_capability().is_ok() {
|
||||
|
||||
@@ -391,9 +391,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp
|
||||
};
|
||||
|
||||
if !config.udp.allow_fragmentation
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket)
|
||||
{
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
|
||||
// Set IP_BOUND_IF for BSD-like
|
||||
if let Some(ref iface) = config.bind_interface {
|
||||
|
||||
@@ -311,9 +311,10 @@ pub async fn bind_outbound_udp_socket(bind_addr: &SocketAddr, config: &ConnectOp
|
||||
};
|
||||
|
||||
if !config.udp.allow_fragmentation
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket) {
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
&& let Err(err) = set_disable_ip_fragmentation(af, &socket)
|
||||
{
|
||||
warn!("failed to disable IP fragmentation, error: {}", err);
|
||||
}
|
||||
|
||||
// Any traffic except localhost should be protected
|
||||
// This is a workaround for VPNService
|
||||
|
||||
@@ -35,11 +35,11 @@ use futures::ready;
|
||||
use tokio::io::Interest;
|
||||
use tokio::{io::ReadBuf, net::ToSocketAddrs};
|
||||
|
||||
use crate::{context::Context, relay::socks5::Address, ServerAddr};
|
||||
use crate::{ServerAddr, context::Context, relay::socks5::Address};
|
||||
|
||||
use super::{
|
||||
sys::{bind_outbound_udp_socket, create_inbound_udp_socket, create_outbound_udp_socket},
|
||||
AcceptOpts, AddrFamily, ConnectOpts,
|
||||
sys::{bind_outbound_udp_socket, create_inbound_udp_socket, create_outbound_udp_socket},
|
||||
};
|
||||
|
||||
/// Message struct for `batch_send`
|
||||
@@ -190,9 +190,10 @@ impl UdpSocket {
|
||||
pub fn poll_send(&self, cx: &mut TaskContext<'_>, buf: &[u8]) -> Poll<io::Result<usize>> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
&& buf.len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
|
||||
self.socket.poll_send(cx, buf)
|
||||
}
|
||||
@@ -202,9 +203,10 @@ impl UdpSocket {
|
||||
pub async fn send(&self, buf: &[u8]) -> io::Result<usize> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
&& buf.len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
|
||||
self.socket.send(buf).await
|
||||
}
|
||||
@@ -213,9 +215,10 @@ impl UdpSocket {
|
||||
pub fn poll_send_to(&self, cx: &mut TaskContext<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
&& buf.len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.len(), mtu)).into();
|
||||
}
|
||||
|
||||
self.socket.poll_send_to(cx, buf, target)
|
||||
}
|
||||
@@ -225,9 +228,10 @@ impl UdpSocket {
|
||||
pub async fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], target: A) -> io::Result<usize> {
|
||||
// Check MTU
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.len() > mtu {
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
&& buf.len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.len(), mtu));
|
||||
}
|
||||
|
||||
self.socket.send_to(buf, target).await
|
||||
}
|
||||
@@ -238,9 +242,10 @@ impl UdpSocket {
|
||||
ready!(self.socket.poll_recv(cx, buf))?;
|
||||
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.filled().len() > mtu {
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
&& buf.filled().len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
@@ -251,9 +256,10 @@ impl UdpSocket {
|
||||
let n = self.socket.recv(buf).await?;
|
||||
|
||||
if let Some(mtu) = self.mtu
|
||||
&& n > mtu {
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
&& n > mtu
|
||||
{
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
@@ -264,9 +270,10 @@ impl UdpSocket {
|
||||
let addr = ready!(self.socket.poll_recv_from(cx, buf))?;
|
||||
|
||||
if let Some(mtu) = self.mtu
|
||||
&& buf.filled().len() > mtu {
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
&& buf.filled().len() > mtu
|
||||
{
|
||||
return Err(make_mtu_error(buf.filled().len(), mtu)).into();
|
||||
}
|
||||
|
||||
Ok(addr).into()
|
||||
}
|
||||
@@ -277,9 +284,10 @@ impl UdpSocket {
|
||||
let (n, addr) = self.socket.recv_from(buf).await?;
|
||||
|
||||
if let Some(mtu) = self.mtu
|
||||
&& n > mtu {
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
&& n > mtu
|
||||
{
|
||||
return Err(make_mtu_error(n, mtu));
|
||||
}
|
||||
|
||||
Ok((n, addr))
|
||||
}
|
||||
|
||||
@@ -160,9 +160,10 @@ where
|
||||
// Wakeup writer task because we have already received the salt
|
||||
#[cfg(feature = "aead-cipher-2022")]
|
||||
if let ProxyServerStreamWriteState::PrepareHeader(waker) = this.writer_state
|
||||
&& let Some(waker) = waker.take() {
|
||||
waker.wake();
|
||||
}
|
||||
&& let Some(waker) = waker.take()
|
||||
{
|
||||
waker.wake();
|
||||
}
|
||||
|
||||
Ok(()).into()
|
||||
}
|
||||
@@ -190,9 +191,10 @@ where
|
||||
} else {
|
||||
// Reader didn't receive the salt from client yet.
|
||||
if let Some(waker) = waker.take()
|
||||
&& !waker.will_wake(cx.waker()) {
|
||||
waker.wake();
|
||||
}
|
||||
&& !waker.will_wake(cx.waker())
|
||||
{
|
||||
waker.wake();
|
||||
}
|
||||
*waker = Some(cx.waker().clone());
|
||||
return Poll::Pending;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
//!
|
||||
//! daemonize is a library for writing system daemons. Inspired by the Python library [thesharp/daemonize](https://github.com/thesharp/daemonize).
|
||||
//!
|
||||
//! The respository is located at <https://github.com/knsd/daemonize/>.
|
||||
@@ -211,7 +210,6 @@ impl<T> Outcome<T> {
|
||||
/// * change root directory;
|
||||
/// * change the pid-file ownership to provided user (and/or) group;
|
||||
/// * execute any provided action just before dropping privileges.
|
||||
///
|
||||
pub struct Daemonize<T> {
|
||||
directory: PathBuf,
|
||||
pid_file: Option<PathBuf>,
|
||||
@@ -339,6 +337,7 @@ impl<T> Daemonize<T> {
|
||||
self.stderr = stdio.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Start daemonization process, terminate parent after first fork, returns privileged action
|
||||
/// result to the child.
|
||||
pub fn start(self) -> Result<T, Error> {
|
||||
|
||||
@@ -756,14 +756,16 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
|
||||
#[cfg(feature = "local-redir")]
|
||||
{
|
||||
if RedirType::tcp_default() != RedirType::NotSupported
|
||||
&& let Some(tcp_redir) = matches.get_one::<String>("TCP_REDIR") {
|
||||
local_config.tcp_redir = tcp_redir.parse::<RedirType>().expect("tcp-redir");
|
||||
}
|
||||
&& let Some(tcp_redir) = matches.get_one::<String>("TCP_REDIR")
|
||||
{
|
||||
local_config.tcp_redir = tcp_redir.parse::<RedirType>().expect("tcp-redir");
|
||||
}
|
||||
|
||||
if RedirType::udp_default() != RedirType::NotSupported
|
||||
&& let Some(udp_redir) = matches.get_one::<String>("UDP_REDIR") {
|
||||
local_config.udp_redir = udp_redir.parse::<RedirType>().expect("udp-redir");
|
||||
}
|
||||
&& let Some(udp_redir) = matches.get_one::<String>("UDP_REDIR")
|
||||
{
|
||||
local_config.udp_redir = udp_redir.parse::<RedirType>().expect("udp-redir");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local-dns")]
|
||||
|
||||
@@ -405,14 +405,16 @@ pub fn create(matches: &ArgMatches) -> ShadowsocksResult<(Runtime, impl Future<O
|
||||
|
||||
// Overrides
|
||||
if matches.get_flag("UDP_ONLY")
|
||||
&& let Some(ref mut m) = config.manager {
|
||||
m.mode = Mode::UdpOnly;
|
||||
}
|
||||
&& let Some(ref mut m) = config.manager
|
||||
{
|
||||
m.mode = Mode::UdpOnly;
|
||||
}
|
||||
|
||||
if matches.get_flag("TCP_AND_UDP")
|
||||
&& let Some(ref mut m) = config.manager {
|
||||
m.mode = Mode::TcpAndUdp;
|
||||
}
|
||||
&& let Some(ref mut m) = config.manager
|
||||
{
|
||||
m.mode = Mode::TcpAndUdp;
|
||||
}
|
||||
|
||||
if let Some(acl_file) = matches.get_one::<String>("ACL") {
|
||||
let acl = AccessControl::load_from_file(acl_file)
|
||||
|
||||
Reference in New Issue
Block a user