]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
client: add show interfaces command (#240)
authorSteven Webster <steven.webster@windriver.com>
Wed, 30 Aug 2017 05:43:54 +0000 (01:43 -0400)
committerVincent Bernat <vincent@bernat.im>
Wed, 30 Aug 2017 05:43:54 +0000 (07:43 +0200)
There are several lldpcli configure commands that take port name(s)
as an optional parameter.

Currently, it is not possible to verify interface specific configuration
for lldpd without access to a neighbour switch/host or packet capture.

It would be useful to have a show interfaces command, similar to the
show neighbors command that allows a user/agent to query the local
TLV information.

This commit leverages the existing port display infrastructure to
enable display of local interfaces.  There are a few differences:

- Do not display the chassis RID, as the local index is always 0
- Show TTL as a seperate TLV based on tx_hold, tx_interval config,
  as internally the port TTL atom info is 0, and the chassis TTL
  atom info has been depreciated.

src/client/client.h
src/client/display.c
src/client/lldpcli.8.in
src/client/show.c
src/daemon/lldpd.c
tests/integration/test_basic.py
tests/integration/test_lldpcli.py

index e3ee35250411f1bbcd62b9380e4ac9460ea5fb6b..8da3e3fed800d0ba139a035a3dfae75d6820904f 100644 (file)
@@ -131,6 +131,8 @@ void display_interfaces_stats(lldpctl_conn_t *, struct writer *,
     struct cmd_env *);
 void display_interface_stats(lldpctl_conn_t *, struct writer *,
     lldpctl_atom_t *);
+void display_local_interfaces(lldpctl_conn_t *, struct writer *,
+    struct cmd_env *, int, int);
 
 
 
index cbd0e31a19b9e774795ed1583747a212a075c90c..27698903dc1be2aa5787fe040cd5b081caf7e2cf 100644 (file)
@@ -349,7 +349,8 @@ display_port(struct writer *w, lldpctl_atom_t *port, int details)
 
        tag_datatag(w, "descr", "PortDescr",
            lldpctl_atom_get_str(port, lldpctl_k_port_descr));
-       if (details)
+       if (details &&
+           lldpctl_atom_get_int(port, lldpctl_k_port_ttl) > 0)
                tag_datatag(w, "ttl", "TTL",
                    lldpctl_atom_get_str(port, lldpctl_k_port_ttl));
 
@@ -473,6 +474,38 @@ display_port(struct writer *w, lldpctl_atom_t *port, int details)
        tag_end(w);
 }
 
+static void
+display_local_ttl(struct writer *w, lldpctl_conn_t *conn, int details)
+{
+       char *ttl;
+       long int tx_hold;
+       long int tx_interval;
+
+       lldpctl_atom_t *configuration;
+       configuration = lldpctl_get_configuration(conn);
+       if (!configuration) {
+               log_warnx("lldpctl", "not able to get configuration. %s",
+                   lldpctl_last_strerror(conn));
+               return;
+       }
+
+       tx_hold = lldpctl_atom_get_int(configuration, lldpctl_k_config_tx_hold);
+       tx_interval = lldpctl_atom_get_int(configuration, lldpctl_k_config_tx_interval);
+
+       if (asprintf(&ttl, "%lu", tx_hold*tx_interval) == -1) {
+               log_warnx("lldpctl", "not enough memory to build TTL.");
+               goto end;
+       }
+
+       tag_start(w, "ttl", "TTL");
+       tag_attr(w, "ttl", "", ttl);
+       tag_end(w);
+       free(ttl);
+end:
+       lldpctl_atom_dec_ref(configuration);
+       return;
+}
+
 static void
 display_vlans(struct writer *w, lldpctl_atom_t *port)
 {
@@ -582,43 +615,51 @@ display_local_chassis(lldpctl_conn_t *conn, struct writer *w,
 
 void
 display_interface(lldpctl_conn_t *conn, struct writer *w, int hidden,
-    lldpctl_atom_t *iface, lldpctl_atom_t *neighbor, int details, int protocol)
+    lldpctl_atom_t *iface, lldpctl_atom_t *port, int details, int protocol)
 {
+       int local = 0;
+
        if (!hidden &&
-           lldpctl_atom_get_int(neighbor, lldpctl_k_port_hidden))
+           lldpctl_atom_get_int(port, lldpctl_k_port_hidden))
                return;
 
        /* user might have specified protocol to filter on display */
        if ((protocol != LLDPD_MODE_MAX) &&
-           (protocol != lldpctl_atom_get_int(neighbor, lldpctl_k_port_protocol)))
+           (protocol != lldpctl_atom_get_int(port, lldpctl_k_port_protocol)))
            return;
 
