Skip to content

feat(machine-a-tron): support DHCP relay mode#3754

Merged
poroh merged 1 commit into
NVIDIA:mainfrom
poroh:machine-a-tron-dhcp-relay-mode
Jul 21, 2026
Merged

feat(machine-a-tron): support DHCP relay mode#3754
poroh merged 1 commit into
NVIDIA:mainfrom
poroh:machine-a-tron-dhcp-relay-mode

Conversation

@poroh

@poroh poroh commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Add a UDP DHCP relay mode to machine-a-tron so simulated machines can exercise the DHCP server through its packet interface instead of obtaining leases directly through the Carbide API.

The new mode performs the DHCP DISCOVER/OFFER and REQUEST/ACK exchanges over UDP, including relay-agent information and the vendor class derived from the simulated hardware. Responses are correlated and validated before completing requests, and pending transactions are cleaned up on timeout or cancellation.

DHCP configuration is represented as a tagged enum so each mode contains all of its required parameters. API mode remains the default for backward compatibility. The local test helper now starts and retains the configured DHCP service, allowing integration environments to use UDP relay mode as well.

Related issues

Closes: #3366

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

UDP relay mode requires:

  • The DHCP server destination address.
  • A local address and port for receiving replies.
  • An advertised IPv4 address reachable from the DHCP server.

The advertised address is placed in giaddr, while the machine’s target subnet is carried using the relay-agent link-selection option. In Kubernetes, the listener may bind to 0.0.0.0, with the pod IP supplied as the advertised address.

Signed-off-by: Dmitry Porokh <dporokh@nvidia.com>
@poroh
poroh requested a review from a team as a code owner July 21, 2026 01:09
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added configurable DHCP modes, including API-based DHCP and UDP relay support.
    • Added configuration options for DHCP server, listening, and advertised addresses.
    • Added validation to prevent invalid DHCP relay settings.
  • Bug Fixes

    • Improved DHCP request handling with response validation, timeout detection, and clearer error reporting.
    • Improved cleanup of DHCP services during application and test shutdown.
  • Tests

    • Expanded coverage for DHCP configuration, relay behavior, concurrent requests, malformed responses, and cancellation.

Walkthrough

Machine-a-tron now supports configurable API-based or UDP-relayed DHCP. The change adds relay packet exchange and validation, routes state-machine requests through a shared DHCP client, and manages relay service startup and shutdown in production and local test harnesses.

Changes

DHCP relay support

Layer / File(s) Summary
DHCP configuration contract
crates/machine-a-tron/src/config.rs, crates/machine-a-tron/config/mat.toml
Adds DhcpType, default API mode, UDP-relay validation, context storage, configuration guidance, and validation tests.
UDP DHCP transport
crates/machine-a-tron/src/dhcp_wrapper_udp.rs, crates/machine-a-tron/Cargo.toml
Implements DHCP Discover/Offer/Request/Ack exchanges, transaction tracking, response validation, vendor extension parsing, timeout handling, cancellation, and concurrency tests.
DHCP client dispatch
crates/machine-a-tron/src/dhcp_wrapper.rs, crates/machine-a-tron/src/lib.rs
Replaces the standalone request helper with DhcpClient, dispatches API or UDP-relay requests, expands errors, and adds crate re-exports.
Machine-a-tron lifecycle integration
crates/machine-a-tron/src/main.rs, crates/machine-a-tron/src/machine_state_machine.rs, crates/api-test-helper/src/machine_a_tron.rs, crates/api-integration-tests/tests/lib.rs
Starts and wires the DHCP client, routes BMC and machine discovery through it, shuts down relay services, and makes local integration tests select API mode explicitly.

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

Sequence Diagram(s)

sequenceDiagram
  participant MachineATron
  participant DhcpClient
  participant UdpDhcpClient
  participant DhcpServer
  MachineATron->>DhcpClient: start(config, api_client)
  DhcpClient->>UdpDhcpClient: start relay when configured
  MachineATron->>DhcpClient: request_ip(request_info)
  DhcpClient->>UdpDhcpClient: dispatch UDP-relay request
  UdpDhcpClient->>DhcpServer: send DHCP Discover and Request
  DhcpServer-->>UdpDhcpClient: return Offer and Ack
  UdpDhcpClient-->>DhcpClient: return DHCP response info
  DhcpClient-->>MachineATron: return assigned IP and interface data
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately states the main change: adding DHCP relay support to machine-a-tron.
Description check ✅ Passed The description matches the implemented relay mode, default API behavior, packet flow, and helper updates.
Linked Issues check ✅ Passed The changes implement optional UDP relay DHCP, preserve API defaulting, add packet validation, and wire the flow through the machine state path.
Out of Scope Changes check ✅ Passed The changes stay focused on DHCP relay mode, config, tests, and helper wiring, with no unrelated feature work evident.
✨ 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

