diff --git a/library/std/src/net/tcp.rs b/library/std/src/net/tcp.rs
index 22d2dfe65a249..de4c9ed3832a0 100644
--- a/library/std/src/net/tcp.rs
+++ b/library/std/src/net/tcp.rs
@@ -3,6 +3,7 @@
 #[cfg(all(test, not(any(target_os = "emscripten", target_os = "xous"))))]
 mod tests;
 
+use crate::ffi::CString;
 use crate::fmt;
 use crate::io::prelude::*;
 use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
@@ -603,6 +604,47 @@ impl TcpStream {
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         self.0.set_nonblocking(nonblocking)
     }
+
+    /// Bind the socket to an interface
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    ///
+    /// use std::net::TcpStream;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let stream = TcpStream::connect("127.0.0.1:8080")
+    ///                        .expect("Couldn't connect to the server...");
+    ///     stream.set_device("eth0")?;
+    ///     Ok(())
+    /// }
+    ///
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        self.0.set_device(ifrname)
+    }
+
+    /// Get the interface this socket is bound to
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    ///
+    /// use std::net::TcpStream;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let stream = TcpStream::connect("127.0.0.1:8080")
+    ///                        .expect("Couldn't connect to the server...");
+    ///     stream.set_device("eth0")?;
+    ///     let name = stream.device()?;
+    ///     assert_eq!(Ok("eth0"), name.to_str());
+    ///     Ok(())
+    /// }
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn device(&self) -> io::Result<CString> {
+        self.0.device()
+    }
 }
 
 // In addition to the `impl`s here, `TcpStream` also has `impl`s for
diff --git a/library/std/src/net/udp.rs b/library/std/src/net/udp.rs
index 32e9086003d6b..d72a63fa5da4a 100644
--- a/library/std/src/net/udp.rs
+++ b/library/std/src/net/udp.rs
@@ -1,6 +1,7 @@
 #[cfg(all(test, not(any(target_os = "emscripten", target_env = "sgx", target_os = "xous"))))]
 mod tests;
 
+use crate::ffi::CString;
 use crate::fmt;
 use crate::io::{self, ErrorKind};
 use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs};
@@ -806,6 +807,45 @@ impl UdpSocket {
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         self.0.set_nonblocking(nonblocking)
     }
+
+    /// Bind the socket to an interface
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    ///
+    /// use std::net::UdpSocket;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let socket = UdpSocket::bind("127.0.0.1:7878").unwrap();
+    ///     socket.set_device("eth0")?;
+    ///     Ok(())
+    /// }
+    ///
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        self.0.set_device(ifrname)
+    }
+
+    /// Get the interface this socket is bound to
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    ///
+    /// use std::net::UdpSocket;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let socket = UdpSocket::bind("127.0.0.1:7878").unwrap();
+    ///     socket.set_device("eth0")?;
+    ///     let name = socket.device()?;
+    ///     assert_eq!(Ok("eth0"), name.to_str());
+    ///     Ok(())
+    /// }
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn device(&self) -> io::Result<CString> {
+        self.0.device()
+    }
 }
 
 // In addition to the `impl`s here, `UdpSocket` also has `impl`s for
diff --git a/library/std/src/os/unix/net/datagram.rs b/library/std/src/os/unix/net/datagram.rs
index a605c3d4a2602..50c0260af0255 100644
--- a/library/std/src/os/unix/net/datagram.rs
+++ b/library/std/src/os/unix/net/datagram.rs
@@ -15,6 +15,7 @@ use libc::MSG_NOSIGNAL;
 #[cfg(any(doc, target_os = "android", target_os = "linux"))]
 use super::{recv_vectored_with_ancillary_from, send_vectored_with_ancillary_to, SocketAncillary};
 use super::{sockaddr_un, SocketAddr};
+use crate::ffi::CString;
 #[cfg(any(doc, target_os = "android", target_os = "linux"))]
 use crate::io::{IoSlice, IoSliceMut};
 use crate::net::Shutdown;
