]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/libsystemd-network/sd-lldp.c
tree-wide: use mfree more
[thirdparty/systemd.git] / src / libsystemd-network / sd-lldp.c
index 885ca62425d8e03b836c890b2bb76b18334066c2..0702241506b1522d82e26a580bc77464a5c943b4 100644 (file)
@@ -1,21 +1,21 @@
 /***
-    This file is part of systemd.
+  This file is part of systemd.
 
-    Copyright (C) 2014 Tom Gundersen
-    Copyright (C) 2014 Susant Sahani
+  Copyright (C) 2014 Tom Gundersen
+  Copyright (C) 2014 Susant Sahani
 
-    systemd is free software; you can redistribute it and/or modify it
-    under the terms of the GNU Lesser General Public License as published by
-    the Free Software Foundation; either version 2.1 of the License, or
-    (at your option) any later version.
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
 
-    systemd is distributed in the hope that it will be useful, but
-    WITHOUT ANY WARRANTY; without even the implied warranty of
-    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-    Lesser General Public License for more details.
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
 
-    You should have received a copy of the GNU Lesser General Public License
-    along with systemd; If not, see <http://www.gnu.org/licenses/>.
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
 #include <arpa/inet.h>
 
 #include "alloc-util.h"
 #include "fd-util.h"
-#include "fileio.h"
-#include "hashmap.h"
 #include "lldp-internal.h"
-#include "lldp-port.h"
-#include "lldp-tlv.h"
-#include "prioq.h"
-#include "siphash24.h"
-#include "string-util.h"
-
-typedef enum LLDPAgentRXState {
-        LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4,
-        LLDP_AGENT_RX_DELETE_AGED_INFO,
-        LLDP_AGENT_RX_LLDP_INITIALIZE,
-        LLDP_AGENT_RX_WAIT_FOR_FRAME,
-        LLDP_AGENT_RX_RX_FRAME,
-        LLDP_AGENT_RX_DELETE_INFO,
-        LLDP_AGENT_RX_UPDATE_INFO,
-        _LLDP_AGENT_RX_STATE_MAX,
-        _LLDP_AGENT_RX_INVALID = -1,
-} LLDPAgentRXState;
-
-/* Section 10.5.2.2 Reception counters */
-struct lldp_agent_statistics {
-        uint64_t stats_ageouts_total;
-        uint64_t stats_frames_discarded_total;
-        uint64_t stats_frames_in_errors_total;
-        uint64_t stats_frames_in_total;
-        uint64_t stats_tlvs_discarded_total;
-        uint64_t stats_tlvs_unrecognized_total;
-};
-
-struct sd_lldp {
-        lldp_port *port;
-
-        Prioq *by_expiry;
-        Hashmap *neighbour_mib;
-
-        sd_lldp_cb_t cb;
-
-        void *userdata;
-
-        LLDPAgentRXState rx_state;
-        lldp_agent_statistics statistics;
-};
-
-static void chassis_id_hash_func(const void *p, struct siphash *state) {
-        const lldp_chassis_id *id = p;
-
-        assert(id);
-        assert(id->data);
-
-        siphash24_compress(&id->length, sizeof(id->length), state);
-        siphash24_compress(id->data, id->length, state);
-}
-
-static int chassis_id_compare_func(const void *_a, const void *_b) {
-        const lldp_chassis_id *a, *b;
+#include "lldp-neighbor.h"
+#include "lldp-network.h"
+#include "socket-util.h"
+#include "ether-addr-util.h"
 
-        a = _a;
-        b = _b;
+#define LLDP_DEFAULT_NEIGHBORS_MAX 128U
 
-        assert(!a->length || a->data);
-        assert(!b->length || b->data);
+static void lldp_flush_neighbors(sd_lldp *lldp) {
+        sd_lldp_neighbor *n;
 
-        if (a->type != b->type)
-                return -1;
-
-        if (a->length != b->length)
-                return a->length < b->length ? -1 : 1;
+        assert(lldp);
 
-        return memcmp(a->data, b->data, a->length);
+        while ((n = hashmap_first(lldp->neighbor_by_id)))
+                lldp_neighbor_unlink(n);
 }
 
-static const struct hash_ops chassis_id_hash_ops = {
-        .hash = chassis_id_hash_func,
-        .compare = chassis_id_compare_func
-};
-
-static void lldp_mib_delete_objects(sd_lldp *lldp);
-static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state);
-static void lldp_run_state_machine(sd_lldp *ll);
-
-static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
-        int r;
-
+static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
         assert(lldp);
