]> 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 3af6133a4ea4f45fc37f8dcf7a02b7e93585dc31..0702241506b1522d82e26a580bc77464a5c943b4 100644 (file)
@@ -41,8 +41,18 @@ static void lldp_flush_neighbors(sd_lldp *lldp) {
                 lldp_neighbor_unlink(n);
 }
 
+static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) {
+        assert(lldp);
+
+        log_lldp("Invoking callback for '%c'.", event);
+
+        if (!lldp->callback)
+                return;
+
+        lldp->callback(lldp, event, n, lldp->userdata);
+}
+
 static int lldp_make_space(sd_lldp *lldp, size_t extra) {
-        sd_lldp_neighbor *n;
         usec_t t = USEC_INFINITY;
         bool changed = false;
 
@@ -52,10 +62,14 @@ static int lldp_make_space(sd_lldp *lldp, size_t extra) {
          * are free. */
 
         for (;;) {
+                _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL;
+
                 n = prioq_peek(lldp->neighbor_by_expiry);
                 if (!n)
                         break;
 
+                sd_lldp_neighbor_ref(n);
+
                 if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra))
                         goto remove_one;
 
@@ -67,66 +81,99 @@ static int lldp_make_space(sd_lldp *lldp, size_t extra) {
 
         remove_one:
                 lldp_neighbor_unlink(n);
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n);
                 changed = true;
         }
 
         return changed;
 }
 
+static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
+        assert(lldp);
+        assert(n);
+
+        /* Don't keep data with a zero TTL */
+        if (n->ttl <= 0)
+                return false;
+
+        /* 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;
+
+        /* 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;
+
+        /* Keep everything else */
+        return true;
+}
+
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor);
+
 static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) {
-        sd_lldp_neighbor *old;
-        bool changed = false;
+        _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL;
+        bool keep;
         int r;
 
         assert(lldp);
         assert(n);
         assert(!n->lldp);
 
+        keep = lldp_keep_neighbor(lldp, n);
+
         /* 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 (!keep) {
+                        lldp_neighbor_unlink(old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+                        return 0;
+                }
+
                 if (lldp_neighbor_equal(n, old)) {
                         /* Is this equal, then restart the TTL counter, but don't do anyting else. */
-                        lldp_neighbor_start_ttl(old);
+                        old->timestamp = n->timestamp;
+                        lldp_start_timer(lldp, old);
+                        lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old);
                         return 0;
                 }
 
                 /* Data changed, remove the old entry, and add a new one */
                 lldp_neighbor_unlink(old);
-                changed = true;
-        }
-
-        /* Then, add the new entry in its place, but only if it has a non-zero TTL. */
-        if (n->ttl <= 0)
-                return changed;
-
-        /* Filter out the filter address */
-        if (!ether_addr_is_null(&lldp->filter_address) &&
-            ether_addr_equal(&lldp->filter_address, &n->source_address))
-                return changed;
 
-        /* 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 changed;
+        } else if (!keep)
+                return 0;
 
         /* Then, make room for at least one new neighbor */
         lldp_make_space(lldp, 1);
 
         r = hashmap_put(lldp->neighbor_by_id, &n->id, n);
         if (r < 0)
-                return r;
+                goto finish;
 
         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);
-                return r;
+                goto finish;
         }
 
         n->lldp = lldp;
 
-        return true;
+        lldp_start_timer(lldp, n);
+        lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n);
+
+        return 1;
+
+finish:
+        if (old)
+                lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old);
+
+        return r;
 }
 
 static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
@@ -141,8 +188,6 @@ static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
         if (r < 0)
                 return r;
 
-        lldp_neighbor_start_ttl(n);
-
         r = lldp_add_neighbor(lldp, n);
         if (r < 0) {
                 log_lldp_errno(r, "Failed to add datagram. Ignoring.");
@@ -150,10 +195,6 @@ static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) {
         }
 
         log_lldp("Successfully processed LLDP datagram.");
-
-        if (r > 0 && lldp->callback) /* Only invoke the callback if something actually changed. */
-                lldp->callback(lldp, lldp->userdata);
-
         return 0;
 }
 
@@ -161,6 +202,7 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
         _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);
@@ -174,21 +216,41 @@ static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, v
                 return -ENOMEM;
 
         length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT);
-        if (length < 0)
+        if (length < 0) {
+                if (errno == EAGAIN || errno == EINTR)
+                        return 0;
+
                 return log_lldp_errno(errno, "Failed to read LLDP datagram: %m");
+        }
 
         if ((size_t) length != n->raw_size) {
                 log_lldp("Packet size mismatch.");
                 return -EINVAL;
         }
 
