From 1e85286ae2f2454c7b8f5a2f3ab69b6682ffd884 Mon Sep 17 00:00:00 2001 From: Michal Wasiak Date: Fri, 3 Jul 2020 20:36:03 +0200 Subject: [PATCH] ports: allow sending LLDP frames on a specified VLAN The current limitation is that the frames are sent only on one VLAN per port. --- NEWS | 1 + src/client/conf-lldp.c | 124 ++++++++++++++++++++++++++++++++ src/client/display.c | 15 ++++ src/client/lldpcli.8.in | 16 +++++ src/daemon/client.c | 4 ++ src/daemon/protocols/lldp.c | 16 ++++- src/lib/atoms/port.c | 15 ++++ src/lib/lldpctl.h | 1 + src/lldpd-structs.h | 4 ++ tests/check_lldp.c | 78 ++++++++++++++++++++ tests/integration/test_basic.py | 16 +++++ 11 files changed, 289 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 86794e13..5596d162 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ lldpd (1.0.6) + Deprecate use of lldpctl_watch_callback(). Use lldpctl_watch_callback2() instead. + Upgrade embedded libevent to 2.1.11-stable + + Add support of sending LLDP frames on a configured VLAN lldpd (1.0.5) * Changes: diff --git a/src/client/conf-lldp.c b/src/client/conf-lldp.c index f82f2ec7..b2e94826 100644 --- a/src/client/conf-lldp.c +++ b/src/client/conf-lldp.c @@ -313,6 +313,85 @@ cmd_chassis_mgmt_advertise(struct lldpctl_conn_t *conn, struct writer *w, return 1; } +static int +cmd_vlan_tx(struct lldpctl_conn_t *conn, struct writer *w, + struct cmd_env *env, void *arg) +{ + lldpctl_atom_t *port; + const char *name; + const char *vlan_id = cmdenv_get(env, "vlan-tx-id"); + const char *vlan_prio = cmdenv_get(env, "vlan-tx-prio"); + const char *vlan_dei = cmdenv_get(env, "vlan-tx-dei"); + + /* Default values are used to disable VLAN */ + int vlan_id_int = -1; + int vlan_prio_int = -1; + int vlan_dei_int = -1; + int vlan_tag = -1; + + if (!arg) + log_debug("lldpctl", "lldp disable VLAN tagging of transmitted LLDP frames"); + else + log_debug("lldpctl", "lldp enable VLAN tagging of transmitted LLDP frames with VLAN ID: '%s', Priority: '%s', DEI: '%s'", + vlan_id, vlan_prio, vlan_dei); + + if (arg) { + if (!vlan_id || !strlen(vlan_id)) { + log_warnx("lldpctl", "no VLAN id for TX specified"); + return 0; + } else { + const char *errstr; + vlan_id_int = strtonum(vlan_id, 0, 4094, &errstr); + if (errstr != NULL) { + log_warnx("lldpctl", "invalid VLAN ID for TX `%s': %s", + vlan_id, errstr); + return 0; + } + } + + if (!vlan_prio || !strlen(vlan_prio)) { + log_warnx("lldpctl", "no VLAN priority for TX specified, using default (0)"); + /* Use default priority */ + vlan_prio_int = 0; + } else { + const char *errstr; + vlan_prio_int = strtonum(vlan_prio, 0, 7, &errstr); + if (errstr != NULL) { + log_warnx("lldpctl", "invalid VLAN piority `%s': %s", + vlan_prio, errstr); + return 0; + } + } + + if (!vlan_dei || !strlen(vlan_dei)) { + log_warnx("lldpctl", "no VLAN Drop eligible indicator (DEI) for TX specified, using default (0)"); + /* Use default priority */ + vlan_dei_int = 0; + } else { + const char *errstr; + vlan_dei_int = strtonum(vlan_dei, 0, 1, &errstr); + if (errstr != NULL) { + log_warnx("lldpctl", "invalid VLAN Drop eligible indicator (DEI) `%s': %s", + vlan_dei, errstr); + return 0; + } + } + /* Priority(3bits) | DEI(1bit) | VID(12bits) */ + vlan_tag = ((vlan_prio_int & 0x7) << 13) | + ((vlan_dei_int & 0x1) << 12) | + (vlan_id_int & 0xfff); + } + + while ((port = cmd_iterate_on_ports(conn, env, &name))) { + if (lldpctl_atom_set_int(port, lldpctl_k_port_vlan_tx, vlan_tag) == NULL) { + log_warnx("lldpctl", "unable to set VLAN TX 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, @@ -685,6 +764,51 @@ register_commands_configure_lldp(struct cmd_node *configure, NEWLINE, "Don't enable management addresses advertisement", NULL, cmd_chassis_mgmt_advertise, NULL); + struct cmd_node *vlan_tx = commands_new( + commands_new(configure_lldp, + "vlan-tx", + "Send LLDP frames with a VLAN tag", + NULL, NULL, NULL), + NULL, "VLAN ID (0-4094)", + NULL, cmd_store_env_value, "vlan-tx-id"); + + struct cmd_node *vlan_tx_prio = commands_new( + commands_new(vlan_tx, + "priority", + "Also set a priority in a VLAN tag (default 0)", + NULL, NULL, NULL), + NULL, "Priority to be included in a VLAN tag (0-7)", + NULL, cmd_store_env_value, "vlan-tx-prio"); + + commands_new(vlan_tx, + NEWLINE, "Enable VLAN tagging of transmitted LLDP frames", + NULL, cmd_vlan_tx, + "enable"); + + commands_new( + vlan_tx_prio, + NEWLINE, "Set VLAN ID and priority for transmitted frames", + NULL, cmd_vlan_tx, "enable"); + + commands_new( + commands_new( + commands_new(vlan_tx_prio, + "dei", + "Also set a Drop eligible indicator (DEI) in a VLAN tag (default 0)", + NULL, NULL, NULL), + NULL, "Drop eligible indicator (DEI) in a VLAN tag (0-don't drop; 1-drop)", + NULL, cmd_store_env_value, "vlan-tx-dei"), + NEWLINE, "Set VLAN ID, priority and DEI for transmitted frames", + NULL, cmd_vlan_tx, "enable"); + + commands_new( + commands_new(unconfigure_lldp, + "vlan-tx", + "Send LLDP frames without VLAN tag", + NULL, NULL, NULL), + NEWLINE, "Disable VLAN tagging of transmitted LLDP frames", + NULL, cmd_vlan_tx, NULL); + #ifdef ENABLE_CUSTOM register_commands_configure_lldp_custom_tlvs(configure_lldp, unconfigure_lldp); diff --git a/src/client/display.c b/src/client/display.c index 5dd914c3..c6011809 100644 --- a/src/client/display.c +++ b/src/client/display.c @@ -343,6 +343,9 @@ display_autoneg(struct writer * w, int advertised, int bithd, int bitfd, char *d static void display_port(struct writer *w, lldpctl_atom_t *port, int details) { + int vlan_tx_tag; + char buf[5]; /* should be enough for printing */ + tag_start(w, "port", "Port"); tag_start(w, "id", "PortID"); tag_attr (w, "type", "", @@ -352,6 +355,18 @@ 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 ((vlan_tx_tag = lldpctl_atom_get_int(port, lldpctl_k_port_vlan_tx)) != -1) { + tag_start(w, "vlanTX", "VlanTX"); + snprintf(buf, sizeof(buf), "%d", vlan_tx_tag & 0xfff); + tag_attr (w, "id", "VID", buf); + snprintf(buf, sizeof(buf), "%d", (vlan_tx_tag >> 13) & 0x7); + tag_attr (w, "prio", "Prio", buf); + snprintf(buf, sizeof(buf), "%d", (vlan_tx_tag >> 12) & 0x1); + tag_attr (w, "dei", "DEI", buf); + tag_end(w); + } + if (details && lldpctl_atom_get_int(port, lldpctl_k_port_ttl) > 0) tag_datatag(w, "ttl", "TTL", diff --git a/src/client/lldpcli.8.in b/src/client/lldpcli.8.in index 08b25d85..6a03b342 100644 --- a/src/client/lldpcli.8.in +++ b/src/client/lldpcli.8.in @@ -550,6 +550,22 @@ main daemon. If it is configured in receive-only mode (with the flag), setting any transmit mode won't have any effect. .Ed +.Cd configure +.Op ports Ar ethX Op ,... +.Cd lldp +.Cd vlan-tx Ar vlan_id +.Op Cd prio Ar priority Op Cd dei Ar dei +.Bd -ragged -offset XXXXXX +Configure the given port to send LLDP frames over a specified VLAN. With VLAN Identifier (VID) as +.Ar vlan_id , +Priority Code Point (PCP) as +.Ar priority , +and Drop Eligible Indicator (DEI) as +.Ar dei . +.Nm lldpd +accepts LLDP frames on all VLANs. +.Ed + .Cd configure .Cd lldp custom-tlv .Op Cd add | replace diff --git a/src/daemon/client.c b/src/daemon/client.c index c5aa1cee..b4a08aae 100644 --- a/src/daemon/client.c +++ b/src/daemon/client.c @@ -390,6 +390,10 @@ _client_handle_set_port(struct lldpd *cfg, port->p_disable_rx = port->p_disable_tx = 1; break; } + if (set->vlan_tx_enabled >= -1) { + port->p_vlan_tx_enabled = set->vlan_tx_enabled; + port->p_vlan_tx_tag = set->vlan_tx_tag; + } #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/protocols/lldp.c b/src/daemon/protocols/lldp.c index a5d93528..ba65cf06 100644 --- a/src/daemon/protocols/lldp.c +++ b/src/daemon/protocols/lldp.c @@ -107,7 +107,21 @@ static int _lldp_send(struct lldpd *global, /* LLDP multicast address */ POKE_BYTES(mcastaddr, ETHER_ADDR_LEN) && /* Source MAC address */ - POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN) && + POKE_BYTES(&hardware->h_lladdr, ETHER_ADDR_LEN))) + goto toobig; + + /* Insert VLAN tag if needed */ + if (port->p_vlan_tx_enabled) { + if (!( + /* VLAN ethertype */ + POKE_UINT16(ETHERTYPE_VLAN) && + /* VLAN Tag Control Information (TCI) */ + /* Priority(3bits) | DEI(1bit) | VID(12bit) */ + POKE_UINT16(port->p_vlan_tx_tag))) + goto toobig; + } + + if (!( /* LLDP frame */ POKE_UINT16(ETHERTYPE_LLDP))) goto toobig; diff --git a/src/lib/atoms/port.c b/src/lib/atoms/port.c index 2fa24959..b316ac4b 100644 --- a/src/lib/atoms/port.c +++ b/src/lib/atoms/port.c @@ -349,6 +349,8 @@ _lldpctl_atom_set_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key, lldpctl_ato return NULL; } + set.vlan_tx_enabled = -1; + switch (key) { case lldpctl_k_port_id: set.local_id = p->port->p_id; @@ -359,6 +361,10 @@ _lldpctl_atom_set_atom_port(lldpctl_atom_t *atom, lldpctl_key_t key, lldpctl_ato case lldpctl_k_port_status: set.rxtx = LLDPD_RXTX_FROM_PORT(p->port); break; + case lldpctl_k_port_vlan_tx: + set.vlan_tx_tag = p->port->p_vlan_tx_tag; + set.vlan_tx_enabled = p->port->p_vlan_tx_enabled; + break; #ifdef ENABLE_DOT3 case lldpctl_k_port_dot3_power: if (value->type != atom_dot3_power) { @@ -520,6 +526,13 @@ _lldpctl_atom_set_int_port(lldpctl_atom_t *atom, lldpctl_key_t key, port->p_disable_rx = !LLDPD_RXTX_RXENABLED(value); port->p_disable_tx = !LLDPD_RXTX_TXENABLED(value); break; + case lldpctl_k_port_vlan_tx: + if (value > -1) { + port->p_vlan_tx_tag = value; + port->p_vlan_tx_enabled = 1; + } else + port->p_vlan_tx_enabled = 0; + break; default: SET_ERROR(atom->conn, LLDPCTL_ERR_NOT_EXIST); return NULL; @@ -622,6 +635,8 @@ _lldpctl_atom_get_int_port(lldpctl_atom_t *atom, lldpctl_key_t key) return port->p_id_subtype; case lldpctl_k_port_hidden: return port->p_hidden_in; + case lldpctl_k_port_vlan_tx: + return port->p_vlan_tx_enabled ? port->p_vlan_tx_tag : -1; #ifdef ENABLE_DOT3 case lldpctl_k_port_dot3_mfs: if (port->p_mfs > 0) diff --git a/src/lib/lldpctl.h b/src/lib/lldpctl.h index 21222bb3..d152e2b7 100644 --- a/src/lib/lldpctl.h +++ b/src/lib/lldpctl.h @@ -738,6 +738,7 @@ 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_dot3_mfs = 1300, /**< `(I)` MFS */ lldpctl_k_port_dot3_aggregid, /**< `(I)` Port aggregation ID */ diff --git a/src/lldpd-structs.h b/src/lldpd-structs.h index 31daef13..88d347c1 100644 --- a/src/lldpd-structs.h +++ b/src/lldpd-structs.h @@ -274,6 +274,8 @@ struct lldpd_port { int p_descr_force; /* Description has been forced by user */ u_int16_t p_mfs; u_int16_t p_ttl; /* TTL for remote port */ + int p_vlan_tx_tag; + int p_vlan_tx_enabled; #ifdef ENABLE_DOT3 /* Dot3 stuff */ @@ -341,6 +343,8 @@ struct lldpd_port_set { char *local_id; char *local_descr; int rxtx; + int vlan_tx_tag; + int vlan_tx_enabled; #ifdef ENABLE_LLDPMED struct lldpd_med_policy *med_policy; struct lldpd_med_loc *med_location; diff --git a/tests/check_lldp.c b/tests/check_lldp.c index 1e8b72c1..5c7f38a4 100644 --- a/tests/check_lldp.c +++ b/tests/check_lldp.c @@ -182,6 +182,83 @@ START_TEST (test_send_rcv_basic) } END_TEST +#define ETHERTYPE_OFFSET 2 * ETHER_ADDR_LEN +#define VLAN_TAG_SIZE 2 +START_TEST (test_send_rcv_vlan_tx) +{ + int n; + struct packet *pkt; + struct lldpd_chassis *nchassis = NULL; + struct lldpd_port *nport = NULL; + int vlan_id = 100; + int vlan_prio = 5; + int vlan_dei = 1; + unsigned int vlan_tag = 0; + unsigned int tmp; + + /* Populate port and chassis */ + hardware.h_lport.p_id_subtype = LLDP_PORTID_SUBTYPE_IFNAME; + hardware.h_lport.p_id = "FastEthernet 1/5"; + hardware.h_lport.p_id_len = strlen(hardware.h_lport.p_id); + hardware.h_lport.p_descr = "Fake port description"; + hardware.h_lport.p_mfs = 1516; + + /* Assembly VLAN tag: Priority(3bits) | DEI(1bit) | VID(12bits) */ + vlan_tag = ((vlan_prio & 0x7) << 13) | + ((vlan_dei & 0x1) << 12) | + (vlan_id & 0xfff); + hardware.h_lport.p_vlan_tx_tag = vlan_tag; + hardware.h_lport.p_vlan_tx_enabled = 1; + chassis.c_id_subtype = LLDP_CHASSISID_SUBTYPE_LLADDR; + chassis.c_id = macaddress; + chassis.c_id_len = ETHER_ADDR_LEN; + chassis.c_name = "First chassis"; + chassis.c_descr = "Chassis description"; + chassis.c_cap_available = chassis.c_cap_enabled = LLDP_CAP_ROUTER; + + /* Build packet */ + n = lldp_send(&test_lldpd, &hardware); + if (n != 0) { + fail("unable to build packet"); + return; + } + if (TAILQ_EMPTY(&pkts)) { + fail("no packets sent"); + return; + } + pkt = TAILQ_FIRST(&pkts); + fail_unless(TAILQ_NEXT(pkt, next) == NULL, "more than one packet sent"); + + /* Check ETHER_TYPE, should be VLAN */ + memcpy(&tmp, (unsigned char*) pkt->data + ETHERTYPE_OFFSET, ETHER_TYPE_LEN); + ck_assert_uint_eq(ntohl(tmp)>>16, ETHERTYPE_VLAN); + + /* Check VLAN tag */ + memcpy(&tmp, (unsigned char*) pkt->data + ETHERTYPE_OFFSET + ETHER_TYPE_LEN, VLAN_TAG_SIZE); + ck_assert_uint_eq(ntohl(tmp)>>16, vlan_tag); + + /* Remove VLAN ethertype and VLAN tag */ + memmove((unsigned char*) pkt->data + ETHERTYPE_OFFSET, + /* move all after VLAN tag */ + (unsigned char*) pkt->data + ETHERTYPE_OFFSET + ETHER_TYPE_LEN + VLAN_TAG_SIZE, + /* size without src and dst MAC, VLAN tag */ + pkt->size - (ETHERTYPE_OFFSET + ETHER_TYPE_LEN + VLAN_TAG_SIZE)); + + /* Decode the packet without VLAN tag, calling lldp_decode() */ + fail_unless(lldp_decode(NULL, pkt->data, pkt->size, &hardware, + &nchassis, &nport) != -1); + if (!nchassis || !nport) { + fail("unable to decode packet"); + return; + } + + /* Verify port values (VLAN information is not checked here) */ + check_received_port(&hardware.h_lport, nport); + /* Verify chassis values */ + check_received_chassis(&chassis, nchassis); +} +END_TEST + #ifdef ENABLE_DOT1 /* This test case tests send and receive of all DOT1 TLVs(2005 and 2009): Port Valn ID, VLAN, Port Protocol VLAN ID, Protocol Identity, @@ -769,6 +846,7 @@ lldp_suite(void) tcase_add_checked_fixture(tc_send, pcap_setup, pcap_teardown); tcase_add_test(tc_send, test_send_rcv_basic); + tcase_add_test(tc_send, test_send_rcv_vlan_tx); #ifdef ENABLE_DOT1 tcase_add_test(tc_send, test_send_rcv_dot1_tlvs); #endif diff --git a/tests/integration/test_basic.py b/tests/integration/test_basic.py index 488b235c..75dc62f5 100644 --- a/tests/integration/test_basic.py +++ b/tests/integration/test_basic.py @@ -397,6 +397,22 @@ def test_port_status_disabled(lldpd, lldpcli, namespaces, links): assert out == {} +def test_port_vlan_tx(lldpd1, lldpd, lldpcli, namespaces): + with namespaces(1): + lldpd() + lldpcli("configure", "ports", "eth0", "lldp", "vlan-tx", "100", "priority", "5", "dei", "1") + out = lldpcli("-f", "keyvalue", "show", "interfaces", "ports", "eth0") + assert out["lldp.eth0.port.vlanTX.id"] == '100' + assert out["lldp.eth0.port.vlanTX.prio"] == '5' + assert out["lldp.eth0.port.vlanTX.dei"] == '1' + # unconfigure VLAN TX + lldpcli("unconfigure", "ports", "eth0", "lldp", "vlan-tx") + out = lldpcli("-f", "keyvalue", "show", "interfaces", "ports", "eth0") + assert not "lldp.eth0.port.vlanTX.id" in out + assert not "lldp.eth0.port.vlanTX.prio" in out + assert not "lldp.eth0.port.vlanTX.dei" in out + + def test_set_interface_alias(lldpd1, lldpd, lldpcli, namespaces): with namespaces(1): lldpcli("configure", "system", "interface", "description") -- 2.39.5