-       lldpctl_atom_t *chassis = lldpctl_atom_get(neighbor, lldpctl_k_port_chassis);
+       /* Infer local / remote port from the port index (remote == 0) */
+       local = lldpctl_atom_get_int(port, lldpctl_k_port_index)>0?1:0;
+
+       lldpctl_atom_t *chassis = lldpctl_atom_get(port, lldpctl_k_port_chassis);
 
        tag_start(w, "interface", "Interface");
        tag_attr(w, "name", "",
            lldpctl_atom_get_str(iface, lldpctl_k_interface_name));
        tag_attr(w, "via" , "via",
-           lldpctl_atom_get_str(neighbor, lldpctl_k_port_protocol));
+           lldpctl_atom_get_str(port, lldpctl_k_port_protocol));
        if (details > DISPLAY_BRIEF) {
-               tag_attr(w, "rid" , "RID",
-                   lldpctl_atom_get_str(chassis, lldpctl_k_chassis_index));
+               if (!local)
+                       tag_attr(w, "rid" , "RID",
+                           lldpctl_atom_get_str(chassis, lldpctl_k_chassis_index));
                tag_attr(w, "age" , "Time",
-                   display_age(lldpctl_atom_get_int(neighbor, lldpctl_k_port_age)));
+                   display_age(lldpctl_atom_get_int(port, lldpctl_k_port_age)));
        }
 
        display_chassis(w, chassis, details);
-       display_port(w, neighbor, details);
+       display_port(w, port, details);
+       if (details && local)
+               display_local_ttl(w, conn, details);
        if (details == DISPLAY_DETAILS) {
-               display_vlans(w, neighbor);
-               display_ppvids(w, neighbor);
-               display_pids(w, neighbor);
-               display_med(w, neighbor, chassis);
+               display_vlans(w, port);
+               display_ppvids(w, port);
+               display_pids(w, port);
+               display_med(w, port, chassis);
        }
 
        lldpctl_atom_dec_ref(chassis);
 
-       display_custom_tlvs(w, neighbor);
+       display_custom_tlvs(w, port);
 
        tag_end(w);
 }
@@ -675,6 +716,34 @@ display_interfaces(lldpctl_conn_t *conn, struct writer *w,
        tag_end(w);
 }
 
+
+/**
+ * Display information about local interfaces.
+ *
+ * @param conn       Connection to lldpd.
+ * @param w          Writer.
+ * @param hidden     Whatever to show hidden ports.
+ * @param env        Environment from which we may find the list of ports.
+ * @param details    Level of details we need (DISPLAY_*).
+ */
+void
+display_local_interfaces(lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env,
+    int hidden, int details)
+{
+       lldpctl_atom_t *iface;
+       int protocol = LLDPD_MODE_MAX;
+
+       tag_start(w, "lldp", "LLDP interfaces");
+       while ((iface = cmd_iterate_on_interfaces(conn, env))) {
+               lldpctl_atom_t *port;
+               port      = lldpctl_get_port(iface);
+               display_interface(conn, w, hidden, iface, port, details, protocol);
+               lldpctl_atom_dec_ref(port);
+       }
+       tag_end(w);
+ }
+
 void
 display_stat(struct writer *w, const char *tag, const char *descr,
        long unsigned int cnt)
index 7dceb6840b6c13cb8ea075a0622f6b411259af52..300587fd781b342be29e9e16aace7f01b5dfe6b0 100644 (file)
@@ -134,6 +134,26 @@ one or several ports, the information displayed is limited to the
 given list of ports.
 .Ed
 
