diff --git a/man/networkctl.xml b/man/networkctl.xml
index 561eb93238..b436797999 100644
--- a/man/networkctl.xml
+++ b/man/networkctl.xml
@@ -287,6 +287,122 @@
+
+
+ dhcp-lease
+ INTERFACE
+ CODE:FORMAT
+ …
+
+
+
+ Show the DHCP message of the acquired lease (i.e. the received DHCPACK message). Takes an
+ interface name and optional DHCP option codes. If no option code is specified, both the DHCP
+ message header and all options are shown. If one or more option codes are specified, only the
+ specified options are shown.
+
+ When called with the option, the entire DHCP message is shown in
+ JSON format. In that case, any specified option codes are ignored.
+
+ By default, each option is displayed in a human-readable format, depending on the option.
+ For example, DNS server addresses are shown as a list of IP addresses (e.g.
+ 192.0.2.1), and the lease time is shown as a human-readable duration (e.g.
+ 8h).
+
+ Each option code may optionally be followed by a formatter, separated by a colon (e.g.
+ 42:address). This can be useful if the default formatting is not appropriate.
+
+
+ The following formatters are supported:
+
+
+
+
+ Use the default formatting.
+
+
+
+
+
+ Show the raw bytes in hexadecimal, separated by colons.
+
+
+
+
+
+ Treat the option as having zero-length data and display yes.
+
+
+
+
+
+ Treat the option as a 1-byte boolean and display yes or no.
+
+
+
+
+
+ Treat the option as a 1-byte unsigned integer.
+
+
+
+
+
+ Treat the option as a 2-byte unsigned integer.
+
+
+
+
+
+ Treat the option as a 4-byte time value in seconds and display it as a human-readable duration.
+
+
+
+
+
+ Treat the option as a string.
+
+
+
+
+
+ Treat the option as one or more IPv4 addresses and display them in human-readable form.
+
+
+
+
+ Produces output similar to:
+ $ networkctl dhcp-lease eth0
+Header:
+ KEY VALUE
+ Hardware Type: ETHER
+Hardware Address: 41:42:43:31:32:33
+ Client Address: 192.0.2.123
+ Server Address: 192.0.2.1
+
+Options:
+CODE NAME DATA
+ 1 subnet mask 255.255.255.0
+ 3 router 192.0.2.1
+ 6 domain name server 192.0.2.1
+ 12 hostname test-node
+ 15 domain name lan
+ 28 broadcast address 192.0.2.255
+ 42 NTP server 192.0.2.11
+ 192.0.2.12
+ 51 lease time 1d
+ 53 message type 5
+ 54 server identifier 192.0.2.1
+ 58 renewal time 11h 5min 38s
+ 59 rebinding time 20h 5min 38s
+ 119 domain search hoge.example.com
+ foo.example.com
+
+
+
+
+
+
lldp
diff --git a/shell-completion/bash/networkctl b/shell-completion/bash/networkctl
index c16768079e..fe186a49a2 100644
--- a/shell-completion/bash/networkctl
+++ b/shell-completion/bash/networkctl
@@ -51,7 +51,7 @@ _networkctl() {
local -A VERBS=(
[STANDALONE]='label reload'
- [LINKS]='status list lldp delete renew up down forcerenew reconfigure'
+ [LINKS]='status dhcp-lease list lldp delete renew up down forcerenew reconfigure'
[FILES_OR_LINKS]='edit cat'
[FILES]='mask unmask'
[BOOL]='persistent-storage'
diff --git a/shell-completion/zsh/_networkctl b/shell-completion/zsh/_networkctl
index cf072c0fcb..c44b346949 100644
--- a/shell-completion/zsh/_networkctl
+++ b/shell-completion/zsh/_networkctl
@@ -7,6 +7,7 @@
_networkctl_cmds=(
'list:List existing links'
'status:Show information about the specified links'
+ 'dhcp-lease:Show DHCP lease'
'lldp:Show Link Layer Discovery Protocol status'
'label:Show address labels'
'delete:Delete virtual netdevs'
@@ -26,7 +27,7 @@
local -a _links
cmd="${${_networkctl_cmds[(r)$words[1]:*]%%:*}}"
case $cmd in
- (list|status|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure)
+ (list|status|dhcp-lease|up|down|cat|edit|lldp|delete|renew|forcerenew|reconfigure)
for link in ${(f)"$(_call_program links networkctl list --no-legend)"}; do _links+=($link[(w)2]:$link); done
if [[ -n "$_links" ]]; then
_describe -t links 'links' _links $( [[ $cmd == (edit|cat) ]] && print -- -P@ )
diff --git a/src/network/meson.build b/src/network/meson.build
index 7370d011d0..0319102492 100644
--- a/src/network/meson.build
+++ b/src/network/meson.build
@@ -128,6 +128,7 @@ networkctl_sources = files(
'networkctl-address-label.c',
'networkctl-config-file.c',
'networkctl-description.c',
+ 'networkctl-dhcp-lease.c',
'networkctl-dump-util.c',
'networkctl-journal.c',
'networkctl-link-info.c',
diff --git a/src/network/networkctl-dhcp-lease.c b/src/network/networkctl-dhcp-lease.c
new file mode 100644
index 0000000000..465bff0979
--- /dev/null
+++ b/src/network/networkctl-dhcp-lease.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-json.h"
+#include "sd-netlink.h"
+#include "sd-varlink.h"
+
+#include "dhcp-message-dump.h"
+#include "log.h"
+#include "networkctl.h"
+#include "networkctl-dhcp-lease.h"
+#include "networkctl-link-info.h"
+#include "networkctl-util.h"
+#include "strv.h"
+
+int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata) {
+ int r;
+
+ /* networkctl dhcp-lease INTERFACE [CODE[:FORMAT] ...] */
+ assert(argc >= 2);
+
+ pager_open(arg_pager_flags);
+
+ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL;
+ r = sd_netlink_open(&rtnl);
+ if (r < 0)
+ return log_error_errno(r, "Failed to connect to netlink: %m");
+
+ _cleanup_(sd_varlink_flush_close_unrefp) sd_varlink *vl = NULL;
+ r = varlink_connect_networkd(&vl);
+ if (r < 0)
+ return r;
+
+ const char *ifname = argv[1];
+
+ _cleanup_(link_info_array_freep) LinkInfo *link = NULL;
+ r = acquire_link_info(vl, rtnl, STRV_MAKE(ifname), &link);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EINVAL; /* already logged in acquire_link_info(). */
+ if (r > 1)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Interface name '%s' matches multiple interfaces.", ifname);
+
+ if (!link->dhcp_message)
+ return log_error_errno(SYNTHETIC_ERRNO(ENODATA),
+ "Interface '%s' does not have DHCPv4 lease.", link->name);
+
+ if (sd_json_format_enabled(arg_json_format_flags)) {
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ r = dhcp_message_build_json(link->dhcp_message, &v);
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON variant from DHCP message: %m");
+
+ r = sd_json_variant_dump(v, arg_json_format_flags, /* f= */ NULL, /* prefix= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to dump JSON variant: %m");
+
+ return 0;
+ }
+
+ DumpDHCPMessageFlag flags = 0;
+ SET_FLAG(flags, DUMP_DHCP_MESSAGE_LEGEND, arg_legend);
+ SET_FLAG(flags, DUMP_DHCP_MESSAGE_FULL, arg_full);
+
+ return dump_dhcp_message(link->dhcp_message, strv_skip(argv, 2), flags);
+}
diff --git a/src/network/networkctl-dhcp-lease.h b/src/network/networkctl-dhcp-lease.h
new file mode 100644
index 0000000000..f068df3b5c
--- /dev/null
+++ b/src/network/networkctl-dhcp-lease.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "shared-forward.h"
+
+int verb_dhcp_lease(int argc, char *argv[], uintptr_t _data, void *userdata);
diff --git a/src/network/networkctl.c b/src/network/networkctl.c
index bdf487bf2e..bf74f10a4a 100644
--- a/src/network/networkctl.c
+++ b/src/network/networkctl.c
@@ -12,6 +12,7 @@
#include "networkctl.h"
#include "networkctl-address-label.h"
#include "networkctl-config-file.h"
+#include "networkctl-dhcp-lease.h"
#include "networkctl-list.h"
#include "networkctl-lldp.h"
#include "networkctl-misc.h"
@@ -42,6 +43,8 @@ VERB_SCOPE(, verb_list_links, "list", "[PATTERN...
"List links");
VERB_SCOPE(, verb_link_status, "status", "[PATTERN...]", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY,
"Show link status");
+VERB_SCOPE(, verb_dhcp_lease, "dhcp-lease", "INTERFACE [CODE[:FORMAT]...]", 2, VERB_ANY, VERB_ONLINE_ONLY,
+ "Show DHCP lease");
VERB_SCOPE(, verb_link_lldp_status, "lldp", "[PATTERN...]", VERB_ANY, VERB_ANY, 0,
"Show LLDP neighbors");
VERB_SCOPE(, verb_list_address_labels, "label", NULL, 1, 1, 0,
diff --git a/tools/command_ignorelist b/tools/command_ignorelist
index 548acff8bd..cb256d6647 100644
--- a/tools/command_ignorelist
+++ b/tools/command_ignorelist
@@ -51,6 +51,15 @@ logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="Kill
logind.conf.xml ./refsect1[title="Options"]/variablelist/varlistentry[term="InhibitDelayMaxSec="]
machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="PRETTY_HOSTNAME="]
machine-info.xml ./refsect1[title="Options"]/refsect2[title="Machine Information"]/variablelist/varlistentry[term="ICON_NAME="]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="auto"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="hex"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="flag"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="bool"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint8"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="uint16"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="time"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="string"]
+networkctl.xml ./refsect1[title="Commands"]/variablelist/varlistentry[term="\n dhcp-lease\n INTERFACE\n CODE:FORMAT\n …\n "]/listitem/variablelist/varlistentry[term="address"]
os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="NAME="]
os-release.xml ./refsect1[title="Options"]/refsect2[title="Information about the version of the operating system"]/variablelist/varlistentry[term="VERSION="]
os-release.xml ./refsect1[title="Options"]/refsect2[title="General information identifying the operating system"]/variablelist/varlistentry[term="ID="]