浏览代码

Implement prefer IPv6 option for the client (#376)

* implement prefer_ipv6 option

* implement prefer_ipv6 option

* remove unused prefer_ipv6

* add prefer_ipv6 to sample config

* fix merge

* remove unused prefer_ipv6

* move prefer_piv6 to correct position in full.toml

* set prefer_ipv6 default value

* run clippy
Dennis Streicher 6 月之前
父节点
当前提交
c4a7893b09
共有 4 个文件被更改,包括 43 次插入10 次删除
  1. 3 3
      src/client.rs
  2. 3 0
      src/config.rs
  3. 36 7
      src/helper.rs
  4. 1 0
      tests/config_test/valid_config/full.toml

+ 3 - 3
src/client.rs

@@ -227,7 +227,7 @@ async fn run_data_channel<T: Transport>(args: Arc<RunDataChannelArgs<T>>) -> Res
             if args.service.service_type != ServiceType::Udp {
                 bail!("Expect UDP traffic. Please check the configuration.")
             }
-            run_data_channel_for_udp::<T>(conn, &args.service.local_addr).await?;
+            run_data_channel_for_udp::<T>(conn, &args.service.local_addr, args.service.prefer_ipv6).await?;
         }
     }
     Ok(())
@@ -255,7 +255,7 @@ async fn run_data_channel_for_tcp<T: Transport>(
 type UdpPortMap = Arc<RwLock<HashMap<SocketAddr, mpsc::Sender<Bytes>>>>;
 
 #[instrument(skip(conn))]
-async fn run_data_channel_for_udp<T: Transport>(conn: T::Stream, local_addr: &str) -> Result<()> {
+async fn run_data_channel_for_udp<T: Transport>(conn: T::Stream, local_addr: &str, prefer_ipv6: bool) -> Result<()> {
     debug!("New data channel starts forwarding");
 
     let port_map: UdpPortMap = Arc::new(RwLock::new(HashMap::new()));
@@ -305,7 +305,7 @@ async fn run_data_channel_for_udp<T: Transport>(conn: T::Stream, local_addr: &st
             // grabbing the writer lock
             let mut m = port_map.write().await;
 
-            match udp_connect(local_addr).await {
+            match udp_connect(local_addr, prefer_ipv6).await {
                 Ok(s) => {
                     let (inbound_tx, inbound_rx) = mpsc::channel(UDP_SENDQ_SIZE);
                     m.insert(packet.from, inbound_tx);

+ 3 - 0
src/config.rs

@@ -63,6 +63,8 @@ pub struct ClientServiceConfig {
     #[serde(skip)]
     pub name: String,
     pub local_addr: String,
+    #[serde(default)] // Default to false
+    pub prefer_ipv6: bool,
     pub token: Option<MaskedString>,
     pub nodelay: Option<bool>,
     pub retry_interval: Option<u64>,
@@ -201,6 +203,7 @@ fn default_client_retry_interval() -> u64 {
 pub struct ClientConfig {
     pub remote_addr: String,
     pub default_token: Option<MaskedString>,
+    pub prefer_ipv6: Option<bool>,
     pub services: HashMap<String, ClientServiceConfig>,
     #[serde(default)]
     pub transport: TransportConfig,

+ 36 - 7
src/helper.rs

@@ -64,16 +64,45 @@ pub fn host_port_pair(s: &str) -> Result<(&str, u16)> {
 }
 
 /// Create a UDP socket and connect to `addr`
-pub async fn udp_connect<A: ToSocketAddrs>(addr: A) -> Result<UdpSocket> {
-    let addr = to_socket_addr(addr).await?;
+pub async fn udp_connect<A: ToSocketAddrs>(addr: A, prefer_ipv6: bool) -> Result<UdpSocket> {
 
-    let bind_addr = match addr {
-        SocketAddr::V4(_) => "0.0.0.0:0",
-        SocketAddr::V6(_) => ":::0",
-    };
+    let (socket_addr, bind_addr);
+
+    match prefer_ipv6 {
+        false => {
+            socket_addr = to_socket_addr(addr).await?;
 
+            bind_addr = match socket_addr {
+                SocketAddr::V4(_) => "0.0.0.0:0",
+                SocketAddr::V6(_) => ":::0",
+            };
+        },
+        true => {
+            let all_host_addresses: Vec<SocketAddr> = lookup_host(addr).await?.collect();
+
+            // Try to find an IPv6 address
+            match all_host_addresses.clone().iter().find(|x| x.is_ipv6()) {
+                Some(socket_addr_ipv6) => {
+                    socket_addr = *socket_addr_ipv6;
+                    bind_addr = ":::0";
+                },
+                None => {
+                    let socket_addr_ipv4 = all_host_addresses.iter().find(|x| x.is_ipv4());
+                    match socket_addr_ipv4 {
+                        None => return Err(anyhow!("Failed to lookup the host")),
+                        // fallback to IPv4
+                        Some(socket_addr_ipv4) => {
+                            socket_addr = *socket_addr_ipv4;
+                            bind_addr = "0.0.0.0:0";
+                        }
+                    }
+                }
+            }
+        }
+    };
     let s = UdpSocket::bind(bind_addr).await?;
-    s.connect(addr).await?;
+    s.connect(socket_addr).await?;
+    s.connect(socket_addr).await?;
     Ok(s)
 }
 

+ 1 - 0
tests/config_test/valid_config/full.toml

@@ -17,6 +17,7 @@ remote_public_key = "key_encoded_in_base64" # Optional
 [client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration
 type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp"
 token = "whatever" # Necessary if `client.default_token` not set
+prefer_ipv6 = false # Optional. If the client prefers to use IPv6 when connecting to the server (e.g.: When the client is behind an ISP's NAT). Default: false
 local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded
 
 [client.services.service2] # Multiple services can be defined