mirror of
https://github.com/shadowsocks/shadowsocks-rust.git
synced 2026-02-09 01:59:16 +08:00
Fix spelling (#667)
* spelling: active * spelling: availability * spelling: because * spelling: behavior * spelling: browsers * spelling: excessive * spelling: extended * spelling: insecure * spelling: miscellaneous * *spelling: negotiate * spelling: particular * spelling: readiness * spelling: recommended * spelling: response * spelling: respectively * spelling: reassemble * spelling: Shadowsocks * spelling: simultaneously * spelling: silent * spelling: stabilized * spelling: unexpectedly * *spelling: UNEXPECTEDLY Co-authored-by: Zimo Li <lzm0@users.noreply.github.com>
This commit is contained in:
@@ -107,7 +107,7 @@ snmalloc = ["snmalloc-rs"]
|
||||
multi-threaded = ["tokio/rt-multi-thread"]
|
||||
|
||||
# Enable Stream Cipher Protocol
|
||||
# WARN: Stream Cipher Protocol is proved to be insecured
|
||||
# WARN: Stream Cipher Protocol is proved to be insecure
|
||||
# https://github.com/shadowsocks/shadowsocks-rust/issues/373
|
||||
# Users should always avoid using these ciphers in practice
|
||||
stream-cipher = ["shadowsocks-service/stream-cipher"]
|
||||
|
||||
@@ -70,7 +70,7 @@ Install from [crates.io](https://crates.io/crates/shadowsocks-rust):
|
||||
```bash
|
||||
# Set default toolchain to nightly
|
||||
rustup default nightly
|
||||
# RECOMMEND: Check the rust-toolchain file in the project root and use the recomended nightly version
|
||||
# RECOMMEND: Check the rust-toolchain file in the project root and use the recommended nightly version
|
||||
# For example:
|
||||
# rustup default nightly-2021-06-03
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod logging;
|
||||
pub mod monitor;
|
||||
pub mod validator;
|
||||
|
||||
pub const EXIT_CODE_SERVER_EXIT_UNEXPECTLY: i32 = exitcode::SOFTWARE;
|
||||
pub const EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY: i32 = exitcode::SOFTWARE;
|
||||
pub const EXIT_CODE_SERVER_ABORTED: i32 = exitcode::SOFTWARE;
|
||||
pub const EXIT_CODE_LOAD_CONFIG_FAILURE: i32 = exitcode::CONFIG;
|
||||
pub const EXIT_CODE_LOAD_ACL_FAILURE: i32 = exitcode::CONFIG;
|
||||
|
||||
@@ -533,8 +533,8 @@ fn main() {
|
||||
match future::select(server, abort_signal).await {
|
||||
// Server future resolved without an error. This should never happen.
|
||||
Either::Left((Ok(..), ..)) => {
|
||||
eprintln!("server exited unexpectly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTLY);
|
||||
eprintln!("server exited unexpectedly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY);
|
||||
}
|
||||
// Server future resolved with error, which are listener errors in most cases
|
||||
Either::Left((Err(err), ..)) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! or you could specify a configuration file. The format of configuration file is defined
|
||||
//! in mod `config`.
|
||||
//!
|
||||
//! *It should be notice that the extented configuration file is not suitable for the server
|
||||
//! *It should be notice that the extended configuration file is not suitable for the server
|
||||
//! side.*
|
||||
|
||||
use std::{net::IpAddr, process, time::Duration};
|
||||
@@ -330,8 +330,8 @@ fn main() {
|
||||
match future::select(server, abort_signal).await {
|
||||
// Server future resolved without an error. This should never happen.
|
||||
Either::Left((Ok(..), ..)) => {
|
||||
eprintln!("server exited unexpectly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTLY);
|
||||
eprintln!("server exited unexpectedly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY);
|
||||
}
|
||||
// Server future resolved with error, which are listener errors in most cases
|
||||
Either::Left((Err(err), ..)) => {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! or you could specify a configuration file. The format of configuration file is defined
|
||||
//! in mod `config`.
|
||||
//!
|
||||
//! *It should be notice that the extented configuration file is not suitable for the server
|
||||
//! *It should be notice that the extended configuration file is not suitable for the server
|
||||
//! side.*
|
||||
|
||||
use std::{net::IpAddr, process, time::Duration};
|
||||
@@ -314,8 +314,8 @@ fn main() {
|
||||
match future::select(server, abort_signal).await {
|
||||
// Server future resolved without an error. This should never happen.
|
||||
Either::Left((Ok(..), ..)) => {
|
||||
eprintln!("server exited unexpectly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTLY);
|
||||
eprintln!("server exited unexpectedly");
|
||||
process::exit(common::EXIT_CODE_SERVER_EXIT_UNEXPECTEDLY);
|
||||
}
|
||||
// Server future resolved with error, which are listener errors in most cases
|
||||
Either::Left((Err(err), ..)) => {
|
||||
|
||||
@@ -41,7 +41,7 @@ def generate_ipset(content, name, location_set, type_set, output_file):
|
||||
'''
|
||||
In the case of IPv4 address the count of hosts for this range. This count does not have to represent a CIDR range.
|
||||
|
||||
But. It seems that it is always a CIDR range in this paticalur file.
|
||||
But. It seems that it is always a CIDR range in this particular file.
|
||||
'''
|
||||
mask = cidr_trans[int(value)]
|
||||
output_file.write(
|
||||
|
||||
@@ -59,7 +59,7 @@ local-socks4 = ["local"]
|
||||
local-tun = ["local", "etherparse", "tun", "rand"]
|
||||
|
||||
# Enable Stream Cipher Protocol
|
||||
# WARN: Stream Cipher Protocol is proved to be insecured
|
||||
# WARN: Stream Cipher Protocol is proved to be insecure
|
||||
# https://github.com/shadowsocks/shadowsocks-rust/issues/373
|
||||
# Users should always avoid using these ciphers in practice
|
||||
stream-cipher = ["shadowsocks/stream-cipher"]
|
||||
|
||||
@@ -412,7 +412,7 @@ impl AccessControl {
|
||||
|
||||
/// Default mode
|
||||
///
|
||||
/// Default behavor for hosts that are not configured
|
||||
/// Default behavior for hosts that are not configured
|
||||
/// - `true` - Proxied
|
||||
/// - `false` - Bypassed
|
||||
pub fn is_default_in_proxy_list(&self) -> bool {
|
||||
|
||||
@@ -213,7 +213,7 @@ impl DnsClient {
|
||||
Ordering::Less => {
|
||||
let err = io::Error::last_os_error();
|
||||
// I have to trust the `s` have already set to non-blocking mode
|
||||
// Becuase windows doesn't have MSG_DONTWAIT
|
||||
// Because windows doesn't have MSG_DONTWAIT
|
||||
err.kind() == ErrorKind::WouldBlock
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ fn check_keep_alive(version: Version, headers: &HeaderMap<HeaderValue>, check_pr
|
||||
let mut conn_keep_alive = !matches!(version, Version::HTTP_09 | Version::HTTP_10);
|
||||
|
||||
if check_proxy {
|
||||
// Modern browers will send Proxy-Connection instead of Connection
|
||||
// Modern browsers will send Proxy-Connection instead of Connection
|
||||
// for HTTP/1.0 proxies which blindly forward Connection to remote
|
||||
//
|
||||
// https://tools.ietf.org/html/rfc7230#appendix-A.1.2
|
||||
|
||||
@@ -41,16 +41,16 @@ impl ProxyHttpStream {
|
||||
|
||||
match cx.connect(domain, stream).await {
|
||||
Ok(s) => {
|
||||
let negociated_h2 = match s.get_ref().negotiated_alpn() {
|
||||
let negotiated_h2 = match s.get_ref().negotiated_alpn() {
|
||||
Ok(Some(alpn)) => alpn == b"h2",
|
||||
Ok(None) => false,
|
||||
Err(err) => {
|
||||
let ierr = io::Error::new(ErrorKind::Other, format!("tls alpn negociate: {}", err));
|
||||
let ierr = io::Error::new(ErrorKind::Other, format!("tls alpn negotiate: {}", err));
|
||||
return Err(ierr);
|
||||
}
|
||||
};
|
||||
|
||||
Ok(ProxyHttpStream::Https(s, negociated_h2))
|
||||
Ok(ProxyHttpStream::Https(s, negotiated_h2))
|
||||
}
|
||||
Err(err) => {
|
||||
let ierr = io::Error::new(ErrorKind::Other, format!("tls connect: {}", err));
|
||||
@@ -106,7 +106,7 @@ impl ProxyHttpStream {
|
||||
})
|
||||
.with_no_client_auth();
|
||||
|
||||
// Try to negociate HTTP/2
|
||||
// Try to negotiate HTTP/2
|
||||
config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
Arc::new(config)
|
||||
});
|
||||
@@ -126,9 +126,9 @@ impl ProxyHttpStream {
|
||||
let tls_stream = connector.connect(host, stream).await?;
|
||||
|
||||
let (_, session) = tls_stream.get_ref();
|
||||
let negociated_http2 = matches!(session.alpn_protocol(), Some(b"h2"));
|
||||
let negotiated_http2 = matches!(session.alpn_protocol(), Some(b"h2"));
|
||||
|
||||
Ok(ProxyHttpStream::Https(tls_stream, negociated_http2))
|
||||
Ok(ProxyHttpStream::Https(tls_stream, negotiated_http2))
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "local-http-native-tls", feature = "local-http-rustls")))]
|
||||
@@ -140,7 +140,7 @@ impl ProxyHttpStream {
|
||||
Err(err)
|
||||
}
|
||||
|
||||
pub fn negociated_http2(&self) -> bool {
|
||||
pub fn negotiated_http2(&self) -> bool {
|
||||
match *self {
|
||||
ProxyHttpStream::Http(..) => false,
|
||||
#[cfg(any(feature = "local-http-native-tls", feature = "local-http-rustls"))]
|
||||
@@ -182,7 +182,7 @@ impl AsyncWrite for ProxyHttpStream {
|
||||
impl Connection for ProxyHttpStream {
|
||||
fn connected(&self) -> Connected {
|
||||
let conn = Connected::new();
|
||||
if self.negociated_http2() {
|
||||
if self.negotiated_http2() {
|
||||
conn.negotiated_h2()
|
||||
} else {
|
||||
conn
|
||||
|
||||
@@ -35,7 +35,7 @@ use crate::{
|
||||
/// Writer for sending packets back to client
|
||||
///
|
||||
/// Currently it requires `async-trait` for `async fn` in trait, which will allocate a `Box`ed `Future` every call of `send_to`.
|
||||
/// This performance issue could be solved when `generic_associated_types` and `generic_associated_types` are stablized.
|
||||
/// This performance issue could be solved when `generic_associated_types` and `generic_associated_types` are stabilized.
|
||||
#[async_trait]
|
||||
pub trait UdpInboundWrite {
|
||||
/// Sends packet `data` received from `remote_addr` back to `peer_addr`
|
||||
@@ -299,7 +299,7 @@ where
|
||||
respond_writer: W,
|
||||
) -> (Arc<UdpAssociationContext<W>>, mpsc::Sender<(Address, Bytes)>) {
|
||||
// Pending packets 1024 should be good enough for a server.
|
||||
// If there are plenty of packets stuck in the channel, dropping exccess packets is a good way to protect the server from
|
||||
// If there are plenty of packets stuck in the channel, dropping excessive packets is a good way to protect the server from
|
||||
// being OOM.
|
||||
let (sender, receiver) = mpsc::channel(1024);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ impl Redir {
|
||||
self.udp_expiry_duration = Some(d);
|
||||
}
|
||||
|
||||
/// Set total UDP association to be kept simutaneously in server
|
||||
/// Set total UDP association to be kept simultaneouslyin server
|
||||
pub fn set_udp_capacity(&mut self, c: usize) {
|
||||
self.udp_capacity = Some(c);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ impl Socks {
|
||||
self.udp_expiry_duration = Some(d);
|
||||
}
|
||||
|
||||
/// Set total UDP association to be kept simutaneously in server
|
||||
/// Set total UDP association to be kept simultaneouslyin server
|
||||
pub fn set_udp_capacity(&mut self, c: usize) {
|
||||
self.udp_capacity = Some(c);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ struct Socks5UdpInboundWriter {
|
||||
#[async_trait]
|
||||
impl UdpInboundWrite for Socks5UdpInboundWriter {
|
||||
async fn send_to(&self, peer_addr: SocketAddr, remote_addr: &Address, data: &[u8]) -> io::Result<()> {
|
||||
// Resssemble packet
|
||||
// Reassemble packet
|
||||
let mut payload_buffer = BytesMut::new();
|
||||
let header = UdpAssociateHeader::new(0, remote_addr.clone());
|
||||
payload_buffer.reserve(header.serialized_len() + data.len());
|
||||
|
||||
@@ -65,7 +65,7 @@ pub enum ResultCode {
|
||||
RequestGranted,
|
||||
/// 91: request rejected or failed
|
||||
RequestRejectedOrFailed,
|
||||
/// 92: request rejected becasue SOCKS server cannot connect to identd on the client
|
||||
/// 92: request rejected because SOCKS server cannot connect to identd on the client
|
||||
RequestRejectedCannotConnect,
|
||||
/// 93: request rejected because the client program and identd report different user-ids
|
||||
RequestRejectedDifferentUserId,
|
||||
|
||||
@@ -58,7 +58,7 @@ impl UdpTun {
|
||||
pub async fn recv_packet(&mut self) -> BytesMut {
|
||||
match self.tun_rx.recv().await {
|
||||
Some(b) => b,
|
||||
None => unreachable!("channel closed unexpectly"),
|
||||
None => unreachable!("channel closed unexpectedly"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ impl Tunnel {
|
||||
self.udp_expiry_duration = Some(d);
|
||||
}
|
||||
|
||||
/// Set total UDP association to be kept simutaneously in server
|
||||
/// Set total UDP association to be kept simultaneouslyin server
|
||||
pub fn set_udp_capacity(&mut self, c: usize) {
|
||||
self.udp_capacity = Some(c);
|
||||
}
|
||||
|
||||
@@ -268,7 +268,7 @@ impl UdpAssociationContext {
|
||||
balancer: PingBalancer,
|
||||
) -> (Arc<UdpAssociationContext>, mpsc::Sender<Bytes>) {
|
||||
// Pending packets 1024 should be good enough for a server.
|
||||
// If there are plenty of packets stuck in the channel, dropping exccess packets is a good way to protect the server from
|
||||
// If there are plenty of packets stuck in the channel, dropping excessive packets is a good way to protect the server from
|
||||
// being OOM.
|
||||
let (sender, receiver) = mpsc::channel(1024);
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ impl Server {
|
||||
error!("servers exited with error: {}", err);
|
||||
}
|
||||
|
||||
let err = io::Error::new(ErrorKind::Other, "server exited unexpectly");
|
||||
let err = io::Error::new(ErrorKind::Other, "server exited unexpectedly");
|
||||
Err(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ impl TcpServerClient {
|
||||
let res = ignore_until_end(&mut stream).await;
|
||||
|
||||
trace!(
|
||||
"slient-drop peer: {} is now closing with result {:?}",
|
||||
"silent-drop peer: {} is now closing with result {:?}",
|
||||
self.peer_addr,
|
||||
res
|
||||
);
|
||||
|
||||
@@ -243,7 +243,7 @@ impl UdpAssociationContext {
|
||||
keepalive_tx: mpsc::Sender<SocketAddr>,
|
||||
) -> (Arc<UdpAssociationContext>, mpsc::Sender<(Address, Bytes)>) {
|
||||
// Pending packets 1024 should be good enough for a server.
|
||||
// If there are plenty of packets stuck in the channel, dropping exccess packets is a good way to protect the server from
|
||||
// If there are plenty of packets stuck in the channel, dropping excessive packets is a good way to protect the server from
|
||||
// being OOM.
|
||||
let (sender, receiver) = mpsc::channel(1024);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ default = [
|
||||
trust-dns = ["trust-dns-resolver", "arc-swap", "notify"]
|
||||
|
||||
# Enable Stream Cipher Protocol
|
||||
# WARN: Stream Cipher Protocol is proved to be insecured
|
||||
# WARN: Stream Cipher Protocol is proved to be insecure
|
||||
# https://github.com/shadowsocks/shadowsocks-rust/issues/373
|
||||
# Users should always avoid using these ciphers in practice
|
||||
stream-cipher = ["shadowsocks-crypto/v1-stream"]
|
||||
|
||||
@@ -198,7 +198,7 @@ async fn trust_dns_notify_update_dns(resolver: Arc<TrustDnsSystemResolver>) -> n
|
||||
update_task = Some(task);
|
||||
}
|
||||
|
||||
error!("auto-reload {} task exited unexpectly", DNS_RESOLV_FILE_PATH);
|
||||
error!("auto-reload {} task exited unexpectedly", DNS_RESOLV_FILE_PATH);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ impl ManagerProtocol for PingRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// `ping` reponse
|
||||
/// `ping` response
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(transparent)]
|
||||
pub struct PingResponse {
|
||||
|
||||
@@ -17,7 +17,7 @@ pub struct TcpSocketOpts {
|
||||
/// `TCP_FASTOPEN`, enables TFO
|
||||
pub fastopen: bool,
|
||||
|
||||
/// `SO_KEEPALIVE` and sets `TCP_KEEPIDLE`, `TCP_KEEPINTVL` and `TCP_KEEPCNT` respectly,
|
||||
/// `SO_KEEPALIVE` and sets `TCP_KEEPIDLE`, `TCP_KEEPINTVL` and `TCP_KEEPCNT` respectively,
|
||||
/// enables keep-alive messages on connection-oriented sockets
|
||||
pub keepalive: Option<Duration>,
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ impl UnixStream {
|
||||
// self.io.poll_write_ready indicates that writable event have been received by tokio,
|
||||
// so it is not a common case that sendto returns EAGAIN.
|
||||
//
|
||||
// Just for double check. If EAGAIN actually returns, clear the readness state.
|
||||
// Just for double check. If EAGAIN actually returns, clear the readiness state.
|
||||
Err(ref err) if err.kind() == ErrorKind::WouldBlock => {
|
||||
ready.clear_ready();
|
||||
}
|
||||
@@ -74,7 +74,7 @@ impl UnixStream {
|
||||
// self.io.poll_write_ready indicates that writable event have been received by tokio,
|
||||
// so it is not a common case that recvto returns EAGAIN.
|
||||
//
|
||||
// Just for double check. If EAGAIN actually returns, clear the readness state.
|
||||
// Just for double check. If EAGAIN actually returns, clear the readiness state.
|
||||
Err(ref err) if err.kind() == ErrorKind::WouldBlock => {
|
||||
ready.clear_ready();
|
||||
}
|
||||
|
||||
12
debian/changelog
vendored
12
debian/changelog
vendored
@@ -46,7 +46,7 @@ shadowsocks-rust (1.11.2) unstable; urgency=medium
|
||||
|
||||
## Security
|
||||
|
||||
- #556 Remove slient dropping when replay was detected
|
||||
- #556 Remove silent dropping when replay was detected
|
||||
|
||||
-- ty <zonyitoo@gmail.com> Sat, 24 July 2021 11:50:00 +0800
|
||||
|
||||
@@ -375,7 +375,7 @@ shadowsocks-rust (1.8.17) unstable; urgency=medium
|
||||
|
||||
## BUG Fixed
|
||||
|
||||
* [#292](https://github.com/shadowsocks/shadowsocks-rust/pull/292) Hold the TCP connection if it failed to decrypt the first packet for preventing activing probing.
|
||||
* [#292](https://github.com/shadowsocks/shadowsocks-rust/pull/292) Hold the TCP connection if it failed to decrypt the first packet for preventing active probing.
|
||||
* [#293](https://github.com/shadowsocks/shadowsocks-rust/pull/293) Keep server running if it fails to create UDP associations.
|
||||
|
||||
-- ty <zonyitoo@gmail.com> Tue, 8 Sep 2020 23:31:20 +0800
|
||||
@@ -440,7 +440,7 @@ shadowsocks-rust (1.8.12) unstable; urgency=medium
|
||||
* [#233](https://github.com/shadowsocks/shadowsocks-rust/issues/233) sslocal supports socks4 protocol (SOCKS4/4a)
|
||||
* Options for LRU cache in UDP relay:
|
||||
* udp_timeout: UDP Association will be kept up to this duration (in seconds)
|
||||
* udp_max_associations: Maximum number of UDP Associations will be kept simutanously
|
||||
* udp_max_associations: Maximum number of UDP Associations will be kept simultaneously
|
||||
|
||||
## BUG Fixed
|
||||
|
||||
@@ -462,7 +462,7 @@ shadowsocks-rust (1.8.11) unstable; urgency=medium
|
||||
|
||||
* [#232](https://github.com/shadowsocks/shadowsocks-rust/issues/232) Send data along with handshake (LOCAL -> REMOTE)
|
||||
* HTTP server supports https target with both native-tls and rustls
|
||||
* For rustls, https connections will try to negociate h2 with ALPN
|
||||
* For rustls, https connections will try to negotiate h2 with ALPN
|
||||
* shadowsocks/shadowsocks-org#161 Support none as dummy cipher's name
|
||||
* Adding local-tunnel feature for controlling tunnel protocol
|
||||
* [#252](https://github.com/shadowsocks/shadowsocks-rust/issues/252) Support udp_max_associations configuration option
|
||||
@@ -554,7 +554,7 @@ shadowsocks-rust (1.8.8) unstable; urgency=medium
|
||||
* Eliminated those connection failures while sslocal server just started
|
||||
* [#191](https://github.com/shadowsocks/shadowsocks-rust/issues/191) Skip IV/nonce duplication check for plain cipher
|
||||
|
||||
## Miscelleous
|
||||
## Miscellaneous
|
||||
|
||||
* Nightly builds on CircleCI: https://circleci.com/gh/shadowsocks/shadowsocks-rust
|
||||
* Obtain release binaries in #Artifacts, for example:
|
||||
@@ -656,7 +656,7 @@ shadowsocks-rust (1.8.0) unstable; urgency=medium
|
||||
* `trust-dns-resolver` - `v0.18`
|
||||
* `libsodium-sys` (switched from `libsodium-ffi` for better build process)
|
||||
* Ping balancer
|
||||
* Calculate scores not only with servers' latency, but also availablity
|
||||
* Calculate scores not only with servers' latency, but also availability
|
||||
* Supports UDP relay
|
||||
* Retry 3 times if it connects failed to remote proxy server automatically
|
||||
|
||||
|
||||
2
debian/control
vendored
2
debian/control
vendored
@@ -12,5 +12,5 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, lsb-base
|
||||
Conflicts: shadowsocks
|
||||
Description: Rust port of shadowsocks, a secure socks5 proxy
|
||||
Shadowsocks is a fast tunnel proxy that helps you bypass firewalls.
|
||||
Shadowsocks-rust was inspired by Shadowsock (in Python). It's rewritten
|
||||
Shadowsocks-rust was inspired by Shadowsocks (in Python). It's rewritten
|
||||
in rust.
|
||||
|
||||
Reference in New Issue
Block a user