@@ -836,6 +837,41 @@ impl UnixDatagram {
         self.0.set_mark(mark)
     }
 
+    /// Bind the socket to an interface
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    /// use std::os::unix::net::UnixDatagram;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let socket = UnixDatagram::unbound()?;
+    ///     socket.set_device("eth0")?;
+    ///     Ok(())
+    /// }
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        self.0.set_device(ifrname)
+    }
+
+    /// Get the interface this socket is bound to
+    ///
+    /// ```no_run
+    /// #![feature(unix_set_device)]
+    /// use std::os::unix::net::UnixDatagram;
+    ///
+    /// fn main() -> std::io::Result<()> {
+    ///     let socket = UnixDatagram::unbound()?;
+    ///     socket.set_device("eth0")?;
+    ///     let name = socket.device()?;
+    ///     assert_eq!(Ok("eth0"), name.to_str());
+    ///     Ok(())
+    /// }
+    /// ```
+    #[unstable(feature = "unix_set_device", issue = "129182")]
+    pub fn device(&self) -> io::Result<CString> {
+        self.0.device()
+    }
     /// Returns the value of the `SO_ERROR` option.
     ///
     /// # Examples
diff --git a/library/std/src/sys/pal/hermit/net.rs b/library/std/src/sys/pal/hermit/net.rs
index 416469c003738..a46389f7e666d 100644
--- a/library/std/src/sys/pal/hermit/net.rs
+++ b/library/std/src/sys/pal/hermit/net.rs
@@ -312,6 +312,14 @@ impl Socket {
     pub fn as_raw(&self) -> RawFd {
         self.0.as_raw_fd()
     }
+
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
 }
 
 impl AsInner<FileDesc> for Socket {
diff --git a/library/std/src/sys/pal/sgx/net.rs b/library/std/src/sys/pal/sgx/net.rs
index f2e751c51194d..69112cecb15be 100644
--- a/library/std/src/sys/pal/sgx/net.rs
+++ b/library/std/src/sys/pal/sgx/net.rs
@@ -207,6 +207,14 @@ impl TcpStream {
     pub fn set_nonblocking(&self, _: bool) -> io::Result<()> {
         sgx_ineffective(())
     }
+
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
 }
 
 impl AsInner<Socket> for TcpStream {
diff --git a/library/std/src/sys/pal/solid/net.rs b/library/std/src/sys/pal/solid/net.rs
index b6a31395095d9..c5ca7b156fa5e 100644
--- a/library/std/src/sys/pal/solid/net.rs
+++ b/library/std/src/sys/pal/solid/net.rs
@@ -380,6 +380,14 @@ impl Socket {
     pub fn as_raw(&self) -> c_int {
         self.as_raw_fd()
     }
+
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
 }
 
 impl FromInner<OwnedFd> for Socket {
diff --git a/library/std/src/sys/pal/unix/net.rs b/library/std/src/sys/pal/unix/net.rs
index bc0e3f4eeeac8..ccecb8ce0b9e0 100644
--- a/library/std/src/sys/pal/unix/net.rs
+++ b/library/std/src/sys/pal/unix/net.rs
@@ -563,6 +563,40 @@ impl Socket {
         setsockopt(self, libc::SOL_SOCKET, option, mark as libc::c_int)
     }
 
+    #[cfg(any(target_os = "linux", target_os = "haiku", target_os = "vxworks"))]
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        let buf: [libc::c_char; libc::IFNAMSIZ] =
+            getsockopt(self, libc::SOL_SOCKET, libc::SO_BINDTODEVICE)?;
+        let s: &[u8] = unsafe { core::slice::from_raw_parts(buf.as_ptr() as *const u8, buf.len()) };
+        let name = CStr::from_bytes_until_nul(s).unwrap();
+        Ok(crate::ffi::CString::new(name.to_bytes()).unwrap())
+    }
+
+    #[cfg(not(any(target_os = "linux", target_os = "haiku", target_os = "vxworks")))]
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    #[cfg(any(target_os = "linux", target_os = "haiku", target_os = "vxworks"))]
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        let istr = ifrname.as_bytes();
+
+        if istr.len() >= libc::IFNAMSIZ {
+            return Err(io::Error::from_raw_os_error(libc::ENAMETOOLONG));
+        }
+
+        let mut buf = [0; libc::IFNAMSIZ];
+        for (src, dst) in istr.iter().zip(&mut buf[..libc::IFNAMSIZ - 1]) {
+            *dst = *src as libc::c_char;
+        }
+        setsockopt(self, libc::SOL_SOCKET, libc::SO_BINDTODEVICE, buf)
+    }
+
+    #[cfg(not(any(target_os = "linux", target_os = "haiku", target_os = "vxworks")))]
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
+
     pub fn take_error(&self) -> io::Result<Option<io::Error>> {
         let raw: c_int = getsockopt(self, libc::SOL_SOCKET, libc::SO_ERROR)?;
         if raw == 0 { Ok(None) } else { Ok(Some(io::Error::from_raw_os_error(raw as i32))) }
diff --git a/library/std/src/sys/pal/wasi/net.rs b/library/std/src/sys/pal/wasi/net.rs
index b4cf94c8781ec..9db57dc1ae99d 100644
--- a/library/std/src/sys/pal/wasi/net.rs
+++ b/library/std/src/sys/pal/wasi/net.rs
@@ -194,6 +194,14 @@ impl TcpStream {
     pub fn into_socket(self) -> Socket {
         self.inner
     }
+
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
 }
 
 impl FromInner<Socket> for TcpStream {
diff --git a/library/std/src/sys/pal/windows/net.rs b/library/std/src/sys/pal/windows/net.rs
index ce995f5ed5af7..6bbb96de0c15d 100644
--- a/library/std/src/sys/pal/windows/net.rs
+++ b/library/std/src/sys/pal/windows/net.rs
@@ -521,6 +521,14 @@ impl Socket {
         debug_assert_eq!(mem::align_of::<c::SOCKET>(), mem::align_of::<RawSocket>());
         unsafe { Self::from_raw_socket(raw as RawSocket) }
     }
+
+    pub fn device(&self) -> io::Result<crate::ffi::CString> {
+        unimplemented!()
+    }
+
+    pub fn set_device(&self, _: &str) -> io::Result<()> {
+        unimplemented!()
+    }
 }
 
 #[unstable(reason = "not public", issue = "none", feature = "fd_read")]
diff --git a/library/std/src/sys_common/net.rs b/library/std/src/sys_common/net.rs
index 25ebeb3502d20..f6c9ef6c00731 100644
--- a/library/std/src/sys_common/net.rs
+++ b/library/std/src/sys_common/net.rs
@@ -1,7 +1,7 @@
 #[cfg(test)]
 mod tests;
 
-use crate::ffi::{c_int, c_void};
+use crate::ffi::{c_int, c_void, CString};
 use crate::io::{self, BorrowedCursor, ErrorKind, IoSlice, IoSliceMut};
 use crate::net::{Ipv4Addr, Ipv6Addr, Shutdown, SocketAddr};
 use crate::sys::common::small_c_string::run_with_cstr;
@@ -352,6 +352,14 @@ impl TcpStream {
     pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
         self.inner.set_nonblocking(nonblocking)
     }
+
+    pub fn device(&self) -> io::Result<CString> {
+        self.inner.device()
+    }
+
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        self.inner.set_device(ifrname)
+    }
 }
 
 impl AsInner<Socket> for TcpStream {
@@ -702,6 +710,14 @@ impl UdpSocket {
         let (addr, len) = addr?.into_inner();
         cvt_r(|| unsafe { c::connect(self.inner.as_raw(), addr.as_ptr(), len) }).map(drop)
     }
+
+    pub fn device(&self) -> io::Result<CString> {
+        self.inner.device()
+    }
+
+    pub fn set_device(&self, ifrname: &str) -> io::Result<()> {
+        self.inner.set_device(ifrname)
+    }
 }
 
 impl FromInner<Socket> for UdpSocket {