+.Cd show interfaces
+.Op ports Ar ethX Op ,...
+.Op Cd details | summary
+.Op Cd hidden
+.Bd -ragged -offset XXXXXX
+Display information about each local interface known by
+.Xr lldpd 8
+daemon. With
+.Cd summary ,
+only the name and the port description of each local interface will be
+displayed. On the other hand, with
+.Cd details ,
+all available information will be displayed, giving a verbose
+view. When using
+.Cd hidden ,
+also display local ports hidden by the smart filter. When specifying
+one or several ports, the information displayed is limited to the
+given list of ports.
+.Ed
+
 .Cd show chassis
 .Op Cd details | summary
 .Bd -ragged -offset XXXXXX
index fa704b864635ca275000d6008ec93715a739415d..8ba8acb44e277396c598f4575d003aaa1a173c21 100644 (file)
@@ -48,6 +48,35 @@ cmd_show_neighbors(struct lldpctl_conn_t *conn, struct writer *w,
        return 1;
 }
 
+/**
+ * Show interfaces.
+ *
+ * The environment will contain the following keys:
+ *  - C{ports} list of ports we want to restrict showing.
+ *  - C{hidden} if we should show hidden ports.
+ *  - C{summary} if we want to show only a summary
+ *  - C{detailed} for a detailed overview
+ */
+static int
+cmd_show_interfaces(struct lldpctl_conn_t *conn, struct writer *w,
+    struct cmd_env *env, void *arg)
+{
+       log_debug("lldpctl", "show interfaces data (%s) %s hidden interfaces",
+           cmdenv_get(env, "summary")?"summary":
+           cmdenv_get(env, "detailed")?"detailed":
+           "normal", cmdenv_get(env, "hidden")?"with":"without");
+       if (cmdenv_get(env, "ports"))
+               log_debug("lldpctl", "restrict to the following ports: %s",
+                   cmdenv_get(env, "ports"));
+
+       display_local_interfaces(conn, w, env, !!cmdenv_get(env, "hidden"),
+           cmdenv_get(env, "summary")?DISPLAY_BRIEF:
+           cmdenv_get(env, "detailed")?DISPLAY_DETAILS:
+           DISPLAY_NORMAL);
+
+       return 1;
+}
+
 /**
  * Show chassis.
  *
@@ -286,6 +315,12 @@ register_commands_show(struct cmd_node *root)
                "Show neighbors data",
                NULL, NULL, NULL);
 
+       struct cmd_node *interfaces = commands_new(
+               show,
+               "interfaces",
+               "Show interfaces data",
+               NULL, NULL, NULL);
+
        struct cmd_node *chassis = commands_new(
                show,
                "chassis",
@@ -306,6 +341,15 @@ register_commands_show(struct cmd_node *root)
 
        register_common_commands(neighbors, 1);
 
+       /* Interfaces data */
+       commands_new(interfaces,
+           NEWLINE,
+           "Show interfaces data",
+           NULL, cmd_show_interfaces, NULL);
+
+       cmd_restrict_ports(interfaces);
+       register_common_commands(interfaces, 0);
+
        /* Chassis data */
        commands_new(chassis,
            NEWLINE,
index 4d3c273cc339c34df40cc071e4e2565bec3bacb6..6603d6041ef121672700fb02d5fecff4eb00eeed 100644 (file)
@@ -1038,6 +1038,7 @@ lldpd_send(struct lldpd_hardware *hardware)
                                    cfg->g_protocols[i].name);
                                cfg->g_protocols[i].send(cfg,
                                    hardware);
+                               hardware->h_lport.p_protocol = cfg->g_protocols[i].mode;
                                sent++;
                                break;
                        }
index 3b23242ce1ec51677dfb613a8e014a8c135e42b5..1b92ef102c8e95ed5516f314bd81535ebe209c38 100644 (file)
@@ -45,6 +45,46 @@ def test_several_neighbors(lldpd, lldpcli, links, namespaces, neighbors):
                 'ns-{}.example.com'.format(i)
 
 
