]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Add dhcp client prefix lease information to networkd json output 29023/head
authorpelaufer <paul@laufernet.com>
Sat, 2 Sep 2023 18:20:44 +0000 (12:20 -0600)
committerpelaufer <paul@laufernet.com>
Sat, 2 Sep 2023 19:45:03 +0000 (13:45 -0600)
src/libsystemd-network/sd-dhcp-client.c
src/network/networkd-json.c
src/systemd/sd-dhcp-client.h
test/test-network/systemd-networkd-tests.py

index 21b6592037405bb0a2195d5e7c8ceb068d1ab9a1..dd222edd8205bdec8a25881c3c59286d51d4edea 100644 (file)
@@ -701,6 +701,18 @@ int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) {
         return 0;
 }
 
+int sd_dhcp_client_get_lease_timestamp(sd_dhcp_client *client, uint64_t *timestamp) {
+        assert_return(client, -EINVAL);
+
+        if (!IN_SET(client->state, DHCP_STATE_BOUND, DHCP_STATE_RENEWING, DHCP_STATE_REBINDING))
+                return -ENODATA;
+
+        if(timestamp)
+                *timestamp = client->request_sent;
+
+        return 0;
+}
+
 int sd_dhcp_client_set_service_type(sd_dhcp_client *client, int type) {
         assert_return(client, -EINVAL);
         assert_return(!sd_dhcp_client_is_running(client), -EBUSY);
index a9e33ac627ec44b2069cd2d1994bf67bb3245be3..86488c981691fecf30980599e36ab178aeb1b536 100644 (file)
@@ -9,6 +9,7 @@
 #include "netif-util.h"
 #include "networkd-address.h"
 #include "networkd-dhcp-common.h"
+#include "networkd-dhcp-prefix-delegation.h"
 #include "networkd-json.h"
 #include "networkd-link.h"
 #include "networkd-manager.h"
@@ -1016,6 +1017,7 @@ static int dhcp_server_append_json(Link *link, JsonVariant **v) {
 
 static int dhcp6_client_lease_append_json(Link *link, JsonVariant **v) {
         _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        uint64_t lease_timestamp_usec;
         int r;
 
         assert(link);
@@ -1024,15 +1026,59 @@ static int dhcp6_client_lease_append_json(Link *link, JsonVariant **v) {
         if (!link->dhcp6_lease)
                 return 0;
 
+        r = sd_dhcp6_lease_get_timestamp(link->dhcp6_lease, CLOCK_BOOTTIME, &lease_timestamp_usec);
+        if (r < 0)
+                return 0;
+
         r = json_build(&w, JSON_BUILD_OBJECT(
-                                JSON_BUILD_PAIR_FINITE_USEC("T1", link->dhcp6_lease->lifetime_t1),
-                                JSON_BUILD_PAIR_FINITE_USEC("T2", link->dhcp6_lease->lifetime_t2)));
+                                JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec",
+                                        link->dhcp6_lease->lifetime_t1 + lease_timestamp_usec),
+                                JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec",
+                                        link->dhcp6_lease->lifetime_t2 + lease_timestamp_usec),
+                                JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec",
+                                        lease_timestamp_usec)));
         if (r < 0)
                 return r;
 
         return json_append_one(v, "Lease", w);
 }
 
