Skip to content

feat: collect LLDP data on DPU and host (1/3)#3418

Open
kurotych wants to merge 8 commits into
NVIDIA:mainfrom
kurotych:lldp-info-cli
Open

feat: collect LLDP data on DPU and host (1/3)#3418
kurotych wants to merge 8 commits into
NVIDIA:mainfrom
kurotych:lldp-info-cli

Conversation

@kurotych

Copy link
Copy Markdown
Contributor

This is the first of three parts implementing the LLDP collection feature described in #3204.

  • Implement the lldp_collector.rs module, which collects the machine's LLDP neighbor data.
  • Add an lldp-neighbors CLI command to the scout and agent services.
  • Add lldpd packet to scout os

Details

Ported and fixed abandoned code

dpu.rs contained leftovers from an earlier attempt at LLDP data collection. That code has been fixed and ported into lldp_collector.rs.

Unlike the old code, which lived in the DPU enumeration path, lldp_collector.rs is machine-agnostic: it enumerates the Ethernet interfaces on any host (DPU, scout, etc.) and collects their LLDP neighbors uniformly. It also filters out self-loopback neighbors.

Concerns multiple neighbors on a single port: the old code assumed this was possible but handled it in a strange manner: only the last neighbor survived (get_port_lldp_info overwrote the result on each iteration).

The other change is switching lldpcli to the -f json0 output format, which is more stable to parse programmatically. The map-keyed -f json format uses dynamic keys (port names, neighbor names) as JSON object keys, whereas json0 renders everything as arrays of objects with fixed keys

Manual test results

The real values were changed

ubuntu@scout:~$ sudo ./forge-scout lldp-neighbors
58:a2:e1:00:00:b0: LldpSwitchData {
    name: "scout-host-01.example.local",
    id: "mac=58:a2:e1:00:00:d4",
    description: "Example-SRE (sre@example.com), <redacted-node-token>",
    local_port: "enp1s0np0",
    ip_address: [
        "192.0.2.195",
        "2001:db8::1",
    ],
    remote_port: "mac=12:00:00:00:4c:eb",
    id_type: "mac",
    id_value: "58:a2:e1:00:00:d4",
    remote_port_type: "mac",
    remote_port_value: "12:00:00:00:4c:eb",
}
6c:b3:11:00:00:70: LldpSwitchData {
    name: "example-switch-01",
    id: "mac=00:01:e8:00:00:ac",
    description: "Dell Real Time Operating System Software. Dell Operating System Version: 2.0. Dell Application Software Version: 9.14(1.10) Copyright (c) 1999-2018 Dell Inc. All Rights Reserved.Build Time: Thu Sep 23 15:32:37 2021",
    local_port: "ens11f0",
    ip_address: [
        "198.51.100.76",
    ],
    remote_port: "ifname=TenGigabitEthernet 0/14",
    id_type: "mac",
    id_value: "00:01:e8:00:00:ac",
    remote_port_type: "ifname",
    remote_port_value: "TenGigabitEthernet 0/14",
}
6c:b3:11:00:00:71: LldpSwitchData {
    name: "example-switch-01",
    id: "mac=00:01:e8:00:00:ac",
    description: "Dell Real Time Operating System Software. Dell Operating System Version: 2.0. Dell Application Software Version: 9.14(1.10) Copyright (c) 1999-2018 Dell Inc. All Rights Reserved.Build Time: Thu Sep 23 15:32:37 2021",
    local_port: "ens11f1",
    ip_address: [
        "198.51.100.76",
    ],
    remote_port: "ifname=TenGigabitEthernet 0/1",
    id_type: "mac",
    id_value: "00:01:e8:00:00:ac",
    remote_port_type: "ifname",
    remote_port_value: "TenGigabitEthernet 0/1",
}
ubuntu@dpu-mgmt:~$ sudo ./forge-dpu-agent lldp-neighbors
58:a2:e1:00:00:c0: LldpSwitchData {
    name: "example-switch-04",
    id: "mac=24:8a:07:00:00:aa",
    description: "Cumulus Linux version 5.11.1 running on Mellanox Technologies Ltd. Mellanox switch",
    local_port: "p0",
    ip_address: [
        "192.0.2.101",
        "fe80::268a:7ff:fe00:aa",
    ],
    remote_port: "ifname=swp1",
    id_type: "mac",
    id_value: "24:8a:07:00:00:aa",
    remote_port_type: "ifname",
    remote_port_value: "swp1",
}
12:00:00:00:4c:eb: LldpSwitchData {
    name: "scout",
    id: "mac=6c:b3:11:00:00:70",
    description: "Ubuntu 24.04.4 LTS Linux 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64",
    local_port: "pf0hpf",
    ip_address: [
        "192.0.2.5",
        "fe80::6eb3:11ff:fe00:70",
    ],
    remote_port: "mac=58:a2:e1:00:00:b0",
    id_type: "mac",
    id_value: "6c:b3:11:00:00:70",
    remote_port_type: "mac",
    remote_port_value: "58:a2:e1:00:00:b0",
}
58:a2:e1:00:00:c1: LldpSwitchData {
    name: "example-switch-05",
    id: "mac=b8:59:9f:00:00:72",
    description: "Cumulus Linux version 5.11.1 running on Mellanox Technologies Ltd. MSN2100",
    local_port: "p1",
    ip_address: [
        "192.0.2.102",
        "fe80::ba59:9fff:fe00:72",
    ],
    remote_port: "ifname=swp1",
    id_type: "mac",
    id_value: "b8:59:9f:00:00:72",
    remote_port_type: "ifname",
    remote_port_value: "swp1",
}

