Skip to content

Commit

Permalink
feat(shadowsocks): Windows bind interface with FriendlyName & Adapter…
Browse files Browse the repository at this point in the history
  • Loading branch information
zonyitoo committed Aug 11, 2023
1 parent 0ed488f commit d3f2095
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 26 deletions.
30 changes: 24 additions & 6 deletions crates/shadowsocks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ edition = "2021"
maintenance = { status = "passively-maintained" }

[features]
default = [
"trust-dns",
]
default = ["trust-dns"]

# Uses trust-dns instead of tokio's builtin DNS resolver
trust-dns = ["trust-dns-resolver", "arc-swap", "notify"]
Expand All @@ -32,7 +30,12 @@ stream-cipher = ["shadowsocks-crypto/v1-stream"]
aead-cipher-extra = ["shadowsocks-crypto/v1-aead-extra"]

# Enable AEAD 2022
aead-cipher-2022 = ["shadowsocks-crypto/v2", "rand/small_rng", "aes", "lru_time_cache"]
aead-cipher-2022 = [
"shadowsocks-crypto/v2",
"rand/small_rng",
"aes",
"lru_time_cache",
]
# Enable AEAD 2022 with extra ciphers
aead-cipher-2022-extra = ["aead-cipher-2022", "shadowsocks-crypto/v2-extra"]

Expand Down Expand Up @@ -65,7 +68,16 @@ futures = "0.3"
async-trait = "0.1"

socket2 = { version = "0.5", features = ["all"] }
tokio = { version = "1.9.0", features = ["io-util", "macros", "net", "parking_lot", "process", "rt", "sync", "time"] }
tokio = { version = "1.9.0", features = [
"io-util",
"macros",
"net",
"parking_lot",
"process",
"rt",
"sync",
"time",
] }
tokio-tfo = "0.2.0"

trust-dns-resolver = { version = "0.23.0-alpha", optional = true }
Expand All @@ -82,7 +94,13 @@ shadowsocks-crypto = { version = "0.5.1", features = ["ring"] }
shadowsocks-crypto = { version = "0.5.1", features = [] }

