diff --git a/src/backend/libc/net/msghdr.rs b/src/backend/libc/net/msghdr.rs
index d212c65a6..1b997323f 100644
--- a/src/backend/libc/net/msghdr.rs
+++ b/src/backend/libc/net/msghdr.rs
@@ -6,12 +6,12 @@
 use crate::backend::c;
 use crate::backend::conv::{msg_control_len, msg_iov_len};
 #[cfg(target_os = "linux")]
-use crate::backend::net::write_sockaddr::encode_sockaddr_xdp;
+use crate::backend::net::write_sockaddr::{encode_sockaddr_nl, encode_sockaddr_xdp};
 use crate::backend::net::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
 
 use crate::io::{self, IoSlice, IoSliceMut};
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{RecvAncillaryBuffer, SendAncillaryBuffer, SocketAddrV4, SocketAddrV6};
 use crate::utils::as_ptr;
 
@@ -78,7 +78,7 @@ pub(crate) fn with_v4_msghdr<R>(
     f({
         let mut h = zero_msghdr();
         h.msg_name = as_ptr(&encoded) as _;
-        h.msg_namelen = size_of::<SocketAddrV4>() as _;
+        h.msg_namelen = size_of::<c::sockaddr_in>() as _;
         h.msg_iov = iov.as_ptr() as _;
         h.msg_iovlen = msg_iov_len(iov.len());
         h.msg_control = control.as_control_ptr().cast();
@@ -99,7 +99,7 @@ pub(crate) fn with_v6_msghdr<R>(
     f({
         let mut h = zero_msghdr();
         h.msg_name = as_ptr(&encoded) as _;
-        h.msg_namelen = size_of::<SocketAddrV6>() as _;
+        h.msg_namelen = size_of::<c::sockaddr_in6>() as _;
         h.msg_iov = iov.as_ptr() as _;
         h.msg_iovlen = msg_iov_len(iov.len());
         h.msg_control = control.as_control_ptr().cast();
@@ -128,7 +128,7 @@ pub(crate) fn with_unix_msghdr<R>(
     })
 }
 
-/// Create a message header intended to send with an IPv6 address.
+/// Create a message header intended to send with an XDP address.
 #[cfg(target_os = "linux")]
 pub(crate) fn with_xdp_msghdr<R>(
     addr: &SocketAddrXdp,
@@ -141,7 +141,29 @@ pub(crate) fn with_xdp_msghdr<R>(
     f({
         let mut h = zero_msghdr();
         h.msg_name = as_ptr(&encoded) as _;
-        h.msg_namelen = size_of::<SocketAddrXdp>() as _;
+        h.msg_namelen = size_of::<c::sockaddr_xdp>() as _;
+        h.msg_iov = iov.as_ptr() as _;
+        h.msg_iovlen = msg_iov_len(iov.len());
+        h.msg_control = control.as_control_ptr().cast();
+        h.msg_controllen = msg_control_len(control.control_len());
+        h
+    })
+}
+
+/// Create a message header intended to send with an Netlink address.
+#[cfg(target_os = "linux")]
+pub(crate) fn with_nl_msghdr<R>(
+    addr: &SocketAddrNl,
+    iov: &[IoSlice<'_>],
+    control: &mut SendAncillaryBuffer<'_, '_, '_>,
+    f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+    let encoded = encode_sockaddr_nl(addr);
+
+    f({
+        let mut h = zero_msghdr();
+        h.msg_name = as_ptr(&encoded) as _;
+        h.msg_namelen = size_of::<c::sockaddr_nl>() as _;
         h.msg_iov = iov.as_ptr() as _;
         h.msg_iovlen = msg_iov_len(iov.len());
         h.msg_control = control.as_control_ptr().cast();
diff --git a/src/backend/libc/net/read_sockaddr.rs b/src/backend/libc/net/read_sockaddr.rs
index d2c9b69e6..4f4c44e4d 100644
--- a/src/backend/libc/net/read_sockaddr.rs
+++ b/src/backend/libc/net/read_sockaddr.rs
@@ -9,7 +9,10 @@ use crate::backend::c;
 use crate::ffi::CStr;
 use crate::io;
 #[cfg(target_os = "linux")]
-use crate::net::xdp::{SockaddrXdpFlags, SocketAddrXdp};
+use crate::net::{
+    netlink::SocketAddrNl,
+    xdp::{SockaddrXdpFlags, SocketAddrXdp},
+};
 use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
 use core::mem::size_of;
 
@@ -332,6 +335,12 @@ unsafe fn inner_read_sockaddr_os(
                 u32::from_be(decode.sxdp_shared_umem_fd),
             ))
         }
+        #[cfg(target_os = "linux")]
+        c::AF_NETLINK => {
+            assert!(len >= size_of::<c::sockaddr_nl>());
+            let decode = &*storage.cast::<c::sockaddr_nl>();
+            SocketAddrAny::Nl(SocketAddrNl::new(decode.nl_pid, decode.nl_groups))
+        }
         other => unimplemented!("{:?}", other),
     }
 }
diff --git a/src/backend/libc/net/syscalls.rs b/src/backend/libc/net/syscalls.rs
index 3fdb7766b..a65a55861 100644
--- a/src/backend/libc/net/syscalls.rs
+++ b/src/backend/libc/net/syscalls.rs
@@ -3,15 +3,15 @@
 #[cfg(unix)]
 use super::addr::SocketAddrUnix;
 #[cfg(target_os = "linux")]
-use super::msghdr::with_xdp_msghdr;
+use super::msghdr::{with_nl_msghdr, with_xdp_msghdr};
 #[cfg(target_os = "linux")]
-use super::write_sockaddr::encode_sockaddr_xdp;
+use super::write_sockaddr::{encode_sockaddr_nl, encode_sockaddr_xdp};
 use crate::backend::c;
 use crate::backend::conv::{borrowed_fd, ret, ret_owned_fd, ret_send_recv, send_recv_len};
 use crate::fd::{BorrowedFd, OwnedFd};
 use crate::io;
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{SocketAddrAny, SocketAddrV4, SocketAddrV6};
 use crate::utils::as_ptr;
 use core::mem::{size_of, MaybeUninit};
@@ -170,6 +170,25 @@ pub(crate) fn sendto_xdp(
     }
 }
 
+#[cfg(target_os = "linux")]
+pub(crate) fn sendto_nl(
+    fd: BorrowedFd<'_>,
+    buf: &[u8],
+    flags: SendFlags,
+    addr: &SocketAddrNl,
+) -> io::Result<usize> {
+    unsafe {
+        ret_send_recv(c::sendto(
+            borrowed_fd(fd),
+            buf.as_ptr().cast(),
+            send_recv_len(buf.len()),
+            bitflags_bits!(flags),
+            as_ptr(&encode_sockaddr_nl(addr)).cast::<c::sockaddr>(),
+            size_of::<c::sockaddr_nl>() as _,
+        ))
+    }
+}
+
 #[cfg(not(any(target_os = "redox", target_os = "wasi")))]
 pub(crate) fn socket(
     domain: AddressFamily,
@@ -253,6 +272,17 @@ pub(crate) fn bind_xdp(sockfd: BorrowedFd<'_>, addr: &SocketAddrXdp) -> io::Resu
     }
 }
 
+#[cfg(target_os = "linux")]
+pub(crate) fn bind_nl(sockfd: BorrowedFd<'_>, addr: &SocketAddrNl) -> io::Result<()> {
+    unsafe {
+        ret(c::bind(
+            borrowed_fd(sockfd),
+            as_ptr(&encode_sockaddr_nl(addr)).cast(),
+            size_of::<c::sockaddr_nl>() as c::socklen_t,
+        ))
+    }
+}
+
 #[cfg(not(any(target_os = "redox", target_os = "wasi")))]
 pub(crate) fn connect_v4(sockfd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
     unsafe {
@@ -455,6 +485,23 @@ pub(crate) fn sendmsg_xdp(
     })
 }
 
+#[cfg(target_os = "linux")]
+pub(crate) fn sendmsg_nl(
+    sockfd: BorrowedFd<'_>,
+    addr: &SocketAddrNl,
+    iov: &[IoSlice<'_>],
+    control: &mut SendAncillaryBuffer<'_, '_, '_>,
+    msg_flags: SendFlags,
+) -> io::Result<usize> {
+    with_nl_msghdr(addr, iov, control, |msghdr| unsafe {
+        ret_send_recv(c::sendmsg(
+            borrowed_fd(sockfd),
+            &msghdr,
+            bitflags_bits!(msg_flags),
+        ))
+    })
+}
+
 #[cfg(not(any(
     apple,
     windows,
diff --git a/src/backend/libc/net/write_sockaddr.rs b/src/backend/libc/net/write_sockaddr.rs
index 29e69e141..e08f2b0df 100644
--- a/src/backend/libc/net/write_sockaddr.rs
+++ b/src/backend/libc/net/write_sockaddr.rs
@@ -7,7 +7,7 @@ use super::addr::SocketAddrUnix;
 use super::ext::{in6_addr_new, in_addr_new, sockaddr_in6_new};
 use crate::backend::c;
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{SocketAddrAny, SocketAddrV4, SocketAddrV6};
 use core::mem::size_of;
 
@@ -22,6 +22,8 @@ pub(crate) unsafe fn write_sockaddr(
         SocketAddrAny::Unix(unix) => write_sockaddr_unix(unix, storage),
         #[cfg(target_os = "linux")]
         SocketAddrAny::Xdp(xdp) => write_sockaddr_xdp(xdp, storage),
+        #[cfg(target_os = "linux")]
+        SocketAddrAny::Nl(nl) => write_sockaddr_nl(nl, storage),
     }
 }
 
@@ -126,3 +128,19 @@ unsafe fn write_sockaddr_xdp(xdp: &SocketAddrXdp, storage: *mut SocketAddrStorag
     core::ptr::write(storage.cast(), encoded);
     size_of::<c::sockaddr_xdp>()
 }
+
+#[cfg(target_os = "linux")]
+pub(crate) fn encode_sockaddr_nl(nl: &SocketAddrNl) -> c::sockaddr_nl {
+    let mut addr: c::sockaddr_nl = unsafe { std::mem::zeroed() };
+    addr.nl_family = c::AF_NETLINK as _;
+    addr.nl_pid = nl.pid();
+    addr.nl_groups = nl.groups();
+    addr
+}
+
+#[cfg(target_os = "linux")]
+unsafe fn write_sockaddr_nl(nl: &SocketAddrNl, storage: *mut SocketAddrStorage) -> usize {
+    let encoded = encode_sockaddr_nl(nl);
+    core::ptr::write(storage.cast(), encoded);
+    size_of::<c::sockaddr_nl>()
+}
diff --git a/src/backend/linux_raw/net/msghdr.rs b/src/backend/linux_raw/net/msghdr.rs
index 3ccce04c9..accc38252 100644
--- a/src/backend/linux_raw/net/msghdr.rs
+++ b/src/backend/linux_raw/net/msghdr.rs
@@ -7,12 +7,12 @@
 
 use crate::backend::c;
 #[cfg(target_os = "linux")]
-use crate::backend::net::write_sockaddr::encode_sockaddr_xdp;
+use crate::backend::net::write_sockaddr::{encode_sockaddr_nl, encode_sockaddr_xdp};
 use crate::backend::net::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
 
 use crate::io::{self, IoSlice, IoSliceMut};
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{RecvAncillaryBuffer, SendAncillaryBuffer, SocketAddrV4, SocketAddrV6};
 use crate::utils::as_ptr;
 
@@ -89,7 +89,7 @@ pub(crate) fn with_v4_msghdr<R>(
 
     f(c::msghdr {
         msg_name: as_ptr(&encoded) as _,
-        msg_namelen: size_of::<SocketAddrV4>() as _,
+        msg_namelen: size_of::<c::sockaddr_in>() as _,
         msg_iov: iov.as_ptr() as _,
         msg_iovlen: msg_iov_len(iov.len()),
         msg_control: control.as_control_ptr().cast(),
@@ -109,7 +109,7 @@ pub(crate) fn with_v6_msghdr<R>(
 
     f(c::msghdr {
         msg_name: as_ptr(&encoded) as _,
-        msg_namelen: size_of::<SocketAddrV6>() as _,
+        msg_namelen: size_of::<c::sockaddr_in6>() as _,
         msg_iov: iov.as_ptr() as _,
         msg_iovlen: msg_iov_len(iov.len()),
         msg_control: control.as_control_ptr().cast(),
@@ -148,7 +148,28 @@ pub(crate) fn with_xdp_msghdr<R>(
 
     f(c::msghdr {
         msg_name: as_ptr(&encoded) as _,
-        msg_namelen: size_of::<SocketAddrXdp>() as _,
+        msg_namelen: size_of::<c::sockaddr_xdp>() as _,
+        msg_iov: iov.as_ptr() as _,
+        msg_iovlen: msg_iov_len(iov.len()),
+        msg_control: control.as_control_ptr().cast(),
+        msg_controllen: msg_control_len(control.control_len()),
+        msg_flags: 0,
+    })
+}
+
+/// Create a message header intended to send with an Netlink address.
+#[cfg(target_os = "linux")]
+pub(crate) fn with_nl_msghdr<R>(
+    addr: &SocketAddrNl,
+    iov: &[IoSlice<'_>],
+    control: &mut SendAncillaryBuffer<'_, '_, '_>,
+    f: impl FnOnce(c::msghdr) -> R,
+) -> R {
+    let encoded = encode_sockaddr_nl(addr);
+
+    f(c::msghdr {
+        msg_name: as_ptr(&encoded) as _,
+        msg_namelen: size_of::<c::sockaddr_xdp>() as _,
         msg_iov: iov.as_ptr() as _,
         msg_iovlen: msg_iov_len(iov.len()),
         msg_control: control.as_control_ptr().cast(),
diff --git a/src/backend/linux_raw/net/read_sockaddr.rs b/src/backend/linux_raw/net/read_sockaddr.rs
index 23e1d641d..119660da4 100644
--- a/src/backend/linux_raw/net/read_sockaddr.rs
+++ b/src/backend/linux_raw/net/read_sockaddr.rs
@@ -5,6 +5,8 @@
 use crate::backend::c;
 use crate::io;
 #[cfg(target_os = "linux")]
+use crate::net::netlink::SocketAddrNl;
+#[cfg(target_os = "linux")]
 use crate::net::xdp::{SockaddrXdpFlags, SocketAddrXdp};
 use crate::net::{Ipv4Addr, Ipv6Addr, SocketAddrAny, SocketAddrUnix, SocketAddrV4, SocketAddrV6};
 use core::mem::size_of;
@@ -216,6 +218,12 @@ pub(crate) unsafe fn read_sockaddr_os(storage: *const c::sockaddr, len: usize) -
                 u32::from_be(decode.sxdp_shared_umem_fd),
             ))
         }
+        #[cfg(target_os = "linux")]
+        c::AF_NETLINK => {
+            assert!(len >= size_of::<c::sockaddr_nl>());
+            let decode = &*storage.cast::<c::sockaddr_nl>();
+            SocketAddrAny::Nl(SocketAddrNl::new(decode.nl_pid, decode.nl_groups))
+        }
         other => unimplemented!("{:?}", other),
     }
 }
diff --git a/src/backend/linux_raw/net/syscalls.rs b/src/backend/linux_raw/net/syscalls.rs
index 4d4427a40..bd6805810 100644
--- a/src/backend/linux_raw/net/syscalls.rs
+++ b/src/backend/linux_raw/net/syscalls.rs
@@ -6,14 +6,14 @@
 #![allow(unsafe_code, clippy::undocumented_unsafe_blocks)]
 
 #[cfg(target_os = "linux")]
-use super::msghdr::with_xdp_msghdr;
+use super::msghdr::{with_nl_msghdr, with_xdp_msghdr};
 use super::msghdr::{
     with_noaddr_msghdr, with_recv_msghdr, with_unix_msghdr, with_v4_msghdr, with_v6_msghdr,
 };
 use super::read_sockaddr::{initialize_family_to_unspec, maybe_read_sockaddr_os, read_sockaddr_os};
 use super::send_recv::{RecvFlags, SendFlags};
 #[cfg(target_os = "linux")]
-use super::write_sockaddr::encode_sockaddr_xdp;
+use super::write_sockaddr::{encode_sockaddr_nl, encode_sockaddr_xdp};
 use super::write_sockaddr::{encode_sockaddr_v4, encode_sockaddr_v6};
 use crate::backend::c;
 use crate::backend::conv::{
@@ -23,7 +23,7 @@ use crate::backend::conv::{
 use crate::fd::{BorrowedFd, OwnedFd};
 use crate::io::{self, IoSlice, IoSliceMut};
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{
     AddressFamily, Protocol, RecvAncillaryBuffer, RecvMsgReturn, SendAncillaryBuffer, Shutdown,
     SocketAddrAny, SocketAddrUnix, SocketAddrV4, SocketAddrV6, SocketFlags, SocketType,
@@ -439,6 +439,37 @@ pub(crate) fn sendmsg_xdp(
     })
 }
 
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn sendmsg_nl(
+    sockfd: BorrowedFd<'_>,
+    addr: &SocketAddrNl,
+    iov: &[IoSlice<'_>],
+    control: &mut SendAncillaryBuffer<'_, '_, '_>,
+    msg_flags: SendFlags,
+) -> io::Result<usize> {
+    with_nl_msghdr(addr, iov, control, |msghdr| {
+        #[cfg(not(target_arch = "x86"))]
+        let result =
+            unsafe { ret_usize(syscall!(__NR_sendmsg, sockfd, by_ref(&msghdr), msg_flags)) };
+
+        #[cfg(target_arch = "x86")]
+        let result = unsafe {
+            ret_usize(syscall!(
+                __NR_socketcall,
+                x86_sys(SYS_SENDMSG),
+                slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+                    sockfd.into(),
+                    by_ref(&msghdr),
+                    msg_flags.into()
+                ])
+            ))
+        };
+
+        result
+    })
+}
+
 #[inline]
 pub(crate) fn shutdown(fd: BorrowedFd<'_>, how: Shutdown) -> io::Result<()> {
     #[cfg(not(target_arch = "x86"))]
@@ -660,6 +691,44 @@ pub(crate) fn sendto_xdp(
     }
 }
 
+#[inline]
+pub(crate) fn sendto_nl(
+    fd: BorrowedFd<'_>,
+    buf: &[u8],
+    flags: SendFlags,
+    addr: &SocketAddrNl,
+) -> io::Result<usize> {
+    let (buf_addr, buf_len) = slice(buf);
+
+    #[cfg(not(target_arch = "x86"))]
+    unsafe {
+        ret_usize(syscall_readonly!(
+            __NR_sendto,
+            fd,
+            buf_addr,
+            buf_len,
+            flags,
+            by_ref(&encode_sockaddr_nl(addr)),
+            size_of::<c::sockaddr_nl, _>()
+        ))
+    }
+    #[cfg(target_arch = "x86")]
+    unsafe {
+        ret_usize(syscall_readonly!(
+            __NR_socketcall,
+            x86_sys(SYS_SENDTO),
+            slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+                fd.into(),
+                buf_addr,
+                buf_len,
+                flags.into(),
+                by_ref(&encode_sockaddr_nl(addr)),
+                size_of::<c::sockaddr_nl, _>(),
+            ])
+        ))
+    }
+}
+
 #[inline]
 pub(crate) unsafe fn recv(
     fd: BorrowedFd<'_>,
@@ -931,6 +1000,32 @@ pub(crate) fn bind_xdp(fd: BorrowedFd<'_>, addr: &SocketAddrXdp) -> io::Result<(
     }
 }
 
+#[cfg(target_os = "linux")]
+#[inline]
+pub(crate) fn bind_nl(fd: BorrowedFd<'_>, addr: &SocketAddrNl) -> io::Result<()> {
+    #[cfg(not(target_arch = "x86"))]
+    unsafe {
+        ret(syscall_readonly!(
+            __NR_bind,
+            fd,
+            by_ref(&encode_sockaddr_nl(addr)),
+            size_of::<c::sockaddr_nl, _>()
+        ))
+    }
+    #[cfg(target_arch = "x86")]
+    unsafe {
+        ret(syscall_readonly!(
+            __NR_socketcall,
+            x86_sys(SYS_BIND),
+            slice_just_addr::<ArgReg<'_, SocketArg>, _>(&[
+                fd.into(),
+                by_ref(&encode_sockaddr_nl(addr)),
+                size_of::<c::sockaddr_nl, _>(),
+            ])
+        ))
+    }
+}
+
 #[inline]
 pub(crate) fn connect_v4(fd: BorrowedFd<'_>, addr: &SocketAddrV4) -> io::Result<()> {
     #[cfg(not(target_arch = "x86"))]
diff --git a/src/backend/linux_raw/net/write_sockaddr.rs b/src/backend/linux_raw/net/write_sockaddr.rs
index fb6e51edb..d3a3a45cd 100644
--- a/src/backend/linux_raw/net/write_sockaddr.rs
+++ b/src/backend/linux_raw/net/write_sockaddr.rs
@@ -4,7 +4,7 @@
 
 use crate::backend::c;
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{SocketAddrAny, SocketAddrStorage, SocketAddrUnix, SocketAddrV4, SocketAddrV6};
 use core::mem::size_of;
 
@@ -18,6 +18,8 @@ pub(crate) unsafe fn write_sockaddr(
         SocketAddrAny::Unix(unix) => write_sockaddr_unix(unix, storage),
         #[cfg(target_os = "linux")]
         SocketAddrAny::Xdp(xdp) => write_sockaddr_xdp(xdp, storage),
+        #[cfg(target_os = "linux")]
+        SocketAddrAny::Nl(nl) => write_sockaddr_nl(nl, storage),
     }
 }
 
@@ -80,3 +82,20 @@ unsafe fn write_sockaddr_xdp(xdp: &SocketAddrXdp, storage: *mut SocketAddrStorag
     core::ptr::write(storage.cast(), encoded);
     size_of::<c::sockaddr_xdp>()
 }
+
+#[cfg(target_os = "linux")]
+pub(crate) fn encode_sockaddr_nl(nl: &SocketAddrNl) -> c::sockaddr_nl {
+    c::sockaddr_nl {
+        nl_family: c::AF_NETLINK as _,
+        nl_pid: nl.pid(),
+        nl_groups: nl.groups(),
+        nl_pad: 0,
+    }
+}
+
+#[cfg(target_os = "linux")]
+unsafe fn write_sockaddr_nl(nl: &SocketAddrNl, storage: *mut SocketAddrStorage) -> usize {
+    let encoded = encode_sockaddr_nl(nl);
+    core::ptr::write(storage.cast(), encoded);
+    size_of::<c::sockaddr_nl>()
+}
diff --git a/src/net/send_recv/mod.rs b/src/net/send_recv/mod.rs
index 1ae4fdb39..69e3d3b0f 100644
--- a/src/net/send_recv/mod.rs
+++ b/src/net/send_recv/mod.rs
@@ -3,10 +3,10 @@
 #![allow(unsafe_code)]
 
 use crate::buffer::split_init;
-#[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
 #[cfg(unix)]
 use crate::net::SocketAddrUnix;
+#[cfg(target_os = "linux")]
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{SocketAddr, SocketAddrAny, SocketAddrV4, SocketAddrV6};
 use crate::{backend, io};
 use backend::fd::{AsFd, BorrowedFd};
@@ -265,6 +265,8 @@ fn _sendto_any(
         SocketAddrAny::Unix(unix) => backend::net::syscalls::sendto_unix(fd, buf, flags, unix),
         #[cfg(target_os = "linux")]
         SocketAddrAny::Xdp(xdp) => backend::net::syscalls::sendto_xdp(fd, buf, flags, xdp),
+        #[cfg(target_os = "linux")]
+        SocketAddrAny::Nl(nl) => backend::net::syscalls::sendto_nl(fd, buf, flags, nl),
     }
 }
 
@@ -401,3 +403,22 @@ pub fn sendto_xdp<Fd: AsFd>(
 ) -> io::Result<usize> {
     backend::net::syscalls::sendto_xdp(fd.as_fd(), buf, flags, addr)
 }
+
+/// `sendto(fd, buf, flags, addr, sizeof(struct sockaddr_nl))`—Writes data
+/// to a socket to a specific Netlink address.
+///
+/// # References
+///  - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/sendto.2.html
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "sendto")]
+pub fn sendto_nl<Fd: AsFd>(
+    fd: Fd,
+    buf: &[u8],
+    flags: SendFlags,
+    addr: &SocketAddrNl,
+) -> io::Result<usize> {
+    backend::net::syscalls::sendto_nl(fd.as_fd(), buf, flags, addr)
+}
diff --git a/src/net/send_recv/msg.rs b/src/net/send_recv/msg.rs
index 8b81c8d8f..a20cc8f4d 100644
--- a/src/net/send_recv/msg.rs
+++ b/src/net/send_recv/msg.rs
@@ -776,6 +776,10 @@ pub fn sendmsg_any(
         Some(SocketAddrAny::Xdp(addr)) => {
             backend::net::syscalls::sendmsg_xdp(socket.as_fd(), addr, iov, control, flags)
         }
+        #[cfg(target_os = "linux")]
+        Some(SocketAddrAny::Nl(addr)) => {
+            backend::net::syscalls::sendmsg_nl(socket.as_fd(), addr, iov, control, flags)
+        }
     }
 }
 
diff --git a/src/net/socket.rs b/src/net/socket.rs
index 1a2157776..39d4678be 100644
--- a/src/net/socket.rs
+++ b/src/net/socket.rs
@@ -4,7 +4,7 @@ use crate::{backend, io};
 use backend::fd::{AsFd, BorrowedFd};
 
 #[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 pub use crate::net::{AddressFamily, Protocol, Shutdown, SocketFlags, SocketType};
 #[cfg(unix)]
 pub use backend::net::addr::SocketAddrUnix;
@@ -172,6 +172,8 @@ fn _bind_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()> {
         SocketAddrAny::Unix(unix) => backend::net::syscalls::bind_unix(sockfd, unix),
         #[cfg(target_os = "linux")]
         SocketAddrAny::Xdp(xdp) => backend::net::syscalls::bind_xdp(sockfd, xdp),
+        #[cfg(target_os = "linux")]
+        SocketAddrAny::Nl(nl) => backend::net::syscalls::bind_nl(sockfd, nl),
     }
 }
 
