]> git.ipfire.org Git - thirdparty/lldpd.git/commitdiff
ports: allow sending LLDP frames on a specified VLAN 410/head
authorMichal Wasiak <michal.wasiak@gmail.com>
Fri, 3 Jul 2020 18:36:03 +0000 (20:36 +0200)
committerMichal Wasiak <michal.wasiak@gmail.com>
Tue, 18 Aug 2020 14:50:17 +0000 (16:50 +0200)
The current limitation is that the frames are sent only on one VLAN per port.

NEWS
src/client/conf-lldp.c
src/client/display.c
src/client/lldpcli.8.in
src/daemon/client.c
src/daemon/protocols/lldp.c
src/lib/atoms/port.c
src/lib/lldpctl.h
src/lldpd-structs.h
tests/check_lldp.c
tests/integration/test_basic.py

diff --git a/NEWS b/NEWS
index 86794e13fa5a67a74fc5736801d319c8996f8a2b..5596d1628c0a19c19d4791eb35a888005fc1229f 100644 (file)
--- 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:
index f82f2ec7c1046f6bb454bde9d16ffde708e0cd95..b2e948266fbb0d1c289dff61f6a90cc16998c93c 100644 (file)
@@ -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);
index 5dd914c3d0187df2b1094c2730298b80958175bb..c6011809f4e132090ea85001c6a427bf99e538cc 100644 (file)
@@ -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",
index 08b25d852b52a9c779d8c4c7bbbfdff8aeb41653..6a03b34227cf8c2de78f12204b58ea3394534d10 100644 (file)
@@ -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
index c5aa1ceeca0161cd4536b012f55302c3bf2390f1..b4a08aae80a8ab019468558e7a474bfd88a3b094 100644 (file)
@@ -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");
index a5d93528ee780ee9cff4b25aa4ec5436891a5408..ba65cf068e5dbb767e97d20d0db6b3f4ad2b0beb 100644 (file)
@@ -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;
index 2fa249594f26c049df09e9066997236004c95c50..b316ac4b81f12ccb31fce979c69dce502bcb790d 100644 (file)
@@ -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)
index 21222bb3dd9b1d42b20a407f47448b4f4338ebb6..d152e2b7700e8a3afd30a2777f0eeb3903f49f1e 100644 (file)
@@ -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 */
index 31daef1368832a4f746a4e8fed6e543329006616..88d347c1cea679eed491405453d600040e50b472 100644 (file)
@@ -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;
index 1e8b72c1d124eaf6119ef703cf99faf1e903aa86..5c7f38a465a4dd43cabcf781663b1b743260d0be 100644 (file)
@@ -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
index 488b235c24e30e1c7bb771e86b2fe8b1810e6e1d..75dc62f5ea6da1d16596ae25b58433f5abe75d22 100644 (file)
@@ -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")