[target.'cfg(windows)'.dependencies]
windows-sys = { version = "0.48", features = ["Win32_Foundation", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_System_IO"] }
windows-sys = { version = "0.48", features = [
"Win32_Foundation",
"Win32_NetworkManagement_IpHelper",
"Win32_NetworkManagement_Ndis",
"Win32_Networking_WinSock",
"Win32_System_IO",
] }

[target.'cfg(unix)'.dependencies]
sendfd = { version = "0.4", features = ["tokio"] }
Expand Down
6 changes: 4 additions & 2 deletions crates/shadowsocks/src/dns_resolver/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ use std::{
use arc_swap::ArcSwap;
use async_trait::async_trait;
use cfg_if::cfg_if;
#[cfg(feature = "trust-dns")]
#[cfg(all(feature = "trust-dns", unix, not(target_os = "android")))]
use log::error;
use log::{log_enabled, trace, Level};
use tokio::net::lookup_host;
#[cfg(feature = "trust-dns")]
#[cfg(all(feature = "trust-dns", unix, not(target_os = "android")))]
use tokio::task::JoinHandle;
#[cfg(feature = "trust-dns")]
use trust_dns_resolver::config::ResolverConfig;
Expand All @@ -40,7 +40,9 @@ pub trait DnsResolve {
#[cfg(feature = "trust-dns")]
pub struct TrustDnsSystemResolver {
resolver: ArcSwap<TrustDnsResolver>,
#[cfg_attr(windows, allow(dead_code))]
connect_opts: ConnectOpts,
#[cfg_attr(windows, allow(dead_code))]
opts: Option<ResolverOpts>,
}

Expand Down
3 changes: 2 additions & 1 deletion crates/shadowsocks/src/dns_resolver/trust_dns_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ use trust_dns_resolver::{
error::ResolveResult,
name_server::{GenericConnector, RuntimeProvider},
proto::{iocompat::AsyncIoTokioAsStd, udp::DnsUdpSocket, TokioTime},
AsyncResolver, TokioHandle,
AsyncResolver,
TokioHandle,
};

use crate::net::{tcp::TcpStream as ShadowTcpStream, udp::UdpSocket as ShadowUdpSocket, ConnectOpts};
Expand Down
125 changes: 110 additions & 15 deletions crates/shadowsocks/src/net/sys/windows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
use std::{
ffi::{c_void, CString},
ffi::{c_void, CStr, CString, OsString},
io::{self, ErrorKind},
mem,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket},
os::windows::{
ffi::OsStringExt,
io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket},
},
pin::Pin,
ptr,
slice,
task::{self, Poll},
};

use bytes::BytesMut;
use log::{error, warn};
use pin_project::pin_project;
use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type};
Expand All @@ -20,12 +25,18 @@ use tokio_tfo::TfoStream;
use windows_sys::{
core::PCSTR,
Win32::{
Foundation::BOOL,
NetworkManagement::IpHelper::if_nametoindex,
Foundation::{BOOL, ERROR_BUFFER_OVERFLOW, ERROR_NO_DATA, ERROR_SUCCESS},
NetworkManagement::IpHelper::{
if_nametoindex,
GetAdaptersAddresses,
GAA_FLAG_INCLUDE_PREFIX,
IP_ADAPTER_ADDRESSES_LH,
},
Networking::WinSock::{
setsockopt,
WSAGetLastError,
WSAIoctl,
AF_UNSPEC,
IPPROTO_IP,
IPPROTO_IPV6,
IPPROTO_TCP,
Expand Down Expand Up @@ -197,20 +208,101 @@ pub async fn create_inbound_tcp_socket(bind_addr: &SocketAddr, _accept_opts: &Ac
}
}

fn find_adapter_interface_index(addr: &SocketAddr, iface: &str) -> io::Result<Option<u32>> {
// https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses

let ip = addr.ip();

unsafe {
let mut ip_adapter_addresses_buffer = BytesMut::with_capacity(15 * 1024);
ip_adapter_addresses_buffer.set_len(15 * 1024);

let mut ip_adapter_addresses_buffer_size: u32 = ip_adapter_addresses_buffer.len() as u32;
loop {
let ret = GetAdaptersAddresses(
AF_UNSPEC as u32,
GAA_FLAG_INCLUDE_PREFIX,
ptr::null(),
ip_adapter_addresses_buffer.as_mut_ptr() as *mut _,
&mut ip_adapter_addresses_buffer_size as *mut _,
);

match ret {
ERROR_SUCCESS => break,
ERROR_BUFFER_OVERFLOW => {
// resize buffer to ip_adapter_addresses_buffer_size
ip_adapter_addresses_buffer.resize(ip_adapter_addresses_buffer_size as usize, 0);
continue;
}
ERROR_NO_DATA => return Ok(None),
_ => {
let err = io::Error::new(
ErrorKind::Other,
format!("GetAdaptersAddresses failed with error: {}", ret),
);
return Err(err);
}
}
}

// IP_ADAPTER_ADDRESSES_LH is a linked-list
let mut current_ip_adapter_address: *mut IP_ADAPTER_ADDRESSES_LH =
ip_adapter_addresses_buffer.as_mut_ptr() as *mut _;
while !current_ip_adapter_address.is_null() {
let ip_adapter_address: &IP_ADAPTER_ADDRESSES_LH = &*current_ip_adapter_address;

// Friendly Name
let friendly_name_len: usize = libc::wcslen(ip_adapter_address.FriendlyName);
let friendly_name_slice: &[u16] = slice::from_raw_parts(ip_adapter_address.FriendlyName, friendly_name_len);
let friendly_name_os = OsString::from_wide(friendly_name_slice); // UTF-16 to UTF-8
if let Some(friendly_name) = friendly_name_os.to_str() {
if friendly_name == iface {
match ip {
IpAddr::V4(..) => return Ok(Some(ip_adapter_address.Anonymous1.Anonymous.IfIndex)),
IpAddr::V6(..) => return Ok(Some(ip_adapter_address.Ipv6IfIndex)),
}
}
}

// Adapter Name
let adapter_name = CStr::from_ptr(ip_adapter_address.AdapterName as *mut _ as *const _);
if adapter_name.to_bytes() == iface.as_bytes() {
match ip {
IpAddr::V4(..) => return Ok(Some(ip_adapter_address.Anonymous1.Anonymous.IfIndex)),
IpAddr::V6(..) => return Ok(Some(ip_adapter_address.Ipv6IfIndex)),
}
}

current_ip_adapter_address = ip_adapter_address.Next;
}
}

Ok(None)
}

fn set_ip_unicast_if<S: AsRawSocket>(socket: &S, addr: &SocketAddr, iface: &str) -> io::Result<()> {
let handle = socket.as_raw_socket() as SOCKET;

unsafe {
// Windows if_nametoindex requires a C-string for interface name
let ifname = CString::new(iface).expect("iface");

// https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff553788(v=vs.85)
let if_index = if_nametoindex(ifname.as_ptr() as PCSTR);
if if_index == 0 {
// If the if_nametoindex function fails and returns zero, it is not possible to determine an error code.
error!("if_nametoindex {} fails", iface);
return Err(io::Error::new(ErrorKind::InvalidInput, "invalid interface name"));
}
// GetAdaptersAddresses
// XXX: It will check all the adapters every time. Would that become a performance issue?
let if_index = match find_adapter_interface_index(addr, iface)? {
Some(idx) => idx,
None => {
// Windows if_nametoindex requires a C-string for interface name
let ifname = CString::new(iface).expect("iface");

// https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff553788(v=vs.85)
let if_index = if_nametoindex(ifname.as_ptr() as PCSTR);
if if_index == 0 {
// If the if_nametoindex function fails and returns zero, it is not possible to determine an error code.
error!("if_nametoindex {} fails", iface);
return Err(io::Error::new(ErrorKind::InvalidInput, "invalid interface name"));
}

if_index
}
};

// https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
let ret = match addr {
Expand All @@ -232,7 +324,10 @@ fn set_ip_unicast_if<S: AsRawSocket>(socket: &S, addr: &SocketAddr, iface: &str)

if ret == SOCKET_ERROR {
let err = io::Error::from_raw_os_error(WSAGetLastError());
error!("set IP_UNICAST_IF / IPV6_UNICAST_IF error: {}", err);
error!(
"set IP_UNICAST_IF / IPV6_UNICAST_IF interface: {}, index: {}, error: {}",
iface, if_index, err
);
return Err(err);
}
}
Expand Down
2 changes: 1 addition & 1 deletion crates/shadowsocks/src/net/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#[cfg(unix)]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket};
use std::os::windows::io::{AsRawSocket, RawSocket};
use std::{
io,
net::SocketAddr,
Expand Down
4 changes: 3 additions & 1 deletion crates/shadowsocks/src/relay/udprelay/aead_2022.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ use std::{

use aes::{
cipher::{BlockDecrypt, BlockEncrypt, KeyInit},
Aes128, Aes256, Block,
Aes128,
Aes256,
Block,
};
use byte_string::ByteStr;
use bytes::{Buf, BufMut, Bytes, BytesMut};
Expand Down

0 comments on commit d3f2095

Please sign in to comment.