@@ -289,6 +291,20 @@ pub fn bind_xdp<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrXdp) -> io::Result<()> {
     backend::net::syscalls::bind_xdp(sockfd.as_fd(), addr)
 }
 
+/// `bind(sockfd, addr, sizeof(struct sockaddr_nl))`—Binds a socket to a Netlink
+/// address.
+///
+/// # References
+///  - [Linux]
+///
+/// [Linux]: https://man7.org/linux/man-pages/man2/bind.2.html
+#[cfg(target_os = "linux")]
+#[inline]
+#[doc(alias = "bind")]
+pub fn bind_nl<Fd: AsFd>(sockfd: Fd, addr: &SocketAddrNl) -> io::Result<()> {
+    backend::net::syscalls::bind_nl(sockfd.as_fd(), addr)
+}
+
 /// `connect(sockfd, addr)`—Initiates a connection to an IP address.
 ///
 /// On Windows, a non-blocking socket returns [`Errno::WOULDBLOCK`] if the
@@ -370,6 +386,8 @@ fn _connect_any(sockfd: BorrowedFd<'_>, addr: &SocketAddrAny) -> io::Result<()>
         SocketAddrAny::Unix(unix) => backend::net::syscalls::connect_unix(sockfd, unix),
         #[cfg(target_os = "linux")]
         SocketAddrAny::Xdp(_) => Err(io::Errno::OPNOTSUPP),
+        #[cfg(target_os = "linux")]
+        SocketAddrAny::Nl(_) => Err(io::Errno::OPNOTSUPP),
     }
 }
 
diff --git a/src/net/socket_addr_any.rs b/src/net/socket_addr_any.rs
index 3be80a3ad..86e22ddc4 100644
--- a/src/net/socket_addr_any.rs
+++ b/src/net/socket_addr_any.rs
@@ -9,10 +9,10 @@
 //! OS-specific socket address representations in memory.
 #![allow(unsafe_code)]
 
-#[cfg(target_os = "linux")]
-use crate::net::xdp::SocketAddrXdp;
 #[cfg(unix)]
 use crate::net::SocketAddrUnix;
+#[cfg(target_os = "linux")]
+use crate::net::{netlink::SocketAddrNl, xdp::SocketAddrXdp};
 use crate::net::{AddressFamily, SocketAddr, SocketAddrV4, SocketAddrV6};
 use crate::{backend, io};
 #[cfg(feature = "std")]
@@ -35,6 +35,9 @@ pub enum SocketAddrAny {
     /// `struct sockaddr_xdp`
     #[cfg(target_os = "linux")]
     Xdp(SocketAddrXdp),
+    /// `struct sockaddr_nl`
+    #[cfg(target_os = "linux")]
+    Nl(SocketAddrNl),
 }
 
 impl From<SocketAddr> for SocketAddrAny {
@@ -80,6 +83,8 @@ impl SocketAddrAny {
             Self::Unix(_) => AddressFamily::UNIX,
             #[cfg(target_os = "linux")]
             Self::Xdp(_) => AddressFamily::XDP,
+            #[cfg(target_os = "linux")]
+            Self::Nl(_) => AddressFamily::NETLINK,
         }
     }
 
