From 7d6652cccf517e24a375c3715b6c2679c127e563 Mon Sep 17 00:00:00 2001 From: ugoldfeld Date: Tue, 19 Aug 2025 20:56:44 +0300 Subject: [PATCH] add support for vlan-advertisements configuration (#740) * 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 | 62 ++++++++++++++++++++++++++++++++++ src/client/lldpcli.8.in | 18 ++++++++++ src/daemon/client.c | 5 +++ src/daemon/interfaces.c | 8 +++++ src/lib/atoms/port.c | 10 +++++- src/lib/lldpctl.h | 4 +-- src/lldpd-structs.c | 2 ++ src/lldpd-structs.h | 5 ++- tests/integration/test_dot1.py | 51 ++++++++++++++++++++++++++++ tests/lldpcli.conf | 2 ++ 10 files changed, 163 insertions(+), 4 deletions(-) diff --git a/src/client/conf-lldp.c b/src/client/conf-lldp.c index 576aff1c..c5ec33a0 100644 --- a/src/client/conf-lldp.c +++ b/src/client/conf-lldp.c @@ -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 diff --git a/src/client/lldpcli.8.in b/src/client/lldpcli.8.in index 817fc7b2..4e18233b 100644 --- a/src/client/lldpcli.8.in +++ b/src/client/lldpcli.8.in @@ -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 diff --git a/src/daemon/client.c b/src/daemon/client.c index eca82430..efe20b57 100644 --- a/src/daemon/client.c +++ b/src/daemon/client.c @@ -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"); diff --git a/src/daemon/interfaces.c b/src/daemon/interfaces.c index 2e9a76dd..58195b85 100644 --- a/src/daemon/interfaces.c +++ b/src/daemon/interfaces.c @@ -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) { diff --git a/src/lib/atoms/port.c b/src/lib/atoms/port.c index f99e18bc..ed129eec 100644 --- a/src/lib/atoms/port.c +++ b/src/lib/atoms/port.c @@ -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; diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h index aee11733..0eb0265d 100644 --- a/src/lib/lldpctl.h +++ b/src/lib/lldpctl.h @@ -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 */ diff --git a/src/lldpd-structs.c b/src/lldpd-structs.c index 6b42713c..9088af11 100644 --- a/src/lldpd-structs.c +++ b/src/lldpd-structs.c @@ -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 diff --git a/src/lldpd-structs.h b/src/lldpd-structs.h index 0501adb6..a7ef70b6 100644 --- a/src/lldpd-structs.h +++ b/src/lldpd-structs.h @@ -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) diff --git a/tests/integration/test_dot1.py b/tests/integration/test_dot1.py index a026b084..ddaf8670 100644 --- a/tests/integration/test_dot1.py +++ b/tests/integration/test_dot1.py @@ -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) diff --git a/tests/lldpcli.conf b/tests/lldpcli.conf index 4afabeec..cd98f4bf 100644 --- a/tests/lldpcli.conf +++ b/tests/lldpcli.conf @@ -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" -- 2.47.3