+static int dhcp6_client_pd_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+        struct in6_addr prefix;
+        uint32_t lifetime_preferred, lifetime_valid;
+        uint8_t prefix_len;
+        uint64_t lease_timestamp_usec;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(v);
+
+        if (!link->network->dhcp6_use_pd_prefix || !link->dhcp6_lease || !dhcp6_lease_has_pd_prefix(link->dhcp6_lease))
+                return 0;
+
+        sd_dhcp6_lease_reset_pd_prefix_iter(link->dhcp6_lease);
+
+        r = sd_dhcp6_lease_get_timestamp(link->dhcp6_lease, CLOCK_BOOTTIME, &lease_timestamp_usec);
+        if (r < 0)
+                return 0;
+
+        while (sd_dhcp6_lease_get_pd(link->dhcp6_lease, &prefix, &prefix_len, &lifetime_preferred, &lifetime_valid) >= 0) {
+                r = json_variant_append_arrayb(&array, JSON_BUILD_OBJECT(
+                                               JSON_BUILD_PAIR_IN6_ADDR("Prefix", &prefix),
+                                               JSON_BUILD_PAIR_UNSIGNED("PrefixLength", prefix_len),
+                                               JSON_BUILD_PAIR_FINITE_USEC("PreferredLifetimeUSec",
+                                                                           sec_to_usec(lifetime_preferred, lease_timestamp_usec)),
+                                               JSON_BUILD_PAIR_FINITE_USEC("ValidLifetimeUSec",
+                                                                           sec_to_usec(lifetime_valid, lease_timestamp_usec))));
+                if (r < 0)
+                        return r;
+        }
+
+        return json_append_one(v, "Prefixes", array);
+}
+
 static int dhcp6_client_append_json(Link *link, JsonVariant **v) {
         _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
         int r;
@@ -1047,9 +1093,106 @@ static int dhcp6_client_append_json(Link *link, JsonVariant **v) {
         if (r < 0)
                 return r;
 
+        r = dhcp6_client_pd_append_json(link, &w);
+        if (r < 0)
+                return r;
+
         return json_append_one(v, "DHCPv6Client", w);
 }
 
+static int dhcp_client_lease_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        uint32_t t1, t2;
+        uint64_t lease_timestamp_usec;
+        int r;
+
+        assert(link);
+        assert(v);
+
+        if (!link->dhcp_client || !link->dhcp_lease)
+                return 0;
+
+        r = sd_dhcp_lease_get_t1(link->dhcp_lease, &t1);
+        if (r < 0)
+                return 0;
+
+        r = sd_dhcp_lease_get_t2(link->dhcp_lease, &t2);
+        if (r < 0)
+                return 0;
+
+        r = sd_dhcp_client_get_lease_timestamp(link->dhcp_client, &lease_timestamp_usec);
+        if (r < 0)
+                return 0;
+
+        r = json_build(&w, JSON_BUILD_OBJECT(
+                                JSON_BUILD_PAIR_FINITE_USEC("Timeout1USec",
+                                                            sec_to_usec(t1, lease_timestamp_usec)),
+                                JSON_BUILD_PAIR_FINITE_USEC("Timeout2USec",
+                                                            sec_to_usec(t2, lease_timestamp_usec)),
+                                JSON_BUILD_PAIR_FINITE_USEC("LeaseTimestampUSec", lease_timestamp_usec)));
+        if (r < 0)
+                return r;
+
+        return json_append_one(v, "Lease", w);
+}
+
+static int dhcp_client_pd_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *addresses = NULL, *array = NULL;
+        uint8_t ipv4masklen, sixrd_prefixlen;
+        struct in6_addr sixrd_prefix;
+        const struct in_addr *br_addresses;
+        size_t n_br_addresses = 0;
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(v);
+
+        if (!link->network->dhcp_use_6rd || !link->dhcp_lease || !dhcp4_lease_has_pd_prefix(link->dhcp_lease))
+                return 0;
+
+        r = sd_dhcp_lease_get_6rd(link->dhcp_lease, &ipv4masklen, &sixrd_prefixlen, &sixrd_prefix, &br_addresses, &n_br_addresses);
+        if (r < 0)
+                return r;
+
+        FOREACH_ARRAY(br_address, br_addresses, n_br_addresses) {
+                r = json_variant_append_arrayb(&addresses, JSON_BUILD_IN4_ADDR(br_address));
+                if (r < 0)
+                        return r;
+        }
+
+        r = json_build(&array, JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR_IN6_ADDR("Prefix", &sixrd_prefix),
+                                       JSON_BUILD_PAIR_UNSIGNED("PrefixLength", sixrd_prefixlen),
+                                       JSON_BUILD_PAIR_UNSIGNED("IPv4MaskLength", ipv4masklen),
+                                       JSON_BUILD_PAIR_VARIANT_NON_NULL("BorderRouters", addresses)));
+        if (r < 0)
+                return r;
+
+        return json_append_one(v, "6rdPrefix", array);
+}
+
+static int dhcp_client_append_json(Link *link, JsonVariant **v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        int r;
+
+        assert(link);
+        assert(v);
+
+        if (!link->dhcp_client)
+                return 0;
+
+        r = dhcp_client_lease_append_json(link, &w);
+        if (r < 0)
+                return r;
+
+        r = dhcp_client_pd_append_json(link, &w);
+        if (r < 0)
+                return r;
+
+        return json_append_one(v, "DHCPv4Client", w);
+}
+
 int link_build_json(Link *link, JsonVariant **ret) {
         _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
         _cleanup_free_ char *type = NULL, *flags = NULL;
@@ -1165,6 +1308,10 @@ int link_build_json(Link *link, JsonVariant **ret) {
         if (r < 0)
                 return r;
 
+        r = dhcp_client_append_json(link, &v);
+        if (r < 0)
+                return r;
+
         r = dhcp6_client_append_json(link, &v);
         if (r < 0)
                 return r;
index ddd445c9f0556ce69b9c2647fd33e7e9f7a85af2..45ab8765d25534c229bc401f2bb3730c0b3bec17 100644 (file)
@@ -329,6 +329,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client);
 int sd_dhcp_client_send_release(sd_dhcp_client *client);
 int sd_dhcp_client_send_decline(sd_dhcp_client *client);
 int sd_dhcp_client_send_renew(sd_dhcp_client *client);
+int sd_dhcp_client_get_lease_timestamp(sd_dhcp_client *client, uint64_t *timestamp);
 
 sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client);
 sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client);
index bc60e9385045699bbaf9a5bf3f2522ba11033ec7..03287f21bd1ac7793642f5e84e7899c014954aec 100755 (executable)
@@ -5672,6 +5672,38 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         tear_down_common()
 
     def test_dhcp6pd(self):
+        def get_dbus_dhcp6_prefix(IF):
+            # busctl call org.freedesktop.network1 /org/freedesktop/network1 org.freedesktop.network1.Manager GetLinkByName s IF
+            out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1',
+                                           '/org/freedesktop/network1', 'org.freedesktop.network1.Manager',
+                                           'GetLinkByName', 's', IF])
+
+            assert out.startswith(b'io ')
+            out = out.strip()
+            assert out.endswith(b'"')
+            out = out.decode()
+            linkPath = out[:-1].split('"')[1]
+
+            print(f"Found {IF} link path: {linkPath}")
+
+            out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1',
+                                           linkPath, 'org.freedesktop.network1.Link', 'Describe'])
+            assert out.startswith(b's "')
+            out = out.strip()
+            assert out.endswith(b'"')
+            json_raw = out[2:].decode()
+            check_json(json_raw)
+            description = json.loads(json_raw) # Convert from escaped sequences to json
+            check_json(description)
+            description = json.loads(description) # Now parse the json
+
+            self.assertIn('DHCPv6Client', description.keys())
+            self.assertIn('Prefixes', description['DHCPv6Client'])
+
+            prefixInfo = description['DHCPv6Client']['Prefixes']
+
+            return prefixInfo
+
         copy_network_unit('25-veth.netdev', '25-dhcp6pd-server.network', '25-dhcp6pd-upstream.network',
                           '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
                           '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
@@ -5690,6 +5722,22 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.wait_online(['veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded',
                           'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable'])
 
+        # Check DBus assigned prefix information to veth99
+        prefixInfo = get_dbus_dhcp6_prefix('veth99')
+
+        self.assertEqual(len(prefixInfo), 1)
+        prefixInfo = prefixInfo[0]
+
+        self.assertIn('Prefix', prefixInfo.keys())
+        self.assertIn('PrefixLength', prefixInfo.keys())
+        self.assertIn('PreferredLifetimeUSec', prefixInfo.keys())
+        self.assertIn('ValidLifetimeUSec', prefixInfo.keys())
+
+        self.assertEqual(prefixInfo['Prefix'][0:6], [63, 254, 5, 1, 255, 255])
+        self.assertEqual(prefixInfo['PrefixLength'], 56)
+        self.assertGreater(prefixInfo['PreferredLifetimeUSec'], 0)
+        self.assertGreater(prefixInfo['ValidLifetimeUSec'], 0)
+
         print('### ip -6 address show dev veth-peer scope global')
         output = check_output('ip -6 address show dev veth-peer scope global')
         print(output)
@@ -6058,6 +6106,41 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.assertIn(f'via ::10.0.0.1 dev {tunnel_name}', output)
 
     def test_dhcp4_6rd(self):
+        def get_dbus_dhcp_6rd_prefix(IF):
+            out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1',
+                                           '/org/freedesktop/network1', 'org.freedesktop.network1.Manager',
+                                           'GetLinkByName', 's', IF])
+
+            assert out.startswith(b'io ')
+            out = out.strip()
+            assert out.endswith(b'"')
+            out = out.decode()
+            linkPath = out[:-1].split('"')[1]
+
+            print(f"Found {IF} link path: {linkPath}")
+
+            out = subprocess.check_output(['busctl', 'call', 'org.freedesktop.network1',
+                                           linkPath, 'org.freedesktop.network1.Link', 'Describe'])
+            assert out.startswith(b's "')
+            out = out.strip()
+            assert out.endswith(b'"')
+            json_raw = out[2:].decode()
+            check_json(json_raw)
+            description = json.loads(json_raw) # Convert from escaped sequences to json
+            check_json(description)
+            description = json.loads(description) # Now parse the json
+
+            self.assertIn('DHCPv4Client', description.keys())
+            self.assertIn('6rdPrefix', description['DHCPv4Client'].keys())
+
+            prefixInfo = description['DHCPv4Client']['6rdPrefix']
+            self.assertIn('Prefix', prefixInfo.keys())
+            self.assertIn('PrefixLength', prefixInfo.keys())
+            self.assertIn('IPv4MaskLength', prefixInfo.keys())
+            self.assertIn('BorderRouters', prefixInfo.keys())
+
+            return prefixInfo
+
         copy_network_unit('25-veth.netdev', '25-dhcp4-6rd-server.network', '25-dhcp4-6rd-upstream.network',
                           '25-veth-downstream-veth97.netdev', '25-dhcp-pd-downstream-veth97.network', '25-dhcp-pd-downstream-veth97-peer.network',
                           '25-veth-downstream-veth98.netdev', '25-dhcp-pd-downstream-veth98.network', '25-dhcp-pd-downstream-veth98-peer.network',
@@ -6080,6 +6163,14 @@ class NetworkdDHCPPDTests(unittest.TestCase, Utilities):
         self.wait_online(['veth99:routable', 'test1:routable', 'dummy98:routable', 'dummy99:degraded',
                           'veth97:routable', 'veth97-peer:routable', 'veth98:routable', 'veth98-peer:routable'])
 
+        # Check the DBus interface for assigned prefix information
+        prefixInfo = get_dbus_dhcp_6rd_prefix('veth99')
+
+        self.assertEqual(prefixInfo['Prefix'], [32,1,13,184,0,0,0,0,0,0,0,0,0,0,0,0]) # 2001:db8::
+        self.assertEqual(prefixInfo['PrefixLength'], 32)
+        self.assertEqual(prefixInfo['IPv4MaskLength'], 8)
+        self.assertEqual(prefixInfo['BorderRouters'], [[10,0,0,1]])
+
         # Test case for a downstream which appears later
         check_output('ip link add dummy97 type dummy')
         self.wait_online(['dummy97:routable'])