-        assert(tlv);
-
-        /* Remove expired packets */
-        if (prioq_size(lldp->by_expiry) > 0) {
 
-                lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO);
+        log_lldp("Invoking callback for '%c'.", event);
 
-                lldp_mib_delete_objects(lldp);
-        }
+        if (!lldp->callback)
+                return;
 
-        r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
-        if (r < 0)
-                goto out;
+        lldp->callback(lldp, event, n, lldp->userdata);
+}
 
-        lldp_set_state(lldp, LLDP_AGENT_RX_UPDATE_INFO);
+static int lldp_make_space(sd_lldp *lldp, size_t extra) {
+        usec_t t = USEC_INFINITY;
+        bool changed = false;
 
-        log_lldp("Packet added. MIB size: %d , PQ size: %d",
-                 hashmap_size(lldp->neighbour_mib),
-                 prioq_size(lldp->by_expiry));
+        assert(lldp);
 
-        lldp->statistics.stats_frames_in_total ++;
+        /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries
+         * are free. */
 
- out:
-        if (r < 0)
-                log_lldp("Receive frame failed: %s", strerror(-r));
+        for (;;) {
+                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
 
-        lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+                n = prioq_peek(lldp->neighbor_by_expiry);
+                if (!n)
+                        break;
 
-        return 0;
-}
+                sd_lldp_neighbor_ref(n);
 
-/* 10.3.2 LLDPDU validation: rxProcessFrame() */
-int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
-        bool system_description = false, system_name = false, chassis_id = false;
-        bool malformed = false, port_id = false, ttl = false, end = false;
-        uint16_t type, len, i, l, t;
-        lldp_port *port;
-        uint8_t *p, *q;
-        sd_lldp *lldp;
-        int r;
+                if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
+                        goto remove_one;
 
-        assert(tlv);
-        assert(length > 0);
+                if (t == USEC_INFINITY)
+                        t = now(clock_boottime_or_monotonic());
 
-        port = (lldp_port *) tlv->userdata;
-        lldp = (sd_lldp *) port->userdata;
+                if (n->until > t)
+                        break;
 
-        if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
-                log_lldp("Port: %s is disabled. Dropping.", lldp->port->ifname);
-                goto out;
+        remove_one:
+                lldp_neighbor_unlink(n);
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
+                changed = true;
         }
 
-        lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
-
-        p = tlv->pdu;
-        p += sizeof(struct ether_header);
+        return changed;
+}
 
-        for (i = 1, l = 0; l <= length; i++) {
+static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        assert(lldp);
+        assert(n);
 
-                memcpy(&t, p, sizeof(uint16_t));
+        /* Don't keep data with a zero TTL */
+        if (n->ttl <= 0)
+                return false;
 
-                type = ntohs(t) >> 9;
-                len = ntohs(t) & 0x01ff;
+        /* Filter out data from the filter address */
+        if (!ether_addr_is_null(&lldp->filter_address) &&
+            ether_addr_equal(&lldp->filter_address, &n->source_address))
+                return false;
 
-                if (type == LLDP_TYPE_END) {
-                        if (len != 0) {
-                                log_lldp("TLV type end must be length 0 (not %d). Dropping.", len);
+        /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with
+         * no caps field set. */
+        if (n->has_capabilities &&
+            (n->enabled_capabilities & lldp->capability_mask) == 0)
+                return false;
 
-                                malformed = true;
-                                goto out;
-                        }
+        /* Keep everything else */
+        return true;
+}
 
-                        end = true;
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
 
-                        break;
-                } else if (type >=_LLDP_TYPE_MAX) {
-                        log_lldp("TLV type: %d not recognized. Dropping.", type);
+static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+        bool keep;
+        int r;
 
-                        malformed = true;
-                        goto out;
-                }
+        assert(lldp);
+        assert(n);
+        assert(!n->lldp);
 
-                /* skip type and length encoding */
-                p += 2;
-                q = p;
+        keep = lldp_keep_neighbor(lldp, n);
 