🤖 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/machine-a-tron/src/config.rs`:
- Around line 412-424: Add explicit deserialization validation for the empty
`DhcpType::Api` variant so any relay-only or other unexpected fields are
rejected when `type = "api"`. Preserve successful parsing of a field-free API
configuration and the existing `UdpRelay` field validation; implement the guard
at the `DhcpType` deserialization boundary rather than relying solely on
`deny_unknown_fields`.
🪄 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: 6ef9e08b-d7b5-4e22-85ea-77131a2b660d

📥 Commits

Reviewing files that changed from the base of the PR and between dcdd5da and c1eb529.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • crates/api-integration-tests/tests/lib.rs
  • crates/api-test-helper/src/machine_a_tron.rs
  • crates/machine-a-tron/Cargo.toml
  • crates/machine-a-tron/config/mat.toml
  • crates/machine-a-tron/src/config.rs
  • crates/machine-a-tron/src/dhcp_wrapper.rs
  • crates/machine-a-tron/src/dhcp_wrapper_udp.rs
  • crates/machine-a-tron/src/lib.rs
  • crates/machine-a-tron/src/machine_state_machine.rs
  • crates/machine-a-tron/src/main.rs

Comment on lines +412 to +424
#[derive(Clone, Copy, Debug, Deserialize, Serialize, Eq, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
pub enum DhcpType {
Api {},
UdpRelay {
/// Destination for relayed DHCP packets.
server_address: SocketAddrV4,
/// Local UDP socket used to receive relayed DHCP replies.
listen_address: SocketAddrV4,
/// Reachable IPv4 address placed in DHCP `giaddr` for server replies.
advertise_address: Ipv4Addr,
},
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant config file and serde version pins.
git ls-files 'crates/machine-a-tron/src/config.rs' 'Cargo.toml' 'Cargo.lock' | sed 's/^/FILE /'

echo
echo "=== config.rs excerpt ==="
sed -n '412,424p' crates/machine-a-tron/src/config.rs

echo
echo "=== serde version references ==="
rg -n 'serde( |"|=)|serde_json' Cargo.toml Cargo.lock crates -g 'Cargo.toml' -g 'Cargo.lock' -g '*.toml' -g '*.rs' | sed -n '1,120p'

Repository: NVIDIA/infra-controller

Length of output: 4183


🌐 Web query:

serde internally tagged enum deny_unknown_fields empty struct variant unknown fields

💡 Result:

In Serde, the #[serde(deny_unknown_fields)] attribute often fails to behave as expected when used with internally tagged enums, particularly for unit variants or empty struct variants [1][2][3]. There are several known limitations and open issues regarding this combination: 1. Failure to Deny on Unit/Empty Struct Variants: When an internally tagged enum uses deny_unknown_fields, it often continues to accept unknown fields when deserializing unit variants (e.g., Variant or Variant {}) [1][2][3]. The deserializer may ignore extraneous fields in the input that do not match the expected structure, contrary to the attribute's purpose [1][2][3]. 2. Architectural Complexity: The implementation of deny_unknown_fields relies on the deserializer being able to track and consume all fields provided in the input [4][5]. Internally tagged enums, however, must often inspect the map/structure to determine which variant is being deserialized, which can interfere with the field-tracking mechanism [4][5]. 3. Flattening Conflicts: Using deny_unknown_fields in conjunction with #[serde(flatten)] is explicitly documented as unsupported [6][7]. This combination often leads to errors because the flattening mechanism buffers fields, making it difficult for the deserializer to determine which fields have been "consumed" versus which are genuinely "unknown" [4][5]. Due to these complexities, these behaviors are well-documented in the Serde issue tracker as limitations rather than bugs that have been resolved [1][2][3]. If strict validation is required, you may need to implement a custom Deserialize manual implementation or perform validation after deserialization.

Citations:


🌐 Web query:

serde 1.0.228 internally tagged enum deny_unknown_fields empty struct variant unknown fields

💡 Result:

In Serde version 1.0.228, using #[serde(deny_unknown_fields)] on an internally tagged enum does not reliably reject unknown fields when applied to unit variants or empty struct variants [1][2][3]. This is a known limitation in how Serde handles deny_unknown_fields for internally tagged enums [1][2][3]. When an enum is internally tagged, Serde must often parse the input to identify the variant [4][5]. In cases involving unit variants or variants that can be represented as empty structs, Serde may not correctly track or enforce the "unknown fields" constraint because it does not treat the variant's payload as a fixed set of fields that must be exhausted [1][3]. As a result, deserialization may succeed even when extraneous fields are present in the input, contrary to the expected behavior of deny_unknown_fields [1][2][3]. There is no standard workaround for this behavior within the current implementation of Serde, as changing it would potentially be a breaking change for existing deserialization logic [3]. Issues tracking this limitation have been discussed in the Serde issue tracker, noting that it particularly affects unit and empty struct variants [1][2][3].

Citations:


Add an explicit guard for stray relay fields on DhcpType::Api

deny_unknown_fields is not a safe contract here: DhcpType::Api {} is an internally tagged empty-struct variant, and Serde can still accept unknown keys on that arm. Add explicit validation so type = "api" with relay-only fields fails deterministically.

🤖 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/machine-a-tron/src/config.rs` around lines 412 - 424, Add explicit
deserialization validation for the empty `DhcpType::Api` variant so any
relay-only or other unexpected fields are rejected when `type = "api"`. Preserve
successful parsing of a field-free API configuration and the existing `UdpRelay`
field validation; implement the guard at the `DhcpType` deserialization boundary
rather than relying solely on `deny_unknown_fields`.

@poroh
poroh merged commit 24cd3f0 into NVIDIA:main Jul 21, 2026
61 checks passed
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.

feat:(machine-a-tron): add optional relayed DHCP packet mode

2 participants