+        /* 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);
+
         return lldp_handle_datagram(lldp, n);
 }
 
+static void lldp_reset(sd_lldp *lldp) {
+        assert(lldp);
+
+        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);
+}
+
 _public_ int sd_lldp_start(sd_lldp *lldp) {
         int r;
 
         assert_return(lldp, -EINVAL);
+        assert_return(lldp->event, -EINVAL);
+        assert_return(lldp->ifindex > 0, -EINVAL);
 
         if (lldp->fd >= 0)
                 return 0;
@@ -199,24 +261,21 @@ _public_ int sd_lldp_start(sd_lldp *lldp) {
         if (lldp->fd < 0)
                 return lldp->fd;
 
-        if (lldp->event) {
-                r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
-                if (r < 0)
-                        goto fail;
+        r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp);
+        if (r < 0)
+                goto fail;
 
-                r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority);
-                if (r < 0)
-                        goto fail;
+        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");
-        }
+        (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io");
 
+        log_lldp("Started LLDP client");
         return 1;
 
 fail:
-        lldp->io_event_source = sd_event_source_unref(lldp->io_event_source);
-        lldp->fd = safe_close(lldp->fd);
-
+        lldp_reset(lldp);
         return r;
 }
 
@@ -226,10 +285,9 @@ _public_ int sd_lldp_stop(sd_lldp *lldp) {
         if (lldp->fd < 0)
                 return 0;
 
-        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);
+        log_lldp("Stopping LLDP client");
 
+        lldp_reset(lldp);
         lldp_flush_neighbors(lldp);
 
         return 1;
@@ -264,6 +322,12 @@ _public_ int sd_lldp_detach_event(sd_lldp *lldp) {
         return 0;
 }
 
+_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) {
+        assert_return(lldp, NULL);
+
+        return lldp->event;
+}
+
 _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) {
         assert_return(lldp, -EINVAL);
 
@@ -273,39 +337,58 @@ _public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *us
         return 0;
 }
 
+_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->ifindex = ifindex;
+        return 0;
+}
+
+_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) {
+
+        if (!lldp)
+                return NULL;
+
+        assert(lldp->n_ref > 0);
+        lldp->n_ref++;
+
+        return lldp;
+}
+
 _public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) {
 
         if (!lldp)
                 return NULL;
 
+        assert(lldp->n_ref > 0);
+        lldp->n_ref --;
+
+        if (lldp->n_ref > 0)
+                return NULL;
+
+        lldp_reset(lldp);
+        sd_lldp_detach_event(lldp);
         lldp_flush_neighbors(lldp);
 
         hashmap_free(lldp->neighbor_by_id);
         prioq_free(lldp->neighbor_by_expiry);
-
-        sd_event_source_unref(lldp->io_event_source);
-        sd_event_source_unref(lldp->timer_event_source);
-        sd_event_unref(lldp->event);
-        safe_close(lldp->fd);
-
-        free(lldp);
-
-        return NULL;
+        return mfree(lldp);
 }
 
-_public_ int sd_lldp_new(sd_lldp **ret, int ifindex) {
+_public_ int sd_lldp_new(sd_lldp **ret) {
         _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL;
         int r;
 
         assert_return(ret, -EINVAL);
-        assert_return(ifindex > 0, -EINVAL);
 
         lldp = new0(sd_lldp, 1);
         if (!lldp)
                 return -ENOMEM;
 
+        lldp->n_ref = 1;
         lldp->fd = -1;
-        lldp->ifindex = ifindex;
         lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX;
         lldp->capability_mask = (uint16_t) -1;
 
@@ -329,8 +412,6 @@ static int neighbor_compare_func(const void *a, const void *b) {
         return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id);
 }
 
-static int lldp_start_timer(sd_lldp *lldp);
-
 static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
         sd_lldp *lldp = userdata;
         int r, q;
@@ -339,23 +420,22 @@ static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) {
         if (r < 0)
                 return log_lldp_errno(r, "Failed to make space: %m");
 
-        q = lldp_start_timer(lldp);
+        q = lldp_start_timer(lldp, NULL);
         if (q < 0)
                 return log_lldp_errno(q, "Failed to restart timer: %m");
 
-        log_lldp("LLDP timer event hit.");
-        if (r > 0 && lldp->callback) /* Invoke callback if we dropped an entry */
-                lldp->callback(lldp, lldp->userdata);
-
         return 0;
 }
 
-static int lldp_start_timer(sd_lldp *lldp) {
+static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) {
         sd_lldp_neighbor *n;
         int r;
 
         assert(lldp);
 
+        if (neighbor)
+                lldp_neighbor_start_ttl(neighbor);
+
         n = prioq_peek(lldp->neighbor_by_expiry);
         if (!n) {
 
@@ -396,9 +476,6 @@ _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
         assert_return(lldp, -EINVAL);
         assert_return(ret, -EINVAL);
 
-        /* Flush out old entries, before we return data */
-        (void) lldp_make_space(lldp, 0);
-
         if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */
                 *ret = NULL;
                 return 0;
@@ -408,7 +485,7 @@ _public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) {
         if (!l)
                 return -ENOMEM;
 
-        r = lldp_start_timer(lldp);
+        r = lldp_start_timer(lldp, NULL);
         if (r < 0) {
                 free(l);
                 return r;
@@ -451,11 +528,10 @@ _public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *
         /* 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 (!addr) {
+        if (addr)
+                lldp->filter_address = *addr;
+        else
                 zero(lldp->filter_address);
-                return 0;
-        }
 
-        lldp->filter_address = *addr;
         return 0;
 }