To be done in later PRs

  • Part 2. Poll LLDP info on the agent/scout side and periodically report it (only when it changed since the last report) to nico-api via a new gRPC endpoint (and separate table).
  • Part 3. Expose the information through the REST API.

Related issues

#3204

Type of Change

  • Add - New feature or capability
  • Change - Changes in existing functionality
  • Fix - Bug fixes
  • Remove - Removed features or deprecated functionality
  • Internal - Internal changes (refactoring, tests, docs, etc.)

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed
  • No testing required (docs, internal refactor, etc.)

Additional Notes

kurotych added 7 commits July 8, 2026 20:11
Move LLDP parsing (get_port_lldp_info and friends) out of the
DPU-specific enumeration into a shared lldp_collector module.

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
… port

Rework lldp collector to consume `lldpcli -f json0` instead of `-f json`.
json0 emits a stable, uniform structure — every field is an array with fixed
keys — whereas `-f json` makes the output structure data-dependent and fragile
to parse.

Add split id_type/id_value and remote_port_type/remote_port_value fields to
LldpSwitchData; the combined id/remote_port strings are kept and marked
deprecated for backward compatibility

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
…oting

Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
@kurotych kurotych requested a review from a team as a code owner July 13, 2026 10:50
@copy-pr-bot

copy-pr-bot Bot commented Jul 13, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 1abae043-495e-4448-b327-2b65ea2edcbb

📥 Commits

Reviewing files that changed from the base of the PR and between 6a6185c and 72b4b5a.

📒 Files selected for processing (1)
  • crates/host-support/src/lldp_collector.rs

Summary by CodeRabbit

  • New Features
    • Added an lldp-neighbors command to display LLDP network neighbors visible on the local host and exit (available via the agent and scout CLIs).
    • Added host-side LLDP neighbor collection with per-interface discovery, including chassis/port details, management IP, and descriptions.
    • Enhanced LLDP neighbor data representation by splitting identifier fields into type/value pairs (keeping legacy fields deprecated for compatibility).
  • Bug Fixes
    • Prevented local self-loopback neighbors from appearing in collected results.
  • Chores
    • Enabled and installed the LLDP daemon (lldpd) in Scout images to ensure reliable neighbor discovery.

Walkthrough

This change introduces a dedicated LLDP collector, updates RPC fields, removes embedded DPU LLDP logic, adds agent and scout CLI commands, and installs and enables lldpd in scout images.

Changes

LLDP discovery

Layer / File(s) Summary
LLDP RPC contract
crates/rpc/proto/machine_discovery.proto, crates/rpc/src/model/hardware_info.rs
Adds split LLDP identity fields, deprecates combined fields, and supplies defaults during conversions.
Dedicated LLDP collector
crates/host-support/src/lldp_collector.rs, crates/host-support/src/lib.rs
Enumerates Ethernet interfaces, reads MAC addresses, queries and parses lldpcli JSON, filters local chassis loops, prints neighbors, and tests parsing behavior.
DPU LLDP decoupling
crates/host-support/src/hardware_enumeration.rs, crates/host-support/src/hardware_enumeration/dpu.rs
Removes embedded LLDP models and queries, leaves DPU switch data empty, and widens helper visibility.
Local LLDP commands
crates/agent/src/*, crates/scout/src/*
Adds LldpNeighbors subcommands that print neighbors and exit before normal startup.
lldpd image setup
pxe/mkosi.profiles/scout-oss-*/mkosi.conf, pxe/mkosi.profiles/scout-oss-*/mkosi.postinst.chroot
Installs lldpd and explicitly enables its service in both scout image profiles.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Operator
  participant ScoutOrAgent
  participant LldpCollector
  participant UdevAndSysfs
  participant Lldpcli
  Operator->>ScoutOrAgent: invoke LldpNeighbors
  ScoutOrAgent->>LldpCollector: print_lldp_neighbors()
  LldpCollector->>UdevAndSysfs: enumerate interfaces and read MACs
  UdevAndSysfs-->>LldpCollector: Ethernet interfaces and MACs
  LldpCollector->>Lldpcli: query neighbors and local chassis as JSON
  Lldpcli-->>LldpCollector: LLDP responses
  LldpCollector-->>ScoutOrAgent: formatted neighbor output
  ScoutOrAgent-->>Operator: exit without normal startup
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding LLDP collection on DPU and host paths.
Description check ✅ Passed The description is clearly related to the changeset and matches the LLDP collection feature work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/host-support/src/hardware_enumeration.rs (1)