-                p += len;
-                l += (len + 2);
+        /* First retrieve the old entry for this MSAP */
+        old = hashmap_get(lldp->neighbor_by_id, &n->id);
+        if (old) {
+                sd_lldp_neighbor_ref(old);
 
-                if (i <= 3) {
-                        if (i != type) {
-                                log_lldp("TLV missing or out of order. Dropping.");
+                if (!keep) {
+                        lldp_neighbor_unlink(old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+                        return 0;
+                }
 
-                                malformed = true;
-                                goto out;
-                        }
+                if (lldp_neighbor_equal(n, old)) {
+                        /* Is this equal, then restart the TTL counter, but don't do anyting else. */
+                        old->timestamp = n->timestamp;
+                        lldp_start_timer(lldp, old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
+                        return 0;
                 }
 
-                switch(type) {
-                case LLDP_TYPE_CHASSIS_ID:
+                /* Data changed, remove the old entry, and add a new one */
+                lldp_neighbor_unlink(old);
 
-                        if (len < 2) {
-                                log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len);
+        } else if (!keep)
+                return 0;
 
-                                malformed = true;
-                                goto out;
-                        }
+        /* Then, make room for at least one new neighbor */
+        lldp_make_space(lldp, 1);
 
-                        if (chassis_id) {
-                                log_lldp("Duplicate Chassis ID TLV found. Dropping.");
+        r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
+        if (r < 0)
+                goto finish;
 
-                                malformed = true;
-                                goto out;
-                        }
+        r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx);
+        if (r < 0) {
+                assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n);
+                goto finish;
+        }
 
-                        /* Look what subtype it has */
-                        if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
-                                log_lldp("Unknown subtype: %d found in Chassis ID TLV. Dropping.", *q);
+        n->lldp = lldp;
 
-                                malformed = true;
-                                goto out;
+        lldp_start_timer(lldp, n);
+        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
 
-                        }
+        return 1;
 
-                        chassis_id = true;
+finish:
+        if (old)
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
 
-                        break;
-                case LLDP_TYPE_PORT_ID:
+        return r;
+}
 
-                        if (len < 2) {
-                                log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len);
+static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        int r;
 
-                                malformed = true;
-                                goto out;
-                        }
+        assert(lldp);
+        assert(n);
 
-                        if (port_id) {
-                                log_lldp("Duplicate Port ID TLV found. Dropping.");
+        r = lldp_neighbor_parse(n);
+        if (r == -EBADMSG) /* Ignore bad messages */
+                return 0;
+        if (r < 0)
+                return r;
 
-                                malformed = true;
-                                goto out;
-                        }
+        r = lldp_add_neighbor(lldp, n);
+        if (r < 0) {
+                log_lldp_errno(r, "Failed to add datagram. Ignoring.");
+                return 0;
+        }
 
-                        /* Look what subtype it has */
-                        if (*q == LLDP_PORT_SUBTYPE_RESERVED || *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
-                                log_lldp("Unknown subtype: %d found in Port ID TLV. Dropping.", *q);
+        log_lldp("Successfully processed LLDP datagram.");
+        return 0;
+}
 
-                                malformed = true;
-                                goto out;
+static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+        ssize_t space, length;
+        sd_lldp *lldp = userdata;
+        struct timespec ts;
 
-                        }
+        assert(fd >= 0);
+        assert(lldp);
 
-                        port_id = true;
+        space = next_datagram_size_fd(fd);
+        if (space < 0)
+                return log_lldp_errno(space, "Failed to determine datagram size to read: %m");
 
-                        break;
-                case LLDP_TYPE_TTL:
+        n = lldp_neighbor_new(space);
+        if (!n)
+                return -ENOMEM;
 
-                        if(len != 2) {
-                                log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len);
+        length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
+        if (length < 0) {
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
 
-                                malformed = true;
-                                goto out;
-                        }
+                return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+        }
 
-                        if (ttl) {
-                                log_lldp("Duplicate TTL TLV found. Dropping.");
+        if ((size_t) length != n->raw_size) {
+                log_lldp("Packet size mismatch.");
+                return -EINVAL;
+        }
 
-                                malformed = true;
-                                goto out;
-                        }
+        /* Try to get the timestamp of this packet if it is known */
+        if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0)
+                triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts));
+        else
+                triple_timestamp_get(&n->timestamp);
 
-                        ttl = true;
+        return lldp_handle_datagram(lldp, n);
+}
 
-                        break;
-                case LLDP_TYPE_SYSTEM_NAME:
+static void lldp_reset(sd_lldp *lldp) {
+        assert(lldp);
 
-                        /* According to RFC 1035 the length of a FQDN is limited to 255 characters */
-                        if (len > 255) {
-                                log_lldp("Received invalid system name length: %d. Dropping.", len);
-                                malformed = true;
-                                goto out;
-                        }
+        lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source);
+        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
+        lldp->fd = safe_close(lldp->fd);
+}
 
-                        if (system_name) {
-                                log_lldp("Duplicate system name found. Dropping.");
-                                malformed = true;
-                                goto out;
-                        }
+_public_ int sd_lldp_start(sd_lldp *lldp) {
+        int r;
 
-                        system_name = true;
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->event, -EINVAL);
+        assert_return(lldp->ifindex > 0, -EINVAL);
 
-                        break;
-                case LLDP_TYPE_SYSTEM_DESCRIPTION:
-
-                        /* 0 <= n <= 255 octets */
-                        if (len > 255) {
-                                log_lldp("Received invalid system description length: %d. Dropping.", len);
-                                malformed = true;
-                                goto out;
-                        }
-
-                        if (system_description) {
-                                log_lldp("Duplicate system description found. Dropping.");
-                                malformed = true;
-                                goto out;
-                        }
-
-                        system_description = true;
-                        break;
-                default:
+        if (lldp->fd >= 0)
+                return 0;
 
-                        if (len == 0) {
-                                log_lldp("TLV type: %d length 0 received. Dropping.", type);
+        assert(!lldp->io_event_source);
 
-                                malformed = true;
-                                goto out;
-                        }
-                        break;
-                }
-        }
+        lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex);
+        if (lldp->fd < 0)
+                return lldp->fd;
 
-        if(!chassis_id || !port_id || !ttl || !end) {
-                log_lldp("One or more mandatory TLV missing. Dropping.");
+        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+        if (r < 0)
+                goto fail;
 
-                malformed = true;
-                goto out;
+        r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
+        if (r < 0)
+                goto fail;
 
-        }
+        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
 
-        r = tlv_packet_parse_pdu(tlv, length);
-        if (r < 0) {
-                log_lldp("Failed to parse the TLV. Dropping.");
+        log_lldp("Started LLDP client");
+        return 1;
 
-                malformed = true;
-                goto out;
-        }
+fail:
+        lldp_reset(lldp);
+        return r;
+}
 
-        return lldp_receive_frame(lldp, tlv);
+_public_ int sd_lldp_stop(sd_lldp *lldp) {
+        assert_return(lldp, -EINVAL);
 
- out:
-        lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+        if (lldp->fd < 0)
+                return 0;
 
-        if (malformed) {
-                lldp->statistics.stats_frames_discarded_total ++;
-                lldp->statistics.stats_frames_in_errors_total ++;
-        }
+        log_lldp("Stopping LLDP client");
 
-        sd_lldp_packet_unref(tlv);
+        lldp_reset(lldp);
+        lldp_flush_neighbors(lldp);
 
-        return 0;
+        return 1;
 }
 
-static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
-        const lldp_neighbour_port *p = a, *q = b;
+_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) {
+        int r;
 
-        if (p->until < q->until)
-                return -1;
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
+        assert_return(!lldp->event, -EBUSY);
 
-        if (p->until > q->until)
-                return 1;
+        if (event)
+                lldp->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&lldp->event);
+                if (r < 0)
+                        return r;
+        }
+
+        lldp->event_priority = priority;
 
         return 0;
 }
 
-static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
+_public_ int sd_lldp_detach_event(sd_lldp *lldp) {
 
-        assert(lldp);
-        assert(state < _LLDP_AGENT_RX_STATE_MAX);
-
-        lldp->rx_state = state;
+        assert_return(lldp, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
 
-        lldp_run_state_machine(lldp);
+        lldp->event = sd_event_unref(lldp->event);
+        return 0;
 }
 
-static void lldp_run_state_machine(sd_lldp *lldp) {
-        if (!lldp->cb)
-                return;
+_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+        assert_return(lldp, NULL);
 
-        switch (lldp->rx_state) {
-        case LLDP_AGENT_RX_UPDATE_INFO:
-                lldp->cb(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata);
-                break;
-        default:
-                break;
-        }
+        return lldp->event;
 }
 
-/* 10.5.5.2.1 mibDeleteObjects ()
- * The mibDeleteObjects () procedure deletes all information in the LLDP remote
- * systems MIB associated with the MSAP identifier if an LLDPDU is received with
- * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
-
-static void lldp_mib_delete_objects(sd_lldp *lldp) {
-        lldp_neighbour_port *p;
-        usec_t t = 0;
-
-        /* Remove all entries that are past their TTL */
-        for (;;) {
-
-                if (prioq_size(lldp->by_expiry) <= 0)
-                        break;
-
-                p = prioq_peek(lldp->by_expiry);
-                if (!p)
-                        break;
+_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
+        assert_return(lldp, -EINVAL);
 
-                if (t <= 0)
-                        t = now(clock_boottime_or_monotonic());
+        lldp->callback = cb;
+        lldp->userdata = userdata;
 
-                if (p->until > t)
-                        break;
+        return 0;
+}
 
-                lldp_neighbour_port_remove_and_free(p);
+_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) {
+        assert_return(lldp, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
+        assert_return(lldp->fd < 0, -EBUSY);
 
-                lldp->statistics.stats_ageouts_total ++;
-        }
+        lldp->ifindex = ifindex;
+        return 0;
 }
 
-static void lldp_mib_objects_flush(sd_lldp *lldp) {
-        lldp_neighbour_port *p, *q;
-        lldp_chassis *c;
-
-        assert(lldp);
-        assert(lldp->neighbour_mib);
-        assert(lldp->by_expiry);
+_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
 
-        /* Drop all packets */
-        while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
+        if (!lldp)
+                return NULL;
 
-                LIST_FOREACH_SAFE(port, p, q, c->ports) {
-                        lldp_neighbour_port_remove_and_free(p);
-                }
-        }
+        assert(lldp->n_ref > 0);
+        lldp->n_ref++;
 
-        assert(hashmap_size(lldp->neighbour_mib) == 0);
-        assert(prioq_size(lldp->by_expiry) == 0);
+        return lldp;
 }
 
-int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
-        _cleanup_free_ char *temp_path = NULL;
-        _cleanup_fclose_ FILE *f = NULL;
-        uint8_t *mac, *port_id, type;
-        lldp_neighbour_port *p;
-        uint16_t data = 0, length = 0;
-        char buf[LINE_MAX];
-        lldp_chassis *c;
-        usec_t time;
-        Iterator i;
-        int r;
-
-        assert(lldp);
-        assert(lldp_file);
-
-        r = fopen_temporary(lldp_file, &f, &temp_path);
-        if (r < 0)
-                goto fail;
+_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
 
-        fchmod(fileno(f), 0644);
-
-        HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
-                LIST_FOREACH(port, p, c->ports) {
-                        _cleanup_free_ char *s = NULL;
-                        char *k, *t;
-
-                        r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length);
-                        if (r < 0)
-                                continue;
-
-                        sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
-                                mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
-
-                        s = strdup(buf);
-                        if (!s) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
-
-                        r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length);
-                        if (r < 0)
-                                continue;
-
-                        if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
-                                k = strndup((char *) port_id, length -1);
-                                if (!k) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
-
-                                sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
-                                free(k);
-                        } else {
-                                mac = port_id;
-                                sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
-                                        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
-                        }
-
-                        k = strappend(s, buf);
-                        if (!k) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
-
-                        free(s);
-                        s = k;
-
-                        time = now(clock_boottime_or_monotonic());
-
-                        /* Don't write expired packets */
-                        if (time - p->until <= 0)
-                                continue;
-
-                        sprintf(buf, "'_TTL="USEC_FMT"' ", p->until);
-
-                        k = strappend(s, buf);
-                        if (!k) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
-
-                        free(s);
-                        s = k;
-
-                        r = sd_lldp_packet_read_system_name(p->packet, &k, &length);
-                        if (r < 0)
-                                k = strappend(s, "'_NAME=N/A' ");
-                        else {
-                                t = strndup(k, length);
-                                if (!t) {
-                                        r = -ENOMEM;
-                                        goto fail;
-                                }
-
-                                k = strjoin(s, "'_NAME=", t, "' ", NULL);
-                                free(t);
-                        }
-
-                        if (!k) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
-
-                        free(s);
-                        s = k;
-
-                        (void) sd_lldp_packet_read_system_capability(p->packet, &data);
-
-                        sprintf(buf, "'_CAP=%x'", data);
-
-                        k = strappend(s, buf);
-                        if (!k) {
-                                r = -ENOMEM;
-                                goto fail;
-                        }
-
-                        free(s);
-                        s = k;
-
-                        fprintf(f, "%s\n", s);
-                }
-        }
+        if (!lldp)
+                return NULL;
 
-        r = fflush_and_check(f);
-        if (r < 0)
-                goto fail;
+        assert(lldp->n_ref > 0);
+        lldp->n_ref --;
 
-        if (rename(temp_path, lldp_file) < 0) {
-                r = -errno;
-                goto fail;
-        }
-
-        return 0;
+        if (lldp->n_ref > 0)
+                return NULL;
 
- fail:
-        if (temp_path)
-                (void) unlink(temp_path);
+        lldp_reset(lldp);
+        sd_lldp_detach_event(lldp);
+        lldp_flush_neighbors(lldp);
 
-        return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file);
+        hashmap_free(lldp->neighbor_by_id);
+        prioq_free(lldp->neighbor_by_expiry);
+        return mfree(lldp);
 }
 
-int sd_lldp_start(sd_lldp *lldp) {
+_public_ int sd_lldp_new(sd_lldp **ret) {
+        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
         int r;
 
-        assert_return(lldp, -EINVAL);
-        assert_return(lldp->port, -EINVAL);
-
-        lldp->port->status = LLDP_PORT_STATUS_ENABLED;
+        assert_return(ret, -EINVAL);
 
-        lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE);
+        lldp = new0(sd_lldp, 1);
+        if (!lldp)
+                return -ENOMEM;
 
-        r = lldp_port_start(lldp->port);
-        if (r < 0) {
-                log_lldp("Failed to start Port : %s , %s",
-                         lldp->port->ifname,
-                         strerror(-r));
+        lldp->n_ref = 1;
+        lldp->fd = -1;
+        lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
+        lldp->capability_mask = (uint16_t) -1;
 
-                lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL);
+        lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops);
+        if (!lldp->neighbor_by_id)
+                return -ENOMEM;
 
+        r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func);
+        if (r < 0)
                 return r;
-        }
 
-        lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+        *ret = lldp;
+        lldp = NULL;
 
         return 0;
 }
 
-int sd_lldp_stop(sd_lldp *lldp) {
-        int r;
+static int neighbor_compare_func(const void *a, const void *b) {
+        const sd_lldp_neighbor * const*x = a, * const *y = b;
 
-        assert_return(lldp, -EINVAL);
-        assert_return(lldp->port, -EINVAL);
+        return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id);
+}
 
-        lldp->port->status = LLDP_PORT_STATUS_DISABLED;
+static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
+        sd_lldp *lldp = userdata;
+        int r, q;
 
-        r = lldp_port_stop(lldp->port);
+        r = lldp_make_space(lldp, 0);
         if (r < 0)
-                return r;
+                return log_lldp_errno(r, "Failed to make space: %m");
 
-        lldp_mib_objects_flush(lldp);
+        q = lldp_start_timer(lldp, NULL);
+        if (q < 0)
+                return log_lldp_errno(q, "Failed to restart timer: %m");
 
         return 0;
 }
 
-int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
+        sd_lldp_neighbor *n;
         int r;
 
-        assert_return(lldp, -EINVAL);
-        assert_return(!lldp->port->event, -EBUSY);
+        assert(lldp);
 
-        if (event)
-                lldp->port->event = sd_event_ref(event);
-        else {
-                r = sd_event_default(&lldp->port->event);
-                if (r < 0)
-                        return r;
-        }
+        if (neighbor)
+                lldp_neighbor_start_ttl(neighbor);
 
-        lldp->port->event_priority = priority;
+        n = prioq_peek(lldp->neighbor_by_expiry);
+        if (!n) {
 
-        return 0;
-}
+                if (lldp->timer_event_source)
+                        return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF);
 
-int sd_lldp_detach_event(sd_lldp *lldp) {
+                return 0;
+        }
 
-        assert_return(lldp, -EINVAL);
+        if (lldp->timer_event_source) {
+                r = sd_event_source_set_time(lldp->timer_event_source, n->until);
+                if (r < 0)
+                        return r;
 
-        lldp->port->event = sd_event_unref(lldp->port->event);
+                return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT);
+        }
 
-        return 0;
-}
+        if (!lldp->event)
+                return 0;
 
-int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) {
-        assert_return(lldp, -EINVAL);
+        r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp);
+        if (r < 0)
+                return r;
 
-        lldp->cb = cb;
-        lldp->userdata = userdata;
+        r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority);
+        if (r < 0)
+                return r;
 
+        (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer");
         return 0;
 }
 
-sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
+_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
+        sd_lldp_neighbor **l = NULL, *n;
+        Iterator i;
+        int k = 0, r;
 
-        if (!lldp)
-                return NULL;
+        assert_return(lldp, -EINVAL);
+        assert_return(ret, -EINVAL);
 
-        /* Drop all packets */
-        lldp_mib_objects_flush(lldp);
+        if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
+                *ret = NULL;
+                return 0;
+        }
 
-        lldp_port_free(lldp->port);
+        l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id));
+        if (!l)
+                return -ENOMEM;
 
-        hashmap_free(lldp->neighbour_mib);
-        prioq_free(lldp->by_expiry);
+        r = lldp_start_timer(lldp, NULL);
+        if (r < 0) {
+                free(l);
+                return r;
+        }
 
-        free(lldp);
-        return NULL;
-}
+        HASHMAP_FOREACH(n, lldp->neighbor_by_id, i)
+                l[k++] = sd_lldp_neighbor_ref(n);
 
-int sd_lldp_new(int ifindex,
-                const char *ifname,
-                const struct ether_addr *mac,
-                sd_lldp **ret) {
-        _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
-        int r;
+        assert((size_t) k == hashmap_size(lldp->neighbor_by_id));
 
-        assert_return(ret, -EINVAL);
-        assert_return(ifindex > 0, -EINVAL);
-        assert_return(ifname, -EINVAL);
-        assert_return(mac, -EINVAL);
+        /* Return things in a stable order */
+        qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func);
+        *ret = l;
 
-        lldp = new0(sd_lldp, 1);
-        if (!lldp)
-                return -ENOMEM;
+        return k;
+}
 
-        r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
-        if (r < 0)
-                return r;
+_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) {
+        assert_return(lldp, -EINVAL);
+        assert_return(m <= 0, -EINVAL);
 
-        lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
-        if (!lldp->neighbour_mib)
-                return -ENOMEM;
+        lldp->neighbors_max = m;
+        lldp_make_space(lldp, 0);
 
-        r = prioq_ensure_allocated(&lldp->by_expiry,
-                                   ttl_expiry_item_prioq_compare_func);
-        if (r < 0)
-                return r;
+        return 0;
+}
 
-        lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL;
+_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) {
+        assert_return(lldp, -EINVAL);
+        assert_return(mask != 0, -EINVAL);
 
-        *ret = lldp;
-        lldp = NULL;
+        lldp->capability_mask = mask;
 
         return 0;
 }
 
-int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs) {
-        lldp_neighbour_port *p;
-        lldp_chassis *c;
-        Iterator iter;
-        unsigned count = 0, i;
-
+_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) {
         assert_return(lldp, -EINVAL);
-        assert_return(tlvs, -EINVAL);
 
-        HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
-                LIST_FOREACH(port, p, c->ports)
-                        count++;
-        }
+        /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so
+         * that our own can be filtered out here. */
 
-        if (!count) {
-                *tlvs = NULL;
-                return 0;
-        }
-
-        *tlvs = new(sd_lldp_packet *, count);
-        if (!*tlvs)
-                return -ENOMEM;
+        if (addr)
+                lldp->filter_address = *addr;
+        else
+                zero(lldp->filter_address);
 
-        i = 0;
-        HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
-                LIST_FOREACH(port, p, c->ports)
-                        (*tlvs)[i++] = sd_lldp_packet_ref(p->packet);
-        }
-
-        return count;
+        return 0;
 }