+def test_one_interface(lldpd1, lldpd, lldpcli, namespaces):
+    with namespaces(2):
+        lldpd()
+    with namespaces(1):
+        out = lldpcli("-f", "keyvalue", "show", "interfaces")
+        assert out['lldp.eth0.chassis.descr'].startswith(
+            "Spectacular GNU/Linux 2016 Linux")
+        assert 'lldp.eth0.age' in out
+        assert 'lldp.eth0.chassis.Router.enabled' in out
+        assert 'lldp.eth0.chassis.Station.enabled' in out
+        del out['lldp.eth0.age']
+        del out['lldp.eth0.chassis.descr']
+        del out['lldp.eth0.chassis.Router.enabled']
+        del out['lldp.eth0.chassis.Station.enabled']
+        assert out == {"lldp.eth0.via": "unknown",
+                       "lldp.eth0.chassis.mac": "00:00:00:00:00:01",
+                       "lldp.eth0.chassis.name": "ns-1.example.com",
+                       "lldp.eth0.chassis.mgmt-ip": "fe80::200:ff:fe00:1",
+                       "lldp.eth0.chassis.Bridge.enabled": "off",
+                       "lldp.eth0.chassis.Wlan.enabled": "off",
+                       "lldp.eth0.port.mac": "00:00:00:00:00:01",
+                       "lldp.eth0.port.descr": "eth0",
+                       "lldp.eth0.ttl.ttl": "120"}
+
+@pytest.mark.parametrize("interfaces", (5, 10, 20))
+def test_several_interfaces(lldpd, lldpcli, links, namespaces, interfaces):
+    for i in range(2, interfaces + 1):
+        links(namespaces(1), namespaces(i))
+    for i in range(1, interfaces + 1):
+        with namespaces(i):
+            lldpd()
+    with namespaces(1):
+        out = lldpcli("-f", "keyvalue", "show", "interfaces")
+        for i in range(2, interfaces + 1):
+            assert out['lldp.eth{}.chassis.mac'.format((i - 2)*2)] == \
+                '00:00:00:00:00:01'
+            assert out['lldp.eth{}.port.mac'.format((i - 2)*2)] == \
+                '00:00:00:00:00:{num:02x}'.format(num=(i - 2)*2 + 1)
+
+
 def test_overrided_description(lldpd1, lldpd, lldpcli, namespaces):
     with namespaces(2):
         lldpd("-S", "Modified description")
index dd5b2d718662759cdcbaf6c7f7f506079a51173c..aafafd6378b96d18d899e688acc64ecdd6dc6841 100644 (file)
@@ -1,4 +1,5 @@
 import pytest
+import shlex
 import time
 import re
 import platform
@@ -14,20 +15,9 @@ def uname():
         platform.version(),
         platform.machine())
 
-
-def test_text_output(lldpd1, lldpd, lldpcli, namespaces, uname):
-    with namespaces(2):
-        lldpd()
-    with namespaces(1):
-        result = lldpcli("show", "neighbors", "details")
-        assert result.returncode == 0
-        if 'Dot3' in pytest.config.lldpd.features:
-            dot3 = """
-    PMD autoneg:  supported: no, enabled: no
-      MAU oper type: 10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable"""
-        else:
-            dot3 = ""
-        expected = """-------------------------------------------------------------------------------
+@pytest.mark.parametrize("command, expected", [
+    ("neighbors",
+     """-------------------------------------------------------------------------------
 LLDP neighbors:
 -------------------------------------------------------------------------------
 Interface:    eth0, via: LLDP, RID: 1, Time: 0 day, 00:00:{seconds}
@@ -45,8 +35,48 @@ Interface:    eth0, via: LLDP, RID: 1, Time: 0 day, 00:00:{seconds}
     PortDescr:    eth1
     TTL:          120{dot3}
 -------------------------------------------------------------------------------