157-157: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Narrow visibility to pub(crate) for consistency and minimal surface.

The only new consumer (lldp_collector) references this through crate::hardware_enumeration::convert_property_to_string, so pub(crate) is sufficient. Since the helper was previously private, no external crate depends on it. This also matches the pub(crate) you correctly chose for PCI_SUBCLASS on Line 45.

♻️ Proposed change
-pub fn convert_property_to_string<'a>(
+pub(crate) fn convert_property_to_string<'a>(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host-support/src/hardware_enumeration.rs` at line 157, Change the
visibility of convert_property_to_string from pub to pub(crate), keeping its
existing signature and behavior unchanged. Ensure the lldp_collector crate-local
reference continues to resolve through crate::hardware_enumeration.
crates/host-support/src/lldp_collector.rs (1)

177-177: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Emit errors as structured fields, not string interpolation. These log sites interpolate the error (and raw JSON payloads) into the message string, which defeats logfmt key/value search. As per coding guidelines, prefer error = %e and named fields over interpolation (tracing::error!(%machine_id, error=%e, "...")).

  • crates/host-support/src/lldp_collector.rs#L177-L177: replace warn!("Could not deserialize local LLDP chassis {json}, {e}") with warn!(error = %e, "Could not deserialize local LLDP chassis") (drop or demote the full json payload to a debug field).
  • crates/host-support/src/lldp_collector.rs#L158-L158: replace with warn!(error = %e, "Could not read local LLDP chassis").
  • crates/host-support/src/lldp_collector.rs#L224-L224: replace tracing::debug!(ifname, "no LLDP neighbor: {e}") with tracing::debug!(ifname, error = %e, "no LLDP neighbor").
  • crates/host-support/src/lldp_collector.rs#L237-L237: replace with warn!(port, error = %e, "Could not discover LLDP peer").
  • crates/host-support/src/lldp_collector.rs#L255-L255: replace with warn!(error = %e, "Could not deserialize LLDP response") (payload to a debug field).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/host-support/src/lldp_collector.rs` at line 177, Update all five LLDP
logging sites in lldp_collector.rs (lines 158, 177, 224, 237, and 255) to emit
errors as structured tracing fields using error = %e and concise static
messages; preserve ifname and port fields where present, and drop or demote raw
JSON payloads to debug fields at the deserialization sites.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/host-support/src/lldp_collector.rs`:
- Around line 231-240: Update get_lldp_port_info to invoke lldpcli directly with
separate arguments for JSON output, neighbor display, and port, removing the
constructed bash -c command. Apply the same direct-argument approach to
get_local_chassis_id, preserving both commands’ existing behavior and error
handling.

---

Nitpick comments:
In `@crates/host-support/src/hardware_enumeration.rs`:
- Line 157: Change the visibility of convert_property_to_string from pub to
pub(crate), keeping its existing signature and behavior unchanged. Ensure the
lldp_collector crate-local reference continues to resolve through
crate::hardware_enumeration.

In `@crates/host-support/src/lldp_collector.rs`:
- Line 177: Update all five LLDP logging sites in lldp_collector.rs (lines 158,
177, 224, 237, and 255) to emit errors as structured tracing fields using error
= %e and concise static messages; preserve ifname and port fields where present,
and drop or demote raw JSON payloads to debug fields at the deserialization
sites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 95d41ba3-c744-4d45-a312-0454ce9d3d75

📥 Commits

Reviewing files that changed from the base of the PR and between 81902e6 and 6a6185c.

📒 Files selected for processing (16)
  • crates/agent/src/command_line.rs
  • crates/agent/src/lib.rs
  • crates/agent/src/main.rs
  • crates/host-support/src/hardware_enumeration.rs
  • crates/host-support/src/hardware_enumeration/dpu.rs
  • crates/host-support/src/lib.rs
  • crates/host-support/src/lldp_collector.rs
  • crates/host-support/test/lldp_query.json
  • crates/rpc/proto/machine_discovery.proto
  • crates/rpc/src/model/hardware_info.rs
  • crates/scout/src/cfg/command_line.rs
  • crates/scout/src/main.rs
  • pxe/mkosi.profiles/scout-oss-aarch64/mkosi.conf
  • pxe/mkosi.profiles/scout-oss-aarch64/mkosi.postinst.chroot
  • pxe/mkosi.profiles/scout-oss-x86_64/mkosi.conf
  • pxe/mkosi.profiles/scout-oss-x86_64/mkosi.postinst.chroot
💤 Files with no reviewable changes (1)
  • crates/host-support/test/lldp_query.json

Comment thread crates/host-support/src/lldp_collector.rs
Signed-off-by: Anatolii Kurotych <akurotych@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant