From 08d5697948087ec0794f0ec0d40a511ab99c0a3d Mon Sep 17 00:00:00 2001
From: Davide Bertola <dade@dadeb.it>
Date: Fri, 6 May 2022 08:45:59 +0200
Subject: [PATCH] add unload api for programs

---
 aya/src/programs/cgroup_skb.rs              | 12 ++++-
 aya/src/programs/cgroup_sysctl.rs           | 12 ++++-
 aya/src/programs/fentry.rs                  | 12 ++++-
 aya/src/programs/fexit.rs                   | 12 ++++-
 aya/src/programs/kprobe.rs                  | 10 ++++
 aya/src/programs/links.rs                   | 11 ++--
 aya/src/programs/lirc_mode2.rs              | 13 ++++-
 aya/src/programs/lsm.rs                     | 12 ++++-
 aya/src/programs/mod.rs                     | 12 +++++
 aya/src/programs/perf_event.rs              | 10 +++-
 aya/src/programs/raw_trace_point.rs         | 12 ++++-
 aya/src/programs/sk_msg.rs                  | 12 ++++-
 aya/src/programs/sk_skb.rs                  | 12 ++++-
 aya/src/programs/sock_ops.rs                | 12 ++++-
 aya/src/programs/socket_filter.rs           | 10 +++-
 aya/src/programs/tc.rs                      | 13 ++++-
 aya/src/programs/tp_btf.rs                  | 12 ++++-
 aya/src/programs/trace_point.rs             | 10 ++++
 aya/src/programs/uprobe.rs                  | 10 +++-
 aya/src/programs/xdp.rs                     | 11 +++-
 test/cases/010_load/030_unload/test.ebpf.rs | 30 +++++++++++
 test/cases/010_load/030_unload/test.rs      | 59 +++++++++++++++++++++
 test/cases/010_load/030_unload/test.sh      | 27 ++++++++++
 23 files changed, 317 insertions(+), 29 deletions(-)
 create mode 100644 test/cases/010_load/030_unload/test.ebpf.rs
 create mode 100755 test/cases/010_load/030_unload/test.rs
 create mode 100755 test/cases/010_load/030_unload/test.sh

diff --git a/aya/src/programs/cgroup_skb.rs b/aya/src/programs/cgroup_skb.rs
index 6852407a..c64f2e15 100644
--- a/aya/src/programs/cgroup_skb.rs
+++ b/aya/src/programs/cgroup_skb.rs
@@ -10,8 +10,8 @@ use crate::{
         bpf_prog_type::BPF_PROG_TYPE_CGROUP_SKB,
     },
     programs::{
-        define_link_wrapper, load_program, FdLink, Link, OwnedLink, ProgAttachLink, ProgramData,
-        ProgramError,
+        define_link_wrapper, load_program, unload_program, FdLink, Link, OwnedLink, ProgAttachLink,
+        ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach, kernel_version},
 };
@@ -72,6 +72,14 @@ impl CgroupSkb {
         load_program(BPF_PROG_TYPE_CGROUP_SKB, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Returns the expected attach type of the program.
     ///
     /// [`CgroupSkb`] programs can specify the expected attach type in their ELF
diff --git a/aya/src/programs/cgroup_sysctl.rs b/aya/src/programs/cgroup_sysctl.rs
index 756702b7..f5330ded 100644
--- a/aya/src/programs/cgroup_sysctl.rs
+++ b/aya/src/programs/cgroup_sysctl.rs
@@ -7,8 +7,8 @@ use std::{
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_SYSCTL, bpf_prog_type::BPF_PROG_TYPE_CGROUP_SYSCTL},
     programs::{
-        define_link_wrapper, load_program, FdLink, Link, OwnedLink, ProgAttachLink, ProgramData,
-        ProgramError,
+        define_link_wrapper, load_program, unload_program, FdLink, Link, OwnedLink, ProgAttachLink,
+        ProgramData, ProgramError,
     },
     sys::{bpf_link_create, bpf_prog_attach, kernel_version},
 };
@@ -59,6 +59,14 @@ impl CgroupSysctl {
         load_program(BPF_PROG_TYPE_CGROUP_SYSCTL, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given cgroup.
     ///
     /// The returned value can be used to detach, see [CgroupSysctl::detach].
diff --git a/aya/src/programs/fentry.rs b/aya/src/programs/fentry.rs
index 86c35326..ff4d3380 100644
--- a/aya/src/programs/fentry.rs
+++ b/aya/src/programs/fentry.rs
@@ -4,8 +4,8 @@ use crate::{
     generated::{bpf_attach_type::BPF_TRACE_FENTRY, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
     programs::{
-        define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
-        OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, utils::attach_raw_tracepoint, FdLink,
+        FdLinkId, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -62,6 +62,14 @@ impl FEntry {
         load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program.
     ///
     /// The returned value can be used to detach, see [FEntry::detach].
diff --git a/aya/src/programs/fexit.rs b/aya/src/programs/fexit.rs
index 408896c2..8c9de97b 100644
--- a/aya/src/programs/fexit.rs
+++ b/aya/src/programs/fexit.rs
@@ -4,8 +4,8 @@ use crate::{
     generated::{bpf_attach_type::BPF_TRACE_FEXIT, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
     programs::{
-        define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
-        OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, utils::attach_raw_tracepoint, FdLink,
+        FdLinkId, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -62,6 +62,14 @@ impl FExit {
         load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program.
     ///
     /// The returned value can be used to detach, see [FExit::detach].
diff --git a/aya/src/programs/kprobe.rs b/aya/src/programs/kprobe.rs
index a5bf0475..3856fb20 100644
--- a/aya/src/programs/kprobe.rs
+++ b/aya/src/programs/kprobe.rs
@@ -12,6 +12,8 @@ use crate::{
     },
 };
 
+use super::unload_program;
+
 /// A kernel probe.
 ///
 /// Kernel probes are eBPF programs that can be attached to almost any function inside
@@ -49,6 +51,14 @@ impl KProbe {
         load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Returns `KProbe` if the program is a `kprobe`, or `KRetProbe` if the
     /// program is a `kretprobe`.
     pub fn kind(&self) -> ProbeKind {
diff --git a/aya/src/programs/links.rs b/aya/src/programs/links.rs
index d98b1a7e..b3ee0e06 100644
--- a/aya/src/programs/links.rs
+++ b/aya/src/programs/links.rs
@@ -79,6 +79,13 @@ impl<T: Link> LinkMap<T> {
             .detach()
     }
 
+    pub(crate) fn remove_all(&mut self) -> Result<(), ProgramError> {
+        for (_, link) in self.links.drain() {
+            link.detach()?;
+        }
+        Ok(())
+    }
+
     pub(crate) fn forget(&mut self, link_id: T::Id) -> Result<T, ProgramError> {
         self.links.remove(&link_id).ok_or(ProgramError::NotAttached)
     }
@@ -86,9 +93,7 @@ impl<T: Link> LinkMap<T> {
 
 impl<T: Link> Drop for LinkMap<T> {
     fn drop(&mut self) {
-        for (_, link) in self.links.drain() {
-            let _ = link.detach();
-        }
+        let _ = self.remove_all();
     }
 }
 
diff --git a/aya/src/programs/lirc_mode2.rs b/aya/src/programs/lirc_mode2.rs
index e210a734..e9eecf3e 100644
--- a/aya/src/programs/lirc_mode2.rs
+++ b/aya/src/programs/lirc_mode2.rs
@@ -3,7 +3,10 @@ use std::os::unix::prelude::{AsRawFd, RawFd};
 
 use crate::{
     generated::{bpf_attach_type::BPF_LIRC_MODE2, bpf_prog_type::BPF_PROG_TYPE_LIRC_MODE2},
-    programs::{load_program, query, Link, OwnedLink, ProgramData, ProgramError, ProgramInfo},
+    programs::{
+        load_program, query, unload_program, Link, OwnedLink, ProgramData, ProgramError,
+        ProgramInfo,
+    },
     sys::{bpf_obj_get_info_by_fd, bpf_prog_attach, bpf_prog_detach, bpf_prog_get_fd_by_id},
 };
 
@@ -58,6 +61,14 @@ impl LircMode2 {
         load_program(BPF_PROG_TYPE_LIRC_MODE2, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given lirc device.
     ///
     /// The returned value can be used to detach, see [LircMode2::detach].
diff --git a/aya/src/programs/lsm.rs b/aya/src/programs/lsm.rs
index 87109df9..abfc6c7b 100644
--- a/aya/src/programs/lsm.rs
+++ b/aya/src/programs/lsm.rs
@@ -3,8 +3,8 @@ use crate::{
     generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM},
     obj::btf::{Btf, BtfKind},
     programs::{
-        define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
-        OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, utils::attach_raw_tracepoint, FdLink,
+        FdLinkId, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -67,6 +67,14 @@ impl Lsm {
         load_program(BPF_PROG_TYPE_LSM, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program.
     ///
     /// The returned value can be used to detach, see [Lsm::detach].
diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs
index a148a8e2..8065770b 100644
--- a/aya/src/programs/mod.rs
+++ b/aya/src/programs/mod.rs
@@ -386,6 +386,18 @@ impl<T: Link> ProgramData<T> {
     }
 }
 
+fn unload_program<T: Link>(data: &mut ProgramData<T>, detach: bool) -> Result<(), ProgramError> {
+    if detach {
+        data.links.remove_all()?;
+    }
+    let fd = data.fd_or_err()?;
+    unsafe {
+        libc::close(fd);
+    }
+    data.fd = None;
+    Ok(())
+}
+
 fn load_program<T: Link>(
     prog_type: bpf_prog_type,
     data: &mut ProgramData<T>,
diff --git a/aya/src/programs/perf_event.rs b/aya/src/programs/perf_event.rs
index 44c15c3e..750a6a11 100644
--- a/aya/src/programs/perf_event.rs
+++ b/aya/src/programs/perf_event.rs
@@ -14,7 +14,7 @@ use crate::{
     programs::{
         load_program, perf_attach,
         perf_attach::{PerfLink, PerfLinkId},
-        OwnedLink, ProgramData, ProgramError,
+        unload_program, OwnedLink, ProgramData, ProgramError,
     },
     sys::perf_event_open,
 };
@@ -128,6 +128,14 @@ impl PerfEvent {
         load_program(BPF_PROG_TYPE_PERF_EVENT, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches to the given perf event.
     ///
     /// The possible values and encoding of the `config` argument depends on the
diff --git a/aya/src/programs/raw_trace_point.rs b/aya/src/programs/raw_trace_point.rs
index fd4656fa..2497c82f 100644
--- a/aya/src/programs/raw_trace_point.rs
+++ b/aya/src/programs/raw_trace_point.rs
@@ -4,8 +4,8 @@ use std::ffi::CString;
 use crate::{
     generated::bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT,
     programs::{
-        define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
-        OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, utils::attach_raw_tracepoint, FdLink,
+        FdLinkId, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -45,6 +45,14 @@ impl RawTracePoint {
         load_program(BPF_PROG_TYPE_RAW_TRACEPOINT, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given tracepoint.
     ///
     /// The returned value can be used to detach, see [RawTracePoint::detach].
diff --git a/aya/src/programs/sk_msg.rs b/aya/src/programs/sk_msg.rs
index 7568e3be..fbaf4893 100644
--- a/aya/src/programs/sk_msg.rs
+++ b/aya/src/programs/sk_msg.rs
@@ -3,8 +3,8 @@ use crate::{
     generated::{bpf_attach_type::BPF_SK_MSG_VERDICT, bpf_prog_type::BPF_PROG_TYPE_SK_MSG},
     maps::sock::SocketMap,
     programs::{
-        define_link_wrapper, load_program, OwnedLink, ProgAttachLink, ProgAttachLinkId,
-        ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, OwnedLink, ProgAttachLink,
+        ProgAttachLinkId, ProgramData, ProgramError,
     },
     sys::bpf_prog_attach,
 };
@@ -69,6 +69,14 @@ impl SkMsg {
         load_program(BPF_PROG_TYPE_SK_MSG, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given sockmap.
     ///
     /// The returned value can be used to detach, see [SkMsg::detach].
diff --git a/aya/src/programs/sk_skb.rs b/aya/src/programs/sk_skb.rs
index bf4f2777..433ce933 100644
--- a/aya/src/programs/sk_skb.rs
+++ b/aya/src/programs/sk_skb.rs
@@ -6,8 +6,8 @@ use crate::{
     },
     maps::sock::SocketMap,
     programs::{
-        define_link_wrapper, load_program, OwnedLink, ProgAttachLink, ProgAttachLinkId,
-        ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, OwnedLink, ProgAttachLink,
+        ProgAttachLinkId, ProgramData, ProgramError,
     },
     sys::bpf_prog_attach,
 };
@@ -62,6 +62,14 @@ impl SkSkb {
         load_program(BPF_PROG_TYPE_SK_SKB, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given socket map.
     ///
     /// The returned value can be used to detach, see [SkSkb::detach].
diff --git a/aya/src/programs/sock_ops.rs b/aya/src/programs/sock_ops.rs
index d4a4d385..77ac2d01 100644
--- a/aya/src/programs/sock_ops.rs
+++ b/aya/src/programs/sock_ops.rs
@@ -4,8 +4,8 @@ use std::os::unix::io::AsRawFd;
 use crate::{
     generated::{bpf_attach_type::BPF_CGROUP_SOCK_OPS, bpf_prog_type::BPF_PROG_TYPE_SOCK_OPS},
     programs::{
-        define_link_wrapper, load_program, OwnedLink, ProgAttachLink, ProgAttachLinkId,
-        ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, OwnedLink, ProgAttachLink,
+        ProgAttachLinkId, ProgramData, ProgramError,
     },
     sys::bpf_prog_attach,
 };
@@ -56,6 +56,14 @@ impl SockOps {
         load_program(BPF_PROG_TYPE_SOCK_OPS, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given cgroup.
     ///
     /// The returned value can be used to detach, see [SockOps::detach].
diff --git a/aya/src/programs/socket_filter.rs b/aya/src/programs/socket_filter.rs
index ef9e0830..4b43cbcb 100644
--- a/aya/src/programs/socket_filter.rs
+++ b/aya/src/programs/socket_filter.rs
@@ -8,7 +8,7 @@ use thiserror::Error;
 
 use crate::{
     generated::{bpf_prog_type::BPF_PROG_TYPE_SOCKET_FILTER, SO_ATTACH_BPF, SO_DETACH_BPF},
-    programs::{load_program, Link, OwnedLink, ProgramData, ProgramError},
+    programs::{load_program, unload_program, Link, OwnedLink, ProgramData, ProgramError},
 };
 
 /// The type returned when attaching a [`SocketFilter`] fails.
@@ -70,6 +70,14 @@ impl SocketFilter {
         load_program(BPF_PROG_TYPE_SOCKET_FILTER, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the filter on the given socket.
     ///
     /// The returned value can be used to detach from the socket, see [SocketFilter::detach].
diff --git a/aya/src/programs/tc.rs b/aya/src/programs/tc.rs
index 941ed18e..0cfe872a 100644
--- a/aya/src/programs/tc.rs
+++ b/aya/src/programs/tc.rs
@@ -10,7 +10,10 @@ use crate::{
     generated::{
         bpf_prog_type::BPF_PROG_TYPE_SCHED_CLS, TC_H_CLSACT, TC_H_MIN_EGRESS, TC_H_MIN_INGRESS,
     },
-    programs::{define_link_wrapper, load_program, Link, OwnedLink, ProgramData, ProgramError},
+    programs::{
+        define_link_wrapper, load_program, unload_program, Link, OwnedLink, ProgramData,
+        ProgramError,
+    },
     sys::{
         netlink_find_filter_with_name, netlink_qdisc_add_clsact, netlink_qdisc_attach,
         netlink_qdisc_detach,
@@ -106,6 +109,14 @@ impl SchedClassifier {
         load_program(BPF_PROG_TYPE_SCHED_CLS, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given `interface`.
     ///
     /// The returned value can be used to detach, see [SchedClassifier::detach].
diff --git a/aya/src/programs/tp_btf.rs b/aya/src/programs/tp_btf.rs
index d68785f3..9c510b6a 100644
--- a/aya/src/programs/tp_btf.rs
+++ b/aya/src/programs/tp_btf.rs
@@ -3,8 +3,8 @@ use crate::{
     generated::{bpf_attach_type::BPF_TRACE_RAW_TP, bpf_prog_type::BPF_PROG_TYPE_TRACING},
     obj::btf::{Btf, BtfKind},
     programs::{
-        define_link_wrapper, load_program, utils::attach_raw_tracepoint, FdLink, FdLinkId,
-        OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, utils::attach_raw_tracepoint, FdLink,
+        FdLinkId, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -65,6 +65,14 @@ impl BtfTracePoint {
         load_program(BPF_PROG_TYPE_TRACING, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program.
     ///
     /// The returned value can be used to detach, see [BtfTracePoint::detach].
diff --git a/aya/src/programs/trace_point.rs b/aya/src/programs/trace_point.rs
index 43d35606..12fd21a9 100644
--- a/aya/src/programs/trace_point.rs
+++ b/aya/src/programs/trace_point.rs
@@ -12,6 +12,8 @@ use crate::{
     sys::perf_event_open_trace_point,
 };
 
+use super::unload_program;
+
 /// The type returned when attaching a [`TracePoint`] fails.
 #[derive(Debug, Error)]
 pub enum TracePointError {
@@ -71,6 +73,14 @@ impl TracePoint {
         load_program(BPF_PROG_TYPE_TRACEPOINT, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches to a given trace point.
     ///
     /// For a list of the available event categories and names, see
diff --git a/aya/src/programs/uprobe.rs b/aya/src/programs/uprobe.rs
index fa1f7a9a..f458fe58 100644
--- a/aya/src/programs/uprobe.rs
+++ b/aya/src/programs/uprobe.rs
@@ -19,7 +19,7 @@ use crate::{
         define_link_wrapper, load_program,
         perf_attach::{PerfLink, PerfLinkId},
         probe::{attach, ProbeKind},
-        OwnedLink, ProgramData, ProgramError,
+        unload_program, OwnedLink, ProgramData, ProgramError,
     },
 };
 
@@ -51,6 +51,14 @@ impl UProbe {
         load_program(BPF_PROG_TYPE_KPROBE, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Returns `UProbe` if the program is a `uprobe`, or `URetProbe` if the
     /// program is a `uretprobe`.
     pub fn kind(&self) -> ProbeKind {
diff --git a/aya/src/programs/xdp.rs b/aya/src/programs/xdp.rs
index 713d4b7d..fe6ef4d8 100644
--- a/aya/src/programs/xdp.rs
+++ b/aya/src/programs/xdp.rs
@@ -12,7 +12,8 @@ use crate::{
         XDP_FLAGS_UPDATE_IF_NOEXIST,
     },
     programs::{
-        define_link_wrapper, load_program, FdLink, Link, OwnedLink, ProgramData, ProgramError,
+        define_link_wrapper, load_program, unload_program, FdLink, Link, OwnedLink, ProgramData,
+        ProgramError,
     },
     sys::{bpf_link_create, kernel_version, netlink_set_xdp_fd},
 };
@@ -81,6 +82,14 @@ impl Xdp {
         load_program(BPF_PROG_TYPE_XDP, &mut self.data)
     }
 
+    /// Unloads the program from the kernel.
+    ///
+    /// If `detach` is true, links will be detached before unloading the program.
+    /// Note that OwnedLinks you obtained using [KProbe::forget_link] will not be detached.
+    pub fn unload(&mut self, detach: bool) -> Result<(), ProgramError> {
+        unload_program(&mut self.data, detach)
+    }
+
     /// Attaches the program to the given `interface`.
     ///
     /// The returned value can be used to detach, see [Xdp::detach].
diff --git a/test/cases/010_load/030_unload/test.ebpf.rs b/test/cases/010_load/030_unload/test.ebpf.rs
new file mode 100644
index 00000000..e3cb6d21
--- /dev/null
+++ b/test/cases/010_load/030_unload/test.ebpf.rs
@@ -0,0 +1,30 @@
+//! ```cargo
+//! [dependencies]
+//! aya-bpf = { path = "../../../../bpf/aya-bpf" }
+//! ```
+
+#![no_std]
+#![no_main]
+
+use aya_bpf::{
+    bindings::xdp_action,
+    macros::xdp,
+    programs::XdpContext,
+};
+
+#[xdp(name="ihaveaverylongname")]
+pub fn pass(ctx: XdpContext) -> u32 {
+    match unsafe { try_pass(ctx) } {
+        Ok(ret) => ret,
+        Err(_) => xdp_action::XDP_ABORTED,
+    }
+}
+
+unsafe fn try_pass(_ctx: XdpContext) -> Result<u32, u32> {
+    Ok(xdp_action::XDP_PASS)
+}
+
+#[panic_handler]
+fn panic(_info: &core::panic::PanicInfo) -> ! {
+    unsafe { core::hint::unreachable_unchecked() }
+}
\ No newline at end of file
diff --git a/test/cases/010_load/030_unload/test.rs b/test/cases/010_load/030_unload/test.rs
new file mode 100755
index 00000000..c74dc5fa
--- /dev/null
+++ b/test/cases/010_load/030_unload/test.rs
@@ -0,0 +1,59 @@
+//! ```cargo
+//! [dependencies]
+//! aya = { path = "../../../../aya" }
+//! ```
+
+use aya::{
+    programs::{Xdp, XdpFlags},
+    Bpf,
+};
+use std::convert::TryInto;
+use std::process::Command;
+
+fn is_loaded() -> bool {
+    let output = Command::new("bpftool").args(&["prog"]).output().unwrap();
+    let stdout = String::from_utf8(output.stdout).unwrap();
+    stdout.contains("xdp  name ihaveaverylongn  tag")
+}
+
+fn assert_loaded(loaded: bool) {
+    let state = is_loaded();
+    if state == loaded {
+        return;
+    }
+    panic!("Expected loaded: {} but was loaded: {}", loaded, state);
+}
+
+fn main() {
+    println!("Loading XDP program");
+    let mut bpf = Bpf::load_file("test.o").unwrap();
+    let dispatcher: &mut Xdp = bpf
+        .program_mut("ihaveaverylongname")
+        .unwrap()
+        .try_into()
+        .unwrap();
+
+    dispatcher.load().unwrap();
+
+    let link = dispatcher.attach("eth0", XdpFlags::default()).unwrap();
+
+    dispatcher.unload(false).unwrap();
+
+    assert_loaded(true);
+
+    dispatcher.detach(link).unwrap();
+
+    assert_loaded(false);
+
+    dispatcher.load().unwrap();
+
+    assert_loaded(true);
+
+    dispatcher.attach("eth0", XdpFlags::default()).unwrap();
+
+    assert_loaded(true);
+
+    dispatcher.unload(true).unwrap();
+
+    assert_loaded(false);
+}
diff --git a/test/cases/010_load/030_unload/test.sh b/test/cases/010_load/030_unload/test.sh
new file mode 100755
index 00000000..05a42c56
--- /dev/null
+++ b/test/cases/010_load/030_unload/test.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+# SUMMARY: Check that long names are properly truncated
+# LABELS:
+
+set -ex
+
+# Source libraries. Uncomment if needed/defined
+#. "${RT_LIB}"
+. "${RT_PROJECT_ROOT}/_lib/lib.sh"
+
+NAME=test
+
+clean_up() {
+    rm -rf ebpf user ${NAME}.o ${NAME}
+    exec_vm rm ${NAME} ${NAME}.o
+}
+
+trap clean_up EXIT
+
+# Test code goes here
+compile_ebpf ${NAME}.ebpf.rs
+compile_user ${NAME}.rs
+
+scp_vm ${NAME}.o
+scp_vm ${NAME}
+
+exec_vm sudo ./${NAME}
\ No newline at end of file