Skip to content

fix(redfish): bracket IPv6 BMC hosts in nv-redfish URLs#3008

Open
CTOmari360 wants to merge 2 commits into
NVIDIA:mainfrom
CTOmari360:fix/redfish-bracket-ipv6-bmc-url
Open

fix(redfish): bracket IPv6 BMC hosts in nv-redfish URLs#3008
CTOmari360 wants to merge 2 commits into
NVIDIA:mainfrom
CTOmari360:fix/redfish-bracket-ipv6-bmc-url

Conversation

@CTOmari360

Copy link
Copy Markdown
Contributor

NvRedfishClientPool::create_bmc builds the BMC base URL with bare format!s and then .expect()s Url::parse. IPv6 hosts were left unbracketed, so an IPv6 BMC produced an invalid authority such as https://2001:db8::1:8443 — which Url::parse rejects, panicking the caller.

The codebase already brackets IPv6 on the sibling path (crates/health/src/endpoint/model.rs::BmcAddr::to_url); the nv_redfish pool was missing the same handling.

This extracts a small, testable build_bmc_url helper that brackets IPv6 literals in every arm:

  • port-only proxy — brackets the BMC's own IpAddr (the panicking case),
  • host / host+port proxy — brackets a config-supplied IPv6 literal proxy host,
  • no override — unchanged; SocketAddr's Display already brackets.

Hostnames, IPv4 literals, and already-bracketed hosts are passed through untouched.

Related issues

Part of the #2237 IPv6 bug bucket; addresses the Redfish-bracket item of #2406. (Scoped to just this fix, so it does not close the multi-item #2406.)

Type of Change

  • Fix - Bug fixes

Breaking Changes

  • This PR contains breaking changes

Testing

  • Unit tests added/updated

cargo test -p carbide-redfish --features test-support nv_redfish::tests — 5 passing, covering each arm. The new port_only_proxy_brackets_ipv6_bmc test fails before this change (produces https://2001:db8::1:8443, asserted against the bracketed https://[2001:db8::1]:8443). cargo clippy -- -D warnings and cargo fmt --check both clean.

Additional Notes

The libredfish proxy path (crates/redfish/src/libredfish/implementation.rs) constructs its endpoint inside the external libredfish crate and is out of scope here.

NvRedfishClientPool::create_bmc built the BMC base URL with bare
format!s and then .expect()ed Url::parse. IPv6 hosts were left
unbracketed, so an IPv6 BMC produced an invalid authority such as
https://2001:db8::1:8443 -- which Url::parse rejects, panicking the
caller. This mirrors the bracketing the health crate's
BmcAddr::to_url already applies on its own path.

Extract a testable build_bmc_url helper that brackets IPv6 literals in
every arm: the port-only proxy path uses the BMC's own IpAddr, and the
host / host+port proxy paths bracket config-supplied IPv6 literals. The
no-override arm is unchanged -- SocketAddr's Display already brackets.

Add unit tests covering each arm; the port-only IPv6 case fails before
this change.

Part of the NVIDIA#2237 IPv6 bug bucket; addresses the Redfish-bracket item of NVIDIA#2406.

Signed-off-by: Omar Refai <[email protected]>
@CTOmari360 CTOmari360 requested a review from a team as a code owner June 30, 2026 03:50
@copy-pr-bot

copy-pr-bot Bot commented Jun 30, 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.

Comment thread crates/redfish/src/nv_redfish/mod.rs Outdated
Comment on lines +205 to +211
fn url_host(host: &str) -> Cow<'_, str> {
if host.parse::<Ipv6Addr>().is_ok() {
Cow::Owned(format!("[{host}]"))
} else {
Cow::Borrowed(host)
}
}

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.

This code doesn't look right. Probably HostPortPair should be able to destinguish IPv6 address from Hostname or return url_host(). This code here looks like patch on wrong / missing behavior of other object.

Could you please move this function there to make incoherent design of HostAndPort more local?

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.

Probably it can be combined with your #3010 PR

@ajf

ajf commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Same question as on the golang code, can we use the url library to do this IPv6 bracketing for us? If we're dealing with URLs we should probably use the url crate.

Per review (ajf): build the BMC URL with the `url` crate instead of
hand-formatting the authority. build_bmc_url now returns a `url::Url`
assembled via `Url::set_ip_host` / `set_host` / `set_port` --
`set_ip_host` brackets IPv6 literals for us, so the manual `[..]`
formatting (and the separate `url_host` helper) is gone, along with the
string-parse-then-`.expect()` at the call site.

Behavior is unchanged: the returned Url is identical to what the old
string produced once parsed (the url crate canonicalizes the https
default port either way). Proxy hosts that are IP literals go through
set_ip_host; hostnames through set_host.

Tests updated to assert on the Url (host_str/port) instead of the raw
string; the IPv6 bracketing cases still guard the original panic.

Part of NVIDIA#2237 (IPv6 Bug Scan Bucket).

Signed-off-by: Omar Refai <[email protected]>
@CTOmari360

Copy link
Copy Markdown
Contributor Author

Done — reworked it to let the url crate handle the bracketing. build_bmc_url now returns a url::Url built with Url::set_ip_host / set_host / set_port; set_ip_host brackets IPv6 literals for us, so the manual [..] formatting and the separate url_host helper are gone, and the call site no longer parses-a-string-then-.expect()s.

Proxy hosts that come in as IP literals go through set_ip_host too (bracketed); hostnames go through set_host. Behavior is unchanged — the returned Url is identical to what the old string produced once parsed (the crate canonicalizes the https default port either way). Tests now assert on the Url (host_str/port) rather than the raw string.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Summary by CodeRabbit

  • Bug Fixes
    • Improved BMC URL handling so IPv6 addresses are formatted correctly, including when used through a port-only proxy.
    • Kept IPv4 and hostname-based connections working as expected.
    • Added coverage for common proxy and address formats to prevent regressions.

Walkthrough

The BMC base URL construction in crates/redfish/src/nv_redfish/mod.rs was refactored from manual string formatting and parsing to a new build_bmc_url helper using url::Url's set_ip_host/set_host APIs, correctly handling IPv6 authority formatting. Unit tests were added.

Changes

BMC URL Authority Construction

Layer / File(s) Summary
URL construction helper
crates/redfish/src/nv_redfish/mod.rs
Adds IpAddr and Url imports, replaces manual https://... string formatting with build_bmc_url(proxy_address.as_ref(), bmc_address), and introduces build_bmc_url, set_ip_host, and set_host helpers to correctly form IPv6/IPv4/hostname authorities.
Regression tests
crates/redfish/src/nv_redfish/mod.rs
Adds unit tests verifying bracketed IPv6 authority for port-only and IPv6-literal proxies, IPv4/hostname passthrough, and canonical Url output.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant create_bmc
  participant build_bmc_url
  participant set_host
  participant Url

  create_bmc->>build_bmc_url: proxy_address, bmc_address
  build_bmc_url->>set_host: host string
  set_host->>set_host: detect IP literal vs hostname
  alt IP literal
    set_host->>Url: set_ip_host
  else hostname
    set_host->>Url: set_host
  end
  Url-->>build_bmc_url: constructed Url
  build_bmc_url-->>create_bmc: Url
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the IPv6 bracket handling fix in nv-redfish URL construction.
Description check ✅ Passed The description directly explains the IPv6 URL bug, the helper refactor, and the added tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/redfish/src/nv_redfish/mod.rs (1)

141-148: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Config-supplied proxy host can now panic the service instead of returning an Error.

create_bmc returns Result<Arc<RedfishBmc>, Error>, but build_bmc_url bypasses that contract: if the configured proxy host in HostPortPair::HostAndPort/HostOnly fails Url::set_host validation (malformed hostname, stray whitespace, an already-bracketed IPv6 literal, etc.), set_host (Lines 219-226) panics via .expect("proxy host is expected to be a valid URL host") instead of surfacing a recoverable error to create_bmc's caller. Since proxy_address is loaded from an ArcSwap (dynamic/admin-configurable), a bad config value would crash the process rather than degrade gracefully.

Consider having build_bmc_url return a Result<Url, Error> (or a dedicated error type) and threading it through create_bmc with ?, so invalid proxy configuration surfaces as a normal Error instead of a panic.

🛡️ Sketch of a non-panicking alternative
-fn set_host(url: &mut Url, host: &str) {
+fn set_host(url: &mut Url, host: &str) -> Result<(), Error> {
     match host.parse::<IpAddr>() {
-        Ok(ip) => set_ip_host(url, ip),
-        Err(_) => url
-            .set_host(Some(host))
-            .expect("proxy host is expected to be a valid URL host"),
+        Ok(ip) => set_ip_host(url, ip),
+        Err(_) => url
+            .set_host(Some(host))
+            .map_err(|e| Error::Bmc(format!("invalid proxy host {host:?}: {e}").into())),
     }
 }

Also applies to: 219-226

🤖 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/redfish/src/nv_redfish/mod.rs` around lines 141 - 148, `create_bmc`
should not be able to panic from invalid proxy configuration; `build_bmc_url`
currently uses `set_host` with `expect`, which breaks the
`Result<Arc<RedfishBmc>, Error>` contract. Change `build_bmc_url` to return
`Result<Url, Error>` (or a dedicated error type), propagate the host-validation
failure instead of calling `expect`, and update `create_bmc` to use `?` when
building the URL so bad values from `proxy_address` surface as a normal error.
🧹 Nitpick comments (1)
crates/redfish/src/nv_redfish/mod.rs (1)

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

Solid regression coverage; consider adding a malformed-host case.

The IPv6 bracketing, IPv4 passthrough, and hostname-passthrough cases are all covered well. Given the panic-on-.expect() concern above, a test asserting the current (undesirable) panic behavior for a malformed proxy host would document the gap and make a future fix easy to verify.

🤖 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/redfish/src/nv_redfish/mod.rs` around lines 228 - 305, Add a
regression test in the nv_redfish tests module for a malformed proxy host to
document the current panic path through build_bmc_url and create_bmc’s
Url::parse/.expect flow. Use the existing helpers and symbols like build_bmc_url
and HostPortPair to construct an invalid host case, then assert the undesired
panic behavior so the gap is covered and a future fix can be verified.
🤖 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.

Outside diff comments:
In `@crates/redfish/src/nv_redfish/mod.rs`:
- Around line 141-148: `create_bmc` should not be able to panic from invalid
proxy configuration; `build_bmc_url` currently uses `set_host` with `expect`,
which breaks the `Result<Arc<RedfishBmc>, Error>` contract. Change
`build_bmc_url` to return `Result<Url, Error>` (or a dedicated error type),
propagate the host-validation failure instead of calling `expect`, and update
`create_bmc` to use `?` when building the URL so bad values from `proxy_address`
surface as a normal error.

---

Nitpick comments:
In `@crates/redfish/src/nv_redfish/mod.rs`:
- Around line 228-305: Add a regression test in the nv_redfish tests module for
a malformed proxy host to document the current panic path through build_bmc_url
and create_bmc’s Url::parse/.expect flow. Use the existing helpers and symbols
like build_bmc_url and HostPortPair to construct an invalid host case, then
assert the undesired panic behavior so the gap is covered and a future fix can
be verified.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 81ad9da2-6e9f-4eb0-a82f-24ccbf3c8343

📥 Commits

Reviewing files that changed from the base of the PR and between d63a8ad and 21f7ed8.

📒 Files selected for processing (1)
  • crates/redfish/src/nv_redfish/mod.rs

@poroh

poroh commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

/ok to test 21f7ed8

/// for us, so an IPv6 BMC (or proxy host) yields a valid URL. A bare, unbracketed
/// `2001:db8::1` authority would otherwise be rejected by the parser.
fn build_bmc_url(proxy_address: &Option<HostPortPair>, bmc_address: SocketAddr) -> Url {
let mut url = Url::parse("https://placeholder.invalid").expect("static base URL is valid");

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.

It is better to return Result<Url,...> here to avoid panicking

}

/// Sets an IP host, letting the `url` crate bracket IPv6 literals.
fn set_ip_host(url: &mut Url, ip: IpAddr) {

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.

This helper is trivial, so it is better just inline it. Also, please avoid using expect in production code.

Ok(ip) => set_ip_host(url, ip),
Err(_) => url
.set_host(Some(host))
.expect("proxy host is expected to be a valid URL host"),

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.

Same here. Please avoid using expect.

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown

🔍 Container Scan Summary

Service Total Critical High Medium Low Other
boot-artifacts-aarch64 3 0 0 3 0 0
boot-artifacts-x86_64 3 0 0 3 0 0
forge-admin-cli-x86_64 272 5 27 89 7 144
machine-validation-runner 769 25 209 278 36 221
machine_validation 769 25 209 278 36 221
machine_validation-aarch64 769 25 209 278 36 221
nvmetal-carbide 769 25 209 278 36 221
TOTAL 3354 105 863 1207 151 1028

Per-CVE detail lives in the per-service grype-* artifacts (JSON + SARIF). Severity counts only — no CVE IDs published here.

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.

3 participants