mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-08 10:56:13 -08:00
refactor(ohos): 拆分 OHRS 包并按 socket 精细保护 VPN 流量 (#2543)
* refactor(ohos): split facade feature and kernel crates * feat(ohos): protect transport sockets individually * fix(ohos): keep local proxy subnets off tun * fix(ohos): expose valid config enum values * refactor(ohos): finalize reusable core boundary * test(ohos): verify split package contracts * fix(port-forward): support wildcard userspace listeners Keep the existing Host listener intact while adding a DataPlane listener for force-smoltcp IPv4 wildcard rules. Keep literal loopback destinations on the local Host path instead of exporting them through an exit node. * chore(ohos): refresh split workspace lockfile * fix(socket): normalize Windows raw socket handles * refactor(socket): carry VPN protection through host bind options * refactor(socket): simplify protection defaults and TUN ingress * refactor(socket): consolidate native protection and socket creation * fix(socket): protect outbound UDP paths * fix(socket): preserve VPN routing for RPC listeners --------- Co-authored-by: FrankHan <frankhan@FrankHans-Mac-mini.local> Co-authored-by: KKRainbow <443152178@qq.com>
This commit is contained in:
63 files changed
+2011
-397
No files matched your search
@@ -262,6 +262,7 @@ impl<R: TcpProxyRuntime + 'static, F: VirtualTcpListenerFactory, C: TcpProxyDest
|
||||
.bind_tcp(
|
||||
TcpListenOptions::proxy_nat(listen_addr).with_bind(
|
||||
TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_context(
|
||||
self.socket_context
|
||||
.clone()
|
||||
|
||||
@@ -67,6 +67,7 @@ where
|
||||
|
||||
fn udp_bind_options(&self) -> UdpBindOptions {
|
||||
UdpBindOptions::socks5()
|
||||
.with_need_protect(false)
|
||||
.with_context(self.socket_context.clone().with_ip_version(IpVersion::V6))
|
||||
.with_local_addr(Some(SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::UNSPECIFIED,
|
||||
@@ -289,6 +290,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(options[0].context.socket_mark, context.socket_mark);
|
||||
assert_eq!(options[0].context.ip_version, IpVersion::V6);
|
||||
assert!(
|
||||
!options[0].need_protect,
|
||||
"SOCKS5 association socket is inbound"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -47,6 +47,10 @@ pub trait DnsRecordResolver: Send + Sync + 'static {
|
||||
/// Submit methods must return without waiting for DNS. Completion methods own
|
||||
/// their returned data and must not retain guest-memory borrows. Cancellation
|
||||
/// removes pending or completed-but-unobserved operation state.
|
||||
/// When the host uses VPN bypass, every underlying DNS socket (including TCP
|
||||
/// fallback) must be protected before sending queries. A system resolver is only
|
||||
/// suitable if the host can guarantee equivalent routing; errors must not fall
|
||||
/// back to unprotected DNS traffic.
|
||||
pub trait HostDnsIo: Send + Sync + 'static {
|
||||
fn submit_resolve(&self, operation: HostOperationId, query: &DnsQuery) -> io::Result<()>;
|
||||
|
||||
|
||||
@@ -13,6 +13,9 @@ use super::socket::{HostOperationId, HostSocketRuntime};
|
||||
/// take retains the operation; a `Ready` take consumes both successful and
|
||||
/// failed results. Cancellation must remove pending and completed-but-unread
|
||||
/// state and is idempotent for an already-absent operation.
|
||||
/// Source-address probes describe the underlay route: if VPN bypass is active,
|
||||
/// protect the probe socket before connect/source-address selection. Protection
|
||||
/// acknowledgement belongs inside this host operation, not a later guest call.
|
||||
pub trait HostConnectorEnvironmentIo: Send + Sync + 'static {
|
||||
fn submit_local_addr_for_remote(
|
||||
&self,
|
||||
|
||||
@@ -31,6 +31,9 @@ pub struct HostUdpBindResult {
|
||||
/// their handles and address metadata. Canceling an operation must atomically
|
||||
/// stop pending creation or close and discard a resource that completed before
|
||||
/// core observed it, so dropping a factory future cannot leak a host socket.
|
||||
/// When bind options request socket protection, the host must complete and
|
||||
/// acknowledge protection before connect, datagram I/O, or successful creation
|
||||
/// completion. A protection error fails the creation operation.
|
||||
pub trait HostSocketFactoryIo: HostSocketIo {
|
||||
fn submit_tcp_connect(
|
||||
&self,
|
||||
@@ -388,7 +391,8 @@ mod tests {
|
||||
.with_local_addr(Some("192.0.2.1:0".parse().unwrap()))
|
||||
.with_socket_mark(Some(7))
|
||||
.with_bind_device(Some("host-device".to_owned()))
|
||||
.with_reuse_port(true),
|
||||
.with_reuse_port(true)
|
||||
.with_need_protect(true),
|
||||
purpose: TcpSocketPurpose::ManualConnect,
|
||||
};
|
||||
let task = tokio::spawn({
|
||||
@@ -407,6 +411,7 @@ mod tests {
|
||||
panic!("operation is not TCP connect");
|
||||
};
|
||||
assert_eq!(submitted, &options);
|
||||
assert!(submitted.bind.need_protect);
|
||||
}
|
||||
|
||||
io.complete_tcp(
|
||||
@@ -436,6 +441,7 @@ mod tests {
|
||||
let (runtime, factory) = test_factory(io.clone());
|
||||
let options = UdpBindOptions {
|
||||
context: crate::socket::SocketContext::default().with_socket_mark(Some(9)),
|
||||
need_protect: false,
|
||||
local_addr: Some("[::]:11013".parse().unwrap()),
|
||||
bind_device: Some("host-device".to_owned()),
|
||||
reuse_addr: true,
|
||||
@@ -459,6 +465,7 @@ mod tests {
|
||||
panic!("operation is not UDP bind");
|
||||
};
|
||||
assert_eq!(submitted, &options);
|
||||
assert!(!submitted.need_protect);
|
||||
}
|
||||
|
||||
io.complete_udp(
|
||||
|
||||
@@ -19,6 +19,9 @@ pub struct HostTcpBindResult {
|
||||
/// Bind cancellation must close a completed but unobserved listener. Accepted
|
||||
/// connections stay in a host-owned listener queue until `take_tcp_accept` is
|
||||
/// called from a guest poll; canceling an accept waiter must not remove one.
|
||||
/// When bind options request socket protection, the host must complete and
|
||||
/// acknowledge protection before bind/listen and before exposing each accepted
|
||||
/// child. A protection error fails the corresponding bind or accept operation.
|
||||
pub trait HostTcpListenerIo: HostSocketIo {
|
||||
fn submit_tcp_bind(
|
||||
&self,
|
||||
|
||||
@@ -10,6 +10,12 @@ pub mod ring;
|
||||
pub mod tcp;
|
||||
pub mod udp;
|
||||
|
||||
/// Keep Rust constructors and deserialization consistent: host sockets normally
|
||||
/// need VPN bypass; local/TUN-facing endpoint constructors explicitly opt out.
|
||||
pub(crate) const fn default_need_protect() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
@@ -55,6 +55,12 @@ pub enum TcpSocketPurpose {
|
||||
pub struct TcpBindOptions {
|
||||
#[serde(default)]
|
||||
pub context: SocketContext,
|
||||
/// Request host VPN bypass during creation, before bind/connect/listen.
|
||||
/// Hosts with a protection service must await its acknowledgement; local
|
||||
/// listeners opt out explicitly. Defaults to true, including when omitted
|
||||
/// from serialized options. Accepted children inherit this requirement.
|
||||
#[serde(default = "super::default_need_protect")]
|
||||
pub need_protect: bool,
|
||||
pub local_addr: Option<SocketAddr>,
|
||||
pub bind_device: Option<String>,
|
||||
/// `None` delegates the platform default to the host socket adapter.
|
||||
@@ -67,6 +73,7 @@ impl TcpBindOptions {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
context: SocketContext::default(),
|
||||
need_protect: super::default_need_protect(),
|
||||
local_addr: None,
|
||||
bind_device: None,
|
||||
reuse_addr: None,
|
||||
@@ -90,6 +97,11 @@ impl TcpBindOptions {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_need_protect(mut self, need_protect: bool) -> Self {
|
||||
self.need_protect = need_protect;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ip_version(mut self, ip_version: IpVersion) -> Self {
|
||||
self.context.ip_version = ip_version;
|
||||
self
|
||||
@@ -250,28 +262,36 @@ impl TcpListenOptions {
|
||||
|
||||
pub fn proxy_nat(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
bind: TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ProxyNat,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn socks5(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
bind: TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::Socks5,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port_forward(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
bind: TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::PortForward,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn port_lease(local_addr: SocketAddr) -> Self {
|
||||
Self {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
bind: TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::PortLease,
|
||||
}
|
||||
}
|
||||
@@ -612,7 +632,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
TcpListenOptions::proxy_nat(local_addr),
|
||||
TcpListenOptions {
|
||||
bind: TcpBindOptions::default().with_local_addr(Some(local_addr)),
|
||||
bind: TcpBindOptions::default()
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr)),
|
||||
purpose: TcpListenPurpose::ProxyNat,
|
||||
}
|
||||
);
|
||||
@@ -633,6 +655,7 @@ mod tests {
|
||||
options,
|
||||
TcpBindOptions {
|
||||
context: SocketContext::default().with_socket_mark(Some(7)),
|
||||
need_protect: true,
|
||||
local_addr: Some(local_addr),
|
||||
bind_device: Some("eth0".to_owned()),
|
||||
reuse_addr: Some(true),
|
||||
@@ -645,6 +668,37 @@ mod tests {
|
||||
#[test]
|
||||
fn tcp_bind_default_delegates_reuse_addr_policy_to_host() {
|
||||
assert_eq!(TcpBindOptions::default().reuse_addr, None);
|
||||
assert!(TcpBindOptions::default().need_protect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_constructor_protection_defaults_match_endpoint_role() {
|
||||
let remote = SocketAddr::from(([192, 0, 2, 1], 11010));
|
||||
let local = SocketAddr::from(([0, 0, 0, 0], 11010));
|
||||
|
||||
assert!(TcpConnectOptions::direct_connect(remote).bind.need_protect);
|
||||
assert!(TcpConnectOptions::proxy_nat(remote).bind.need_protect);
|
||||
assert!(TcpListenOptions::direct_connect(local).bind.need_protect);
|
||||
assert!(TcpListenOptions::hole_punch(local).bind.need_protect);
|
||||
assert!(TcpListenOptions::manual_connect(local).bind.need_protect);
|
||||
assert!(!TcpListenOptions::proxy_nat(local).bind.need_protect);
|
||||
assert!(!TcpListenOptions::socks5(local).bind.need_protect);
|
||||
assert!(!TcpListenOptions::port_forward(local).bind.need_protect);
|
||||
assert!(!TcpListenOptions::port_lease(local).bind.need_protect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcp_bind_serde_defaults_to_protected_and_preserves_opt_out() {
|
||||
let options: TcpBindOptions = serde_json::from_str(
|
||||
r#"{"local_addr":null,"bind_device":null,"reuse_addr":null,"reuse_port":false,"only_v6":false}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(options.need_protect);
|
||||
let local = TcpListenOptions::port_forward("0.0.0.0:15555".parse().unwrap());
|
||||
let restored: TcpListenOptions =
|
||||
serde_json::from_str(&serde_json::to_string(&local).unwrap()).unwrap();
|
||||
assert_eq!(restored, local);
|
||||
assert!(!restored.bind.need_protect);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -39,6 +39,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::HolePunchControl,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -51,6 +52,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::HolePunchCandidate,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -63,6 +65,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::DirectConnect,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -75,6 +78,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::PortBoundListener,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -87,6 +91,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::Socks5,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -99,7 +104,7 @@ fn bind_options_constructors_describe_socket_purpose() {
|
||||
);
|
||||
assert_eq!(
|
||||
UdpBindOptions::default(),
|
||||
UdpBindOptions::hole_punch_control()
|
||||
UdpBindOptions::hole_punch_control().with_need_protect(true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +128,7 @@ fn session_connect_request_keeps_peer_scoped_udp_shape() {
|
||||
reuse_port: false,
|
||||
only_v6: false,
|
||||
purpose: UdpSocketPurpose::PortBoundListener,
|
||||
need_protect: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2045,6 +2051,7 @@ async fn v4_hole_punch_control_sender_uses_factory_socket() {
|
||||
factory.bind_options(),
|
||||
vec![
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_need_protect(false)
|
||||
.with_context(context.with_ip_version(IpVersion::V4))
|
||||
.with_local_addr(Some(SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
@@ -2089,6 +2096,7 @@ async fn v6_hole_punch_control_sender_uses_factory_socket() {
|
||||
factory.bind_options(),
|
||||
vec![
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_need_protect(false)
|
||||
.with_context(context.with_ip_version(IpVersion::V6))
|
||||
.with_local_addr(Some(SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::LOCALHOST,
|
||||
|
||||
@@ -141,6 +141,7 @@ where
|
||||
let socket = factory
|
||||
.bind_udp(
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_need_protect(false)
|
||||
.with_context(context.with_ip_version(IpVersion::V4))
|
||||
.with_local_addr(Some(SocketAddr::V4(SocketAddrV4::new(
|
||||
Ipv4Addr::LOCALHOST,
|
||||
@@ -167,6 +168,7 @@ where
|
||||
let socket = factory
|
||||
.bind_udp(
|
||||
UdpBindOptions::hole_punch_control()
|
||||
.with_need_protect(false)
|
||||
.with_context(context.with_ip_version(IpVersion::V6))
|
||||
.with_local_addr(Some(SocketAddr::V6(SocketAddrV6::new(
|
||||
Ipv6Addr::LOCALHOST,
|
||||
@@ -199,6 +201,11 @@ pub enum UdpSocketPurpose {
|
||||
pub struct UdpBindOptions {
|
||||
#[serde(default)]
|
||||
pub context: SocketContext,
|
||||
/// Request host VPN bypass before bind or datagram I/O. Protection must be
|
||||
/// acknowledged inside creation, not emitted as a fire-and-forget event.
|
||||
/// Defaults to true; local/TUN-facing endpoints explicitly opt out.
|
||||
#[serde(default = "crate::socket::default_need_protect")]
|
||||
pub need_protect: bool,
|
||||
pub local_addr: Option<SocketAddr>,
|
||||
pub bind_device: Option<String>,
|
||||
pub reuse_addr: bool,
|
||||
@@ -211,6 +218,7 @@ impl UdpBindOptions {
|
||||
fn for_purpose(purpose: UdpSocketPurpose) -> Self {
|
||||
Self {
|
||||
context: SocketContext::default(),
|
||||
need_protect: crate::socket::default_need_protect(),
|
||||
local_addr: None,
|
||||
bind_device: None,
|
||||
reuse_addr: false,
|
||||
@@ -252,11 +260,15 @@ impl UdpBindOptions {
|
||||
}
|
||||
|
||||
pub fn port_forward(local_addr: SocketAddr) -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::PortForward).with_local_addr(Some(local_addr))
|
||||
Self::for_purpose(UdpSocketPurpose::PortForward)
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr))
|
||||
}
|
||||
|
||||
pub fn port_lease(local_addr: SocketAddr) -> Self {
|
||||
Self::for_purpose(UdpSocketPurpose::PortLease).with_local_addr(Some(local_addr))
|
||||
Self::for_purpose(UdpSocketPurpose::PortLease)
|
||||
.with_need_protect(false)
|
||||
.with_local_addr(Some(local_addr))
|
||||
}
|
||||
|
||||
pub fn with_local_addr(mut self, local_addr: Option<SocketAddr>) -> Self {
|
||||
@@ -274,6 +286,11 @@ impl UdpBindOptions {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_need_protect(mut self, need_protect: bool) -> Self {
|
||||
self.need_protect = need_protect;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ip_version(mut self, ip_version: IpVersion) -> Self {
|
||||
self.context.ip_version = ip_version;
|
||||
self
|
||||
@@ -302,7 +319,44 @@ impl UdpBindOptions {
|
||||
|
||||
impl Default for UdpBindOptions {
|
||||
fn default() -> Self {
|
||||
Self::hole_punch_control()
|
||||
// Preserve the existing default purpose/socket setup; local-only
|
||||
// control packets opt out explicitly at their loopback call sites.
|
||||
Self::for_purpose(UdpSocketPurpose::HolePunchControl)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod option_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn udp_constructor_protection_defaults_match_endpoint_role() {
|
||||
let local = SocketAddr::from(([0, 0, 0, 0], 11010));
|
||||
|
||||
assert!(UdpBindOptions::default().need_protect);
|
||||
assert!(UdpBindOptions::hole_punch_control().need_protect);
|
||||
assert!(UdpBindOptions::hole_punch_candidate().need_protect);
|
||||
assert!(UdpBindOptions::direct_connect().need_protect);
|
||||
assert!(UdpBindOptions::port_bound_listener(local).need_protect);
|
||||
assert!(UdpBindOptions::proxy_nat().need_protect);
|
||||
assert!(UdpBindOptions::stun_probe().need_protect);
|
||||
assert!(UdpBindOptions::socks5().need_protect);
|
||||
assert!(!UdpBindOptions::port_forward(local).need_protect);
|
||||
assert!(!UdpBindOptions::port_lease(local).need_protect);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn udp_bind_serde_defaults_to_protected_and_preserves_opt_out() {
|
||||
let options: UdpBindOptions = serde_json::from_str(
|
||||
r#"{"local_addr":null,"bind_device":null,"reuse_addr":false,"reuse_port":false,"only_v6":false,"purpose":"DirectConnect"}"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert!(options.need_protect);
|
||||
let local = UdpBindOptions::port_forward("0.0.0.0:15555".parse().unwrap());
|
||||
let restored: UdpBindOptions =
|
||||
serde_json::from_str(&serde_json::to_string(&local).unwrap()).unwrap();
|
||||
assert_eq!(restored, local);
|
||||
assert!(!restored.need_protect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,30 +114,36 @@ unsafe extern "C" {
|
||||
pub(crate) fn take_udp_send_ready(operation: u64) -> i32;
|
||||
|
||||
/// Starts a TCP connection using an encoded `TcpConnectOptions` document.
|
||||
/// Requested socket protection must complete before the host connects.
|
||||
pub(crate) fn start_tcp_connect(operation: u64, options: u32, options_len: u32) -> i32;
|
||||
|
||||
/// Copies the completed TCP connection handle and addresses into `result`.
|
||||
pub(crate) fn take_tcp_connect(operation: u64, result: u32, result_len: u32) -> i32;
|
||||
|
||||
/// Starts a UDP bind using an encoded `UdpBindOptions` document.
|
||||
/// Starts a UDP bind using an encoded `UdpBindOptions` document. Requested
|
||||
/// socket protection must complete before bind or datagram I/O.
|
||||
pub(crate) fn start_udp_bind(operation: u64, options: u32, options_len: u32) -> i32;
|
||||
|
||||
/// Copies the completed UDP socket handle and local address into `result`.
|
||||
pub(crate) fn take_udp_bind(operation: u64, result: u32, result_len: u32) -> i32;
|
||||
|
||||
/// Starts a TCP listener bind using an encoded `TcpListenOptions` document.
|
||||
/// Requested socket protection must complete before bind/listen.
|
||||
pub(crate) fn start_tcp_bind(operation: u64, options: u32, options_len: u32) -> i32;
|
||||
|
||||
/// Copies the completed listener handle and local address into `result`.
|
||||
pub(crate) fn take_tcp_bind(operation: u64, result: u32, result_len: u32) -> i32;
|
||||
|
||||
/// Starts accepting one TCP stream from a listener handle.
|
||||
/// Starts accepting one TCP stream from a listener handle. A protected
|
||||
/// listener's accepted child must be protected before it is exposed.
|
||||
pub(crate) fn start_tcp_accept(handle: u64, operation: u64) -> i32;
|
||||
|
||||
/// Copies the accepted TCP stream handle and addresses into `result`.
|
||||
pub(crate) fn take_tcp_accept(operation: u64, result: u32, result_len: u32) -> i32;
|
||||
|
||||
/// Starts an address-record DNS lookup for an encoded [`crate::host::dns::DnsQuery`].
|
||||
/// When VPN bypass is active, the host must protect every underlying DNS
|
||||
/// socket before sending a query or opening a DNS TCP connection.
|
||||
pub(crate) fn start_dns_resolve(operation: u64, query: u32, query_len: u32) -> i32;
|
||||
|
||||
/// Probes or copies the encoded DNS address result for `operation`.
|
||||
@@ -159,6 +165,8 @@ unsafe extern "C" {
|
||||
pub(crate) fn take_dns_srv(operation: u64, result: u32, result_capacity: u32) -> i32;
|
||||
|
||||
/// Starts finding the local address and source context needed to reach `remote_addr`.
|
||||
/// When VPN bypass is active, the host must protect the underlying route-
|
||||
/// probe socket before connecting or sending through it.
|
||||
pub(crate) fn start_local_addr_for_remote(
|
||||
operation: u64,
|
||||
remote_addr: u32,
|
||||
|
||||
@@ -13,13 +13,13 @@ use crate::socket::{
|
||||
|
||||
use super::socket::{SOCKET_ADDRESS_LEN, decode_socket_address, encode_socket_address};
|
||||
|
||||
const OPTIONS_VERSION: u8 = 2;
|
||||
const OPTIONS_VERSION: u8 = 3;
|
||||
pub(crate) const TCP_SOCKET_RESULT_LEN: usize = 8 + SOCKET_ADDRESS_LEN * 2;
|
||||
pub(crate) const BOUND_SOCKET_RESULT_LEN: usize = 8 + SOCKET_ADDRESS_LEN;
|
||||
|
||||
pub(crate) fn encode_tcp_connect_options(options: &TcpConnectOptions) -> io::Result<Vec<u8>> {
|
||||
let mut encoded = Vec::with_capacity(
|
||||
75 + context_variable_len(&options.bind.context)
|
||||
76 + context_variable_len(&options.bind.context)
|
||||
+ bind_device_len(&options.bind.bind_device),
|
||||
);
|
||||
encoded.push(OPTIONS_VERSION);
|
||||
@@ -44,13 +44,14 @@ pub(crate) fn encode_tcp_connect_options(options: &TcpConnectOptions) -> io::Res
|
||||
TcpSocketPurpose::PortForward => 7,
|
||||
TcpSocketPurpose::DataPlane => 8,
|
||||
});
|
||||
encoded.push(u8::from(options.bind.need_protect));
|
||||
encode_bind_device(&mut encoded, &options.bind.bind_device)?;
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
pub(crate) fn encode_udp_bind_options(options: &UdpBindOptions) -> io::Result<Vec<u8>> {
|
||||
let mut encoded = Vec::with_capacity(
|
||||
48 + context_variable_len(&options.context) + bind_device_len(&options.bind_device),
|
||||
49 + context_variable_len(&options.context) + bind_device_len(&options.bind_device),
|
||||
);
|
||||
encoded.push(OPTIONS_VERSION);
|
||||
encode_optional_address(&mut encoded, options.local_addr);
|
||||
@@ -69,13 +70,14 @@ pub(crate) fn encode_udp_bind_options(options: &UdpBindOptions) -> io::Result<Ve
|
||||
UdpSocketPurpose::PortForward => 7,
|
||||
UdpSocketPurpose::PortLease => 8,
|
||||
});
|
||||
encoded.push(u8::from(options.need_protect));
|
||||
encode_bind_device(&mut encoded, &options.bind_device)?;
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
pub(crate) fn encode_tcp_listen_options(options: &TcpListenOptions) -> io::Result<Vec<u8>> {
|
||||
let mut encoded = Vec::with_capacity(
|
||||
48 + context_variable_len(&options.bind.context)
|
||||
49 + context_variable_len(&options.bind.context)
|
||||
+ bind_device_len(&options.bind.bind_device),
|
||||
);
|
||||
encoded.push(OPTIONS_VERSION);
|
||||
@@ -97,6 +99,7 @@ pub(crate) fn encode_tcp_listen_options(options: &TcpListenOptions) -> io::Resul
|
||||
TcpListenPurpose::PortForward => 5,
|
||||
TcpListenPurpose::PortLease => 6,
|
||||
});
|
||||
encoded.push(u8::from(options.bind.need_protect));
|
||||
encode_bind_device(&mut encoded, &options.bind.bind_device)?;
|
||||
Ok(encoded)
|
||||
}
|
||||
@@ -221,13 +224,13 @@ mod tests {
|
||||
purpose: TcpSocketPurpose::ManualConnect,
|
||||
};
|
||||
let encoded = encode_tcp_connect_options(&options).unwrap();
|
||||
assert_eq!(encoded.len(), 82);
|
||||
assert_eq!(encoded.len(), 83);
|
||||
assert_eq!(encoded[0], OPTIONS_VERSION);
|
||||
assert_eq!(&encoded[55..66], &[2, 1, 1, 2, 3, 4, 0, 0, 0, 0, 0]);
|
||||
assert_eq!(&encoded[66..70], &[2, 1, 1, 3]);
|
||||
assert_eq!(encoded[70], 1);
|
||||
assert_eq!(&encoded[71..75], &7_u32.to_be_bytes());
|
||||
assert_eq!(&encoded[75..], b"device0");
|
||||
assert_eq!(&encoded[66..71], &[2, 1, 1, 3, 1]);
|
||||
assert_eq!(encoded[71], 1);
|
||||
assert_eq!(&encoded[72..76], &7_u32.to_be_bytes());
|
||||
assert_eq!(&encoded[76..], b"device0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -239,16 +242,16 @@ mod tests {
|
||||
.with_bind(TcpBindOptions::default().with_bind_device(Some(String::new()))),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&none[70..75], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&empty[70..75], &[1, 0, 0, 0, 0]);
|
||||
assert_eq!(&none[71..76], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&empty[71..76], &[1, 0, 0, 0, 0]);
|
||||
|
||||
let udp_none = encode_udp_bind_options(&UdpBindOptions::direct_connect()).unwrap();
|
||||
let udp_empty = encode_udp_bind_options(
|
||||
&UdpBindOptions::direct_connect().with_bind_device(Some(String::new())),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&udp_none[43..48], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&udp_empty[43..48], &[1, 0, 0, 0, 0]);
|
||||
assert_eq!(&udp_none[44..49], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&udp_empty[44..49], &[1, 0, 0, 0, 0]);
|
||||
|
||||
let listen_none = encode_tcp_listen_options(&TcpListenOptions::direct_connect(
|
||||
"192.0.2.1:11013".parse().unwrap(),
|
||||
@@ -259,8 +262,8 @@ mod tests {
|
||||
.with_bind(TcpBindOptions::default().with_bind_device(Some(String::new()))),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(&listen_none[43..48], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&listen_empty[43..48], &[1, 0, 0, 0, 0]);
|
||||
assert_eq!(&listen_none[44..49], &[0, 0, 0, 0, 0]);
|
||||
assert_eq!(&listen_empty[44..49], &[1, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -329,6 +332,34 @@ mod tests {
|
||||
assert_eq!(udp[42], 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encodes_socket_protection_request_in_existing_options() {
|
||||
let remote = "192.0.2.2:11013".parse().unwrap();
|
||||
let local = "0.0.0.0:11013".parse().unwrap();
|
||||
|
||||
let tcp_true =
|
||||
encode_tcp_connect_options(&TcpConnectOptions::direct_connect(remote)).unwrap();
|
||||
let tcp_false = encode_tcp_connect_options(
|
||||
&TcpConnectOptions::direct_connect(remote)
|
||||
.with_bind(TcpBindOptions::default().with_need_protect(false)),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(tcp_true[70], 1);
|
||||
assert_eq!(tcp_false[70], 0);
|
||||
|
||||
let listen_true =
|
||||
encode_tcp_listen_options(&TcpListenOptions::direct_connect(local)).unwrap();
|
||||
let listen_false = encode_tcp_listen_options(&TcpListenOptions::proxy_nat(local)).unwrap();
|
||||
assert_eq!(listen_true[43], 1);
|
||||
assert_eq!(listen_false[43], 0);
|
||||
|
||||
let udp_true = encode_udp_bind_options(&UdpBindOptions::direct_connect()).unwrap();
|
||||
let udp_false =
|
||||
encode_udp_bind_options(&UdpBindOptions::socks5().with_need_protect(false)).unwrap();
|
||||
assert_eq!(udp_true[43], 1);
|
||||
assert_eq!(udp_false[43], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encodes_gateway_purposes_with_stable_values() {
|
||||
let remote = "192.0.2.2:443".parse().unwrap();
|
||||
|
||||
Reference in new issue
Block a user