-"""
+"""),
+    ("interfaces",
+     """-------------------------------------------------------------------------------
+LLDP interfaces:
+-------------------------------------------------------------------------------
+Interface:    eth0, via: unknown, Time: {time}
+  Chassis:
+    ChassisID:    mac 00:00:00:00:00:01
+    SysName:      ns-1.example.com
+    SysDescr:     Spectacular GNU/Linux 2016 {uname}
+    MgmtIP:       fe80::200:ff:fe00:1
+    Capability:   Bridge, off
+    Capability:   Router, {router}
+    Capability:   Wlan, off
+    Capability:   Station, {station}
+  Port:
+    PortID:       mac 00:00:00:00:00:01
+    PortDescr:    eth0{dot3}
+  TTL:          120
+-------------------------------------------------------------------------------
+""")], ids=["neighbors", "interfaces"])
+def test_text_output(lldpd1, lldpd, lldpcli, namespaces, uname, command,
+                     expected):
+    with namespaces(2):
+        lldpd()
+    with namespaces(1):
+        result = lldpcli(
+            *shlex.split("show {} details".format(command)))
+        assert result.returncode == 0
         out = result.stdout.decode('ascii')
+
+        if 'Dot3' in pytest.config.lldpd.features:
+            dot3 = """
+    PMD autoneg:  supported: no, enabled: no
+      MAU oper type: 10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable"""
+        else:
+            dot3 = ""
+
+        out = result.stdout.decode('ascii')
+        time = re.search(r'^Interface: .*Time: (.*)$',
+                            out,
+                            re.MULTILINE).group(1)
         seconds = re.search(r'^Interface: .*(\d\d)$',
                             out,
                             re.MULTILINE).group(1)
@@ -58,55 +88,78 @@ Interface:    eth0, via: LLDP, RID: 1, Time: 0 day, 00:00:{seconds}
                             re.MULTILINE).group(1)
         out = re.sub(r' *$', '', out, flags=re.MULTILINE)
         assert out == expected.format(seconds=seconds,
+                                      time=time,
                                       router=router,
                                       station=station,
                                       uname=uname,
                                       dot3=dot3)
 
-
 @pytest.mark.skipif('JSON' not in pytest.config.lldpcli.outputs,
                     reason="JSON not supported")
-def test_json_output(lldpd1, lldpd, lldpcli, namespaces, uname):
+@pytest.mark.parametrize("command, expected", [
+    ("neighbors",
+     {"lldp": {
+        "interface": {
+          "eth0": {
+            "via": "LLDP",
+            "rid": "1",
+            "chassis": {
+              "ns-2.example.com": {
+                "id": {
+                  "type": "mac",
+                  "value": "00:00:00:00:00:02"},
+                "descr": "Spectacular GNU/Linux 2016 {}".format(uname),
+                "mgmt-ip": "fe80::200:ff:fe00:2",
+                "capability": [
+                  {"type": "Bridge", "enabled": False},
+                  {"type": "Wlan", "enabled": False},]}},
+            "port": {
+              "id": {
+                "type": "mac",
+                "value": "00:00:00:00:00:02"},
+              "descr": "eth1",
+              "ttl": "120"}}}}}),
+    ("interfaces",
+     {"lldp": {
+        "interface": {
+          "eth0": {
+            "via": "unknown",
+            "chassis": {
+              "ns-1.example.com": {
+                "id": {
+                  "type": "mac",
+                  "value": "00:00:00:00:00:01"},
+                "descr": "Spectacular GNU/Linux 2016 {}".format(uname),
+                "mgmt-ip": "fe80::200:ff:fe00:1",
+                "capability": [
+                  {"type": "Bridge", "enabled": False},
+                  {"type": "Wlan", "enabled": False},]}},
+            "port": {
+              "id": {
+                "type": "mac",
+                "value": "00:00:00:00:00:01"},
+              "descr": "eth0"},
+            "ttl": {
+              "ttl": "120"}}}}})], ids=["neighbors", "interfaces"])
+def test_json_output(lldpd1, lldpd, lldpcli, namespaces, uname, command,
+                     expected):
     with namespaces(2):
         lldpd()
     with namespaces(1):
-        result = lldpcli("-f", "json", "show", "neighbors", "details")
+        result = lldpcli(
+            *shlex.split("-f json show {} details".format(command)))
         assert result.returncode == 0
         out = result.stdout.decode('ascii')
         j = json.loads(out)
 
         eth0 = j['lldp']['interface']['eth0']
+        name = next(k for k,v in eth0['chassis'].items() if k.startswith('ns'))
         del eth0['age']
-        del eth0['chassis']['ns-2.example.com']['capability'][3]
-        del eth0['chassis']['ns-2.example.com']['capability'][1]
-        expected = {"lldp": {
-            "interface": {"eth0": {
-                "via": "LLDP",
-                "rid": "1",
-                "chassis": {
-                    "ns-2.example.com": {
-                        "id": {
-                            "type": "mac",
-                            "value": "00:00:00:00:00:02"
-                        },
-                        "descr": "Spectacular GNU/Linux 2016 {}".format(uname),
-                        "mgmt-ip": "fe80::200:ff:fe00:2",
-                        "capability": [
-                            {"type": "Bridge", "enabled": False},
-                            {"type": "Wlan", "enabled": False},
-                        ]
-                    }
-                },
-                "port": {
-                    "id": {
-                        "type": "mac",
-                        "value": "00:00:00:00:00:02"
-                    },
-                    "descr": "eth1",
-                    "ttl": "120"
-                }
-            }}
-        }}
+        del eth0['chassis'][name]['capability'][3]
+        del eth0['chassis'][name]['capability'][1]
+
+        descr = "Spectacular GNU/Linux 2016 {}".format(uname)
+        expected['lldp']['interface']['eth0']['chassis'][name]["descr"] = descr
 
         if 'Dot3' in pytest.config.lldpd.features:
             expected['lldp']['interface']['eth0']['port']['auto-negotiation'] = {
@@ -114,25 +167,14 @@ def test_json_output(lldpd1, lldpd, lldpcli, namespaces, uname):
                 "supported": False,
                 "current": "10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable"
             }
-        assert j == expected
 
+        assert j == expected
 
 @pytest.mark.skipif('JSON' not in pytest.config.lldpcli.outputs,
                     reason="JSON not supported")
-def test_json0_output(lldpd1, lldpd, lldpcli, namespaces, uname):
-    with namespaces(2):
-        lldpd()
-    with namespaces(1):
-        result = lldpcli("-f", "json0", "show", "neighbors", "details")
-        assert result.returncode == 0
-        out = result.stdout.decode('ascii')
-        j = json.loads(out)
-
-        eth0 = j['lldp'][0]['interface'][0]
-        del eth0['age']
-        del eth0['chassis'][0]['capability'][3]
-        del eth0['chassis'][0]['capability'][1]
-        expected = {"lldp": [{
+@pytest.mark.parametrize("command, expected", [
+    ("neighbors",
+     {"lldp": [{
             "interface": [{
                 "name": "eth0",
                 "via": "LLDP",
@@ -159,7 +201,53 @@ def test_json0_output(lldpd1, lldpd, lldpcli, namespaces, uname):
                     "ttl": [{"value": "120"}]
                 }]
             }]}
-        ]}
+        ]}),
+    ("interfaces",
+     {"lldp": [{
+            "interface": [{
+                "name": "eth0",
+                "via": "unknown",
+                "chassis": [{
+                    "id": [{
+                        "type": "mac",
+                        "value": "00:00:00:00:00:01"
+                    }],
+                    "name": [{"value": "ns-1.example.com"}],
+                    "descr": [{"value": "Spectacular GNU/Linux 2016 {}".format(uname)}],
+                    "mgmt-ip": [{"value": "fe80::200:ff:fe00:1"}],
+                    "capability": [
+                        {"type": "Bridge", "enabled": False},
+                        {"type": "Wlan", "enabled": False},
+                    ]}
+                ],
+                "port": [{
+                    "id": [{
+                        "type": "mac",
+                        "value": "00:00:00:00:00:01"
+                    }],
+                    "descr": [{"value": "eth0"}]
+                }],
+                "ttl": [{"ttl": "120"}]
+            }]}
+        ]})], ids=["neighbors", "interfaces"])
+def test_json0_output(lldpd1, lldpd, lldpcli, namespaces, uname, command,
+                      expected):
+    with namespaces(2):
+        lldpd()
+    with namespaces(1):
+        result = lldpcli(
+            *shlex.split("-f json0 show {} details".format(command)))
+        assert result.returncode == 0
+        out = result.stdout.decode('ascii')
+        j = json.loads(out)
+
+        eth0 = j['lldp'][0]['interface'][0]
+        del eth0['age']
+        del eth0['chassis'][0]['capability'][3]
+        del eth0['chassis'][0]['capability'][1]
+
+        descr = "Spectacular GNU/Linux 2016 {}".format(uname)
+        expected['lldp'][0]['interface'][0]['chassis'][0]["descr"][0]['value'] = descr
 
         if 'Dot3' in pytest.config.lldpd.features:
             expected['lldp'][0]['interface'][0]['port'][0]['auto-negotiation'] = [{
@@ -173,28 +261,9 @@ def test_json0_output(lldpd1, lldpd, lldpcli, namespaces, uname):
 
 @pytest.mark.skipif('XML' not in pytest.config.lldpcli.outputs,
                     reason="XML not supported")
-def test_xml_output(lldpd1, lldpd, lldpcli, namespaces, uname):
-    with namespaces(2):
-        lldpd()
-    with namespaces(1):
-        result = lldpcli("-f", "xml", "show", "neighbors", "details")
-        assert result.returncode == 0
-        out = result.stdout.decode('ascii')
-        xml = ET.fromstring(out)
-
-        age = xml.findall('./interface[1]')[0].attrib['age']
-        router = xml.findall("./interface[1]/chassis/"
-                           "capability[@type='Router']")[0].attrib['enabled']
-        station = xml.findall("./interface[1]/chassis/"
-                            "capability[@type='Station']")[0].attrib['enabled']
-        if 'Dot3' in pytest.config.lldpd.features:
-            dot3 = """
-   <auto-negotiation enabled="no" label="PMD autoneg" supported="no">
-    <current label="MAU oper type">10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable</current>
-   </auto-negotiation>"""
-        else:
-            dot3 = ""
-        expected = ET.fromstring("""<?xml version="1.0" encoding="UTF-8"?>
+@pytest.mark.parametrize("command, expected", [
+    ("neighbors",
+"""<?xml version="1.0" encoding="UTF-8"?>
 <lldp label="LLDP neighbors">
  <interface label="Interface" name="eth0" via="LLDP" rid="1" age="{age}">
   <chassis label="Chassis">
@@ -214,11 +283,57 @@ def test_xml_output(lldpd1, lldpd, lldpcli, namespaces, uname):
   </port>
  </interface>
 </lldp>
-        """.format(age=age,
-                   router=router,
-                   station=station,
-                   uname=uname,
-                   dot3=dot3))
+"""),
+("interfaces",
+"""<?xml version="1.0" encoding="UTF-8"?>
+<lldp label="LLDP interfaces">
+ <interface label="Interface" name="eth0" via="unknown" age="{age}">
+  <chassis label="Chassis">
+   <id label="ChassisID" type="mac">00:00:00:00:00:01</id>
+   <name label="SysName">ns-1.example.com</name>
+   <descr label="SysDescr">Spectacular GNU/Linux 2016 {uname}</descr>
+   <mgmt-ip label="MgmtIP">fe80::200:ff:fe00:1</mgmt-ip>
+   <capability label="Capability" type="Bridge" enabled="off"/>
+   <capability label="Capability" type="Router" enabled="{router}"/>
+   <capability label="Capability" type="Wlan" enabled="off"/>
+   <capability label="Capability" type="Station" enabled="{station}"/>
+  </chassis>
+  <port label="Port">
+   <id label="PortID" type="mac">00:00:00:00:00:01</id>
+   <descr label="PortDescr">eth0</descr>{dot3}
+  </port>
+  <ttl label="TTL" ttl="120"/>
+ </interface>
+</lldp>
+""")], ids=["neighbors", "interfaces"])
+def test_xml_output(lldpd1, lldpd, lldpcli, namespaces, uname, command,
+                    expected):
+    with namespaces(2):
+        lldpd()
+    with namespaces(1):
+        result = lldpcli(
+            *shlex.split("-f xml show {} details".format(command)))
+        assert result.returncode == 0
+        out = result.stdout.decode('ascii')
+        xml = ET.fromstring(out)
+
+        age = xml.findall('./interface[1]')[0].attrib['age']
+        router = xml.findall("./interface[1]/chassis/"
+                           "capability[@type='Router']")[0].attrib['enabled']
+        station = xml.findall("./interface[1]/chassis/"
+                            "capability[@type='Station']")[0].attrib['enabled']
+        if 'Dot3' in pytest.config.lldpd.features:
+            dot3 = """
+   <auto-negotiation enabled="no" label="PMD autoneg" supported="no">
+    <current label="MAU oper type">10GigBaseCX4 - X copper over 8 pair 100-Ohm balanced cable</current>
+   </auto-negotiation>"""
+        else:
+            dot3 = ""
+        expected = ET.fromstring(expected.format(age=age,
+                                                 router=router,
+                                                 station=station,
+                                                 uname=uname,
+                                                 dot3=dot3))
         assert ET.tostring(xml) == ET.tostring(expected)
 
 
@@ -233,6 +348,9 @@ def test_configure_one_port(lldpd1, lldpd, lldpcli, namespaces, links):
                            "spare class class-3").split())
         assert result.returncode == 0
         time.sleep(3)
+        out = lldpcli("-f", "keyvalue", "show", "interfaces", "details")
+        assert 'lldp.eth1.port.power.device-type' not in out
+        assert out['lldp.eth3.port.power.device-type'] == 'PSE'
     with namespaces(1):
         out = lldpcli("-f", "keyvalue", "show", "neighbors", "details")
         assert out['lldp.eth0.port.descr'] == 'eth1'
@@ -251,6 +369,8 @@ def test_new_port_take_default(lldpd1, lldpd, lldpcli, namespaces, links):
                            "spare class class-3").split())
         assert result.returncode == 0
         time.sleep(3)
+        out = lldpcli("-f", "keyvalue", "show", "interfaces", "details")
+        assert out['lldp.eth1.port.power.device-type'] == 'PSE'
     with namespaces(1):
         # Check this worked
         out = lldpcli("-f", "keyvalue", "show", "neighbors", "details")
@@ -262,6 +382,9 @@ def test_new_port_take_default(lldpd1, lldpd, lldpcli, namespaces, links):
         out = lldpcli("-f", "keyvalue", "show", "neighbors", "details")
         assert out['lldp.eth2.port.descr'] == 'eth3'
         assert out['lldp.eth2.port.power.device-type'] == 'PSE'
+    with namespaces(2):
+        out = lldpcli("-f", "keyvalue", "show", "interfaces", "details")
+        assert out['lldp.eth3.port.power.device-type'] == 'PSE'
 
 
 @pytest.mark.skipif('Dot3' not in pytest.config.lldpd.features,
@@ -279,6 +402,8 @@ def test_port_keep_configuration(lldpd1, lldpd, lldpcli, namespaces, links):
         time.sleep(4)
         links.up('eth3')
         time.sleep(4)
+        out = lldpcli("-f", "keyvalue", "show", "interfaces", "details")
+        assert out['lldp.eth3.port.power.device-type'] == 'PSE'
     with namespaces(1):
         out = lldpcli("-f", "keyvalue", "show", "neighbors", "details")
         assert out['lldp.eth2.port.descr'] == 'eth3'
@@ -364,5 +489,7 @@ def test_return_code(lldpd1, lldpcli, namespaces):
     with namespaces(1):
         result = lldpcli("show", "neighbors")
         assert result.returncode == 0
+        result = lldpcli("show", "interfaces")
+        assert result.returncode == 0
         result = lldpcli("unknown", "command")
         assert result.returncode == 1