@@ -309,6 +309,7 @@ pub unsafe extern "C" fn softether_create(
309309 half_connection : config. half_connection != 0 ,
310310 timeout_seconds : config. timeout_seconds . max ( 5 ) as u64 ,
311311 mtu : config. mtu . clamp ( 576 , 1500 ) as u16 ,
312+ ip_version : config. ip_version . into ( ) ,
312313 use_encrypt : config. use_encrypt != 0 ,
313314 use_compress : config. use_compress != 0 ,
314315 udp_accel : config. udp_accel != 0 ,
@@ -819,25 +820,111 @@ async fn connect_and_run_inner(
819820 // established yet. DHCP needs to both send and receive on the same connection.
820821 let original_direction = conn_mgr. enable_primary_bidirectional ( ) ;
821822
822- let dhcp_config = match perform_dhcp ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await {
823- Ok ( config) => {
823+ // Perform DHCP based on ip_version setting
824+ let ( dhcp_config, dhcpv6_config) = match config. ip_version {
825+ crate :: config:: IpVersion :: Auto => {
826+ // Auto: Try IPv4 DHCP (required), then try DHCPv6 (optional)
824827 log_message (
825828 callbacks,
826829 1 ,
827- & format ! (
828- "[RUST] DHCP complete: IP={}, Gateway={:?}, DNS={:?}" ,
829- config. ip, config. gateway, config. dns1
830- ) ,
830+ "[RUST] IP version: Auto (IPv4 required, IPv6 optional)" ,
831831 ) ;
832- config
832+ let dhcp = match perform_dhcp ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await
833+ {
834+ Ok ( cfg) => {
835+ log_message (
836+ callbacks,
837+ 1 ,
838+ & format ! (
839+ "[RUST] DHCP complete: IP={}, Gateway={:?}, DNS={:?}" ,
840+ cfg. ip, cfg. gateway, cfg. dns1
841+ ) ,
842+ ) ;
843+ cfg
844+ }
845+ Err ( e) => {
846+ if let Some ( dir) = original_direction {
847+ conn_mgr. restore_primary_direction ( dir) ;
848+ }
849+ log_message ( callbacks, 3 , & format ! ( "[RUST] DHCP failed: {e}" ) ) ;
850+ return Err ( e) ;
851+ }
852+ } ;
853+
854+ // Try DHCPv6 (optional)
855+ log_message ( callbacks, 1 , "[RUST] Attempting DHCPv6 for IPv6 address..." ) ;
856+ let dhcpv6 = perform_dhcpv6 ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await ;
857+ if dhcpv6. is_some ( ) {
858+ log_message (
859+ callbacks,
860+ 1 ,
861+ "[RUST] DHCPv6 successful - dual-stack configured" ,
862+ ) ;
863+ } else {
864+ log_message ( callbacks, 1 , "[RUST] DHCPv6 not available - IPv4 only" ) ;
865+ }
866+
867+ ( Some ( dhcp) , dhcpv6)
833868 }
834- Err ( e) => {
835- // Restore direction before returning error
836- if let Some ( dir) = original_direction {
837- conn_mgr. restore_primary_direction ( dir) ;
869+ crate :: config:: IpVersion :: IPv4Only => {
870+ // IPv4 only: Only perform DHCP, skip DHCPv6
871+ log_message (
872+ callbacks,
873+ 1 ,
874+ "[RUST] IP version: IPv4 only (skipping DHCPv6)" ,
875+ ) ;
876+ let dhcp = match perform_dhcp ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await
877+ {
878+ Ok ( cfg) => {
879+ log_message (
880+ callbacks,
881+ 1 ,
882+ & format ! (
883+ "[RUST] DHCP complete: IP={}, Gateway={:?}, DNS={:?}" ,
884+ cfg. ip, cfg. gateway, cfg. dns1
885+ ) ,
886+ ) ;
887+ cfg
888+ }
889+ Err ( e) => {
890+ if let Some ( dir) = original_direction {
891+ conn_mgr. restore_primary_direction ( dir) ;
892+ }
893+ log_message ( callbacks, 3 , & format ! ( "[RUST] DHCP failed: {e}" ) ) ;
894+ return Err ( e) ;
895+ }
896+ } ;
897+ ( Some ( dhcp) , None )
898+ }
899+ crate :: config:: IpVersion :: IPv6Only => {
900+ // IPv6 only: Only perform DHCPv6, skip DHCP
901+ log_message (
902+ callbacks,
903+ 1 ,
904+ "[RUST] IP version: IPv6 only (skipping IPv4 DHCP)" ,
905+ ) ;
906+ let dhcpv6 = perform_dhcpv6 ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await ;
907+ match dhcpv6 {
908+ Some ( cfg) => {
909+ log_message (
910+ callbacks,
911+ 1 ,
912+ & format ! ( "[RUST] DHCPv6 complete: IPv6 address obtained" ) ,
913+ ) ;
914+ ( None , Some ( cfg) )
915+ }
916+ None => {
917+ if let Some ( dir) = original_direction {
918+ conn_mgr. restore_primary_direction ( dir) ;
919+ }
920+ log_message (
921+ callbacks,
922+ 3 ,
923+ "[RUST] DHCPv6 failed - no IPv6 address available" ,
924+ ) ;
925+ return Err ( crate :: error:: Error :: DhcpFailed ( "DHCPv6 failed" . into ( ) ) ) ;
926+ }
838927 }
839- log_message ( callbacks, 3 , & format ! ( "[RUST] DHCP failed: {e}" ) ) ;
840- return Err ( e) ;
841928 }
842929 } ;
843930
@@ -883,22 +970,9 @@ async fn connect_and_run_inner(
883970 ) ;
884971 }
885972
886- // Try DHCPv6 for IPv6 address (optional - doesn't fail if server doesn't support it)
887- log_message ( callbacks, 1 , "[RUST] Attempting DHCPv6 for IPv6 address..." ) ;
888- let dhcpv6_config = perform_dhcpv6 ( & mut conn_mgr, mac, callbacks, config. use_compress ) . await ;
889- if dhcpv6_config. is_some ( ) {
890- log_message (
891- callbacks,
892- 1 ,
893- "[RUST] DHCPv6 successful - dual-stack configured" ,
894- ) ;
895- } else {
896- log_message ( callbacks, 1 , "[RUST] DHCPv6 not available - IPv4 only" ) ;
897- }
898-
899973 // Create session info from DHCP config (include MAC for Kotlin to use)
900974 let session = create_session_from_dhcp (
901- & dhcp_config,
975+ dhcp_config. as_ref ( ) ,
902976 dhcpv6_config. as_ref ( ) ,
903977 actual_server_ip,
904978 server_ip,
@@ -912,12 +986,19 @@ async fn connect_and_run_inner(
912986 }
913987 update_state ( atomic_state, callbacks, SoftEtherState :: Connected ) ;
914988
989+ // Log connection info
990+ let ip_info = match ( & dhcp_config, & dhcpv6_config) {
991+ ( Some ( v4) , Some ( _v6) ) => format ! ( "IPv4: {}, IPv6: configured" , v4. ip) ,
992+ ( Some ( v4) , None ) => format ! ( "IPv4: {}" , v4. ip) ,
993+ ( None , Some ( v6) ) => format ! ( "IPv6: {}" , v6. ip) ,
994+ ( None , None ) => "No IP configured" . to_string ( ) ,
995+ } ;
915996 log_message (
916997 callbacks,
917998 1 ,
918999 & format ! (
919- "[RUST] Connected! IP: {}, Server: {}" ,
920- dhcp_config . ip , actual_server_ip
1000+ "[RUST] Connected! {}, Server: {}" ,
1001+ ip_info , actual_server_ip
9211002 ) ,
9221003 ) ;
9231004
@@ -982,14 +1063,31 @@ async fn connect_and_run_inner(
9821063 } ;
9831064
9841065 // Run the packet loop
1066+ // Note: packet loop requires DhcpConfig for ARP. IPv6-only mode needs a dummy config.
1067+ let dhcp_for_loop = dhcp_config. unwrap_or_else ( || {
1068+ // For IPv6-only mode, create a dummy DhcpConfig
1069+ // ARP won't work, but IPv6 NDP will handle neighbor discovery
1070+ DhcpConfig {
1071+ ip : std:: net:: Ipv4Addr :: UNSPECIFIED ,
1072+ netmask : std:: net:: Ipv4Addr :: UNSPECIFIED ,
1073+ gateway : None ,
1074+ dns1 : None ,
1075+ dns2 : None ,
1076+ server_id : None ,
1077+ lease_time : 0 ,
1078+ renewal_time : 0 ,
1079+ rebinding_time : 0 ,
1080+ domain_name : String :: new ( ) ,
1081+ }
1082+ } ) ;
9851083 log_message ( callbacks, 1 , "[RUST] Starting packet loop..." ) ;
9861084 run_packet_loop (
9871085 & mut conn_mgr,
9881086 running,
9891087 callbacks. clone ( ) ,
9901088 tx_recv,
9911089 mac,
992- dhcp_config ,
1090+ dhcp_for_loop ,
9931091 final_auth. rc4_key_pair . as_ref ( ) ,
9941092 config. qos ,
9951093 config. use_compress ,
@@ -999,9 +1097,10 @@ async fn connect_and_run_inner(
9991097 . await
10001098}
10011099
1002- /// Create session info from DHCP and optional DHCPv6 config
1100+ /// Create session info from optional DHCP and optional DHCPv6 config.
1101+ /// At least one of dhcp or dhcpv6 must be Some.
10031102fn create_session_from_dhcp (
1004- dhcp : & DhcpConfig ,
1103+ dhcp : Option < & DhcpConfig > ,
10051104 dhcpv6 : Option < & Dhcpv6Config > ,
10061105 server_ip : Ipv4Addr ,
10071106 original_server_ip : Ipv4Addr ,
@@ -1031,6 +1130,19 @@ fn create_session_from_dhcp(
10311130 | ( octets[ 3 ] as u32 )
10321131 }
10331132
1133+ // Extract IPv4 info if available
1134+ let ( ip_address, subnet_mask, gateway, dns1, dns2) = if let Some ( v4) = dhcp {
1135+ (
1136+ ip_to_u32 ( v4. ip ) ,
1137+ ip_to_u32 ( v4. netmask ) ,
1138+ v4. gateway . map ( ip_to_u32) . unwrap_or ( 0 ) ,
1139+ v4. dns1 . map ( ip_to_u32) . unwrap_or ( 0 ) ,
1140+ v4. dns2 . map ( ip_to_u32) . unwrap_or ( 0 ) ,
1141+ )
1142+ } else {
1143+ ( 0 , 0 , 0 , 0 , 0 )
1144+ } ;
1145+
10341146 // Extract IPv6 info if available
10351147 let ( ipv6_address, ipv6_prefix_len, dns1_v6, dns2_v6) = if let Some ( v6) = dhcpv6 {
10361148 (
@@ -1044,11 +1156,11 @@ fn create_session_from_dhcp(
10441156 } ;
10451157
10461158 SoftEtherSession {
1047- ip_address : ip_to_u32 ( dhcp . ip ) ,
1048- subnet_mask : ip_to_u32 ( dhcp . netmask ) ,
1049- gateway : dhcp . gateway . map ( ip_to_u32 ) . unwrap_or ( 0 ) ,
1050- dns1 : dhcp . dns1 . map ( ip_to_u32 ) . unwrap_or ( 0 ) ,
1051- dns2 : dhcp . dns2 . map ( ip_to_u32 ) . unwrap_or ( 0 ) ,
1159+ ip_address,
1160+ subnet_mask,
1161+ gateway,
1162+ dns1,
1163+ dns2,
10521164 connected_server_ip : server_ip_str,
10531165 original_server_ip : original_ip_str,
10541166 server_version : 0 ,
0 commit comments