SOCKS5 RFC1929 Username/Password Authentication (#788)

This commit is contained in:
zonyitoo
2022-03-16 15:57:47 +08:00
parent a9215fbd02
commit 6bf2a439c8
13 changed files with 476 additions and 53 deletions

43
Cargo.lock generated
View File

@@ -357,9 +357,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
checksum = "fdbfe11fe19ff083c48923cf179540e8cd0535903dc35e178a1fdeeb59aef51f"
dependencies = [
"cfg-if",
"crossbeam-utils",
@@ -367,9 +367,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.7"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"cfg-if",
"lazy_static",
@@ -1066,9 +1066,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.119"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
checksum = "ad5c14e80759d0939d013e6ca49930e59fc53dd8e5009132f76240c179380c09"
[[package]]
name = "libmimalloc-sys"
@@ -1219,19 +1219,6 @@ dependencies = [
"libmimalloc-sys",
]
[[package]]
name = "mio"
version = "0.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "mio"
version = "0.8.1"
@@ -1288,9 +1275,9 @@ dependencies = [
[[package]]
name = "notify"
version = "5.0.0-pre.13"
version = "5.0.0-pre.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245d358380e2352c2d020e8ee62baac09b3420f1f6c012a31326cfced4ad487d"
checksum = "d13c22db70a63592e098fb51735bab36646821e6389a0ba171f3549facdf0b74"
dependencies = [
"bitflags",
"crossbeam-channel",
@@ -1299,7 +1286,7 @@ dependencies = [
"inotify",
"kqueue",
"libc",
"mio 0.7.14",
"mio",
"walkdir",
"winapi",
]
@@ -2011,7 +1998,7 @@ dependencies = [
[[package]]
name = "shadowsocks"
version = "1.14.0"
version = "1.14.1"
dependencies = [
"arc-swap 1.5.0",
"async-trait",
@@ -2066,7 +2053,7 @@ dependencies = [
[[package]]
name = "shadowsocks-rust"
version = "1.14.1"
version = "1.14.2"
dependencies = [
"build-time",
"byte_string",
@@ -2098,7 +2085,7 @@ dependencies = [
[[package]]
name = "shadowsocks-service"
version = "1.14.1"
version = "1.14.2"
dependencies = [
"arc-swap 1.5.0",
"async-trait",
@@ -2262,9 +2249,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.86"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b"
checksum = "ebd69e719f31e88618baa1eaa6ee2de5c9a1c004f1e9ecdb58e8352a13f20a01"
dependencies = [
"proc-macro2",
"quote",
@@ -2393,7 +2380,7 @@ dependencies = [
"bytes",
"libc",
"memchr",
"mio 0.8.1",
"mio",
"num_cpus",
"once_cell",
"parking_lot 0.12.0",

View File

@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-rust"
version = "1.14.1"
version = "1.14.2"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"

View File

@@ -464,7 +464,9 @@ Example configuration:
"local_port": 1080,
// OPTIONAL. Setting the `mode` for this specific local server instance.
// If not set, it will derive from the outer `mode`
"mode": "tcp_and_udp"
"mode": "tcp_and_udp",
// OPTIONAL. Authentication configuration file
"socks5_auth_config_path": "/path/to/auth.json"
},
{
// SOCKS5, SOCKS4/4a local server

View File

@@ -1,6 +1,6 @@
[package]
name = "shadowsocks-service"
version = "1.14.1"
version = "1.14.2"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"

View File

@@ -75,6 +75,8 @@ use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig};
use crate::acl::AccessControl;
#[cfg(feature = "local-dns")]
use crate::local::dns::NameServerAddr;
#[cfg(feature = "local")]
use crate::local::socks::config::Socks5AuthConfig;
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
@@ -246,6 +248,11 @@ struct SSLocalExtConfig {
#[cfg(feature = "local-tun")]
#[serde(skip_serializing_if = "Option::is_none")]
tun_interface_address: Option<String>,
/// SOCKS5
#[cfg(feature = "local")]
#[serde(skip_serializing_if = "Option::is_none")]
socks5_auth_config_path: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
@@ -823,6 +830,10 @@ pub struct LocalConfig {
/// Set `IPV6_V6ONLY` for listener socket
pub ipv6_only: bool,
/// SOCKS5 Authentication configuration
#[cfg(feature = "local")]
pub socks5_auth: Socks5AuthConfig,
}
impl LocalConfig {
@@ -859,6 +870,9 @@ impl LocalConfig {
tun_device_fd_from_path: None,
ipv6_only: false,
#[cfg(feature = "local")]
socks5_auth: Socks5AuthConfig::default(),
}
}
@@ -1420,6 +1434,11 @@ impl Config {
local_config.tun_interface_name = Some(tun_interface_name);
}
#[cfg(feature = "local")]
if let Some(socks5_auth_config_path) = local.socks5_auth_config_path {
local_config.socks5_auth = Socks5AuthConfig::load_from_file(&socks5_auth_config_path)?;
}
nconfig.local.push(local_config);
}
}
@@ -2118,6 +2137,9 @@ impl fmt::Display for Config {
tun_interface_name: local.tun_interface_name.clone(),
#[cfg(feature = "local-tun")]
tun_interface_address: local.tun_interface_address.as_ref().map(ToString::to_string),
#[cfg(feature = "local")]
socks5_auth_config_path: None,
};
jlocals.push(jlocal);
}

View File

@@ -199,6 +199,7 @@ pub async fn create(config: Config) -> io::Result<Server> {
let mut server = Socks::with_context(context.clone());
server.set_mode(local_config.mode);
server.set_socks5_auth(local_config.socks5_auth);
if let Some(c) = config.udp_max_associations {
server.set_udp_capacity(c);

View File

@@ -0,0 +1,141 @@
//! SOCK protocol configuration
use std::{
collections::HashMap,
fs::OpenOptions,
io::{self, ErrorKind, Read},
path::Path,
};
use log::trace;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct SSSocks5AuthPasswordUserConfig {
user_name: String,
password: String,
}
#[derive(Deserialize, Debug)]
struct SSSocks5AuthPasswordConfig {
users: Vec<SSSocks5AuthPasswordUserConfig>,
}
#[derive(Deserialize, Debug)]
struct SSSocks5AuthConfig {
#[serde(skip_serializing_if = "Option::is_none")]
password: Option<SSSocks5AuthPasswordConfig>,
}
/// SOCKS5 Authentication method
#[derive(Debug, Clone)]
pub struct Socks5AuthConfig {
pub passwd: Socks5AuthPasswdConfig,
}
impl Socks5AuthConfig {
/// Create a new SOCKS5 Authentication configuration
pub fn new() -> Socks5AuthConfig {
Socks5AuthConfig {
passwd: Socks5AuthPasswdConfig::new(),
}
}
/// Load from configuration file
///
/// ```json
/// {
/// "password": {
/// "users": [
/// {
/// "user_name": "USER_NAME",
/// "password": "PASSWORD"
/// }
/// ]
/// }
/// }
pub fn load_from_file<P: AsRef<Path> + ?Sized>(filename: &P) -> io::Result<Socks5AuthConfig> {
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: SSSocks5AuthConfig = match json5::from_str(&content) {
Ok(c) => c,
Err(err) => return Err(io::Error::new(ErrorKind::Other, err)),
};
let mut passwd = Socks5AuthPasswdConfig::new();
if let Some(p) = jconf.password {
for user in p.users {
passwd.add_user(user.user_name, user.password);
}
}
Ok(Socks5AuthConfig { passwd })
}
/// Check if authentication is required
pub fn auth_required(&self) -> bool {
self.passwd.total_users() > 0
}
}
impl Default for Socks5AuthConfig {
fn default() -> Socks5AuthConfig {
Socks5AuthConfig::new()
}
}
/// SOCKS5 server User/Password Authentication configuration
///
/// RFC1929 https://datatracker.ietf.org/doc/html/rfc1929
#[derive(Debug, Clone)]
pub struct Socks5AuthPasswdConfig {
passwd: HashMap<String, String>,
}
impl Socks5AuthPasswdConfig {
/// Create an empty `Passwd` configuration
pub fn new() -> Socks5AuthPasswdConfig {
Socks5AuthPasswdConfig { passwd: 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.passwd.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.passwd.get(user_name.as_ref()) {
Some(pwd) => pwd == password.as_ref(),
None => false,
}
}
/// Total users
pub fn total_users(&self) -> usize {
self.passwd.len()
}
}
impl Default for Socks5AuthPasswdConfig {
fn default() -> Socks5AuthPasswdConfig {
Socks5AuthPasswdConfig::new()
}
}

View File

@@ -3,6 +3,7 @@
pub use self::server::Socks;
pub mod client;
pub mod config;
pub mod server;
#[cfg(feature = "local-socks4")]
pub mod socks4;

View File

@@ -13,6 +13,8 @@ use crate::local::{context::ServiceContext, loadbalancing::PingBalancer};
use self::socks4::Socks4TcpHandler;
use self::socks5::{Socks5TcpHandler, Socks5UdpServer};
use super::config::Socks5AuthConfig;
#[cfg(feature = "local-socks4")]
mod socks4;
mod socks5;
@@ -24,6 +26,7 @@ pub struct Socks {
udp_expiry_duration: Option<Duration>,
udp_capacity: Option<usize>,
udp_bind_addr: Option<ServerAddr>,
socks5_auth: Arc<Socks5AuthConfig>,
}
impl Default for Socks {
@@ -47,6 +50,7 @@ impl Socks {
udp_expiry_duration: None,
udp_capacity: None,
udp_bind_addr: None,
socks5_auth: Arc::new(Socks5AuthConfig::default()),
}
}
@@ -73,6 +77,11 @@ impl Socks {
self.udp_bind_addr = Some(a);
}
/// Set SOCKS5 Username/Password Authentication configuration
pub fn set_socks5_auth(&mut self, p: Socks5AuthConfig) {
self.socks5_auth = Arc::new(p);
}
/// Start serving
pub async fn run(self, client_config: &ServerAddr, balancer: PingBalancer) -> io::Result<()> {
let mut vfut = Vec::new();
@@ -130,6 +139,7 @@ impl Socks {
let context = self.context.clone();
let udp_bind_addr = udp_bind_addr.clone();
let mode = self.mode;
let socks5_auth = self.socks5_auth.clone();
tokio::spawn(Socks::handle_tcp_client(
context,
@@ -138,6 +148,7 @@ impl Socks {
balancer,
peer_addr,
mode,
socks5_auth,
));
}
}
@@ -150,6 +161,7 @@ impl Socks {
balancer: PingBalancer,
peer_addr: SocketAddr,
mode: Mode,
socks5_auth: Arc<Socks5AuthConfig>,
) -> io::Result<()> {
use std::io::ErrorKind;
@@ -166,7 +178,7 @@ impl Socks {
}
0x05 => {
let handler = Socks5TcpHandler::new(context, udp_bind_addr, balancer, mode);
let handler = Socks5TcpHandler::new(context, udp_bind_addr, balancer, mode, socks5_auth);
handler.handle_socks5_client(stream, peer_addr).await
}

View File

@@ -3,6 +3,7 @@
use std::{
io::{self, ErrorKind},
net::{Ipv4Addr, SocketAddr},
str,
sync::Arc,
};
@@ -16,6 +17,8 @@ use shadowsocks::{
Error as Socks5Error,
HandshakeRequest,
HandshakeResponse,
PasswordAuthenticationInitialRequest,
PasswordAuthenticationResponse,
Reply,
TcpRequestHeader,
TcpResponseHeader,
@@ -29,6 +32,7 @@ use crate::{
context::ServiceContext,
loadbalancing::PingBalancer,
net::AutoProxyClientStream,
socks::config::Socks5AuthConfig,
utils::establish_tcp_tunnel,
},
net::utils::ignore_until_end,
@@ -39,6 +43,7 @@ pub struct Socks5TcpHandler {
udp_bind_addr: Option<Arc<ServerAddr>>,
balancer: PingBalancer,
mode: Mode,
auth: Arc<Socks5AuthConfig>,
}
impl Socks5TcpHandler {
@@ -47,12 +52,132 @@ impl Socks5TcpHandler {
udp_bind_addr: Option<Arc<ServerAddr>>,
balancer: PingBalancer,
mode: Mode,
auth: Arc<Socks5AuthConfig>,
) -> Socks5TcpHandler {
Socks5TcpHandler {
context,
udp_bind_addr,
balancer,
mode,
auth,
}
}
async fn check_auth(&self, stream: &mut TcpStream, handshake_req: &HandshakeRequest) -> io::Result<()> {
use std::io::Error;
let allow_none = !self.auth.auth_required();
for method in handshake_req.methods.iter() {
match *method {
socks5::SOCKS5_AUTH_METHOD_PASSWORD => {
let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_PASSWORD);
trace!("reply handshake {:?}", resp);
resp.write_to(stream).await?;
return self.check_auth_password(stream).await;
}
socks5::SOCKS5_AUTH_METHOD_NONE => {
if !allow_none {
trace!("none authentication method is not allowed");
} else {
let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NONE);
trace!("reply handshake {:?}", resp);
resp.write_to(stream).await?;
return Ok(());
}
}
_ => {
trace!("unsupported authentication method {}", method);
}
}
}
let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE);
resp.write_to(stream).await?;
trace!("reply handshake {:?}", resp);
return Err(Error::new(
ErrorKind::Other,
"currently shadowsocks-rust does not support authentication",
));
}
async fn check_auth_password(&self, stream: &mut TcpStream) -> io::Result<()> {
use std::io::Error;
const PASSWORD_AUTH_STATUS_FAILURE: u8 = 255;
// Read initiation negociation
let init = match PasswordAuthenticationInitialRequest::read_from(stream).await {
Ok(i) => i,
Err(err) => {
let rsp = PasswordAuthenticationResponse::new(err.as_reply().as_u8());
let _ = rsp.write_to(stream).await;
return Err(Error::new(
ErrorKind::Other,
format!("Username/Password Authentication Initial request failed: {}", err),
));
}
};
let user_name = match str::from_utf8(&init.uname) {
Ok(u) => u,
Err(..) => {
let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE);
let _ = rsp.write_to(stream).await;
return Err(Error::new(
ErrorKind::Other,
"Username/Password Authentication Initial request uname contains invaid characters",
));
}
};
let password = match str::from_utf8(&init.passwd) {
Ok(u) => u,
Err(..) => {
let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE);
let _ = rsp.write_to(stream).await;
return Err(Error::new(
ErrorKind::Other,
"Username/Password Authentication Initial request passwd contains invaid characters",
));
}
};
if self.auth.passwd.check_user(user_name, password) {
trace!(
"socks5 authenticated with Username/Password method, user: {}, password: {}",
user_name,
password
);
let rsp = PasswordAuthenticationResponse::new(0);
rsp.write_to(stream).await?;
Ok(())
} else {
let rsp = PasswordAuthenticationResponse::new(PASSWORD_AUTH_STATUS_FAILURE);
rsp.write_to(stream).await?;
error!(
"socks5 rejected Username/Password user: {}, password: {}",
user_name, password
);
return Err(Error::new(
ErrorKind::Other,
format!(
"Username/Password Authentication failed, user: {}, password: {}",
user_name, password
),
));
}
}
@@ -72,23 +197,7 @@ impl Socks5TcpHandler {
};
trace!("socks5 {:?}", handshake_req);
if !handshake_req.methods.contains(&socks5::SOCKS5_AUTH_METHOD_NONE) {
use std::io::Error;
let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NOT_ACCEPTABLE);
resp.write_to(&mut stream).await?;
return Err(Error::new(
ErrorKind::Other,
"currently shadowsocks-rust does not support authentication",
));
} else {
// Reply to client
let resp = HandshakeResponse::new(socks5::SOCKS5_AUTH_METHOD_NONE);
trace!("reply handshake {:?}", resp);
resp.write_to(&mut stream).await?;
}
self.check_auth(&mut stream, &handshake_req).await?;
// 2. Fetch headers
let header = match TcpRequestHeader::read_from(&mut stream).await {

View File

@@ -1,6 +1,6 @@
[package]
name = "shadowsocks"
version = "1.14.0"
version = "1.14.1"
authors = ["Shadowsocks Contributors"]
description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls."
repository = "https://github.com/shadowsocks/shadowsocks-rust"

View File

@@ -103,7 +103,7 @@ pub enum Reply {
impl Reply {
#[inline]
#[rustfmt::skip]
fn as_u8(self) -> u8 {
pub fn as_u8(self) -> u8 {
match self {
Reply::Succeeded => consts::SOCKS5_REPLY_SUCCEEDED,
Reply::GeneralFailure => consts::SOCKS5_REPLY_GENERAL_FAILURE,
@@ -120,7 +120,7 @@ impl Reply {
#[inline]
#[rustfmt::skip]
fn from_u8(code: u8) -> Reply {
pub fn from_u8(code: u8) -> Reply {
match code {
consts::SOCKS5_REPLY_SUCCEEDED => Reply::Succeeded,
consts::SOCKS5_REPLY_GENERAL_FAILURE => Reply::GeneralFailure,
@@ -793,3 +793,150 @@ impl UdpAssociateHeader {
3 + self.address.serialized_len()
}
}
/// Username/Password Authentication Inittial Negociation
///
/// https://datatracker.ietf.org/doc/html/rfc1929
///
/// ```plain
/// +----+------+----------+------+----------+
/// |VER | ULEN | UNAME | PLEN | PASSWD |
/// +----+------+----------+------+----------+
/// | 1 | 1 | 1 to 255 | 1 | 1 to 255 |
/// +----+------+----------+------+----------+
/// ```
pub struct PasswordAuthenticationInitialRequest {
pub uname: Vec<u8>,
pub passwd: Vec<u8>,
}
impl PasswordAuthenticationInitialRequest {
/// Create a Username/Password Authentication Request
pub fn new<U, P>(uname: U, passwd: P) -> PasswordAuthenticationInitialRequest
where
U: Into<Vec<u8>>,
P: Into<Vec<u8>>,
{
let uname = uname.into();
let passwd = passwd.into();
assert!(
uname.len() > 0 && uname.len() <= u8::MAX as usize && passwd.len() > 0 && passwd.len() <= u8::MAX as usize
);
PasswordAuthenticationInitialRequest { uname, passwd }
}
/// Read from a reader
pub async fn read_from<R>(r: &mut R) -> Result<PasswordAuthenticationInitialRequest, Error>
where
R: AsyncRead + Unpin,
{
let mut ver_buf = [0u8; 1];
let _ = r.read_exact(&mut ver_buf).await?;
// The only valid subnegociation version
if ver_buf[0] != 0x01 {
return Err(Error::Reply(Reply::GeneralFailure));
}
let mut ulen_buf = [0u8; 1];
let _ = r.read_exact(&mut ulen_buf).await?;
let ulen = ulen_buf[0] as usize;
if ulen == 0 {
return Err(Error::Reply(Reply::GeneralFailure));
}
let mut uname = vec![0u8; ulen];
if ulen > 0 {
let _ = r.read_exact(&mut uname).await?;
}
let mut plen_buf = [0u8; 1];
let _ = r.read_exact(&mut plen_buf).await?;
let plen = plen_buf[0] as usize;
if plen == 0 {
return Err(Error::Reply(Reply::GeneralFailure));
}
let mut passwd = vec![0u8; plen];
if plen > 0 {
let _ = r.read_exact(&mut passwd).await?;
}
Ok(PasswordAuthenticationInitialRequest { uname, passwd })
}
/// Write to a writer
pub async fn write_to<W>(&self, w: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
let mut buf = BytesMut::with_capacity(self.serialized_len());
self.write_to_buf(&mut buf);
w.write_all(&buf).await
}
/// Write to buffer
fn write_to_buf<B: BufMut>(&self, buf: &mut B) {
buf.put_u8(0x01);
buf.put_u8(self.uname.len() as u8);
buf.put_slice(&self.uname);
buf.put_u8(self.passwd.len() as u8);
buf.put_slice(&self.passwd);
}
/// Length in bytes
#[inline]
pub fn serialized_len(&self) -> usize {
1 + 1 + self.uname.len() + 1 + self.passwd.len()
}
}
pub struct PasswordAuthenticationResponse {
pub status: u8,
}
impl PasswordAuthenticationResponse {
pub fn new(status: u8) -> PasswordAuthenticationResponse {
PasswordAuthenticationResponse { status }
}
/// Read from a reader
pub async fn read_from<R>(r: &mut R) -> Result<PasswordAuthenticationResponse, Error>
where
R: AsyncRead + Unpin,
{
let mut buf = [0u8; 2];
let _ = r.read_exact(&mut buf);
if buf[0] != 0x01 {
return Err(Error::Reply(Reply::GeneralFailure));
}
Ok(PasswordAuthenticationResponse { status: buf[1] })
}
/// Write to a writer
pub async fn write_to<W>(&self, w: &mut W) -> io::Result<()>
where
W: AsyncWrite + Unpin,
{
let mut buf = BytesMut::with_capacity(self.serialized_len());
self.write_to_buf(&mut buf);
w.write_all(&buf).await
}
/// Write to buffer
fn write_to_buf<B: BufMut>(&self, buf: &mut B) {
buf.put_u8(0x01);
buf.put_u8(self.status);
}
/// Length in bytes
#[inline]
pub fn serialized_len(&self) -> usize {
2
}
}

View File

@@ -27,7 +27,8 @@ use shadowsocks_service::{
use crate::logging;
use crate::{
config::{Config as ServiceConfig, RuntimeMode},
monitor, validator,
monitor,
validator,
};
/// Defines command line options