]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
add support for vlan-advertisements configuration (#740)
authorugoldfeld <ugoldfeld@nvidia.com>
Tue, 19 Aug 2025 17:56:44 +0000 (20:56 +0300)
committerGitHub <noreply@github.com>
Tue, 19 Aug 2025 17:56:44 +0000 (19:56 +0200)
* add support for vlan-advertisements configuration

adding new configurations configure [ports ethx [,..]] lldp vlan-advertisements pattern and
unconfigure [ports ethx,[,...]] lldp vlan-advertisements pattern.
The commands enable control of which vlans are advertised

* Code review changes

Updated manual page.
Added add an example of invocation in tests/lldpcli.conf.
Added tests to tests/integration/test_dot1.py.
Vlan formatting fixes.

* more CR fixes

* Fix memory leak

fixing port->p_vlan_advertise_pattern memory leak

src/client/conf-lldp.c
src/client/lldpcli.8.in
src/daemon/client.c
src/daemon/interfaces.c
src/lib/atoms/port.c
src/lib/lldpctl.h
src/lldpd-structs.c
src/lldpd-structs.h
tests/integration/test_dot1.py
tests/lldpcli.conf

index 576aff1c74b5e9f57bb18ffde3d7668b2ea72055..c5ec33a0892498a341765a90ef2b5f2833ea0cba 100644 (file)
@@ -472,6 +472,52 @@ cmd_vlan_tx(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
        return 1;
 }
 
+static int
+cmd_set_vlan_pattern(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+    const void *arg)
+{
+       lldpctl_atom_t *port;
+       const char *name;
+
+       const char *value = cmdenv_get(env, "vlan-pattern");
+       
+       log_info("lldpctl", "VLAN pattern set to new value %s",value ? value : "(none)");
+
+       while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+               if (lldpctl_atom_set_str(port, lldpctl_k_port_vlan_advertise_pattern, value) ==
+                       NULL) {
+                       log_warnx("lldpctl",
+                               "unable to set VLAN pattern config on %s."
+                               " %s",
+                               name, lldpctl_last_strerror(conn));
+               }
+       }
+
+       return 1;
+}
+
+static int
+cmd_unset_vlan_pattern(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
+    const void *arg)
+{
+       log_debug("lldpctl", "unset VLAN pattern");
+       lldpctl_atom_t *port;
+       const char *name;
+
+       while ((port = cmd_iterate_on_ports(conn, env, &name))) {
+               if (lldpctl_atom_set_str(port, lldpctl_k_port_vlan_advertise_pattern, "*") ==
+                       NULL) {
+                       log_warnx("lldpctl",
+                               "unable to set VLAN pattern config on %s."
+                               " %s",
+                               name, lldpctl_last_strerror(conn));
+               }
+       }
+
+       return 1;
+}
+
+
 #ifdef ENABLE_CUSTOM
 static int
 cmd_custom_tlv_set(struct lldpctl_conn_t *conn, struct writer *w, struct cmd_env *env,
@@ -817,6 +863,22 @@ register_commands_configure_lldp(struct cmd_node *configure,
            NEWLINE, "Disable VLAN tagging of transmitted LLDP frames", NULL,
            cmd_vlan_tx, NULL);
 
+
+       /* Now handle vlan advertisements configuration. */
+       struct cmd_node *configure_lldp_vlan_advertisements = commands_new(configure_lldp,
+           "vlan-advertisements", "Configure vlan address advertisements", NULL, NULL, NULL);
+       commands_new(commands_new(commands_new(configure_lldp_vlan_advertisements, "pattern",
+                       "Set VLAN pattern", NULL, NULL, NULL),
+               NULL, "VLAN pattern (comma-separated list of wildcards)",
+               NULL, cmd_store_env_value, "vlan-pattern"),
+               NEWLINE, "Set active VLAN pattern", NULL, cmd_set_vlan_pattern, NULL);
+
+       struct cmd_node *unconfigure_vlan_advertisements =
+           commands_new(unconfigure_lldp, "vlan-advertisements", "Unconfigure vlan address advertisements", NULL, NULL, NULL); 
+       commands_new(commands_new(unconfigure_vlan_advertisements, "pattern",
+               "Delete any VLAN pattern", NULL, NULL, NULL),
+               NEWLINE, "Clear VLAN pattern", NULL, cmd_unset_vlan_pattern, NULL);
+
 #ifdef ENABLE_CUSTOM
        register_commands_configure_lldp_custom_tlvs(configure_lldp, unconfigure_lldp);
 #endif
index 817fc7b2dd1ac80a0a324793f41fa169e70acd1d..4e18233bb419d2f835c04c0c1446f8dd20650552 100644 (file)
@@ -666,6 +666,24 @@ and subtype
 is specified, remove specific instances of custom TLV.
 .Ed
 
+.Cd configure
+.Op ports Ar ethX Op ,...
+.Cd  lldp vlan-advertisements pattern Ar pattern
+.Bd -ragged -offset XXXXXX
+Configure VLAN advertisements for the given port as a comma separated values (CSV).
+As for interfaces (described above), this option can use wildcards and inversions.
+Without this option,
+.Nm lldpd
+will advertise all VLANS.
+.Ed
+
+.Cd unconfigure
+.Op ports Ar ethX Op ,...
+.Cd  lldp vlan-advertisements pattern
+.Bd -ragged -offset XXXXXX
+Remove any previously configured VLAN advertisements pattern for the given port.
+.Ed
+
 .Cd configure lldp fast-start
 .Cd enable | tx-interval Ar interval
 .Bd -ragged -offset XXXXXX
index eca82430666742100fe60203e650718dbe1bd368..efe20b57a136bb1d5eda9c6ea0ddd022ab7f645a 100644 (file)
@@ -495,6 +495,11 @@ _client_handle_set_port(struct lldpd *cfg, struct lldpd_port *port,
                port->p_vlan_tx_enabled = set->vlan_tx_enabled;
                port->p_vlan_tx_tag = set->vlan_tx_tag;
        }
+       if (set->vlan_advertise_pattern) {
+               log_debug("rpc", "requested change to VLAN advertise pattern");
+               free(port->p_vlan_advertise_pattern);
+               port->p_vlan_advertise_pattern = strdup(set->vlan_advertise_pattern);
+       }
 #ifdef ENABLE_LLDPMED
        if (set->med_policy && set->med_policy->type > 0) {
                log_debug("rpc", "requested change to MED policy");
index 2e9a76ddde6fc99f09703ae29f5b483721132a03..58195b855b83ed950e23919c16774040713b89b1 100644 (file)
@@ -221,6 +221,14 @@ iface_append_vlan(struct lldpd *cfg, struct interfaces_device *vlan,
                        vlan_id = (i * 32) + bit;
                        if (asprintf(&name, "vlan%d", vlan_id) == -1) return;
 
+                       //match vlan id with pattern
+                       if (port->p_vlan_advertise_pattern &&
+                               (PATTERN_MATCH_DENIED == pattern_match(name+4, port->p_vlan_advertise_pattern, 0))){
+                               log_debug("interfaces", "exlude VLAN %s advertisement", name);
+                               free(name);
+                               return;
+                       }
+
                        /* Check if the VLAN is already here. */
                        TAILQ_FOREACH (v, &port->p_vlans, v_entries)
                                if (strncmp(name, v->v_name, IFNAMSIZ) == 0) {
index f99e18bcda2f4e15c651d8eb9a55dee3092e4436..ed129eec594520290f70cd38a49be53ec4491cce 100644 (file)
@@ -530,6 +530,9 @@ _lldpctl_atom_set_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key,
                set.vlan_tx_tag = p->port->p_vlan_tx_tag;
                set.vlan_tx_enabled = p->port->p_vlan_tx_enabled;
                break;
+       case lldpctl_k_port_vlan_advertise_pattern:
+               set.vlan_advertise_pattern = p->port->p_vlan_advertise_pattern;
+               break;
 #ifdef ENABLE_DOT3
        case lldpctl_k_port_dot3_power:
                if (value->type != atom_dot3_power) {
@@ -669,7 +672,8 @@ _lldpctl_atom_get_str_port(lldpctl_atom_t *atom, lldpctl_key_t key)
                return NULL;
        case lldpctl_k_port_descr:
                return port->p_descr;
-
+       case lldpctl_k_port_vlan_advertise_pattern:
+               return port->p_vlan_advertise_pattern;
 #ifdef ENABLE_DOT3
        case lldpctl_k_port_dot3_mautype:
                return map_lookup(operational_mau_type_values, port->p_macphy.mau_type);
@@ -740,6 +744,10 @@ _lldpctl_atom_set_str_port(lldpctl_atom_t *atom, lldpctl_key_t key, const char *
                free(port->p_descr);
                port->p_descr = strdup(value);
                break;
+       case lldpctl_k_port_vlan_advertise_pattern:
+               free(port->p_vlan_advertise_pattern);
+               port->p_vlan_advertise_pattern = strdup(value);
+               break;
        default:
                SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST);
                return NULL;
index aee117337b8993f26be02da64cfcdd2c358ffb10..0eb0265d104a387893b024f8cf33a3f64669bd4f 100644 (file)
@@ -763,8 +763,8 @@ typedef enum {
        lldpctl_k_port_status, /**< `(IS,WO)` Operational status of this (local) port */
        lldpctl_k_port_chassis, /**< `(A)` Chassis associated to the port */
        lldpctl_k_port_ttl, /**< `(I)` TTL for port, 0 if info is attached to chassis */
-       lldpctl_k_port_vlan_tx, /**< `(I,W)` VLAN tag for TX on port, -1 VLAN disabled
-                                */
+       lldpctl_k_port_vlan_tx, /**< `(I,W)` VLAN tag for TX on port, -1 VLAN disabled */
+       lldpctl_k_port_vlan_advertise_pattern, /**< `(S,W)` Pattern of enabled vlan advertisements */
 
        lldpctl_k_port_dot3_mfs = 1300,         /**< `(I)` MFS */
        lldpctl_k_port_dot3_aggregid,           /**< `(I)` Port aggregation ID */
index 6b42713cfc7771a7fe179b06ae7707e95469df25..9088af11b21e020546cd0cc30cf1de08f3d4e1f8 100644 (file)
@@ -211,6 +211,8 @@ lldpd_port_cleanup(struct lldpd_port *port, int all)
                        port->p_chassis->c_refcount--;
                        port->p_chassis = NULL;
                }
+               free(port->p_vlan_advertise_pattern);
+               port->p_vlan_advertise_pattern = NULL;
 #ifdef ENABLE_CUSTOM
                lldpd_custom_list_cleanup(port);
 #endif
index 0501adb6525a36ec24a400fe839702b37be0024e..a7ef70b62c51bc41241bacb8d111fdfa0cb63f3d 100644 (file)
@@ -271,7 +271,7 @@ struct lldpd_port {
        u_int16_t p_ttl; /* TTL for remote port */
        int p_vlan_tx_tag;
        int p_vlan_tx_enabled;
-
+       char *p_vlan_advertise_pattern;
 #ifdef ENABLE_DOT3
        /* Dot3 stuff */
        u_int32_t p_aggregid;
@@ -306,6 +306,7 @@ MARSHAL_POINTER(lldpd_port, lldpd_chassis, p_chassis)
 MARSHAL_IGNORE(lldpd_port, p_lastframe)
 MARSHAL_FSTR(lldpd_port, p_id, p_id_len)
 MARSHAL_STR(lldpd_port, p_descr)
+MARSHAL_STR(lldpd_port, p_vlan_advertise_pattern)
 #ifdef ENABLE_LLDPMED
 MARSHAL_SUBSTRUCT(lldpd_port, lldpd_med_loc, p_med_location[0])
 MARSHAL_SUBSTRUCT(lldpd_port, lldpd_med_loc, p_med_location[1])
@@ -338,6 +339,7 @@ struct lldpd_port_set {
        char *ifname;
        char *local_id;
        char *local_descr;
+       char *vlan_advertise_pattern;
        int rxtx;
        int vlan_tx_tag;
        int vlan_tx_enabled;
@@ -359,6 +361,7 @@ MARSHAL_BEGIN(lldpd_port_set)
 MARSHAL_STR(lldpd_port_set, ifname)
 MARSHAL_STR(lldpd_port_set, local_id)
 MARSHAL_STR(lldpd_port_set, local_descr)
+MARSHAL_STR(lldpd_port_set, vlan_advertise_pattern)
 #ifdef ENABLE_LLDPMED
 MARSHAL_POINTER(lldpd_port_set, lldpd_med_policy, med_policy)
 MARSHAL_POINTER(lldpd_port_set, lldpd_med_loc, med_location)
index a026b08498109d73c9a01e9c347b2c092b1c650c..ddaf867086e6d463bfe431aa5b455fa71948a3d9 100644 (file)
@@ -39,4 +39,55 @@ class TestLldpDot1(object):
             assert "lldp.eth0.vlan" not in out
             assert "lldp.eth0.age" in out
 
+    def test_vlan_advertisement_inclusive(self, lldpd1, lldpd, lldpcli, namespaces, links):
+        vlan = [100, 200, 300, 4000]
+        with namespaces(1):
+            lldpd()
+            for v in vlan:
+                links.vlan("vlan{}".format(v), v, "eth0")
+            result = lldpcli("configure", "ports", "eth0", "lldp", "vlan-advertisements", "pattern","300,4000")
+            assert result.returncode == 0 == 0
+            out = lldpcli("-f", "keyvalue", "show", "interface", "details")
+            # We know that lldpd is walking interfaces in index order
+            assert out["lldp.eth0.vlan"] == [
+                "vlan300",
+                "vlan4000",
+            ]
+            assert out["lldp.eth0.vlan.vlan-id"] == ["300", "4000"]
+
+    def test_vlan_advertisement_exclusive(self, lldpd1, lldpd, lldpcli, namespaces, links):
+        vlan = [100, 200, 300, 4000]
+        with namespaces(1):
+            lldpd()
+            for v in vlan:
+                links.vlan("vlan{}".format(v), v, "eth0")
+            result = lldpcli("configure", "ports", "eth0", "lldp", "vlan-advertisements", "pattern","*,!300,!4000")
+            assert result.returncode == 0 == 0
+            out = lldpcli("-f", "keyvalue", "show", "interface", "details")
+            # We know that lldpd is walking interfaces in index order
+            assert out["lldp.eth0.vlan"] == [
+                "vlan100",
+                "vlan200",
+            ]
+            assert out["lldp.eth0.vlan.vlan-id"] == ["100", "200"]
+
+    def test_vlan_advertisement_unconfigure(self, lldpd1, lldpd, lldpcli, namespaces, links):
+        vlan = [100, 200, 300, 4000]
+        with namespaces(1):
+            lldpd()
+            for v in vlan:
+                links.vlan("vlan{}".format(v), v, "eth0")
+            result = lldpcli("configure", "ports", "eth0", "lldp", "vlan-advertisements", "pattern","*,!300,!4000")
+            assert result.returncode == 0 == 0
+            result = lldpcli("unconfigure", "ports", "eth0", "lldp", "vlan-advertisements", "pattern")
+            assert result.returncode == 0 == 0
+            out = lldpcli("-f", "keyvalue", "show", "interface", "details")
+            # We know that lldpd is walking interfaces in index order
+            assert out["lldp.eth0.vlan"] == [
+                "vlan100",
+                "vlan200",
+                "vlan300",
+                "vlan4000",
+            ]
+            assert out["lldp.eth0.vlan.vlan-id"] == ["100", "200", "300", "4000"]
     # TODO: PI and PPVID (but lldpd doesn't know how to generate them)
index 4afabeecfae702bfff05fceefe906f4fbeb1d43c..cd98f4bf33fd617771fec912aeca265e072b1ee7 100644 (file)
@@ -55,6 +55,8 @@ configure ports eth0 med power pd source pse priority high value 5000
 configure dot3 power pse supported enabled paircontrol powerpairs spare class class-3
 configure ports eth0 dot3 power pse supported enabled paircontrol powerpairs spare class class-3
 configure dot3 power pd supported enabled powerpairs spare class class-3 type 1 source pse priority low requested 10000 allocated 15000
+configure ports eth0 lldp vlan-advertisements pattern "*,!300,!4000"
+unconfigure ports eth0 lldp vlan-advertisements pattern 
 
 configure inventory hardware-revision "SQRT2-1.41421356237309504880"
 configure inventory software-revision "E-2.7182818284590452354"