@@ -117,6 +122,8 @@ impl fmt::Debug for SocketAddrAny {
             Self::Unix(unix) => unix.fmt(fmt),
             #[cfg(target_os = "linux")]
             Self::Xdp(xdp) => xdp.fmt(fmt),
+            #[cfg(target_os = "linux")]
+            Self::Nl(nl) => nl.fmt(fmt),
         }
     }
 }
diff --git a/src/net/types.rs b/src/net/types.rs
index 12e228e10..0450ba687 100644
--- a/src/net/types.rs
+++ b/src/net/types.rs
@@ -1051,6 +1051,52 @@ pub mod netlink {
     /// `NETLINK_GET_STRICT_CHK`
     #[cfg(linux_kernel)]
     pub const GET_STRICT_CHK: Protocol = Protocol(new_raw_protocol(c::NETLINK_GET_STRICT_CHK as _));
+
+    /// A Netlink socket address.
+    ///
+    /// Used to bind to Netlink socket.
+    ///
+    /// Not ABI compatible with `struct sockaddr_nl`
+    #[derive(Clone, PartialEq, PartialOrd, Eq, Ord, Hash, Debug)]
+    pub struct SocketAddrNl {
+        nl_pid: u32,
+        nl_groups: u32,
+    }
+
+    impl SocketAddrNl {
+        /// Construct a new NETLINK address.
+        #[inline]
+        pub fn new(pid: u32, groups: u32) -> Self {
+            Self {
+                nl_pid: pid,
+                nl_groups: groups,
+            }
+        }
+
+        /// Return port ID.
+        #[inline]
+        pub fn pid(&self) -> u32 {
+            self.nl_pid
+        }
+
+        /// Set port ID.
+        #[inline]
+        pub fn set_pid(&mut self, pid: u32) {
+            self.nl_pid = pid;
+        }
+
+        /// Return multicast group mask.
+        #[inline]
+        pub fn groups(&self) -> u32 {
+            self.nl_groups
+        }
+
+        /// Set multicast group mask.
+        #[inline]
+        pub fn set_groups(&mut self, groups: u32) {
+            self.nl_groups = groups;
+        }
+    }
 }
 
 /// `ETH_P_*` constants.