11use std:: {
2+ ffi:: CString ,
23 io:: { self , ErrorKind } ,
34 mem,
45 net:: { IpAddr , Ipv4Addr , Ipv6Addr , SocketAddr } ,
@@ -20,7 +21,10 @@ use winapi::{
2021 ctypes:: { c_char, c_int} ,
2122 shared:: {
2223 minwindef:: { BOOL , DWORD , FALSE , LPDWORD , LPVOID } ,
23- ws2def:: IPPROTO_TCP ,
24+ netioapi:: if_nametoindex,
25+ ntdef:: PCSTR ,
26+ ws2def:: { IPPROTO_IP , IPPROTO_IPV6 , IPPROTO_TCP } ,
27+ ws2ipdef:: IPV6_UNICAST_IF ,
2428 } ,
2529 um:: {
2630 mswsock:: SIO_UDP_CONNRESET ,
@@ -35,6 +39,10 @@ use crate::net::{sys::set_common_sockopt_for_connect, AddrFamily, ConnectOpts};
3539// https://github.com/retep998/winapi-rs/issues/856
3640const TCP_FASTOPEN : DWORD = 15 ;
3741
42+ // ws2ipdef.h
43+ // https://github.com/retep998/winapi-rs/pull/1007
44+ const IP_UNICAST_IF : DWORD = 31 ;
45+
3846/// A `TcpStream` that supports TFO (TCP Fast Open)
3947#[ pin_project( project = TcpStreamProj ) ]
4048pub enum TcpStream {
@@ -49,6 +57,11 @@ impl TcpStream {
4957 SocketAddr :: V6 ( ..) => TcpSocket :: new_v6 ( ) ?,
5058 } ;
5159
60+ // Binds to a specific network interface (device)
61+ if let Some ( ref iface) = opts. bind_interface {
62+ set_ip_unicast_if ( & socket, addr, iface) ?;
63+ }
64+
5265 set_common_sockopt_for_connect ( addr, & socket, opts) ?;
5366
5467 if !opts. tcp . fastopen {
@@ -166,6 +179,51 @@ pub fn set_tcp_fastopen<S: AsRawSocket>(socket: &S) -> io::Result<()> {
166179 Ok ( ( ) )
167180}
168181
182+ fn set_ip_unicast_if < S : AsRawSocket > ( socket : & S , addr : SocketAddr , iface : & str ) -> io:: Result < ( ) > {
183+ let handle = socket. as_raw_socket ( ) as SOCKET ;
184+
185+ unsafe {
186+ // Windows if_nametoindex requires a C-string for interface name
187+ let ifname = CString :: new ( iface) . expect ( "iface" ) ;
188+
189+ // https://docs.microsoft.com/en-us/previous-versions/windows/hardware/drivers/ff553788(v=vs.85)
190+ let if_index = if_nametoindex ( ifname. as_ptr ( ) as PCSTR ) ;
191+ if if_index == 0 {
192+ // If the if_nametoindex function fails and returns zero, it is not possible to determine an error code.
193+ error ! ( "if_nametoindex {} fails" , iface) ;
194+ return Err ( io:: Error :: new ( ErrorKind :: InvalidInput , "invalid interface name" ) ) ;
195+ }
196+
197+ // https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
198+ let if_index = if_index as DWORD ;
199+
200+ let ret = match addr {
201+ SocketAddr :: V4 ( ..) => setsockopt (
202+ handle,
203+ IPPROTO_IP as c_int ,
204+ IP_UNICAST_IF as c_int ,
205+ & if_index as * const _ as * const c_char ,
206+ mem:: size_of_val ( & if_index) as c_int ,
207+ ) ,
208+ SocketAddr :: V6 ( ..) => setsockopt (
209+ handle,
210+ IPPROTO_IPV6 as c_int ,
211+ IPV6_UNICAST_IF as c_int ,
212+ & if_index as * const _ as * const c_char ,
213+ mem:: size_of_val ( & if_index) as c_int ,
214+ ) ,
215+ } ;
216+
217+ if ret == SOCKET_ERROR {
218+ let err = io:: Error :: from_raw_os_error ( WSAGetLastError ( ) ) ;
219+ error ! ( "set IP_UNICAST_IF / IPV6_UNICAST_IF error: {}" , err) ;
220+ return Err ( err) ;
221+ }
222+ }
223+
224+ Ok ( ( ) )
225+ }
226+
169227fn disable_connection_reset ( socket : & UdpSocket ) -> io:: Result < ( ) > {
170228 let handle = socket. as_raw_socket ( ) as SOCKET ;
171229
@@ -271,6 +329,10 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, opts: &ConnectOpts) -> i
271329 let socket = UdpSocket :: bind ( bind_addr) . await ?;
272330 disable_connection_reset ( & socket) ?;
273331
332+ if let Some ( ref iface) = opts. bind_interface {
333+ set_ip_unicast_if ( & socket, bind_addr, iface) ?;
334+ }
335+
274336 Ok ( socket)
275337}
276338
0 commit comments