-
Notifications
You must be signed in to change notification settings - Fork 125
feat: adding dual stack #863
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
44acd54
0fadb5f
1f40f81
6c262b0
fe3626c
f84835e
1bfc868
087c22d
f016d37
d44a4da
236f2d0
c12640e
e237026
b8260bb
085b6eb
c6b84ec
1db8635
4d847c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,16 @@ | ||
| use super::{inbound::InboundHandlerTrait, tun::TunDatagram}; | ||
| use crate::{ | ||
| app::dispatcher::Dispatcher, | ||
| proxy::{datagram::UdpPacket, utils::apply_tcp_options}, | ||
| proxy::{ | ||
| datagram::UdpPacket, | ||
| utils::{ToCanonical, apply_tcp_options, try_create_dualstack_socket}, | ||
| }, | ||
| session::{Network, Session, Type}, | ||
| }; | ||
|
|
||
| use async_trait::async_trait; | ||
| use std::{ | ||
| io, | ||
| net::SocketAddr, | ||
| os::fd::{AsFd, AsRawFd}, | ||
| sync::Arc, | ||
|
|
@@ -54,12 +58,13 @@ impl InboundHandlerTrait for TproxyInbound { | |
| } | ||
|
|
||
| async fn listen_tcp(&self) -> std::io::Result<()> { | ||
| let socket = socket2::Socket::new( | ||
| socket2::Domain::IPV4, | ||
| socket2::Type::STREAM, | ||
| None, | ||
| )?; | ||
| socket.set_ip_transparent_v4(true)?; | ||
| let (socket, dualstack) = | ||
| try_create_dualstack_socket(self.addr, socket2::Type::STREAM)?; | ||
| set_ip_transparent(&socket, self.addr.is_ipv6())?; | ||
| if dualstack { | ||
| // set ipv4 transparent | ||
| set_ip_transparent(&socket, false)?; | ||
| } | ||
| socket.set_nonblocking(true)?; | ||
| socket.bind(&self.addr.into())?; | ||
| socket.listen(1024)?; | ||
|
|
@@ -68,16 +73,17 @@ impl InboundHandlerTrait for TproxyInbound { | |
|
|
||
| loop { | ||
| let (socket, _) = listener.accept().await?; | ||
| let src_addr = socket.peer_addr()?; | ||
| if !self.allow_lan && src_addr.ip() != socket.local_addr()?.ip() { | ||
| let src_addr = socket.peer_addr()?.to_canonical(); | ||
| // for dualstack socket src_addr may be ipv4 or ipv6 | ||
| if !self.allow_lan && !src_addr.ip().is_loopback() { | ||
|
||
| warn!("Connection from {} is not allowed", src_addr); | ||
| continue; | ||
| } | ||
|
|
||
| apply_tcp_options(&socket)?; | ||
|
|
||
| // local_addr is getsockname | ||
| let orig_dst = socket.local_addr()?; | ||
| let orig_dst = socket.local_addr()?.to_canonical(); | ||
|
|
||
| let sess = Session { | ||
| network: Network::Tcp, | ||
|
|
@@ -98,9 +104,12 @@ impl InboundHandlerTrait for TproxyInbound { | |
| } | ||
|
|
||
| async fn listen_udp(&self) -> std::io::Result<()> { | ||
| let socket = | ||
| socket2::Socket::new(socket2::Domain::IPV4, socket2::Type::DGRAM, None)?; | ||
| let (socket, dual_stack) = | ||
| try_create_dualstack_socket(self.addr, socket2::Type::DGRAM)?; | ||
| socket.set_ip_transparent_v4(true)?; | ||
| if dual_stack { | ||
| set_ip_transparent(&socket, true)?; | ||
| } | ||
| socket.set_nonblocking(true)?; | ||
| socket.set_broadcast(true)?; | ||
|
|
||
|
|
@@ -223,3 +232,33 @@ async fn handle_inbound_datagram( | |
| let _ = futures::future::join(fut1, fut2).await; | ||
| Ok(()) | ||
| } | ||
|
|
||
| // socket2 doesn't provide set_ip_transparent_v6 | ||
| // So we must implement it ourselves | ||
| fn set_ip_transparent(socket: &socket2::Socket, ipv6: bool) -> io::Result<()> { | ||
| let fd = socket.as_raw_fd(); | ||
|
|
||
| let (opt, level) = if ipv6 { | ||
| (libc::IPV6_TRANSPARENT, libc::IPPROTO_IPV6) | ||
| } else { | ||
| (libc::IP_TRANSPARENT, libc::IPPROTO_IP) | ||
| }; | ||
|
|
||
| let enable: libc::c_int = 1; | ||
|
|
||
| unsafe { | ||
| let ret = libc::setsockopt( | ||
| fd, | ||
| level, | ||
| opt, | ||
| &enable as *const _ as *const _, | ||
| std::mem::size_of_val(&enable) as libc::socklen_t, | ||
| ); | ||
|
|
||
| if ret != 0 { | ||
| return Err(io::Error::last_os_error()); | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -198,3 +198,42 @@ | |
| .ok()? | ||
| } | ||
| } | ||
|
|
||
| /// Convert ipv6 mapped ipv4 address back to ipv4. Other address remain | ||
| /// unchanged. e.g. ::ffff:127.0.0.1 -> 127.0.0.1 | ||
| pub trait ToCanonical { | ||
|
Check warning on line 204 in clash-lib/src/proxy/utils/socket_helpers.rs
|
||
| fn to_canonical(self) -> SocketAddr; | ||
| } | ||
|
|
||
| impl ToCanonical for SocketAddr { | ||
| fn to_canonical(mut self) -> SocketAddr { | ||
| self.set_ip(self.ip().to_canonical()); | ||
| self | ||
| } | ||
| } | ||
|
|
||
| /// Create dualstack socket if it can | ||
| /// If failed, fallback to single stack silently | ||
| pub fn try_create_dualstack_socket( | ||
|
Check warning on line 217 in clash-lib/src/proxy/utils/socket_helpers.rs
|
||
| addr: SocketAddr, | ||
| tcp_or_udp: socket2::Type, | ||
| ) -> std::io::Result<(socket2::Socket, bool)> { | ||
| let domain = if addr.is_ipv4() { | ||
| socket2::Domain::IPV4 | ||
| } else { | ||
| socket2::Domain::IPV6 | ||
| }; | ||
| let mut dualstack = false; | ||
| let socket = socket2::Socket::new(domain, tcp_or_udp, None)?; | ||
| if addr.is_ipv6() && addr.ip().is_unspecified() { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why checking
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dualstack only works when binding to a ipv6 unspecified address
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah i see, thanks! |
||
| if let Err(e) = socket.set_only_v6(false) { | ||
| // If setting dualstack fails, fallback to single stack | ||
| tracing::warn!( | ||
| "dualstack not supported, falling back to ipv6 only: {e}" | ||
| ); | ||
| } else { | ||
| dualstack = true; | ||
| } | ||
| }; | ||
| Ok((socket, dualstack)) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function parameter is confusing.. i think you mean enable for non ipv6 here but passing a false value sounds like disable it...
maybe rename the
set_ip_transparenttoset_ip_transparent_v6and stay with usingsocket.set_ip_transparent_v4here if that works?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok