Repository: tokio-rs/mio
Pull request #1392 (2020-11-06 20:15:58)

Add UDP test sending empty data
I found this locally while cleaning up my branches. Code by @kleimkuhler.

Other discussion: nodejs/node#30030



Patch:
diff --git a/src/net/mod.rs b/src/net/mod.rs
index de1be0d..4df701d 100644
--- a/src/net/mod.rs
+++ b/src/net/mod.rs
@@ -8,7 +8,7 @@
 //! [portability guidelines]: ../struct.Poll.html#portability
 
 mod tcp;
-pub use self::tcp::{TcpListener, TcpSocket, TcpStream};
+pub use self::tcp::{TcpListener, TcpSocket, TcpStream, TcpKeepalive};
 
 mod udp;
 pub use self::udp::UdpSocket;
diff --git a/src/net/tcp/mod.rs b/src/net/tcp/mod.rs
index b39b909..4e47aee 100644
--- a/src/net/tcp/mod.rs
+++ b/src/net/tcp/mod.rs
@@ -2,7 +2,7 @@ mod listener;
 pub use self::listener::TcpListener;
 
 mod socket;
-pub use self::socket::TcpSocket;
+pub use self::socket::{TcpSocket, TcpKeepalive};
 
 mod stream;
 pub use self::stream::TcpStream;
diff --git a/src/net/tcp/socket.rs b/src/net/tcp/socket.rs
index 249505a..f1f3e7c 100644
--- a/src/net/tcp/socket.rs
+++ b/src/net/tcp/socket.rs
@@ -22,13 +22,36 @@ pub struct TcpSocket {
     sys: sys::tcp::TcpSocket,
 }
 
+/// Configures a socket's TCP keepalive parameters.
+#[derive(Debug, Default, Clone)]
+pub struct TcpKeepalive {
+    pub(crate) time: Option<Duration>,
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "windows",
+    ))]
+    pub(crate) interval: Option<Duration>,
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))]
+    pub(crate) retries: Option<u32>,
+}
+
 impl TcpSocket {
     /// Create a new IPv4 TCP socket.
     ///
     /// This calls `socket(2)`.
     pub fn new_v4() -> io::Result<TcpSocket> {
-        sys::tcp::new_v4_socket().map(|sys| TcpSocket {
-            sys
+        sys::tcp::new_v4_socket().map(|sys| {
+            TcpSocket { sys }
         })
     }
 
@@ -36,8 +59,8 @@ impl TcpSocket {
     ///
     /// This calls `socket(2)`.
     pub fn new_v6() -> io::Result<TcpSocket> {
-        sys::tcp::new_v6_socket().map(|sys| TcpSocket {
-            sys
+        sys::tcp::new_v6_socket().map(|sys| {
+            TcpSocket { sys }
         })
     }
 
@@ -168,7 +191,133 @@ impl TcpSocket {
     pub fn get_send_buffer_size(&self) -> io::Result<u32> {
         sys::tcp::get_send_buffer_size(self.sys)
     }
-    
+
+    /// Sets whether keepalive messages are enabled to be sent on this socket.
+    ///
+    /// This will set the `SO_KEEPALIVE` option on this socket.
+    pub fn set_keepalive(&self, keepalive: bool) -> io::Result<()> {
+        sys::tcp::set_keepalive(self.sys, keepalive)
+    }
+
+    /// Returns whether or not TCP keepalive probes will be sent by this socket.
+    pub fn get_keepalive(&self) -> io::Result<bool> {
+        sys::tcp::get_keepalive(self.sys)
+    }
+
+    /// Sets parameters configuring TCP keepalive probes for this socket.
+    ///
+    /// The supported parameters depend on the operating system, and are
+    /// configured using the [`TcpKeepalive`] struct. At a minimum, all systems
+    /// support configuring the [keepalive time]: the time after which the OS
+    /// will start sending keepalive messages on an idle connection.
+    ///
+    /// # Notes
+    ///
+    /// * This will enable TCP keepalive on this socket, if it is not already
+    ///   enabled.
+    /// * On some platforms, such as Windows, any keepalive parameters *not*
+    ///   configured by the `TcpKeepalive` struct passed to this function may be
+    ///   overwritten with their default values. Therefore, this function should
+    ///   either only be called once per socket, or the same parameters should
+    ///   be passed every time it is called.
+    ///
+    /// # Examples
+    /// ```
+    /// use mio::net::{TcpSocket, TcpKeepalive};
+    /// use std::time::Duration;
+    ///
+    /// # fn main() -> Result<(), std::io::Error> {
+    /// let socket = TcpSocket::new_v6()?;
+    /// let keepalive = TcpKeepalive::default()
+    ///     .with_time(Duration::from_secs(4));
+    ///     // Depending on the target operating system, we may also be able to 
+    ///     // configure the keepalive probe interval and/or the number of retries
+    ///     // here as well.
+    ///
+    /// socket.set_keepalive_params(keepalive)?;
+    /// # Ok(()) }
+    /// ```
+    ///
+    /// [`TcpKeepalive`]: ../struct.TcpKeepalive.html
+    /// [keepalive time]: ../struct.TcpKeepalive.html#method.with_time
+    pub fn set_keepalive_params(&self, keepalive: TcpKeepalive) -> io::Result<()> {
+        self.set_keepalive(true)?;
+        sys::tcp::set_keepalive_params(self.sys, keepalive)
+    }
+
+    /// Returns the amount of time after which TCP keepalive probes will be sent
+    /// on idle connections.
+    ///
+    /// If `None`, then keepalive messages are disabled.
+    ///
+    /// This returns the value of `SO_KEEPALIVE` + `IPPROTO_TCP` on OpenBSD,
+    /// NetBSD, and Haiku, `TCP_KEEPALIVE` on macOS and iOS, and `TCP_KEEPIDLE`
+    /// on all other Unix operating systems. On Windows, it is not possible to
+    /// access the value of TCP keepalive parameters after they have been set.
+    ///
+    /// Some platforms specify this value in seconds, so sub-second
+    /// specifications may be omitted.
+    #[cfg_attr(docsrs, doc(cfg(not(target_os = "windows"))))]
+    #[cfg(not(target_os = "windows"))]
+    pub fn get_keepalive_time(&self) -> io::Result<Option<Duration>> {
+        sys::tcp::get_keepalive_time(self.sys)
+    }
+
+    /// Returns the time interval between TCP keepalive probes, if TCP keepalive is
+    /// enabled on this socket.
+    ///
+    /// If `None`, then keepalive messages are disabled.
+    ///
+    /// This returns the value of `TCP_KEEPINTVL` on supported Unix operating
+    /// systems. On Windows, it is not possible to access the value of TCP
+    /// keepalive parameters after they have been set..
+    ///
+    /// Some platforms specify this value in seconds, so sub-second
+    /// specifications may be omitted.
+    #[cfg_attr(docsrs, doc(cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))))]
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))]
+    pub fn get_keepalive_interval(&self) -> io::Result<Option<Duration>> {
+        sys::tcp::get_keepalive_interval(self.sys)
+    }
+
+    /// Returns the maximum number of TCP keepalive probes that will be sent before
+    /// dropping a connection, if TCP keepalive is enabled on this socket.
+    ///
+    /// If `None`, then keepalive messages are disabled.
+    ///
+    /// This returns the value of `TCP_KEEPCNT` on Unix operating systems that
+    /// support this option. On Windows, it is not possible to access the value
+    /// of TCP keepalive parameters after they have been set.
+    #[cfg_attr(docsrs, doc(cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))))]
+    #[cfg(any(
+        target_os = "linux", 
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))]
+    pub fn get_keepalive_retries(&self) -> io::Result<Option<u32>> {
+        sys::tcp::get_keepalive_retries(self.sys)
+    }
+
     /// Returns the local address of this socket
     ///
     /// Will return `Err` result in windows if called before calling `bind`
@@ -238,3 +387,83 @@ impl FromRawSocket for TcpSocket {
         TcpSocket { sys: socket as sys::tcp::TcpSocket }
     }
 }
+
+impl TcpKeepalive {
+    // Sets the amount of time after which TCP keepalive probes will be sent
+    /// on idle connections.
+    ///
+    /// This will set the value of `SO_KEEPALIVE` + `IPPROTO_TCP` on OpenBSD,
+    /// NetBSD, and Haiku, `TCP_KEEPALIVE` on macOS and iOS, and `TCP_KEEPIDLE`
+    /// on all other Unix operating systems. On Windows, this sets the value of
+    /// the `tcp_keepalive` struct's `keepalivetime` field.
+    ///
+    /// Some platforms specify this value in seconds, so sub-second
+    /// specifications may be omitted.
+    pub fn with_time(self, time: Duration) -> Self {
+        Self {
+            time: Some(time),
+            ..self
+        }
+    }
+
+    /// Sets the time interval between TCP keepalive probes.
+    /// This sets the value of `TCP_KEEPINTVL` on supported Unix operating
+    /// systems. On Windows, this sets the value of the `tcp_keepalive` struct's
+    /// `keepaliveinterval` field.
+    ///
+    /// Some platforms specify this value in seconds, so sub-second
+    /// specifications may be omitted.
+    #[cfg_attr(docsrs, doc(cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "windows"
+    ))))]
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+        target_os = "windows"
+    ))]
+    pub fn with_interval(self, interval: Duration) -> Self {
+        Self {
+            interval: Some(interval),
+            ..self
+        }
+    }
+
+    /// Sets the maximum number of TCP keepalive probes that will be sent before
+    /// dropping a connection, if TCP keepalive is enabled on this socket.
+    ///
+    /// This will set the value of `TCP_KEEPCNT` on Unix operating systems that
+    /// support this option.
+    #[cfg_attr(docsrs, doc(cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))))]
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))]
+    pub fn with_retries(self, retries: u32) -> Self {
+        Self {
+            retries: Some(retries),
+            ..self
+        }
+    }
+
+    /// Returns a new, empty set of TCP keepalive parameters.
+    pub fn new() -> Self {
+        Self::default()
+    }
+}
\ No newline at end of file
diff --git a/src/sys/shell/tcp.rs b/src/sys/shell/tcp.rs
index f51d6ca..2017bda 100644
--- a/src/sys/shell/tcp.rs
+++ b/src/sys/shell/tcp.rs
@@ -1,6 +1,7 @@
 use std::io;
 use std::net::{self, SocketAddr};
 use std::time::Duration;
+use crate::net::TcpKeepalive;
 
 pub(crate) type TcpSocket = i32;
 
@@ -70,6 +71,59 @@ pub(crate) fn get_send_buffer_size(_: TcpSocket) -> io::Result<u32> {
     os_required!();
 }
 
+pub(crate) fn set_keepalive(_: TcpSocket, _: bool) -> io::Result<()> {
+    os_required!();
+}
+
+pub(crate) fn get_keepalive(_: TcpSocket) -> io::Result<bool> {
+    os_required!();
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "windows",
+))]
+pub(crate) fn set_keepalive_params(_: TcpSocket, _: TcpKeepalive) -> io::Result<()> {
+    os_required!()
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+pub(crate) fn get_keepalive_time(_: TcpSocket) -> io::Result<Option<Duration>> {
+    os_required!()
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+pub(crate) fn get_keepalive_interval(_: TcpSocket) -> io::Result<Option<Duration>> {
+    os_required!()
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+pub(crate) fn get_keepalive_retries(_: TcpSocket) -> io::Result<Option<u32>> {
+    os_required!()
+}
+
 pub fn accept(_: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
     os_required!();
 }
diff --git a/src/sys/unix/net.rs b/src/sys/unix/net.rs
index e7fc6fc..ce9a7fb 100644
--- a/src/sys/unix/net.rs
+++ b/src/sys/unix/net.rs
@@ -1,4 +1,5 @@
-use std::net::SocketAddr;
+use std::mem;
+use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
 
 pub(crate) fn new_ip_socket(
     addr: SocketAddr,
@@ -64,32 +65,99 @@ pub(crate) fn new_socket(
     socket
 }
 
-pub(crate) fn socket_addr(addr: &SocketAddr) -> (*const libc::sockaddr, libc::socklen_t) {
-    use std::mem::size_of_val;
+/// A type with the same memory layout as `libc::sockaddr`. Used in converting Rust level
+/// SocketAddr* types into their system representation. The benefit of this specific
+/// type over using `libc::sockaddr_storage` is that this type is exactly as large as it
+/// needs to be and not a lot larger. And it can be initialized cleaner from Rust.
+#[repr(C)]
+pub(crate) union SocketAddrCRepr {
+    v4: libc::sockaddr_in,
+    v6: libc::sockaddr_in6,
+}
+
+impl SocketAddrCRepr {
+    pub(crate) fn as_ptr(&self) -> *const libc::sockaddr {
+        self as *const _ as *const libc::sockaddr
+    }
+}
 
+/// Converts a Rust `SocketAddr` into the system representation.
+pub(crate) fn socket_addr(addr: &SocketAddr) -> (SocketAddrCRepr, libc::socklen_t) {
     match addr {
-        SocketAddr::V4(ref addr) => (
-            addr as *const _ as *const libc::sockaddr,
-            size_of_val(addr) as libc::socklen_t,
-        ),
-        SocketAddr::V6(ref addr) => (
-            addr as *const _ as *const libc::sockaddr,
-            size_of_val(addr) as libc::socklen_t,
-        ),
+        SocketAddr::V4(ref addr) => {
+            // `s_addr` is stored as BE on all machine and the array is in BE order.
+            // So the native endian conversion method is used so that it's never swapped.
+            let sin_addr = libc::in_addr { s_addr: u32::from_ne_bytes(addr.ip().octets()) };
+
+            let sockaddr_in = libc::sockaddr_in {
+                sin_family: libc::AF_INET as libc::sa_family_t,
+                sin_port: addr.port().to_be(),
+                sin_addr,
+                sin_zero: [0; 8],
+                #[cfg(any(
+                    target_os = "dragonfly",
+                    target_os = "freebsd",
+                    target_os = "ios",
+                    target_os = "macos",
+                    target_os = "netbsd",
+                    target_os = "openbsd"
+                ))]
+                sin_len: 0,
+            };
+
+            let sockaddr = SocketAddrCRepr { v4: sockaddr_in };
+            (sockaddr, mem::size_of::<libc::sockaddr_in>() as libc::socklen_t)
+        }
+        SocketAddr::V6(ref addr) => {
+            let sockaddr_in6 = libc::sockaddr_in6 {
+                sin6_family: libc::AF_INET6 as libc::sa_family_t,
+                sin6_port: addr.port().to_be(),
+                sin6_addr: libc::in6_addr { s6_addr: addr.ip().octets() },
+                sin6_flowinfo: addr.flowinfo(),
+                sin6_scope_id: addr.scope_id(),
+                #[cfg(any(
+                    target_os = "dragonfly",
+                    target_os = "freebsd",
+                    target_os = "ios",
+                    target_os = "macos",
+                    target_os = "netbsd",
+                    target_os = "openbsd"
+                ))]
+                sin6_len: 0,
+                #[cfg(any(target_os = "solaris", target_os = "illumos"))]
+                __sin6_src_id: 0,
+            };
+
+            let sockaddr = SocketAddrCRepr { v6: sockaddr_in6 };
+            (sockaddr, mem::size_of::<libc::sockaddr_in6>() as libc::socklen_t)
+        }
     }
 }
 
-/// `storage` must be initialised to `sockaddr_in` or `sockaddr_in6`.
+/// Converts a `libc::sockaddr` compatible struct into a native Rust `SocketAddr`.
+///
+/// # Safety
+///
+/// `storage` must have the `ss_family` field correctly initialized.
+/// `storage` must be initialised to a `sockaddr_in` or `sockaddr_in6`.
 pub(crate) unsafe fn to_socket_addr(
     storage: *const libc::sockaddr_storage,
 ) -> std::io::Result<SocketAddr> {
     match (*storage).ss_family as libc::c_int {
-        libc::AF_INET => Ok(SocketAddr::V4(
-            *(storage as *const libc::sockaddr_in as *const _),
-        )),
-        libc::AF_INET6 => Ok(SocketAddr::V6(
-            *(storage as *const libc::sockaddr_in6 as *const _),
-        )),
+        libc::AF_INET => {
+            // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in.
+            let addr: &libc::sockaddr_in = &*(storage as *const libc::sockaddr_in);
+            let ip = Ipv4Addr::from(addr.sin_addr.s_addr.to_ne_bytes());
+            let port = u16::from_be(addr.sin_port);
+            Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
+        },
+        libc::AF_INET6 => {
+            // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6.
+            let addr: &libc::sockaddr_in6 = &*(storage as *const libc::sockaddr_in6);
+            let ip = Ipv6Addr::from(addr.sin6_addr.s6_addr);
+            let port = u16::from_be(addr.sin6_port);
+            Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, addr.sin6_flowinfo, addr.sin6_scope_id)))
+        },
         _ => Err(std::io::ErrorKind::InvalidInput.into()),
     }
 }
diff --git a/src/sys/unix/tcp.rs b/src/sys/unix/tcp.rs
index 86d2734..9e1d700 100644
--- a/src/sys/unix/tcp.rs
+++ b/src/sys/unix/tcp.rs
@@ -1,13 +1,26 @@
-use std::io;
 use std::convert::TryInto;
+use std::io;
 use std::mem;
 use std::mem::{size_of, MaybeUninit};
 use std::net::{self, SocketAddr};
-use std::time::Duration;
 use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::time::Duration;
 
 use crate::sys::unix::net::{new_socket, socket_addr, to_socket_addr};
-
+use crate::net::TcpKeepalive;
+
+#[cfg(any(target_os = "openbsd", target_os = "netbsd", target_os = "haiku"))]
+use libc::SO_KEEPALIVE as KEEPALIVE_TIME;
+#[cfg(any(target_os = "macos", target_os = "ios"))]
+use libc::TCP_KEEPALIVE as KEEPALIVE_TIME;
+#[cfg(not(any(
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "openbsd",
+    target_os = "netbsd",
+    target_os = "haiku"
+)))]
+use libc::TCP_KEEPIDLE as KEEPALIVE_TIME;
 pub type TcpSocket = libc::c_int;
 
 pub(crate) fn new_v4_socket() -> io::Result<TcpSocket> {
@@ -20,14 +33,14 @@ pub(crate) fn new_v6_socket() -> io::Result<TcpSocket> {
 
 pub(crate) fn bind(socket: TcpSocket, addr: SocketAddr) -> io::Result<()> {
     let (raw_addr, raw_addr_length) = socket_addr(&addr);
-    syscall!(bind(socket, raw_addr, raw_addr_length))?;
+    syscall!(bind(socket, raw_addr.as_ptr(), raw_addr_length))?;
     Ok(())
 }
 
 pub(crate) fn connect(socket: TcpSocket, addr: SocketAddr) -> io::Result<net::TcpStream> {
     let (raw_addr, raw_addr_length) = socket_addr(&addr);
 
-    match syscall!(connect(socket, raw_addr, raw_addr_length)) {
+    match syscall!(connect(socket, raw_addr.as_ptr(), raw_addr_length)) {
         Err(err) if err.raw_os_error() != Some(libc::EINPROGRESS) => {
             Err(err)
         }
@@ -55,7 +68,8 @@ pub(crate) fn set_reuseaddr(socket: TcpSocket, reuseaddr: bool) -> io::Result<()
         libc::SO_REUSEADDR,
         &val as *const libc::c_int as *const libc::c_void,
         size_of::<libc::c_int>() as libc::socklen_t,
-    )).map(|_| ())
+    ))
+    .map(|_| ())
 }
 
 pub(crate) fn get_reuseaddr(socket: TcpSocket) -> io::Result<bool> {
@@ -83,7 +97,8 @@ pub(crate) fn set_reuseport(socket: TcpSocket, reuseport: bool) -> io::Result<()
         libc::SO_REUSEPORT,
         &val as *const libc::c_int as *const libc::c_void,
         size_of::<libc::c_int>() as libc::socklen_t,
-    )).map(|_| ())
+    ))
+    .map(|_| ())
 }
 
 #[cfg(all(unix, not(any(target_os = "solaris", target_os = "illumos"))))]
@@ -118,7 +133,9 @@ pub(crate) fn get_localaddr(socket: TcpSocket) -> io::Result<SocketAddr> {
 pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result<()> {
     let val: libc::linger = libc::linger {
         l_onoff: if dur.is_some() { 1 } else { 0 },
-        l_linger: dur.map(|dur| dur.as_secs() as libc::c_int).unwrap_or_default(),
+        l_linger: dur
+            .map(|dur| dur.as_secs() as libc::c_int)
+            .unwrap_or_default(),
     };
     syscall!(setsockopt(
         socket,
@@ -126,7 +143,8 @@ pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result
         libc::SO_LINGER,
         &val as *const libc::linger as *const libc::c_void,
         size_of::<libc::linger>() as libc::socklen_t,
-    )).map(|_| ())
+    ))
+    .map(|_| ())
 }
 
 pub(crate) fn get_linger(socket: TcpSocket) -> io::Result<Option<Duration>> {
@@ -160,10 +178,9 @@ pub(crate) fn set_recv_buffer_size(socket: TcpSocket, size: u32) -> io::Result<(
     .map(|_| ())
 }
 
-pub(crate) fn get_recv_buffer_size(socket: TcpSocket) -> io::Result<u32> {    
+pub(crate) fn get_recv_buffer_size(socket: TcpSocket) -> io::Result<u32> {
     let mut optval: libc::c_int = 0;
     let mut optlen = size_of::<libc::c_int>() as libc::socklen_t;
-
     syscall!(getsockopt(
         socket,
         libc::SOL_SOCKET,
@@ -187,7 +204,7 @@ pub(crate) fn set_send_buffer_size(socket: TcpSocket, size: u32) -> io::Result<(
     .map(|_| ())
 }
 
-pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {    
+pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {
     let mut optval: libc::c_int = 0;
     let mut optlen = size_of::<libc::c_int>() as libc::socklen_t;
 
@@ -202,6 +219,203 @@ pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {
     Ok(optval as u32)
 }
 
+pub(crate) fn set_keepalive(socket: TcpSocket, keepalive: bool) -> io::Result<()> {
+    let val: libc::c_int = if keepalive { 1 } else { 0 };
+    syscall!(setsockopt(
+        socket,
+        libc::SOL_SOCKET,
+        libc::SO_KEEPALIVE,
+        &val as *const _ as *const libc::c_void,
+        size_of::<libc::c_int>() as libc::socklen_t
+    ))
+    .map(|_| ())
+}
+
+pub(crate) fn get_keepalive(socket: TcpSocket) -> io::Result<bool> {
+    let mut optval: libc::c_int = 0;
+    let mut optlen = mem::size_of::<libc::c_int>() as libc::socklen_t;
+
+    syscall!(getsockopt(
+        socket,
+        libc::SOL_SOCKET,
+        libc::SO_KEEPALIVE,
+        &mut optval as *mut _ as *mut _,
+        &mut optlen,
+    ))?;
+
+    Ok(optval != 0)
+}
+
+pub(crate) fn set_keepalive_params(socket: TcpSocket, keepalive: TcpKeepalive) -> io::Result<()> {
+    if let Some(dur) = keepalive.time {
+        set_keepalive_time(socket, dur)?;
+    }
+
+    #[cfg(any(
+        target_os = "linux",
+        target_os = "macos",
+        target_os = "ios",
+        target_os = "freebsd",
+        target_os = "netbsd",
+    ))]
+    {
+        if let Some(dur) = keepalive.interval {
+            set_keepalive_interval(socket, dur)?;
+        }
+    
+        if let Some(retries) = keepalive.retries {
+            set_keepalive_retries(socket, retries)?;
+        }
+    }
+
+
+    Ok(())
+}
+
+fn set_keepalive_time(socket: TcpSocket, time: Duration) -> io::Result<()> {
+    let time_secs = time
+        .as_secs()
+        .try_into()
+        .ok()
+        .unwrap_or_else(i32::max_value);
+    syscall!(setsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        KEEPALIVE_TIME,
+        &(time_secs as libc::c_int) as *const _ as *const libc::c_void,
+        size_of::<libc::c_int>() as libc::socklen_t
+    ))
+    .map(|_| ())
+}
+
+pub(crate) fn get_keepalive_time(socket: TcpSocket) -> io::Result<Option<Duration>> {
+    if !get_keepalive(socket)? {
+        return Ok(None);
+    }
+
+    let mut optval: libc::c_int = 0;
+    let mut optlen = mem::size_of::<libc::c_int>() as libc::socklen_t;
+    syscall!(getsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        KEEPALIVE_TIME,
+        &mut optval as *mut _ as *mut _,
+        &mut optlen,
+    ))?;
+
+    Ok(Some(Duration::from_secs(optval as u64)))
+}
+
+/// Linux, FreeBSD, and NetBSD support setting the keepalive interval via
+/// `TCP_KEEPINTVL`.
+/// See:
+/// - https://man7.org/linux/man-pages/man7/tcp.7.html
+/// - https://www.freebsd.org/cgi/man.cgi?query=tcp#end
+/// - http://man.netbsd.org/tcp.4#DESCRIPTION
+///
+/// OpenBSD does not:
+/// https://man.openbsd.org/tcp
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+fn set_keepalive_interval(socket: TcpSocket, interval: Duration) -> io::Result<()> {
+    let interval_secs = interval
+        .as_secs()
+        .try_into()
+        .ok()
+        .unwrap_or_else(i32::max_value);
+    syscall!(setsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        libc::TCP_KEEPINTVL,
+        &(interval_secs as libc::c_int) as *const _ as *const libc::c_void,
+        size_of::<libc::c_int>() as libc::socklen_t
+    ))
+    .map(|_| ())
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+pub(crate) fn get_keepalive_interval(socket: TcpSocket) -> io::Result<Option<Duration>> {
+    if !get_keepalive(socket)? {
+        return Ok(None);
+    }
+
+    let mut optval: libc::c_int = 0;
+    let mut optlen = mem::size_of::<libc::c_int>() as libc::socklen_t;
+    syscall!(getsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        libc::TCP_KEEPINTVL,
+        &mut optval as *mut _ as *mut _,
+        &mut optlen,
+    ))?;
+
+    Ok(Some(Duration::from_secs(optval as u64)))
+}
+
+/// Linux, macOS/iOS, FreeBSD, and NetBSD support setting the number of TCP
+/// keepalive retries via `TCP_KEEPCNT`.
+/// See:
+/// - https://man7.org/linux/man-pages/man7/tcp.7.html
+/// - https://www.freebsd.org/cgi/man.cgi?query=tcp#end
+/// - http://man.netbsd.org/tcp.4#DESCRIPTION
+///
+/// OpenBSD does not:
+/// https://man.openbsd.org/tcp
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+fn set_keepalive_retries(socket: TcpSocket, retries: u32) -> io::Result<()> {
+    let retries = retries.try_into().ok().unwrap_or_else(i32::max_value);
+    syscall!(setsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        libc::TCP_KEEPCNT,
+        &(retries as libc::c_int) as *const _ as *const libc::c_void,
+        size_of::<libc::c_int>() as libc::socklen_t
+    ))
+    .map(|_| ())
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+pub(crate) fn get_keepalive_retries(socket: TcpSocket) -> io::Result<Option<u32>> {
+    if !get_keepalive(socket)? {
+        return Ok(None);
+    }
+
+    let mut optval: libc::c_int = 0;
+    let mut optlen = mem::size_of::<libc::c_int>() as libc::socklen_t;
+    syscall!(getsockopt(
+        socket,
+        libc::IPPROTO_TCP,
+        libc::TCP_KEEPCNT,
+        &mut optval as *mut _ as *mut _,
+        &mut optlen,
+    ))?;
+
+    Ok(Some(optval as u32))
+}
+
 pub fn accept(listener: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
     let mut addr: MaybeUninit<libc::sockaddr_storage> = MaybeUninit::uninit();
     let mut length = size_of::<libc::sockaddr_storage>() as libc::socklen_t;
diff --git a/src/sys/unix/udp.rs b/src/sys/unix/udp.rs
index 947a60a..e9c4d4c 100644
--- a/src/sys/unix/udp.rs
+++ b/src/sys/unix/udp.rs
@@ -11,7 +11,7 @@ pub fn bind(addr: SocketAddr) -> io::Result<net::UdpSocket> {
 
     socket.and_then(|socket| {
         let (raw_addr, raw_addr_length) = socket_addr(&addr);
-        syscall!(bind(socket, raw_addr, raw_addr_length))
+        syscall!(bind(socket, raw_addr.as_ptr(), raw_addr_length))
             .map_err(|err| {
                 // Close the socket if we hit an error, ignoring the error
                 // from closing since we can't pass back two errors.
diff --git a/src/sys/windows/net.rs b/src/sys/windows/net.rs
index 1eb9fc2..2de98fa 100644
--- a/src/sys/windows/net.rs
+++ b/src/sys/windows/net.rs
@@ -1,10 +1,13 @@
 use std::io;
-use std::mem::size_of_val;
+use std::mem;
 use std::net::SocketAddr;
 use std::sync::Once;
 
 use winapi::ctypes::c_int;
-use winapi::shared::ws2def::SOCKADDR;
+use winapi::shared::inaddr::{in_addr_S_un, IN_ADDR};
+use winapi::shared::in6addr::{in6_addr_u, IN6_ADDR};
+use winapi::shared::ws2def::{AF_INET, AF_INET6, ADDRESS_FAMILY, SOCKADDR, SOCKADDR_IN};
+use winapi::shared::ws2ipdef::{SOCKADDR_IN6_LH, SOCKADDR_IN6_LH_u};
 use winapi::um::winsock2::{ioctlsocket, socket, FIONBIO, INVALID_SOCKET, SOCKET};
 
 /// Initialise the network stack for Windows.
@@ -41,15 +44,65 @@ pub(crate) fn new_socket(domain: c_int, socket_type: c_int) -> io::Result<SOCKET
     })
 }
 
-pub(crate) fn socket_addr(addr: &SocketAddr) -> (*const SOCKADDR, c_int) {
+/// A type with the same memory layout as `SOCKADDR`. Used in converting Rust level
+/// SocketAddr* types into their system representation. The benefit of this specific
+/// type over using `SOCKADDR_STORAGE` is that this type is exactly as large as it
+/// needs to be and not a lot larger. And it can be initialized cleaner from Rust.
+#[repr(C)]
+pub(crate) union SocketAddrCRepr {
+    v4: SOCKADDR_IN,
+    v6: SOCKADDR_IN6_LH,
+}
+
+impl SocketAddrCRepr {
+    pub(crate) fn as_ptr(&self) -> *const SOCKADDR {
+        self as *const _ as *const SOCKADDR
+    }
+}
+
+pub(crate) fn socket_addr(addr: &SocketAddr) -> (SocketAddrCRepr, c_int) {
     match addr {
-        SocketAddr::V4(ref addr) => (
-            addr as *const _ as *const SOCKADDR,
-            size_of_val(addr) as c_int,
-        ),
-        SocketAddr::V6(ref addr) => (
-            addr as *const _ as *const SOCKADDR,
-            size_of_val(addr) as c_int,
-        ),
+        SocketAddr::V4(ref addr) => {
+            // `s_addr` is stored as BE on all machine and the array is in BE order.
+            // So the native endian conversion method is used so that it's never swapped.
+            let sin_addr = unsafe {
+                let mut s_un = mem::zeroed::<in_addr_S_un>();
+                *s_un.S_addr_mut() = u32::from_ne_bytes(addr.ip().octets());
+                IN_ADDR { S_un: s_un }
+            };
+
+            let sockaddr_in = SOCKADDR_IN {
+                sin_family: AF_INET as ADDRESS_FAMILY,
+                sin_port: addr.port().to_be(),
+                sin_addr,
+                sin_zero: [0; 8],
+            };
+
+            let sockaddr = SocketAddrCRepr { v4: sockaddr_in };
+            (sockaddr, mem::size_of::<SOCKADDR_IN>() as c_int)
+        },
+        SocketAddr::V6(ref addr) => {
+            let sin6_addr = unsafe {
+                let mut u = mem::zeroed::<in6_addr_u>();
+                *u.Byte_mut() = addr.ip().octets();
+                IN6_ADDR { u }
+            };
+            let u = unsafe {
+                let mut u = mem::zeroed::<SOCKADDR_IN6_LH_u>();
+                *u.sin6_scope_id_mut() = addr.scope_id();
+                u
+            };
+
+            let sockaddr_in6 = SOCKADDR_IN6_LH {
+                sin6_family: AF_INET6 as ADDRESS_FAMILY,
+                sin6_port: addr.port().to_be(),
+                sin6_addr,
+                sin6_flowinfo: addr.flowinfo(),
+                u,
+            };
+
+            let sockaddr = SocketAddrCRepr { v6: sockaddr_in6 };
+            (sockaddr, mem::size_of::<SOCKADDR_IN6_LH>() as c_int)
+        }
     }
 }
diff --git a/src/sys/windows/tcp.rs b/src/sys/windows/tcp.rs
index 4baf556..6757b44 100644
--- a/src/sys/windows/tcp.rs
+++ b/src/sys/windows/tcp.rs
@@ -1,22 +1,25 @@
 use std::io;
+use std::convert::TryInto;
 use std::mem::size_of;
-use std::net::{self, SocketAddr, SocketAddrV4, SocketAddrV6};
+use std::net::{self, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
 use std::time::Duration;
-use std::convert::TryInto;
+use std::ptr;
 use std::os::windows::io::FromRawSocket;
 use std::os::windows::raw::SOCKET as StdSocket; // winapi uses usize, stdlib uses u32/u64.
 
-use winapi::ctypes::{c_char, c_int, c_ushort};
-use winapi::shared::ws2def::{SOCKADDR_STORAGE, AF_INET, SOCKADDR_IN};
+use winapi::ctypes::{c_char, c_int, c_ushort, c_ulong};
+use winapi::shared::ws2def::{SOCKADDR_STORAGE, AF_INET, AF_INET6, SOCKADDR_IN};
 use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH;
+use winapi::shared::mstcpip;
 
-use winapi::shared::minwindef::{BOOL, TRUE, FALSE};
+use winapi::shared::minwindef::{BOOL, TRUE, FALSE, DWORD, LPVOID, LPDWORD};
 use winapi::um::winsock2::{
     self, closesocket, linger, setsockopt, getsockopt, getsockname, PF_INET, PF_INET6, SOCKET, SOCKET_ERROR,
-    SOCK_STREAM, SOL_SOCKET, SO_LINGER, SO_REUSEADDR, SO_RCVBUF, SO_SNDBUF,
+    SOCK_STREAM, SOL_SOCKET, SO_LINGER, SO_REUSEADDR, SO_RCVBUF, SO_SNDBUF, SO_KEEPALIVE, WSAIoctl, LPWSAOVERLAPPED,
 };
 
 use crate::sys::windows::net::{init, new_socket, socket_addr};
+use crate::net::TcpKeepalive;
 
 pub(crate) type TcpSocket = SOCKET;
 
@@ -35,7 +38,7 @@ pub(crate) fn bind(socket: TcpSocket, addr: SocketAddr) -> io::Result<()> {
 
     let (raw_addr, raw_addr_length) = socket_addr(&addr);
     syscall!(
-        bind(socket, raw_addr, raw_addr_length),
+        bind(socket, raw_addr.as_ptr(), raw_addr_length),
         PartialEq::eq,
         SOCKET_ERROR
     )?;
@@ -48,7 +51,7 @@ pub(crate) fn connect(socket: TcpSocket, addr: SocketAddr) -> io::Result<net::Tc
     let (raw_addr, raw_addr_length) = socket_addr(&addr);
 
     let res = syscall!(
-        connect(socket, raw_addr, raw_addr_length),
+        connect(socket, raw_addr.as_ptr(), raw_addr_length),
         PartialEq::eq,
         SOCKET_ERROR
     );
@@ -108,23 +111,32 @@ pub(crate) fn get_reuseaddr(socket: TcpSocket) -> io::Result<bool> {
 }
 
 pub(crate) fn get_localaddr(socket: TcpSocket) -> io::Result<SocketAddr> {
-    let mut addr: SOCKADDR_STORAGE = unsafe { std::mem::zeroed() };
-    let mut length = std::mem::size_of_val(&addr) as c_int;
+    let mut storage: SOCKADDR_STORAGE = unsafe { std::mem::zeroed() };
+    let mut length = std::mem::size_of_val(&storage) as c_int;
 
     match unsafe { getsockname(
         socket,
-        &mut addr as *mut _ as *mut _,
+        &mut storage as *mut _ as *mut _,
         &mut length
     ) } {
         SOCKET_ERROR => Err(io::Error::last_os_error()),
         _ => {
-            let storage: *const SOCKADDR_STORAGE = (&addr) as *const _;
-            if addr.ss_family as c_int == AF_INET {
-                let sock_addr : SocketAddrV4 = unsafe { *(storage as *const SOCKADDR_IN as *const _) };
-                Ok(sock_addr.into())
+            if storage.ss_family as c_int == AF_INET {
+                // Safety: if the ss_family field is AF_INET then storage must be a sockaddr_in.
+                let addr: &SOCKADDR_IN = unsafe { &*(&storage as *const _ as *const SOCKADDR_IN) };
+                let ip_bytes = unsafe { addr.sin_addr.S_un.S_un_b() };
+                let ip = Ipv4Addr::from([ip_bytes.s_b1, ip_bytes.s_b2, ip_bytes.s_b3, ip_bytes.s_b4]);
+                let port = u16::from_be(addr.sin_port);
+                Ok(SocketAddr::V4(SocketAddrV4::new(ip, port)))
+            } else if storage.ss_family as c_int == AF_INET6 {
+                // Safety: if the ss_family field is AF_INET6 then storage must be a sockaddr_in6.
+                let addr: &SOCKADDR_IN6_LH = unsafe { &*(&storage as *const _ as *const SOCKADDR_IN6_LH) };
+                let ip = Ipv6Addr::from(*unsafe { addr.sin6_addr.u.Byte() });
+                let port = u16::from_be(addr.sin6_port);
+                let scope_id = unsafe { *addr.u.sin6_scope_id() };
+                Ok(SocketAddr::V6(SocketAddrV6::new(ip, port, addr.sin6_flowinfo, scope_id)))
             } else {
-                let sock_addr : SocketAddrV6 = unsafe { *(storage as *const SOCKADDR_IN6_LH as *const _) };
-                Ok(sock_addr.into())
+                Err(std::io::ErrorKind::InvalidInput.into())
             }
         },
     }
@@ -229,6 +241,82 @@ pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {
     }
 }
 
+pub(crate) fn set_keepalive(socket: TcpSocket, keepalive: bool) -> io::Result<()> {
+    let val: BOOL = if keepalive { TRUE } else { FALSE };
+    match unsafe { setsockopt(
+        socket,
+        SOL_SOCKET,
+        SO_KEEPALIVE,
+        &val as *const _ as *const c_char,
+        size_of::<BOOL>() as c_int
+    ) } {
+        SOCKET_ERROR => Err(io::Error::last_os_error()),
+        _ => Ok(()),
+    }
+}
+
+pub(crate) fn get_keepalive(socket: TcpSocket) -> io::Result<bool> {
+    let mut optval: c_char = 0;
+    let mut optlen = size_of::<BOOL>() as c_int;
+
+    match unsafe { getsockopt(
+        socket,
+        SOL_SOCKET,
+        SO_KEEPALIVE,
+        &mut optval as *mut _ as *mut _,
+        &mut optlen,
+    ) } {
+        SOCKET_ERROR => Err(io::Error::last_os_error()),
+        _ => Ok(optval != FALSE as c_char),
+    }
+}
+
+pub(crate) fn set_keepalive_params(socket: TcpSocket, keepalive: TcpKeepalive) -> io::Result<()> {
+    /// Windows configures keepalive time/interval in a u32 of milliseconds.
+    fn dur_to_ulong_ms(dur: Duration) -> c_ulong {
+        dur.as_millis().try_into().ok().unwrap_or_else(u32::max_value)
+    }
+
+    // If any of the fields on the `tcp_keepalive` struct were not provided by
+    // the user, just leaving them zero will clobber any existing value.
+    // Unfortunately, we can't access the current value, so we will use the
+    // defaults if a value for the time or interval was not not provided.
+    let time = keepalive.time.unwrap_or_else(|| {
+        // The default value is two hours, as per
+        // https://docs.microsoft.com/en-us/windows/win32/winsock/sio-keepalive-vals
+        let two_hours = 2 * 60 * 60;
+        Duration::from_secs(two_hours)
+    });
+
+    let interval = keepalive.interval.unwrap_or_else(|| {
+        // The default value is one second, as per
+        // https://docs.microsoft.com/en-us/windows/win32/winsock/sio-keepalive-vals
+        Duration::from_secs(1)
+    });
+
+    let mut keepalive = mstcpip::tcp_keepalive {
+        // Enable keepalive
+        onoff: 1,
+        keepalivetime: dur_to_ulong_ms(time),
+        keepaliveinterval: dur_to_ulong_ms(interval),
+    };
+
+    let mut out = 0;
+    match unsafe { WSAIoctl(
+        socket,
+        mstcpip::SIO_KEEPALIVE_VALS,
+        &mut keepalive as *mut _ as LPVOID,
+        size_of::<mstcpip::tcp_keepalive>() as DWORD,
+        ptr::null_mut() as LPVOID,
+        0 as DWORD,
+        &mut out as *mut _ as LPDWORD,
+        0 as LPWSAOVERLAPPED,
+        None,
+    ) } {
+        0 => Ok(()),
+        _ => Err(io::Error::last_os_error())
+    }
+}
 
 pub(crate) fn accept(listener: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
     // The non-blocking state of `listener` is inherited. See
diff --git a/src/sys/windows/udp.rs b/src/sys/windows/udp.rs
index 667c775..ba2aeac 100644
--- a/src/sys/windows/udp.rs
+++ b/src/sys/windows/udp.rs
@@ -12,7 +12,7 @@ pub fn bind(addr: SocketAddr) -> io::Result<net::UdpSocket> {
     new_ip_socket(addr, SOCK_DGRAM).and_then(|socket| {
         let (raw_addr, raw_addr_length) = socket_addr(&addr);
         syscall!(
-            win_bind(socket, raw_addr, raw_addr_length,),
+            win_bind(socket, raw_addr.as_ptr(), raw_addr_length,),
             PartialEq::eq,
             SOCKET_ERROR
         )


Test patch:
diff --git a/tests/tcp_socket.rs b/tests/tcp_socket.rs
index e5fc537..4dac73f 100644
--- a/tests/tcp_socket.rs
+++ b/tests/tcp_socket.rs
@@ -1,6 +1,6 @@
 #![cfg(all(feature = "os-poll", feature = "tcp"))]
 
-use mio::net::TcpSocket;
+use mio::net::{TcpKeepalive, TcpSocket};
 use std::io;
 use std::time::Duration;
 
@@ -40,6 +40,89 @@ fn set_reuseport() {
     let _ = socket.listen(128).unwrap();
 }
 
+#[test]
+fn set_keepalive() {
+    let addr = "127.0.0.1:0".parse().unwrap();
+
+    let socket = TcpSocket::new_v4().unwrap();
+    socket.set_keepalive(false).unwrap();
+    assert_eq!(false, socket.get_keepalive().unwrap());
+
+    socket.set_keepalive(true).unwrap();
+    assert_eq!(true, socket.get_keepalive().unwrap());
+
+    socket.bind(addr).unwrap();
+
+    let _ = socket.listen(128).unwrap();
+}
+
+#[test]
+fn set_keepalive_time() {
+    let dur = Duration::from_secs(4); // Chosen by fair dice roll, guaranteed to be random
+    let addr = "127.0.0.1:0".parse().unwrap();
+
+    let socket = TcpSocket::new_v4().unwrap();
+    socket
+        .set_keepalive_params(TcpKeepalive::default().with_time(dur))
+        .unwrap();
+
+    // It's not possible to access keepalive parameters on Windows...
+    #[cfg(not(target_os = "windows"))]
+    assert_eq!(Some(dur), socket.get_keepalive_time().unwrap());
+
+    socket.bind(addr).unwrap();
+
+    let _ = socket.listen(128).unwrap();
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+    target_os = "windows"
+))]
+#[test]
+fn set_keepalive_interval() {
+    let dur = Duration::from_secs(4); // Chosen by fair dice roll, guaranteed to be random
+    let addr = "127.0.0.1:0".parse().unwrap();
+
+    let socket = TcpSocket::new_v4().unwrap();
+    socket
+        .set_keepalive_params(TcpKeepalive::default().with_interval(dur))
+        .unwrap();
+    // It's not possible to access keepalive parameters on Windows...
+    #[cfg(not(target_os = "windows"))]
+    assert_eq!(Some(dur), socket.get_keepalive_interval().unwrap());
+
+    socket.bind(addr).unwrap();
+
+    let _ = socket.listen(128).unwrap();
+}
+
+#[cfg(any(
+    target_os = "linux",
+    target_os = "macos",
+    target_os = "ios",
+    target_os = "freebsd",
+    target_os = "netbsd",
+))]
+#[test]
+fn set_keepalive_retries() {
+    let addr = "127.0.0.1:0".parse().unwrap();
+
+    let socket = TcpSocket::new_v4().unwrap();
+    socket
+        .set_keepalive_params(TcpKeepalive::default().with_retries(16))
+        .unwrap();
+    assert_eq!(Some(16), socket.get_keepalive_retries().unwrap());
+
+    socket.bind(addr).unwrap();
+
+    let _ = socket.listen(128).unwrap();
+}
+
 #[test]
 fn get_localaddr() {
     let expected_addr = "127.0.0.1:0".parse().unwrap();
diff --git a/tests/udp_socket.rs b/tests/udp_socket.rs
index 874654d..b265023 100644
--- a/tests/udp_socket.rs
+++ b/tests/udp_socket.rs
@@ -28,6 +28,39 @@ const ID1: Token = Token(2);
 const ID2: Token = Token(3);
 const ID3: Token = Token(4);
 
+#[test]
+fn empty_datagram() {
+    const EMPTY: &[u8] = b"";
+
+    let (mut poll, mut events) = init_with_poll();
+    let mut s1 = UdpSocket::bind(any_local_address()).unwrap();
+    let mut s2 = UdpSocket::bind(any_local_address()).unwrap();
+
+    poll.registry()
+        .register(&mut s1, ID1, Interest::WRITABLE)
+        .unwrap();
+    poll.registry()
+        .register(&mut s2, ID2, Interest::READABLE)
+        .unwrap();
+
+    expect_events(
+        &mut poll,
+        &mut events,
+        vec![ExpectEvent::new(ID1, Interest::WRITABLE)],
+    );
+
+    checked_write!(s1.send_to(EMPTY, s2.local_addr().unwrap()));
+
+    let mut buf = [0; 10];
+
+    expect_events(
+        &mut poll,
+        &mut events,
+        vec![ExpectEvent::new(ID2, Interest::READABLE)],
+    );
+    expect_read!(s2.recv_from(&mut buf), EMPTY, s1.local_addr().unwrap());
+}
+
 #[test]
 fn is_send_and_sync() {
     assert_send::<UdpSocket>();