From 8558e4cadd70551566c11863734b25b4fa080294 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 00:16:04 +0200 Subject: [PATCH 01/13] Rewrite the documentation with Sentry Maven Skin 8 and a full page set (#113) Build: oss-parent 5, maven-site-plugin 4.0.0-M16 with maven-skin-tools 1.8.01 (the inherited 3.12.1 randomly failed with a Velocity AbstractMethodError), sentry-maven-skin 8.0.01, UTF-8 report encoding, surefire report, SpotBugs report pinned to 4.10.4.1, changelog report disabled. Search, dark mode, copy-to-clipboard and llms.txt come with the skin. Documentation: 14 pages in Getting Started / Usage / Reference menus (overview, installation, preparing the BMC, chassis status, FRU inventory, sensors and the text output format, configuration, timeouts and errors, low-level API, Serial over LAN, supported commands, troubleshooting, upgrading, migrating from Verax), written from the code. The examples and error messages were checked against a Lenovo IMM; Serial over LAN is documented from the code only. site.css fixes the IMPORTANT callouts in dark mode. README and AGENTS.md updated. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 6 +- README.md | 14 +- pom.xml | 49 +++- src/site/markdown/chassis-status.md | 76 ++++++ src/site/markdown/configuration.md | 117 +++++++++ src/site/markdown/fru-inventory.md | 104 ++++++++ src/site/markdown/index.md | 190 ++++++++------ src/site/markdown/installation.md | 84 ++++++ src/site/markdown/low-level-api.md | 248 ++++++++++++++++++ src/site/markdown/migrating-from-verax.md | 69 +++++ src/site/markdown/preparing-the-bmc.md | 148 +++++++++++ src/site/markdown/sensors.md | 183 +++++++++++++ src/site/markdown/serial-over-lan.md | 113 ++++++++ src/site/markdown/supported-commands.md | 118 +++++++++ src/site/markdown/timeouts-and-errors.md | 130 +++++++++ src/site/markdown/troubleshooting.md | 121 +++++++++ src/site/markdown/upgrading.md | 101 +++++++ src/site/resources/css/site.css | 10 +- ...shub-logo.png => metricshub-logo-only.png} | Bin src/site/site.xml | 68 ++++- 20 files changed, 1844 insertions(+), 105 deletions(-) create mode 100644 src/site/markdown/chassis-status.md create mode 100644 src/site/markdown/configuration.md create mode 100644 src/site/markdown/fru-inventory.md create mode 100644 src/site/markdown/installation.md create mode 100644 src/site/markdown/low-level-api.md create mode 100644 src/site/markdown/migrating-from-verax.md create mode 100644 src/site/markdown/preparing-the-bmc.md create mode 100644 src/site/markdown/sensors.md create mode 100644 src/site/markdown/serial-over-lan.md create mode 100644 src/site/markdown/supported-commands.md create mode 100644 src/site/markdown/timeouts-and-errors.md create mode 100644 src/site/markdown/troubleshooting.md create mode 100644 src/site/markdown/upgrading.md rename src/site/resources/images/{metricshub-logo.png => metricshub-logo-only.png} (100%) diff --git a/AGENTS.md b/AGENTS.md index 78010e7..1fdb4c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,11 +26,13 @@ Unit tests must not depend on a real BMC. Exercising the RMCP+ session code agai ## Code quality reports -Code quality reports (checkstyle, pmd/cpd, spotbugs) are generated by `mvn verify site` into ./target/checkstyle-result.xml, ./target/pmd.xml, ./target/cpd.xml and ./target/spotbugsXml.xml. Checkstyle and PMD are gated (the build fails on any error); CPD is gated on duplications of 100 tokens or more (`cpd-check` at `verify`), while the site report lists those of 50 tokens or more; SpotBugs is not yet gated (issue #116 tracks the clean-up). Extract shared code instead of copying it: `AbstractSensorRecord` holds what the Full, Compact and Event-Only sensor records share, and `IpmiCommandCoder.validateResponse()` the response checks of every command. Do not add new violations: check the reports for the files you changed before committing and submitting your code. On JDK 21+ the SpotBugs plugin version inherited from the parent POM cannot read the JDK class files; run `mvn com.github.spotbugs:spotbugs-maven-plugin:4.10.4.1:spotbugs` instead. +Code quality reports (checkstyle, pmd/cpd, spotbugs) are generated by `mvn verify site` into ./target/checkstyle-result.xml, ./target/pmd.xml, ./target/cpd.xml and ./target/spotbugsXml.xml. Checkstyle and PMD are gated (the build fails on any error); CPD is gated on duplications of 100 tokens or more (`cpd-check` at `verify`), while the site report lists those of 50 tokens or more; SpotBugs is not yet gated (issue #116 tracks the clean-up). Extract shared code instead of copying it: `AbstractSensorRecord` holds what the Full, Compact and Event-Only sensor records share, and `IpmiCommandCoder.validateResponse()` the response checks of every command. Do not add new violations: check the reports for the files you changed before committing and submitting your code. The SpotBugs site report runs spotbugs-maven-plugin 4.10.4.1 (pinned in ``, as the 4.9.3.0 of the parent POM cannot read the class files of JDK 21+); to run SpotBugs alone, use `mvn com.github.spotbugs:spotbugs-maven-plugin:4.10.4.1:spotbugs`. ## Documentation -Always make sure that public API changes are properly documented in src/site/markdown/*.md and that README.md is always up-to-date. The published documentation is https://metricshub.org/ipmi-java (see issue #113 for the planned overhaul). +Always make sure that public API changes are properly documented in src/site/markdown/*.md and that README.md is always up-to-date. The published documentation is https://metricshub.org/ipmi-java. + +The site is built with maven-site-plugin 4 and the Sentry Maven Skin (https://sentrysoftware.org/sentry-maven-skin/, see its "Vibe Writing" page for the syntax: callouts, tabs, `toc` macro). Every page starts with `keywords:` and `description:` headers, has a single H1 and the `toc` macro, and is listed in `src/site/site.xml` (Getting Started, Usage or Reference menu). Keep the pages true to the code: verify examples against the API (and, where they show output, against a real BMC), and link known limitations to their GitHub issue instead of hiding them. User-visible changes go to `upgrading.md`. ## IPMI specifics diff --git a/README.md b/README.md index f6afafd..fa10627 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,19 @@ This project is a fork of the excellent [IPMI Library for Java by Verax Systems] See **[Project Documentation](https://metricshub.org/ipmi-java)** and the [Javadoc](https://metricshub.org/ipmi-java/apidocs) for more information on how to use this library in your code. -The IPMI Java Client is a library that communicates with the IPMI host, fetches Field Replaceable Units (FRUs) and Sensors information then reports these information as a text output. +The IPMI Java Client talks to the Baseboard Management Controller (BMC) of a server over IPMI 2.0 over LAN (RMCP+): it reads the chassis status, the Field Replaceable Units (FRUs) and every sensor, as Java objects or as the text output that MetricsHub parses, and its low-level API sends any IPMI command (System Event Log, chassis control, Serial over LAN). It requires Java 8 or later. + +```java +IpmiClientConfiguration config = new IpmiClientConfiguration("bmc.example.com", "monitor", password, null, false, 120); +System.out.println(IpmiClient.getChassisStatusAsStringResult(config)); +System.out.println(IpmiClient.getFrusAndSensorsAsStringResult(config)); +``` + +The BMC must have IPMI over LAN enabled and an account with the User privilege: see [Preparing the BMC](https://metricshub.org/ipmi-java/preparing-the-bmc.html). ## Upgrading -Version 1.2.03 makes the `protected` fields of the protocol classes (`AbstractIpmiRunner`, `MessageHandler`, `IpmiLanMessage`, `ConfidentialityAlgorithm`, `IntegrityAlgorithm`) `private`. Subclasses must use the new `protected` accessors instead; see [Upgrading from 1.2.02](https://metricshub.org/ipmi-java/#upgrading-from-1-2-02) for the list. The `IpmiClient` API is unchanged. The Full, Compact and Event-Only sensor records now share the `AbstractSensorRecord` superclass, and commands can check responses with `IpmiCommandCoder.validateResponse()`; both are described on the same page. +Version 1.2.03 makes the `protected` fields of the protocol classes (`AbstractIpmiRunner`, `MessageHandler`, `IpmiLanMessage`, `ConfidentialityAlgorithm`, `IntegrityAlgorithm`) `private`. Subclasses must use the new `protected` accessors instead; see [Upgrading from 1.2.02](https://metricshub.org/ipmi-java/upgrading.html#upgrading-from-1-2-02) for the list. The `IpmiClient` API is unchanged. The Full, Compact and Event-Only sensor records now share the `AbstractSensorRecord` superclass, and commands can check responses with `IpmiCommandCoder.validateResponse()`; both are described on the same page. ## Build instructions @@ -22,6 +30,8 @@ This is a simple Maven project. Build with: mvn verify ``` +`mvn verify site` also builds the documentation in `target/site` (sources in [src/site](src/site)). + ## Code format The code is formatted with the MetricsHub Eclipse formatter profile ([metricshub-eclipse-formatter.xml](metricshub-eclipse-formatter.xml), shared with the other MetricsHub Java projects), and the build fails on unformatted code. Simply run the below command before committing: diff --git a/pom.xml b/pom.xml index 141ec6d..43bef44 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.metricshub oss-parent - 4 + 5 ipmi-java @@ -68,8 +68,14 @@ 8 + + UTF-8 + + 2025-11-20T18:41:44Z @@ -184,12 +190,53 @@ false + + + + maven-site-plugin + 4.0.0-M16 + + + org.sentrysoftware.maven + maven-skin-tools + 1.8.01 + + + + + + + + maven-surefire-report-plugin + 3.6.0 + + + + + com.github.spotbugs + spotbugs-maven-plugin + 4.10.4.1 + + + + + maven-changelog-plugin + + + + + + + maven-pmd-plugin diff --git a/src/site/markdown/chassis-status.md b/src/site/markdown/chassis-status.md new file mode 100644 index 0000000..2afbad3 --- /dev/null +++ b/src/site/markdown/chassis-status.md @@ -0,0 +1,76 @@ +keywords: chassis status, power state, power on, power off, power restore policy, intrusion, fault, get chassis status +description: Read the chassis status of a server through its BMC — power state, last power event, power restore policy, faults, intrusion and front panel flags. + +# Chassis Status + + + +The IPMI **Get Chassis Status** command reports whether the server is powered on, why it last +changed power state, what it does when mains power returns, and a few fault and front panel +flags. It is a single request: reading it is fast, and it is a good way to check that the +credentials work. + +## As text + +```java +String status = IpmiClient.getChassisStatusAsStringResult(config); +// "System power state is up" or "System power state is down" +``` + +The text reports the power state only. + +## As an object + +```java +GetChassisStatusResponseData status = IpmiClient.getChassisStatus(config); + +System.out.println("Power: " + (status.isPowerOn() ? "on" : "off")); +System.out.println("Restore policy: " + status.getPowerRestorePolicy()); +System.out.println("Intrusion: " + status.isChassisIntrusionActive()); +``` + +[`GetChassisStatusResponseData`](apidocs/org/metricshub/ipmi/core/coding/commands/chassis/GetChassisStatusResponseData.html) +decodes the response (IPMI 2.0, section 28.2): + +| Method | Meaning | +| --- | --- | +| **Current power state** | | +| `isPowerOn()` | System power is on | +| `isPowerOverload()` | The system was shut down because of a power overload | +| `isInterlock()` | A power interlock (a switch that cuts power when the chassis is open) is active | +| `isPowerFault()` | A fault was detected in the main power subsystem | +| `isPowerControlFault()` | The power controller tried to change the power state and failed | +| `getPowerRestorePolicy()` | What happens when mains power returns: `PoweredOff`, `PowerRestored` (back to the previous state) or `PoweredUp` | +| **Last power event** | | +| `wasIpmiPowerOn()` | The last power-on was requested through IPMI | +| `wasPowerFault()` | The last power-down was caused by a power fault | +| `wasInterlock()` | The last power-down was caused by a power interlock | +| `wasPowerOverload()` | The last power-down was caused by a power overload | +| `acFailed()` | Mains (AC) power was lost | +| **Miscellaneous chassis state** | | +| `isChassisIntrusionActive()` | The chassis intrusion sensor is active (the case is or was open) | +| `isFrontPanelLockoutActive()` | The power off and reset buttons of the front panel are disabled | +| `driveFaultDetected()` | A drive fault was detected | +| `coolingFaultDetected()` | A cooling or fan fault was detected | +| `isChassisIdentifyCommandSupported()`, `getChassisIdentifyState()` | Whether the identify LED can be read, and its state: `Off`, `TemporaryOn`, `IndefiniteOn` | +| **Front panel buttons** (optional in the response) | | +| `isFrontPanelButtonCapabilitiesSet()` | The BMC returned the front panel button byte; the methods below throw `IllegalAccessException` otherwise | +| `isPowerOffButtonDisabled()`, `isResetButtonDisabled()`, ... | State of each button, and whether it can be disabled (`...DisableAllowed()`) | + +> [!NOTE] +> `getChassisIdentifyState()` throws `IllegalAccessError` when +> `isChassisIdentifyCommandSupported()` is `false`: check it first. `getPowerRestorePolicy()` +> throws `IllegalArgumentException` when the BMC reports the policy as *unknown* +> ([#87](https://github.com/metricshub/ipmi-java/issues/87)). + +Not every BMC fills every flag: the intrusion, drive and cooling bits in particular are optional +in the specification, and a BMC that does not implement them reports `false`. + +## Controlling the power + +`IpmiClient` only reads. To power the server on or off or reset it, send the +[`ChassisControl`](apidocs/org/metricshub/ipmi/core/coding/commands/chassis/ChassisControl.html) +command with the [low-level API](low-level-api.html#sending-commands), in a session opened with +the **Operator** or **Administrator** privilege. The supported power commands are `PowerDown`, +`PowerUp` and `HardReset`. Exposing power control in `IpmiClient` is tracked in +[#104](https://github.com/metricshub/ipmi-java/issues/104). diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md new file mode 100644 index 0000000..9787595 --- /dev/null +++ b/src/site/markdown/configuration.md @@ -0,0 +1,117 @@ +keywords: configuration, ipmiclientconfiguration, credentials, password, bmc key, kg, skipauth, timeout, pingperiod, keep-alive, port +description: Every option of IpmiClientConfiguration — host and port, credentials, BMC key, skipAuth, the overall timeout and the keep-alive period — with their defaults and exact semantics. + +# Configuration + + + +Every call of [`IpmiClient`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) takes an +[`IpmiClientConfiguration`](apidocs/org/metricshub/ipmi/client/IpmiClientConfiguration.html), +which carries the target, the credentials and the timing options. It is a plain mutable object: +build it once per BMC and reuse it for every call; the client never modifies it. + +## Constructors + +```java +// host, user, password, BMC key, skipAuth, timeout (s) +new IpmiClientConfiguration("bmc.example.com", "monitor", password, null, false, 120); + +// ... with a UDP port other than 623 +new IpmiClientConfiguration("bmc.example.com", 6230, "monitor", password, null, false, 120); + +// ... with a keep-alive period (ms), 0 to disable the keep-alive messages +new IpmiClientConfiguration("bmc.example.com", "monitor", password, null, false, 120, 0); +``` + +Every option also has a setter (`setPort(int)`, `setPingPeriod(long)`, ...), so the options of +one constructor can be combined with those of another. + +## Options + +| Option | Default | Details | +| --- | --- | --- | +| `hostname` | required | [Host and port](#host-and-port) | +| `port` | `623` | [Host and port](#host-and-port) | +| `username`, `password` | required | [Credentials](#credentials) | +| `bmcKey` | `null` | [BMC key](#bmc-key) | +| `skipAuth` | required | [skipAuth](#skipauth) | +| `timeout` | required, in **seconds** | [Timeout](#timeout) | +| `pingPeriod` | `-1`: 30 000 ms | [Keep-alive](#keep-alive) | + +### Host and port + +`hostname` is the host name or the IP address (IPv4 or IPv6) of the **BMC**, not of the server's +operating system. It is resolved with `InetAddress.getByName()` at each call. + +`port` is the UDP port of the BMC, **623** by default. Change it only for a BMC behind a NAT +or a proxy that forwards another port to 623. The local UDP port is always an ephemeral one, +chosen by the operating system for each session. + +### Credentials + +`username` and `password` are the IPMI account of the BMC: see +[Preparing the BMC](preparing-the-bmc.html#creating-the-account). The password is a `char[]`; +the library converts it to a `String` internally to open the session and does not clear the +array, so clear it yourself once you no longer need the configuration. + +The client opens every session with the **User** privilege level, which is enough for every +`IpmiClient` method. + +### BMC key + +`bmcKey` is the **BMC key (Kg)** of the BMC, as raw bytes, for BMCs configured with *two-key* +logins. Leave it `null` (the default on virtually every BMC): the session keys are then derived +from the password. See [Preparing the BMC](preparing-the-bmc.html#bmc-key-kg). + +### skipAuth + +Despite its name, `skipAuth` does **not** skip authentication: the session is always +authenticated with the user name and password (RAKP handshake). It chooses how the cipher suite is +picked: + +| `skipAuth` | Before opening the session | Cipher suite | Privilege | +| --- | --- | --- | --- | +| `false` (recommended) | Get Channel Cipher Suites, then Get Channel Authentication Capabilities | Picked from the BMC's list, [by position](preparing-the-bmc.html#how-ipmiclient-chooses-the-suite) | User | +| `true` | Nothing: the session is opened directly | Always **3** (RAKP-HMAC-SHA1, HMAC-SHA1-96, AES-CBC-128) | User | + +Use `true` to force suite 3 on a BMC whose suite list would make the position rule pick a suite +the client does not implement, or to save two round trips per call. + +### Timeout + +`timeout` is the **overall deadline of each `IpmiClient` call, in seconds**: opening the session, +every command, and closing the session. When it expires, the call is cancelled and throws +`java.util.concurrent.TimeoutException`. Walking a large SDR repository or reading many FRUs can +take tens of seconds on a slow BMC: 120 s is a safe value. + +`getFrusAndSensorsAsStringResult()` makes two calls (FRUs, then sensors), each with this +deadline, so it can take up to twice the timeout. + +The timeout of each **message** is a different setting, 5 minutes by default; see +[Timeouts and Errors](timeouts-and-errors.html). + +### Keep-alive + +While a session is open, the client sends a no-op message (Get Channel Authentication +Capabilities) every `pingPeriod` **milliseconds**, so that the BMC does not close the session for +inactivity during a long collection. + +| `pingPeriod` | Behavior | +| --- | --- | +| `-1` (default) | The `pingPeriod` of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults): 30 000 ms | +| `> 0` | One keep-alive message every `pingPeriod` ms | +| `0` (or any other negative value) | No keep-alive messages | + +Each `IpmiClient` call opens its own session and closes it when it is done, so the keep-alive +only matters for calls that last longer than the BMC's session inactivity timeout (typically +60 s). Disable it (`0`) to keep the traffic to the strict minimum. + +## Thread safety + +`IpmiClient` methods are static and keep no state between calls: each call creates its own +connector, local UDP port, session and worker thread. Calls can run in parallel, for different +BMCs. Parallel sessions against the **same BMC** are not reliable: BMCs accept a limited number of +sessions and drop replies under load, and parallel sessions from one JVM lose far more replies +than the same sessions from separate processes +([#97](https://github.com/metricshub/ipmi-java/issues/97)). Query a given BMC from one thread at a +time. diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md new file mode 100644 index 0000000..a0e59bf --- /dev/null +++ b/src/site/markdown/fru-inventory.md @@ -0,0 +1,104 @@ +keywords: fru, field replaceable unit, inventory, serial number, part number, manufacturer, product, board, chassis, read fru data +description: Read the FRU inventory of a server through its BMC — which FRUs are read, the Board, Chassis and Product areas, and the FRU lines of the text output. + +# FRU Inventory + + + +**Field Replaceable Units (FRUs)** — the chassis, the system board, power supplies, risers, +backplanes, sometimes the memory modules — carry a small EEPROM with their manufacturer, product +name, part number and serial number. The BMC exposes them through the FRU commands, and +describes which ones exist in its SDR repository. + +```java +List frus = IpmiClient.getFrus(config); +``` + +## How the FRUs are read + +[`IpmiClient.getFrus()`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) opens a session and: + +1. reads **FRU 0**, the built-in FRU of the BMC (usually the system board), with Get FRU + Inventory Area Info and Read FRU Data; +2. walks the **SDR repository** and, for each **FRU Device Locator** record of a *logical* FRU + device (one accessed with the FRU commands of the BMC), reads that FRU the same way; +3. attaches FRU 0 to the first **Compact Sensor** record of the system board entity, under the + name ` `. + +The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC +but makes large FRUs slow to read: a few seconds per FRU on some BMCs +([#102](https://github.com/metricshub/ipmi-java/issues/102)). + +A FRU that cannot be read — not present, or answering with an error at some offset — is logged at +the `WARN` level and reported truncated, or not at all; it never fails the whole call. Physical +FRU devices (EEPROMs on a private I²C bus, read with Master Write-Read) are not read. + +## The `Fru` object + +Each [`Fru`](apidocs/org/metricshub/ipmi/client/model/Fru.html) holds: + +* `getFruLocator()` — the + [`FruDeviceLocatorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/FruDeviceLocatorRecord.html) + that describes the FRU: `getName()`, `getFruEntityId()` and `getFruEntityInstance()` (what the + FRU is: a power supply, a processor board, ...), `getDeviceId()` (the FRU ID); +* `getFruRecords()` — the decoded information areas of the FRU, among: + +| Record | Fields | +| --- | --- | +| [`BoardInfo`](apidocs/org/metricshub/ipmi/core/coding/commands/fru/record/BoardInfo.html) | `getBoardManufacturer()`, `getBoardProductName()`, `getBoardSerialNumber()`, `getBoardPartNumber()`, `getMfgDate()`, `getFruFileId()`, `getCustomBoardInfo()` | +| [`ProductInfo`](apidocs/org/metricshub/ipmi/core/coding/commands/fru/record/ProductInfo.html) | `getManufacturerName()`, `getProductName()`, `getProductModelNumber()`, `getProductVersion()`, `getProductSerialNumber()`, `getAssetTag()`, `getFruFileId()`, `getCustomProductInfo()` | +| [`ChassisInfo`](apidocs/org/metricshub/ipmi/core/coding/commands/fru/record/ChassisInfo.html) | `getChassisType()`, `getChassisPartNumber()`, `getChassisSerialNumber()`, `getCustomChassisInfo()` | + +The MultiRecord area (power supply, DC output, management access records) is decoded by the +library but not returned by `getFrus()`; read it with the [low-level API](low-level-api.html) and +`ReadFruData.decodeFruData()` if you need it. + +```java +for (Fru fru : IpmiClient.getFrus(config)) { + for (FruRecord record : fru.getFruRecords()) { + if (record instanceof ProductInfo) { + ProductInfo product = (ProductInfo) record; + System.out.println(product.getManufacturerName() + " " + product.getProductName() + + " S/N " + product.getProductSerialNumber()); + } + } +} +``` + +## FRU lines of the text output + +[`getFrusAndSensorsAsStringResult()`](sensors.html#text-output-format) starts with one line per +FRU: + +```text +FRU;$vendor;$model;$serialNumber +``` + +For example (serial numbers masked): + +```text +FRU;LENOVO;RD350;S4M00000 - 00000000000001 +FRU;LITEON;PS-2451-6L-LF;0000 +FRU;LENOVO;Riser 1x16;8SSC50A00000V1SH4AX0000 - SC50A00000 +``` + +The fields come from the **Product** area when it has a manufacturer name, and from the **Board** +area otherwise: + +| Field | Product area | Board area | +| --- | --- | --- | +| `$vendor` | Manufacturer name | Board manufacturer | +| `$model` | Product name | Board product name | +| `$serialNumber` | Product serial number, followed by ` - ` and the product part/model number when both exist | Board serial number, followed by ` - ` and the board part number when both exist | + +A FRU with neither a vendor nor a model is not listed. The lines are sorted by how complete and +how central they are: + +1. the system chassis and system board, with a model and a serial number (from the Product + area); +2. the front and back panel boards, then the other FRUs, with a model and a serial number (from + the Product area); +3. every other FRU (incomplete Product area, or Board area only). + +The same vendor, model and serial number are repeated on the +[device lines](sensors.html#device-state-lines) of the sensors that belong to the same entity. diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 58dc7bd..3bb5020 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,109 +1,129 @@ +keywords: ipmi java client, ipmi 2.0, rmcp+, bmc, hardware monitoring, sensors, fru, overview +description: A Java client for IPMI 2.0 over LAN (RMCP+): read the chassis power state, the FRU inventory and every sensor of a server's BMC, or send any IPMI command yourself. + # IPMI Java Client -The IPMI Java Client is a library that communicates with the IPMI host, fetches Field Replaceable Units (FRUs) and Sensors information then reports these information as a text output. + -## How to run the IPMI Client inside Java +## Overview -Add IPMI in the list of dependencies in your [Maven **pom.xml**](https://maven.apache.org/pom.html): +The **IPMI Java Client** talks to the **Baseboard Management Controller (BMC)** of a server +(Dell iDRAC, HPE iLO, Lenovo XClarity Controller, OpenBMC, and the BMC firmwares of most other +boards) over **IPMI 2.0 over LAN (RMCP+)**, on UDP port 623. It lets a Java application: -```xml - - - ${project.groupId} - ${project.artifactId} - ${project.version} - - -``` +* read the **chassis status**: power on or off, last power event, power restore policy, faults, + intrusion ([Chassis Status](chassis-status.html)), +* read the **FRU inventory**: manufacturer, product name, part and serial numbers of the chassis, + boards, power supplies and other Field Replaceable Units ([FRU Inventory](fru-inventory.html)), +* read **every sensor** of the SDR repository: temperatures, voltages, fan speeds, currents, + power and energy readings with their thresholds, and the discrete states (presence, redundancy, + failure, ...) ([Sensors](sensors.html)), and +* send **any IPMI command** through the low-level connector, including the System Event Log + and chassis control commands, and open a **Serial over LAN** console + ([Low-Level API](low-level-api.html), [Serial over LAN](serial-over-lan.html)). -Invoke the IPMI Client: +The library is the IPMI engine of [MetricsHub](https://metricshub.com) hardware monitoring: its +high-level [`IpmiClient`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) returns the +inventory and the sensors either as Java objects or as the semicolon-separated text that the +MetricsHub connectors parse ([text output format](sensors.html#text-output-format)). -```java +It is a fork of the [IPMI Library for Java by Verax Systems](https://en.wikipedia.org/wiki/Verax_IPMI), +with the RAKP-HMAC-SHA256 and RAKP-HMAC-MD5 authentication algorithms added, tolerance for the OEM +records that vendors put in their SDR repository, and many fixes. Moving from the Verax library is +mostly a package rename ([Migrating from Verax](migrating-from-verax.html)). -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import org.metricshub.ipmi.client.IpmiClient; -import org.metricshub.ipmi.client.IpmiClientConfiguration; +## Add the dependency -public class IpmiMain { - public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { - - final String hostname = "my-host"; - final String username = "my-username"; - final char[] password = new char[] { 'p', 'a', 's', 's' }; - final boolean noAuth = false; - final byte[] bmcKey = null; - final long timeout = 120; - // Set pingPeriod to 0 to turn off keep-alive messages sent to the remote host. - final long pingPeriod = 30000; - - // Instantiates a new IPMI client configuration using the credentials above - final IpmiClientConfiguration ipmiClientConfiguration = new IpmiClientConfiguration( - hostname, - username, - password, - bmcKey, - noAuth, - timeout, - pingPeriod - ); - - // Get the Chassis' status - final String chassisStatusResult = IpmiClient.getChassisStatusAsStringResult(ipmiClientConfiguration); - - System.out.println("Chassis status:"); - System.out.println(chassisStatusResult); - - // Get FRUs and Sensors - final String sensorsResult = IpmiClient.getFrusAndSensorsAsStringResult(ipmiClientConfiguration); - - System.out.println("Sensors:"); - System.out.println(sensorsResult); - } -} +The library requires **Java 8** or later and is published on +[Maven Central](https://central.sonatype.com/artifact/${project.groupId}/${project.artifactId}): +```xml + + ${project.groupId} + ${project.artifactId} + ${project.version} + ``` -## Upgrading from 1.2.02 +See [Installation](installation.html) for Gradle, the dependencies and logging. -The `IpmiClient` API is unchanged. Classes that **extend** the library's protocol classes must replace direct access to formerly `protected` fields, which are now `private`, with the new `protected` accessors: +## Quick start -| Class | Former field | Accessor | -| --- | --- | --- | -| `AbstractIpmiRunner` | `ipmiConfiguration` | `getIpmiConfiguration()` | -| `AbstractIpmiRunner` | `connector` | `getConnector()` | -| `AbstractIpmiRunner` | `handle` | `getHandle()` | -| `AbstractIpmiRunner` | `nextRecId` | `getNextRecId()`, `setNextRecId(int)` | -| `MessageHandler` | `messageQueue` | `getMessageQueue()` | -| `MessageHandler` | `connection` | `getConnection()` | -| `MessageHandler` | `lastReceivedSequenceNumber` | `getLastReceivedSequenceNumber()`, `setLastReceivedSequenceNumber(int)` | -| `IpmiLanMessage` | `networkFunction` | `getNetworkFunctionCode()`, `setNetworkFunctionCode(byte)` | -| `ConfidentialityAlgorithm` | `sik` | `getSik()` | -| `IntegrityAlgorithm` | `sik` | `getSik()`, `setSik(byte[])` | +> [!NOTE] +> **On the BMC**, IPMI over LAN must be enabled, UDP port 623 reachable, and the account must be +> allowed to log in over LAN with at least the **User** privilege. Several vendors ship with IPMI +> over LAN disabled. See [Preparing the BMC](preparing-the-bmc.html). -`IpmiClient`, `IpmiResultConverter`, `Utils`, `DeviceDescription`, `ReadingTypeDescription` and `MessageComposer` are now `final` (they only had private constructors, so they could not be subclassed anyway). +Everything starts with an +[`IpmiClientConfiguration`](apidocs/org/metricshub/ipmi/client/IpmiClientConfiguration.html) and +the static methods of [`IpmiClient`](apidocs/org/metricshub/ipmi/client/IpmiClient.html): -### Sensor records +```java +import org.metricshub.ipmi.client.IpmiClient; +import org.metricshub.ipmi.client.IpmiClientConfiguration; -`FullSensorRecord`, `CompactSensorRecord` and `EventOnlyRecord` now extend the new `AbstractSensorRecord` (itself a `SensorRecord`), which holds the fields the three record types share: sensor owner and number, entity, sensor type, event/reading type, direction, name (ID string), capabilities, units and record sharing. Their getters and setters keep the same signatures, so existing code compiles unchanged, and code that handles several record types can use `AbstractSensorRecord` instead of testing each type: +public class Example { -```java -if (record instanceof AbstractSensorRecord) { - AbstractSensorRecord sensor = (AbstractSensorRecord) record; - System.out.println(sensor.getName() + ": " + sensor.getSensorType()); + public static void main(String[] args) throws Exception { + IpmiClientConfiguration config = new IpmiClientConfiguration( + "bmc.example.com", // host name or IP address of the BMC + "monitor", // user name + "the-password".toCharArray(), // password + null, // BMC key (Kg), only with two-key authentication + false, // skipAuth: discover the cipher suites first + 120); // overall timeout of each call, in seconds + + // "System power state is up" + System.out.println(IpmiClient.getChassisStatusAsStringResult(config)); + + // One line per FRU, per device with states, and per sensor reading + System.out.println(IpmiClient.getFrusAndSensorsAsStringResult(config)); + } } ``` -A record type now also inherits the getters of fields it does not define, which return defaults: +Which prints (from a Lenovo server, serial numbers masked): + +```text +System power state is up +FRU;LENOVO;RD350;S4M00000 - 00000000000001 +FRU;LITEON;PS-2451-6L-LF;0000 +Power Unit;3;Power Unit 3;;;;PSU Redundancy=Fully Redundant +Power Supply;1;Power Supply 1;;;;PSU1 Present=Presence detected +Temperature;0008;Ambient Temp;Air Inlet 1;17.0;37;39 +PowerConsumption;000d;System Power;Power Unit 2;92.0 +Fan;0014;Fan 1;Fan Device 1;6600.0;1600; +Voltage;0022;System 3.3V;System Board 1;3380.0;3040;3560 +``` -| Record | Field | Value | -| --- | --- | --- | -| `EventOnlyRecord` (no reading) | `getRateUnit()`, `getModifierUnitUsage()`, `getSensorBaseUnit()`, `getSensorModifierUnit()` | `null` | -| `EventOnlyRecord` (no reading) | `isHysteresisReadable()`, `isThresholdsReadable()` | `false` | -| `FullSensorRecord` (a single sensor) | `getShareCount()`, `getIdInstanceModifierOffset()` | `0` | -| `FullSensorRecord` (a single sensor) | `getIdInstanceModifierType()` | `null` | -| `FullSensorRecord` (a single sensor) | `isEntityInstanceIncrements()` | `false` | +The same data is available as Java objects: -### Command responses +```java +GetChassisStatusResponseData status = IpmiClient.getChassisStatus(config); +List frus = IpmiClient.getFrus(config); +List sensors = IpmiClient.getSensors(config); +``` -Commands that extend `IpmiCommandCoder` can call the new `protected` method `validateResponse(IpmiMessage)`, which checks that a message is a successful response to the command and returns its data: it throws `IllegalArgumentException` for a response to another command or a payload that is not an IPMI LAN response, and `IPMIException` for a completion code other than `Ok`. The message of the `IllegalArgumentException` now names the command class (three commands used to name the wrong command). +Each call opens its own RMCP+ session, sends its commands, closes the session and releases its +UDP port; nothing has to be closed by the caller. Each call throws `TimeoutException` when it does +not complete within the configured timeout, and `ExecutionException` wrapping the cause when the +session cannot be opened or a command fails ([Timeouts and Errors](timeouts-and-errors.html)). + +## Where to go next + +* [Installation](installation.html) — coordinates, supported JDKs, dependencies and logging +* [Preparing the BMC](preparing-the-bmc.html) — enabling IPMI over LAN, the account, privilege + level and cipher suites, the firewall +* [Chassis Status](chassis-status.html) — power state and chassis flags +* [FRU Inventory](fru-inventory.html) — how FRUs are read and which fields are reported +* [Sensors](sensors.html) — readings, thresholds, states, and the text output format +* [Configuration](configuration.html) — every option of `IpmiClientConfiguration` +* [Timeouts and Errors](timeouts-and-errors.html) — overall and per-message timeouts, retries, + exceptions and logging +* [Low-Level API](low-level-api.html) — `IpmiConnector`: sessions, cipher suites, any IPMI command +* [Serial over LAN](serial-over-lan.html) — a console on the server's serial port +* [Supported Commands](supported-commands.html) — commands, cipher suites, SDR and FRU records +* [Troubleshooting](troubleshooting.html) — common failures and how to diagnose them with + `ipmitool` or `ipmiutil` +* [Upgrading](upgrading.html) — changes between versions +* [Migrating from Verax](migrating-from-verax.html) — moving from the Verax IPMI Library for Java diff --git a/src/site/markdown/installation.md b/src/site/markdown/installation.md new file mode 100644 index 0000000..d597711 --- /dev/null +++ b/src/site/markdown/installation.md @@ -0,0 +1,84 @@ +keywords: install, maven, gradle, dependency, jdk, java 8, slf4j, logging +description: Add the IPMI Java Client to your build, the supported JDKs, its single dependency (the SLF4J API), and how to configure its logging. + +# Installation + + + +## Add it to your build + +The library is published on +[Maven Central](https://central.sonatype.com/artifact/${project.groupId}/${project.artifactId}): + +> [!TABS] +> * Maven +> ```xml +> +> ${project.groupId} +> ${project.artifactId} +> ${project.version} +> +> ``` +> * Gradle (Groovy) +> ```groovy +> implementation '${project.groupId}:${project.artifactId}:${project.version}' +> ``` +> * Gradle (Kotlin) +> ```kotlin +> implementation("${project.groupId}:${project.artifactId}:${project.version}") +> ``` + +Versions up to 1.2.00 were published as `org.sentrysoftware:ipmi`, with the +`org.sentrysoftware.ipmi` packages; see [Upgrading](upgrading.html#upgrading-from-1-2-00-and-earlier). + +## Requirements + +| Requirement | Detail | +| --- | --- | +| Java | **Java 8** or later. The library is compiled for Java 8 and built and tested on JDK 17. | +| Cryptography | The standard JCE algorithms only: `HmacSHA1`, `HmacSHA256`, `HmacMD5`, `AES/CBC/NoPadding`. No extra security provider is needed. | +| Network | UDP from the machine running the client to port **623** of the BMC (or the port set with [`setPort()`](configuration.html#host-and-port)). Each session also binds an ephemeral local UDP port. | +| A BMC | With IPMI 2.0 over LAN enabled and an account allowed to log in: see [Preparing the BMC](preparing-the-bmc.html). IPMI 1.5-only BMCs are not supported. | + +## Dependencies + +The only runtime dependency is the **SLF4J 2 API** (`org.slf4j:slf4j-api`), through which the +library logs. Nothing else is pulled in: the RMCP+ protocol, the RAKP handshake and the +encryption are implemented with the JDK alone. + +## Logging + +The library logs through [SLF4J](https://www.slf4j.org/) under the `org.metricshub.ipmi` logger +hierarchy. Add the SLF4J provider of your logging framework (`logback-classic`, `slf4j-reload4j`, +`log4j-slf4j2-impl`, `slf4j-simple`, ...) to see the messages; without a provider, SLF4J prints a +one-time warning and discards them. + +What is logged, and at which level: + +| Level | Messages | +| --- | --- | +| `ERROR` | A value the decoders do not know (`Invalid value: ...` for an entity ID, sensor type or unit), a failed session handshake, exceptions in the receiving and keep-alive threads, and the `InterruptedException` of a session interrupted by the [overall timeout](timeouts-and-errors.html#overall-timeout). | +| `WARN` | An SDR record that cannot be decoded and is skipped, a FRU that cannot be read or decoded (the FRU is then truncated or missing), a message that failed and is resent, a packet whose integrity check failed. | +| `INFO` | Every lookup of a [`connection.properties`](timeouts-and-errors.html#library-wide-defaults) value, and every message removed from the queue after its timeout. | +| `DEBUG` | Each message sent, with its tag and attempt number; a session that could not be closed cleanly. | + +> [!TIP] +> The `INFO` messages are noisy: set the `org.metricshub.ipmi` logger to `WARN` in production, +> and to `DEBUG` when diagnosing a BMC ([Troubleshooting](troubleshooting.html)). + +With `slf4j-simple`, for example: + +```bash +java -Dorg.slf4j.simpleLogger.log.org.metricshub.ipmi=warn -cp ... MyApp +``` + +## Building from source + +```bash +git clone https://github.com/metricshub/ipmi-java.git +cd ipmi-java +mvn verify +``` + +`mvn verify site` also generates this documentation and the reports (Javadoc, tests, Checkstyle, +PMD, SpotBugs) in `target/site`. diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md new file mode 100644 index 0000000..a56db21 --- /dev/null +++ b/src/site/markdown/low-level-api.md @@ -0,0 +1,248 @@ +keywords: low-level api, ipmiconnector, ipmiasyncconnector, session, cipher suite, privilege level, sendmessage, ipmi command, sel, system event log, chassis control, custom command +description: Open RMCP+ sessions and send any IPMI command with IpmiConnector — choosing the cipher suite and privilege level, timeouts, reading the System Event Log, controlling the power, the asynchronous connector, and writing your own commands. + +# Low-Level API + + + +[`IpmiClient`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) covers what monitoring needs. +Everything else goes through the protocol layer of the `org.metricshub.ipmi.core` packages, +which `IpmiClient` itself uses: + +| Class | Role | +| --- | --- | +| [`IpmiConnector`](apidocs/org/metricshub/ipmi/core/api/sync/IpmiConnector.html) | Synchronous API: open sessions and send a command, waiting for its response. Start here. | +| [`IpmiAsyncConnector`](apidocs/org/metricshub/ipmi/core/api/async/IpmiAsyncConnector.html) | Asynchronous API: send commands and receive the responses through a listener. | +| [`ConnectionHandle`](apidocs/org/metricshub/ipmi/core/api/async/ConnectionHandle.html) | Identifies one connection (one BMC) of a connector, with its cipher suite and privilege level. | +| The commands, in [`org.metricshub.ipmi.core.coding.commands`](apidocs/org/metricshub/ipmi/core/coding/commands/package-summary.html) | One class per IPMI request (`GetChassisStatus`, `GetSdr`, `GetSelEntry`, ...), and one `...ResponseData` class per response. See [Supported Commands](supported-commands.html). | +| [`SerialOverLan`](apidocs/org/metricshub/ipmi/core/api/sol/SerialOverLan.html) | A Serial over LAN console. See [Serial over LAN](serial-over-lan.html). | + +## A complete session + +```java +import java.net.InetAddress; +import java.util.List; + +import org.metricshub.ipmi.core.api.async.ConnectionHandle; +import org.metricshub.ipmi.core.api.sync.IpmiConnector; +import org.metricshub.ipmi.core.coding.commands.IpmiVersion; +import org.metricshub.ipmi.core.coding.commands.PrivilegeLevel; +import org.metricshub.ipmi.core.coding.commands.chassis.GetChassisStatus; +import org.metricshub.ipmi.core.coding.commands.chassis.GetChassisStatusResponseData; +import org.metricshub.ipmi.core.coding.protocol.AuthenticationType; +import org.metricshub.ipmi.core.coding.security.CipherSuite; + +public class LowLevelExample { + + public static void main(String[] args) throws Exception { + // Bind a local UDP port: 0 lets the operating system choose a free one + IpmiConnector connector = new IpmiConnector(0); + try { + // Register a connection to the BMC, on UDP port 623 + ConnectionHandle handle = connector.createConnection(InetAddress.getByName("bmc.example.com")); + + // Wait at most 5 s for each reply instead of 5 min + connector.setTimeout(handle, 5000); + + // Pick a cipher suite among those the BMC offers: 17 if available + List suites = connector.getAvailableCipherSuites(handle); + CipherSuite cipherSuite = suites + .stream() + .filter(suite -> suite.getId() == 17) + .findFirst() + .orElse(suites.get(suites.size() - 1)); + + // Declare the cipher suite and the privilege level, then log in (RAKP handshake) + connector.getChannelAuthenticationCapabilities(handle, cipherSuite, PrivilegeLevel.User); + connector.openSession(handle, "monitor", "the-password", null); + + // Send any command, and cast the response to the matching ResponseData class + GetChassisStatusResponseData status = (GetChassisStatusResponseData) connector + .sendMessage(handle, new GetChassisStatus(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus)); + System.out.println("Power is " + (status.isPowerOn() ? "on" : "off")); + + connector.closeSession(handle); + } finally { + // Close every connection and release the local UDP port + connector.tearDown(); + } + } +} +``` + +The four steps before the first command are mandatory and must come in this order: +`createConnection()`, `getAvailableCipherSuites()`, `getChannelAuthenticationCapabilities()`, +`openSession()`. Calling them out of order fails with +`ConnectionException: Illegal connection state: ...`. + +## Connections and connectors + +An `IpmiConnector` owns one local UDP port and the threads that send and receive on it. It can +hold connections to several BMCs at once, each identified by its `ConnectionHandle`, and each +with its own session. Two connectors cannot share a local port: create one connector per local +port (or always pass `0`), and call `tearDown()` when you are done with it. + +| Method | Purpose | +| --- | --- | +| `IpmiConnector(int port)`, `IpmiConnector(int port, InetAddress address)` | Bind the given local port (`0`: any free port), on all interfaces or on one. | +| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0`: none). | +| `createConnection(InetAddress address[, int port])` | Register a connection to a BMC (port 623 by default). | +| `createConnection(InetAddress address, [int port,] CipherSuite cipherSuite, PrivilegeLevel level)` | The same, skipping the cipher suite and capabilities steps: call `openSession()` next. | +| `closeSession(handle)` | Log out (Close Session). | +| `closeConnection(handle)` | Forget the connection. | +| `tearDown()` | Close every connection and release the local port. | + +### Choosing the cipher suite + +`getAvailableCipherSuites()` returns the suites the BMC offers, in the BMC's order. Choose one +the library implements: **3** or **17** in practice (see the +[table](preparing-the-bmc.html#cipher-suites)). To skip the discovery when you already know the +suite, build it and pass it to `createConnection()`: + +```java +// Suite 17: RAKP-HMAC-SHA256, HMAC-SHA256-128, AES-CBC-128 +CipherSuite suite17 = new CipherSuite((byte) 17, + new AuthenticationRakpHmacSha256().getCode(), + new ConfidentialityAesCbc128().getCode(), + new IntegrityHmacSha256_128().getCode()); +ConnectionHandle handle = connector.createConnection(address, 623, suite17, PrivilegeLevel.User); +connector.openSession(handle, "monitor", "the-password", null); +``` + +`Connection.getDefaultCipherSuite()` returns suite 3 built the same way. + +### Privilege level + +The [`PrivilegeLevel`](apidocs/org/metricshub/ipmi/core/coding/commands/PrivilegeLevel.html) +requested in `getChannelAuthenticationCapabilities()` (or `createConnection()`) is the level of +the session: `User` for reading, `Operator` for most control commands (chassis control, setting +the boot device), `Administrator` for configuration commands and Serial over LAN. The account +must be allowed that level on the LAN channel, or the handshake fails. + +### Timeouts + +`setTimeout(handle, ms)` sets the [per-message timeout](timeouts-and-errors.html#per-message-timeout-and-retries) +of one connection. Call it right after `createConnection()`: it then also applies to the +handshake. `sendMessage()` retries a message `getRetries()` times (3 by default) before throwing. +Unlike `IpmiClient`, the low-level API has no overall timeout: bound it yourself if you need one. + +## Sending commands + +Every command class takes the IPMI version (`IpmiVersion.V20`), the session's cipher suite +(`handle.getCipherSuite()` or the one you chose), `AuthenticationType.RMCPPlus`, and its own +parameters. `sendMessage()` returns the matching `...ResponseData`, or throws: + +* `IPMIException` when the BMC answers with an error completion code (`getCompletionCode()`), +* `ConnectionException` when no reply came after all the tries, or the connection is not in a + state that allows sending, +* `IllegalArgumentException` when the response does not match the request. + +### Reading the System Event Log + +```java +GetSelInfoResponseData info = (GetSelInfoResponseData) connector + .sendMessage(handle, new GetSelInfo(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus)); +System.out.println("SEL entries: " + info.getEntriesCount()); + +int reservationId = ((ReserveSelResponseData) connector + .sendMessage(handle, new ReserveSel(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus))) + .getReservationId(); + +int recordId = 0; // 0: the first entry +while (recordId != 0xFFFF) { // 0xFFFF: no more entries + GetSelEntryResponseData entry = (GetSelEntryResponseData) connector + .sendMessage(handle, new GetSelEntry(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, + reservationId, recordId)); + SelRecord record = entry.getSelRecord(); + System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " + + record.getEventDirection()); + recordId = entry.getNextRecordId(); +} +``` + +```text +SEL entries: 643 +Wed May 15 11:15:25 CEST 2024 EventLoggingDisabled LogAreaReset Assertion +Wed May 15 11:16:11 CEST 2024 Voltage LimitNotExceeded Assertion +Wed May 15 11:23:27 CEST 2024 PowerUnit PowerOffOrDown Assertion +``` + +Exposing the SEL in `IpmiClient` is tracked in +[#103](https://github.com/metricshub/ipmi-java/issues/103). + +### Controlling the power + +In a session opened with the `Operator` or `Administrator` privilege: + +```java +connector.sendMessage(handle, + new ChassisControl(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, PowerCommand.PowerUp)); +``` + +`PowerCommand` supports `PowerUp`, `PowerDown` (immediate, without shutting down the operating +system) and `HardReset`. + +### Writing your own command + +A command the library does not implement is a subclass of +[`IpmiCommandCoder`](apidocs/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.html): +return its network function and command code, build the request data in `preparePayload()`, and +decode the response in `getResponseData()`, where `validateResponse()` checks the response and +the completion code and returns the response data bytes. A minimal Get Device ID: + +```java +public class GetDeviceId extends IpmiCommandCoder { + + public GetDeviceId(CipherSuite cipherSuite) { + super(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus); + } + + @Override + public NetworkFunction getNetworkFunction() { + return NetworkFunction.ApplicationRequest; + } + + @Override + public byte getCommandCode() { + return 0x01; // Get Device ID (IPMI 2.0, section 20.1) + } + + @Override + protected IpmiLanMessage preparePayload(int sequenceNumber) { + // No request data + return new IpmiLanRequest(getNetworkFunction(), getCommandCode(), null, + TypeConverter.intToByte(sequenceNumber)); + } + + @Override + public ResponseData getResponseData(IpmiMessage message) throws IPMIException { + byte[] data = validateResponse(message); // throws IPMIException on an error completion code + return new GetDeviceIdResponseData(data); // your own ResponseData implementation + } +} +``` + +## Asynchronous API + +[`IpmiAsyncConnector`](apidocs/org/metricshub/ipmi/core/api/async/IpmiAsyncConnector.html) has +the same session methods, but its `sendMessage(handle, request, isOneWay)` returns at once with +the **tag** of the message. Responses are delivered to the +[`IpmiResponseListener`](apidocs/org/metricshub/ipmi/core/api/async/IpmiResponseListener.html)s +registered with `registerListener()`, as an `IpmiResponseData` (with the `ResponseData`) or an +`IpmiError` (with the exception), carrying the tag and the connection handle of the request: + +```java +asyncConnector.registerListener(response -> { + if (response instanceof IpmiResponseData) { + ResponseData data = ((IpmiResponseData) response).getResponseData(); + // handle the response to the message tagged response.getTag() + } else { + Exception error = ((IpmiError) response).getException(); + } +}); +int tag = asyncConnector.sendMessage(handle, new GetChassisStatus(IpmiVersion.V20, cipherSuite, + AuthenticationType.RMCPPlus), false); +``` + +The listener is called from the library's own threads: return quickly. The synchronous +`IpmiConnector` is built on this API. diff --git a/src/site/markdown/migrating-from-verax.md b/src/site/markdown/migrating-from-verax.md new file mode 100644 index 0000000..c7009f5 --- /dev/null +++ b/src/site/markdown/migrating-from-verax.md @@ -0,0 +1,69 @@ +keywords: verax, vxipmi, ipmi library for java, migration, package rename, com.veraxsystems.vxipmi +description: Move from the IPMI Library for Java by Verax Systems (com.veraxsystems.vxipmi) to the IPMI Java Client — package rename, Maven dependency, logging, and what changed in the fork. + +# Migrating from Verax + + + +The IPMI Java Client is a fork of the +[IPMI Library for Java by Verax Systems](https://en.wikipedia.org/wiki/Verax_IPMI) (*vxipmi*). +Its protocol layer, `org.metricshub.ipmi.core`, **is** the Verax library, under a new package +name and with the changes listed below; the high-level `org.metricshub.ipmi.client` API is new. +Code written for the Verax library keeps working after a package rename. + +## Steps + +1. Replace the Verax jar with the Maven dependency: + + ```xml + + ${project.groupId} + ${project.artifactId} + ${project.version} + + ``` + +2. Rename the packages in the imports: `com.veraxsystems.vxipmi` becomes + `org.metricshub.ipmi.core`, with the same sub-packages: + + | Verax | IPMI Java Client | + | --- | --- | + | `com.veraxsystems.vxipmi.api.sync.IpmiConnector` | `org.metricshub.ipmi.core.api.sync.IpmiConnector` | + | `com.veraxsystems.vxipmi.api.async.*` | `org.metricshub.ipmi.core.api.async.*` | + | `com.veraxsystems.vxipmi.api.sol.*` | `org.metricshub.ipmi.core.api.sol.*` | + | `com.veraxsystems.vxipmi.coding.*` | `org.metricshub.ipmi.core.coding.*` | + | `com.veraxsystems.vxipmi.common.*`, `connection.*`, `sm.*`, `transport.*` | `org.metricshub.ipmi.core.common.*`, ... | + + A search and replace of `com.veraxsystems.vxipmi.` with `org.metricshub.ipmi.core.` does it. + +3. Configure the logging of the `org.metricshub.ipmi` loggers: the library logs through the SLF4J + API, so add the SLF4J provider of your logging framework + ([Logging](installation.html#logging)). + +4. If you edited the `connection.properties` or `vxipmi.properties` files of the Verax jar to + change the timeouts, set the same values with `PropertiesManager.setProperty()` at startup + instead ([Library-wide defaults](timeouts-and-errors.html#library-wide-defaults)). + +5. If you subclassed protocol classes and accessed their `protected` fields, use the accessors + that replaced them ([Upgrading from 1.2.02](upgrading.html#protected-fields)). + +## What changed in the fork + +| Area | Change | +| --- | --- | +| Cipher suites | RAKP-HMAC-SHA256 and RAKP-HMAC-MD5 authentication, HMAC-SHA256-128 and HMAC-MD5-128 integrity: suites 6 to 8 and 15 to 17 work, in addition to 0 to 3. | +| Keep-alive | `IpmiConnector(int port, long pingPeriod)` sets the keep-alive period of the connector, or disables it (`0`). | +| SDR repository | Every OEM record type (`C0h` – `FFh`, not only `C0h`) is decoded as `OemRecord`, and a record that cannot be decoded is skipped instead of aborting the walk. Truncated Get SDR replies are read again in chunks. | +| Sensor records | Full, Compact and Event-Only records share `AbstractSensorRecord` ([Upgrading](upgrading.html#sensor-records)). | +| Commands | `IpmiCommandCoder.validateResponse()` checks the response of every command ([Upgrading](upgrading.html#command-responses)). | +| Connection thread | The busy loop of `Connection.run()` that used a full CPU core is fixed. | +| High-level API | `IpmiClient` reads the chassis status, the FRUs and the sensors in one call each ([Overview](index.html)). | +| Logging | Through the SLF4J 2 API. | +| Java | Java 8 or later. | + +## License + +The Verax library is published under the GNU GPL v3. This fork is published under the +**GNU LGPL v3**: it uses the Verax code under a commercial (non-GPL) license granted by Verax +Systems to Sentry Software. The source files derived from the Verax library keep Verax Systems +as copyright holder. diff --git a/src/site/markdown/preparing-the-bmc.md b/src/site/markdown/preparing-the-bmc.md new file mode 100644 index 0000000..458d008 --- /dev/null +++ b/src/site/markdown/preparing-the-bmc.md @@ -0,0 +1,148 @@ +keywords: prerequisites, ipmi over lan, bmc, udp 623, firewall, user, privilege level, cipher suites, kg, two-key, ipmitool, ipmiutil +description: Prerequisites on the BMC — enabling IPMI over LAN, the account and its privilege level, the cipher suites the client can use, the BMC key, and the firewall. + +# Preparing the BMC + + + +Three things must be true on the **Baseboard Management Controller** of the monitored server +before this client can talk to it: + +1. **IPMI over LAN is enabled**, on UDP port 623, and reachable through the network, +2. the **account** is allowed to log in over the LAN channel with at least the **User** + privilege, and +3. the BMC offers at least one **cipher suite** the client implements. + +Nothing has to be installed on the server or in its operating system: the client talks to the BMC +only, and works whether the server is powered on or off, as long as the BMC has standby power. + +## What this client needs + +| Requirement | Detail | +| --- | --- | +| IPMI over LAN | Enabled on the LAN channel of the BMC (usually channel 1). Many recent BMCs ship with it **disabled**. | +| IPMI 2.0 (RMCP+) | The client only opens RMCP+ sessions. IPMI 1.5-only BMCs (RMCP with MD2/MD5 or straight password authentication) are not supported. | +| UDP port 623 | Open from the machine running the client to the BMC. The port can be changed with [`setPort()`](configuration.html#host-and-port). | +| An account | Enabled, with a password, allowed to log in over IPMI on the LAN channel. | +| Privilege level | **User** for everything `IpmiClient` does; **Administrator** for [Serial over LAN](serial-over-lan.html). | +| A supported cipher suite | Suite **3** or **17** in practice; see [Cipher suites](#cipher-suites). | + +## Enabling IPMI over LAN + +On most BMCs it is a single option of the web interface, usually in the network or security +settings: *IPMI Over LAN* (Dell iDRAC), *IPMI/DCMI over LAN* (HPE iLO), *IPMI over LAN* (Lenovo +XClarity Controller), *IPMI* in the network services of Supermicro and most ASPEED/AMI firmwares. + +From the server's operating system, with `ipmitool` and the local KCS interface, the same is done +with: + +```bash +# Show the LAN channel configuration (IP address, enabled cipher suites, ...) +ipmitool lan print 1 + +# Enable IPMI messaging on LAN channel 1 +ipmitool lan set 1 access on +``` + +## Creating the account + +Use an account dedicated to monitoring, with the **User** privilege only: everything +[`IpmiClient`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) sends (Get Chassis Status, +Get SDR, Get Sensor Reading, Read FRU Data, ...) is allowed at the User level, and the client +always requests that level for its sessions. With `ipmitool`, assuming user slot 3 is free: + +```bash +ipmitool user list 1 +ipmitool user set name 3 monitor +ipmitool user set password 3 +ipmitool user enable 3 +# Allow IPMI over LAN for this user, with the User privilege (2) +ipmitool channel setaccess 1 3 link=on ipmi=on callin=on privilege=2 +``` + +> [!NOTE] +> IPMI passwords are at most **20 bytes** (16 on older BMCs), and the account must be enabled +> *and* allowed on the LAN channel (`ipmi=on`): an account that can log in to the web interface +> is not necessarily allowed to log in over IPMI. + +If the BMC enforces a maximum privilege per cipher suite (`Cipher Suite Priv Max` in +`ipmitool lan print 1`), make sure the suite the client uses allows at least `USER`. + +## Cipher suites + +An RMCP+ session uses a **cipher suite**: one authentication algorithm (for the RAKP handshake), +one integrity algorithm (signing the messages) and one confidentiality algorithm (encrypting +them). The client implements: + +| Suite | Authentication | Integrity | Confidentiality | Supported | +| --- | --- | --- | --- | --- | +| 0 | none | none | none | yes (avoid it) | +| 1 | RAKP-HMAC-SHA1 | none | none | yes | +| 2 | RAKP-HMAC-SHA1 | HMAC-SHA1-96 | none | yes | +| **3** | RAKP-HMAC-SHA1 | HMAC-SHA1-96 | AES-CBC-128 | **yes** | +| 4, 5 | RAKP-HMAC-SHA1 | HMAC-SHA1-96 | xRC4-128, xRC4-40 | no | +| 6 | RAKP-HMAC-MD5 | none | none | yes | +| 7 | RAKP-HMAC-MD5 | HMAC-MD5-128 | none | yes | +| 8 | RAKP-HMAC-MD5 | HMAC-MD5-128 | AES-CBC-128 | yes | +| 9, 10 | RAKP-HMAC-MD5 | HMAC-MD5-128 | xRC4-128, xRC4-40 | no | +| 11 – 14 | RAKP-HMAC-MD5 | MD5-128 | none, AES-CBC-128, xRC4 | no | +| 15 | RAKP-HMAC-SHA256 | none | none | yes | +| 16 | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | none | yes | +| **17** | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | AES-CBC-128 | **yes** | +| 18, 19 | RAKP-HMAC-SHA256 | HMAC-SHA256-128 | xRC4-128, xRC4-40 | no | + +Suites **3** and **17** sign and encrypt every message, and every current BMC offers at least +one of them. Prefer **17** where available. + +### How `IpmiClient` chooses the suite + +By default (`skipAuth` set to `false`), each call first asks the BMC for its list of cipher +suites (Get Channel Cipher Suites), then picks one **by its position in that list**: the 4th +suite if the BMC offers at least 4, otherwise the 3rd, the 2nd, or the only one. A BMC that +offers `3, 17` gets suite 17; a BMC that offers `0, 1, 2, 3, 17` gets suite 3. + +The suite is not checked against the table above. If the position rule lands on a suite the +client does not implement (an xRC4 or MD5-128 suite), the session cannot be opened. To control the +suite: + +* **restrict the suites offered by the BMC** — `ipmitool lan print 1` lists them + (`RMCP+ Cipher Suites`), and the web interface or `ipmitool lan set 1 cipher_privs` can disable + the ones you do not want; or +* set **`skipAuth` to `true`**: the client then skips the discovery and always uses + **suite 3** with the User privilege, which fails on a BMC that does not offer suite 3 + ([Configuration](configuration.html#skipauth)); or +* open the session yourself with the [low-level API](low-level-api.html#choosing-the-cipher-suite). + +## BMC key (Kg) + +Some BMCs can be configured with a **BMC key (Kg)** for *two-key* logins: the session keys are +then derived from this key instead of the user's password. When it is set, pass it as the +`bmcKey` of the [configuration](configuration.html#bmc-key) (the same key as `ipmitool -k` or +`-y`). Leave `bmcKey` to `null` otherwise: this is the default on virtually every BMC. + +## Firewall + +| From | To | Protocol / port | +| --- | --- | --- | +| The machine running the client (any local port) | The BMC | **UDP 623** (RMCP / RMCP+) | +| The BMC (port 623) | The machine running the client (the same local port) | UDP replies | + +A stateful firewall needs the outbound rule only. Each session binds its own ephemeral local UDP +port, so a stateless firewall must accept UDP replies from port 623 on the whole ephemeral range. + +## Checking access with `ipmitool` or `ipmiutil` + +Run the same request from the machine that will run the client, with the same account, privilege +level and cipher suite: if it fails there, the problem is on the BMC or in the network, not in +the client. + +```bash +# ipmitool: RMCP+ (lanplus), User privilege, cipher suite 17 +ipmitool -I lanplus -H bmc.example.com -U monitor -P 'the-password' -L USER -C 17 chassis status +ipmitool -I lanplus -H bmc.example.com -U monitor -P 'the-password' -L USER -C 17 sdr elist + +# ipmiutil: IPMI LAN 2.0 (-F lan2), cipher suite 17 (-J 17), User privilege (-V 2) +ipmiutil health -N bmc.example.com -U monitor -P 'the-password' -F lan2 -J 17 -V 2 +``` + +See [Troubleshooting](troubleshooting.html) for the usual failures. diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md new file mode 100644 index 0000000..26167f8 --- /dev/null +++ b/src/site/markdown/sensors.md @@ -0,0 +1,183 @@ +keywords: sensors, sdr, sensor data record, sensor reading, thresholds, temperature, voltage, fan, power, states, text output format, ipmiresultconverter +description: Read every sensor of a server through its BMC — how the SDR repository is walked, the Sensor object, readings and thresholds, discrete states, and the text output format of getFrusAndSensorsAsStringResult. + +# Sensors + + + +The BMC describes its sensors in the **Sensor Data Record (SDR) repository**: one record per +sensor, with its name, what it measures (the *entity*: a processor, a power supply, the system +board, ...), its unit, the formula that converts its raw reading, and its thresholds. The client +walks this repository and reads each sensor. + +```java +List sensors = IpmiClient.getSensors(config); +``` + +## How the sensors are read + +[`IpmiClient.getSensors()`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) opens a session +and: + +1. walks the SDR repository from the first record to the last with **Get SDR**, reading large + records in chunks when the BMC cannot return them at once, and reserving the repository + (**Reserve SDR Repository**) when the BMC requires it or cancels the reservation; +2. sends **Get Sensor Reading** for every **Full Sensor** and **Compact Sensor** record. + +Event-Only records (sensors without a reading), the locator and association records, and the +OEM records are part of the walk but are not returned. A record the library cannot decode is +logged and skipped; the walk goes on +([Supported Commands](supported-commands.html#sdr-records)). + +The walk reads only the sensors of the **BMC's** SDR repository, through the BMC itself: sensors +owned by satellite controllers that the BMC does not bridge, and the Device SDRs of other +controllers, are not read ([#84](https://github.com/metricshub/ipmi-java/issues/84), +[#105](https://github.com/metricshub/ipmi-java/issues/105)). + +## The `Sensor` object + +Each [`Sensor`](apidocs/org/metricshub/ipmi/client/model/Sensor.html) holds: + +| Method | Content | +| --- | --- | +| `getName()` | The sensor name of the SDR record (`CPU1 Temp`, `PSU2 Present`, ...) | +| `getEntityId()`, `getDeviceId()` | The entity the sensor belongs to: its type (`EntityId.Processor`, `EntityId.PowerSupply`, ...) and instance number | +| `isFull()`, `isCompact()` | Whether the record is a Full Sensor record (an analog sensor with a conversion formula and thresholds) or a Compact one (usually a discrete sensor) | +| `getRecord()` | The decoded record: a [`FullSensorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/FullSensorRecord.html) or a [`CompactSensorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/CompactSensorRecord.html), both [`AbstractSensorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.html) | +| `getData()` | The [`GetSensorReadingResponseData`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/GetSensorReadingResponseData.html), or `null` when the BMC has no reading for the sensor (completion code `DataNotPresent`) | +| `getStates()` | The asserted states, as `sensorName=state|sensorName=state...`, or an empty string | + +### Readings + +For a Full Sensor record, the reading is converted to the sensor's unit with the record's +linear formula (`M`, `B` and the exponents of IPMI 2.0, section 36.3): + +```java +for (Sensor sensor : IpmiClient.getSensors(config)) { + if (sensor.isFull() && sensor.getData() != null) { + FullSensorRecord record = (FullSensorRecord) sensor.getRecord(); + double value = sensor.getData().getSensorReading(record); + System.out.println(sensor.getName() + " = " + value + " " + record.getSensorBaseUnit() + + " (upper critical: " + record.getUpperCriticalThreshold() + ")"); + } +} +``` + +```text +Ambient Temp = 17.0 DegreesC (upper critical: 39.0) +System Power = 92.0 Watts (upper critical: 0.0) +Fan 1 = 6600.0 Rpm (upper critical: 0.0) +System 3.3V = 3.38 Volts (upper critical: 3.56) +``` + +`getSensorBaseUnit()` returns a +[`SensorUnit`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorUnit.html); +the six thresholds (`getLowerNonCriticalThreshold()` to `getUpperNonRecoverableThreshold()`) +are converted with the same formula, and are `0.0` when the BMC does not define them. + +> [!WARNING] +> Known limitations of the decoding, by the IPMI 2.0 specification +> (not all of them reproduced on real hardware): +> +> * a sensor whose reading is flagged *unavailable* or whose scanning is disabled is reported +> with a reading of `0.0` ([#110](https://github.com/metricshub/ipmi-java/issues/110)); +> * the non-linear conversions and the readability of each threshold are not fully handled +> ([#83](https://github.com/metricshub/ipmi-java/issues/83)); +> * the threshold status bits of the reading are mis-mapped +> ([#82](https://github.com/metricshub/ipmi-java/issues/82)); +> * a Compact record that describes several shared sensors is reported as a single sensor +> ([#100](https://github.com/metricshub/ipmi-java/issues/100)). + +### States + +Discrete sensors (presence, redundancy, power supply status, processor status, ...) report a set +of asserted **states** instead of a value. `getStates()` returns each asserted state with a +description, in the wording of `ipmiutil`, as `sensorName=state`, separated with `|`: + +```text +PSU Redundancy=Fully Redundant +PSU1 Present=Presence detected +CPU0_Status=Presence detected +``` + +The raw states are available as +`getData().getStatesAsserted(record.getSensorType(), record.getEventReadingType())`, a list of +[`ReadingType`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/ReadingType.html). +For OEM sensors (event/reading type `0x7F`), whose states the specification does not define, +the state is the raw reading: `sensorName=0xHHLL`. + +## Text output format + +[`IpmiClient.getFrusAndSensorsAsStringResult()`](apidocs/org/metricshub/ipmi/client/IpmiClient.html) +reads the FRUs, then the sensors (two sessions, see +[#102](https://github.com/metricshub/ipmi-java/issues/102)), and converts them with +[`IpmiResultConverter`](apidocs/org/metricshub/ipmi/client/IpmiResultConverter.html) into +semicolon-separated lines, the format that MetricsHub's IPMI connectors parse. The lines come in +three groups, in this order: + +```text +FRU;LENOVO;RD350;S4M00000 - 00000000000001 +FRU;LITEON;PS-2451-6L-LF;0000 +Power Unit;3;Power Unit 3;;;;PSU Redundancy=Fully Redundant +Power Supply;1;Power Supply 1;;;;PSU1 Present=Presence detected +Power Supply;2;Power Supply 2;;;;PSU2 Present=Presence detected +Temperature;0008;Ambient Temp;Air Inlet 1;17.0;37;39 +Temperature;0009;CPU1 DTS;Processor 1;-44.0;; +PowerConsumption;000d;System Power;Power Unit 2;92.0 +Fan;0014;Fan 1;Fan Device 1;6600.0;1600; +Voltage;0022;System 3.3V;System Board 1;3380.0;3040;3560 +``` + +### FRU lines + +`FRU;$vendor;$model;$serialNumber` — one per FRU, see +[FRU Inventory](fru-inventory.html#fru-lines-of-the-text-output). + +### Device state lines + +```text +$deviceType;$deviceId;$deviceUniqueId;$vendor;$model;$serialNumber;$states +``` + +One line per **entity** (device) that has at least one sensor with an asserted state: + +| Field | Content | +| --- | --- | +| `$deviceType` | The entity type, in the wording of `ipmiutil`: `System Board`, `Processor`, `Power Supply`, `Fan Device`, `Memory Device`, `Disk or Disk Bay`, ... | +| `$deviceId` | The entity instance number | +| `$deviceUniqueId` | `$deviceType $deviceId`, for example `Power Supply 1` | +| `$vendor`, `$model`, `$serialNumber` | From the FRU of the same entity type and instance, if any; empty otherwise | +| `$states` | The states of every sensor of the entity, `sensorName=state` separated with `|` | + +Sensors that report `Device Absent` are left out. + +### Reading lines + +One line per Full Sensor record with a reading, for the units below; sensors in other units are +not reported, and neither are sensors with no reading (raw value `0xFF`). + +```text +Temperature;$sensorId;$sensorName;$deviceUniqueId;$value;$threshold1;$threshold2 +Voltage;$sensorId;$sensorName;$deviceUniqueId;$value;$threshold1;$threshold2 +Fan;$sensorId;$sensorName;$deviceUniqueId;$value;$threshold1;$threshold2 +Current;$sensorId;$sensorName;$deviceUniqueId;$value +PowerConsumption;$sensorId;$sensorName;$deviceUniqueId;$value +Energy;$sensorId;$sensorName;$deviceUniqueId;$value +``` + +| Line | Unit of `$value` | `$threshold1` | `$threshold2` | +| --- | --- | --- | --- | +| Temperature | °C (°F and K are converted) | Upper non-critical | Upper critical, else upper non-recoverable | +| Voltage | **mV** | Lower non-critical, else lower critical, else lower non-recoverable | Upper non-critical, else upper critical, else upper non-recoverable | +| Fan | RPM | Lower critical, else lower non-recoverable | Lower non-critical | +| Current | A | | | +| PowerConsumption | W | | | +| Energy | J | | | + +* `$sensorId` is the SDR record ID, as 4 lowercase hexadecimal digits. +* `$deviceUniqueId` is the entity of the sensor, as in the device state lines. +* Thresholds are rounded to integers, in the same unit as `$value`, and empty when the BMC does + not define them. + +The chassis status has its own text form: +[`getChassisStatusAsStringResult()`](chassis-status.html#as-text). diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md new file mode 100644 index 0000000..b905caa --- /dev/null +++ b/src/site/markdown/serial-over-lan.md @@ -0,0 +1,113 @@ +keywords: serial over lan, sol, console, serial port, serialoverlan, payload, administrator, break +description: Open a Serial over LAN (SOL) console to a server's serial port through its BMC — sessions, cipher suite selection, reading and writing, serial port operations, events, and closing. + +# Serial over LAN + + + +**Serial over LAN (SOL)** redirects the server's serial port — the BIOS setup, the boot loader, +a Linux or Windows EMS serial console — to an IPMI session. The library implements it in +[`org.metricshub.ipmi.core.api.sol`](apidocs/org/metricshub/ipmi/core/api/sol/package-summary.html), +on top of the [low-level API](low-level-api.html). + +## Prerequisites + +* SOL is **enabled** on the BMC (`ipmitool sol info 1`, `ipmitool sol set enabled true 1`), and the + server's firmware or operating system writes to the serial port that the BMC redirects, at the + bit rate configured for SOL. +* The account has the **Administrator** privilege on the LAN channel, and the SOL payload is + enabled for it (`ipmitool sol payload enable 1 `). +* No other SOL session is active: a BMC usually supports a single SOL session at a time. + +## Opening a console + +```java +import java.nio.charset.StandardCharsets; + +import org.metricshub.ipmi.core.api.sol.SerialOverLan; +import org.metricshub.ipmi.core.api.sol.SpecificCipherSuiteSelector; +import org.metricshub.ipmi.core.api.sync.IpmiConnector; +import org.metricshub.ipmi.core.connection.Connection; + +IpmiConnector connector = new IpmiConnector(0); + +try (SerialOverLan sol = new SerialOverLan(connector, "bmc.example.com", "admin", "the-password", + new SpecificCipherSuiteSelector(Connection.getDefaultCipherSuite()))) { + + sol.writeString("\r\n", StandardCharsets.US_ASCII); // wake the console up + Thread.sleep(1000); + System.out.print(sol.readString(StandardCharsets.US_ASCII, 4096, 2000)); +} +``` + +This constructor opens a dedicated session with the **Administrator** privilege, activates the +SOL payload, and owns the session: **closing the `SerialOverLan` closes the session and tears +down the connector** passed to it. Use a new connector for each console opened this way. + +The cipher suite is chosen by a +[`CipherSuiteSelectionHandler`](apidocs/org/metricshub/ipmi/core/api/sol/CipherSuiteSelectionHandler.html), +which receives the suites the BMC offers and returns the one to use. +[`SpecificCipherSuiteSelector`](apidocs/org/metricshub/ipmi/core/api/sol/SpecificCipherSuiteSelector.html) +always returns the suite it was built with (suite 3 above); implement the interface to pick +one from the list, for example suite 17 when offered: + +```java +CipherSuiteSelectionHandler prefer17 = suites -> suites + .stream() + .filter(suite -> suite.getId() == 17) + .findFirst() + .orElse(Connection.getDefaultCipherSuite()); +``` + +| Constructor | Use | +| --- | --- | +| `SerialOverLan(connector, host, user, password, selector)` | New session on UDP port 623 | +| `SerialOverLan(connector, host, port, user, password, selector)` | New session on another port | +| `SerialOverLan(connector, session)` | Reuse a session opened with the low-level API; closing the console leaves it, and the connector, open. If the BMC serves SOL on another UDP port, the console uses an existing session on that port, or opens one (which closing the console closes, with the connector). | + +If the session's privilege is too low to activate the payload, the client raises it to +Administrator (Set Session Privilege Level) and tries again. The constructors throw `SOLException` when +the payload cannot be activated (SOL disabled, no free payload instance, privilege refused). + +## Reading and writing + +Writes block until the BMC acknowledges the data, and return `false` when it is rejected: + +| Method | Writes | +| --- | --- | +| `writeBytes(byte[])`, `writeByte(byte)` | Raw bytes | +| `writeString(String, Charset)` | A string, encoded with the given charset (`writeString(String)` uses the platform charset) | +| `writeIntArray(int[])`, `writeInt(int)` | Values from 0 to 255, as bytes | + +Received characters are buffered as they arrive. Reads take from this buffer: + +| Method | Behavior | +| --- | --- | +| `readBytes()`, `readString(Charset)` | Everything available now, possibly nothing | +| `readBytes(int count)`, `readString(Charset, int count)` | At most `count` bytes available now | +| `readBytes(int count, int timeoutMs)`, `readString(Charset, int count, int timeoutMs)` | Wait until `count` bytes are available or the timeout expires, then return what is available | +| `readIntArray(...)` | The same, as values from 0 to 255 | + +Without a `Charset`, `readString(...)` uses the platform charset. + +## Serial port operations and events + +`invokeOperations(SolOperation...)` acts on the remote serial port: + +| `SolOperation` | Effect | +| --- | --- | +| `Break` | Send a serial break (for example the Linux magic SysRq) | +| `FlushInbound`, `FlushOutbound` | Flush the BMC's buffers in either direction | +| `CTS` | De-assert CTS (Clear To Send) to the server's serial controller | +| `DCD_DSR` | De-assert DCD and DSR to the server's serial controller | +| `RingWOR` | Assert the Ring Indicator (wake on ring) | + +A [`SolEventListener`](apidocs/org/metricshub/ipmi/core/api/sol/SolEventListener.html) +registered with `registerEventListener()` receives the status the BMC reports +([`SolStatus`](apidocs/org/metricshub/ipmi/core/coding/payload/sol/SolStatus.html): +`CharacterTransferUnavailable`, `SolDeactivated`, `TransmitOverrun`, `Break`, `RtsAsserted`, +`DtrAsserted`), either spontaneously (`processRequestEvent`) or in the acknowledgement of a +message you sent (`processResponseEvent`). + +`SolDeactivated` means the BMC closed the console, for example because another user activated +SOL: open a new `SerialOverLan` to continue. diff --git a/src/site/markdown/supported-commands.md b/src/site/markdown/supported-commands.md new file mode 100644 index 0000000..4f4bbe5 --- /dev/null +++ b/src/site/markdown/supported-commands.md @@ -0,0 +1,118 @@ +keywords: supported commands, ipmi commands, cipher suites, sdr record types, oem records, fru records, multirecord, completion codes +description: Reference of the IPMI commands, cipher suites, SDR record types and FRU records the library implements, and how it handles OEM and unknown records. + +# Supported Commands + + + +The library implements **IPMI 2.0 over LAN** (RMCP+ sessions, IPMI 2.0 section 13) with the +commands below. Each command is a class of +[`org.metricshub.ipmi.core.coding.commands`](apidocs/org/metricshub/ipmi/core/coding/commands/package-summary.html), +sent with the [low-level API](low-level-api.html#sending-commands); the last column shows which +ones `IpmiClient` uses. A command not listed here can be added by +[extending `IpmiCommandCoder`](low-level-api.html#writing-your-own-command). + +## Commands + +| Command | Class | NetFn / Cmd | Used by `IpmiClient` | +| --- | --- | --- | --- | +| **Session** | | | | +| Get Channel Authentication Capabilities | `GetChannelAuthenticationCapabilities` | App / `38h` | every call, and the keep-alive | +| Get Channel Cipher Suites | `GetChannelCipherSuites` | App / `54h` | every call (unless `skipAuth`) | +| RMCP+ Open Session | `OpenSession` | (payload) | every call | +| RAKP Message 1 / 3 | `Rakp1`, `Rakp3` | (payload) | every call | +| Set Session Privilege Level | `SetSessionPrivilegeLevel` | App / `3Bh` | | +| Close Session | `CloseSession` | App / `3Ch` | every call | +| **Chassis** | | | | +| Get Chassis Status | `GetChassisStatus` | Chassis / `01h` | `getChassisStatus()` | +| Chassis Control | `ChassisControl` | Chassis / `02h` | | +| **SDR repository and sensors** | | | | +| Get SDR Repository Info | `GetSdrRepositoryInfo` | Storage / `20h` | | +| Reserve SDR Repository | `ReserveSdrRepository` | Storage / `22h` | `getSensors()`, `getFrus()` | +| Get SDR | `GetSdr` | Storage / `23h` | `getSensors()`, `getFrus()` | +| Get Sensor Reading | `GetSensorReading` | Sensor/Event / `2Dh` | `getSensors()` | +| **FRU** | | | | +| Get FRU Inventory Area Info | `GetFruInventoryAreaInfo` | Storage / `10h` | `getFrus()` | +| Read FRU Data | `ReadFruData` | Storage / `11h` | `getFrus()` | +| **System Event Log** | | | | +| Get SEL Info | `GetSelInfo` | Storage / `40h` | | +| Reserve SEL | `ReserveSel` | Storage / `42h` | | +| Get SEL Entry | `GetSelEntry` | Storage / `43h` | | +| **Payloads (Serial over LAN)** | | | | +| Activate Payload (SOL) | `ActivateSolPayload` | App / `48h` | | +| Deactivate Payload | `DeactivatePayload` | App / `49h` | | +| Get Payload Activation Status | `GetPayloadActivationStatus` | App / `4Ah` | | +| Get Channel Payload Support | `GetChannelPayloadSupport` | App / `4Eh` | | + +The `IpmiClient` calls need nothing above the **User** privilege; Chassis Control needs +**Operator**, the payload commands and Serial over LAN **Administrator**. + +## Cipher suites + +| Algorithm | Implemented | +| --- | --- | +| Authentication | RAKP-none, RAKP-HMAC-SHA1, RAKP-HMAC-MD5, RAKP-HMAC-SHA256 | +| Integrity | none, HMAC-SHA1-96, HMAC-MD5-128, HMAC-SHA256-128 — **not** MD5-128 | +| Confidentiality | none, AES-CBC-128 — **not** xRC4-128 or xRC4-40 | + +Hence the cipher suites 0 to 3, 6 to 8, and 15 to 17; see the +[full table](preparing-the-bmc.html#cipher-suites). MD5-128 and xRC4 are tracked in +[#106](https://github.com/metricshub/ipmi-java/issues/106). + +## SDR records + +[`SensorRecord.populateSensorRecord()`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorRecord.html) +decodes the records of the SDR repository (IPMI 2.0, section 43) into these classes: + +| Type | Record | Class | Returned by `getSensors()` | +| --- | --- | --- | --- | +| `01h` | Full Sensor | `FullSensorRecord` | yes, with its reading | +| `02h` | Compact Sensor | `CompactSensorRecord` | yes, with its reading | +| `03h` | Event-Only | `EventOnlyRecord` | no | +| `08h` | Entity Association | `EntityAssociationRecord` | no | +| `09h` | Device-relative Entity Association | `DeviceRelativeEntityAssiciationRecord` | no | +| `10h` | Generic Device Locator | `GenericDeviceLocatorRecord` | no | +| `11h` | FRU Device Locator | `FruDeviceLocatorRecord` | no (drives `getFrus()`) | +| `12h` | Management Controller Device Locator | `ManagementControllerDeviceLocatorRecord` | no | +| `13h` | Management Controller Confirmation | `ManagementControllerConfirmationRecord` | no | +| `C0h` – `FFh` | OEM | `OemRecord` | no | + +Full, Compact and Event-Only records share +[`AbstractSensorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.html) +(owner, entity, sensor type, event/reading type, units, name...). + +### OEM and unknown records + +IPMI 2.0 reserves the record types `C0h` to `FFh` for OEM use, and vendors do use them: a +GIGABYTE BMC tested with this library returns 19 records of type `D0h`, a Lenovo IMM one of type +`C0h`. They are all decoded as an +[`OemRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/OemRecord.html), +which keeps the manufacturer ID and the whole vendor-defined payload as raw bytes. + +A record that cannot be decoded at all — a reserved type such as the deprecated BMC Message +Channel Info record (`14h`), a record shorter than its header, an empty or truncated reply — is +**skipped**: the client logs it at the `WARN` level and goes on with the next record, so one +unexpected record never costs the whole sensor list. When a BMC answers a whole-record Get SDR +with fewer bytes than the record declares, the client reads the record again in chunks. + +## FRU records + +[`ReadFruData.decodeFruData()`](apidocs/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.html) +decodes the FRU information (Platform Management FRU Information Storage Definition v1.0): + +| Area | Class | +| --- | --- | +| Chassis Info | `ChassisInfo` | +| Board Info | `BoardInfo` | +| Product Info | `ProductInfo` | +| MultiRecord: Power Supply Information | `PowerSupplyInfo` | +| MultiRecord: DC Output, DC Load | `DcOutputInfo`, `DcLoadInfo` | +| MultiRecord: Management Access | `ManagementAccessInfo` | +| MultiRecord: Base / Extended Compatibility | `BaseCompatibilityInfo`, `ExtendedCompatibilityInfo` | +| MultiRecord: OEM | `OemInfo` | + +`IpmiClient.getFrus()` returns the Chassis, Board and Product areas only. FRUs in another format, +such as the SPD data of memory modules, are not decoded +([#107](https://github.com/metricshub/ipmi-java/issues/107)), and are logged and left out. +Known decoding issues of the FRU areas are listed in +[#85](https://github.com/metricshub/ipmi-java/issues/85). diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md new file mode 100644 index 0000000..6c898a1 --- /dev/null +++ b/src/site/markdown/timeouts-and-errors.md @@ -0,0 +1,130 @@ +keywords: timeout, per-message timeout, retries, lost udp reply, timeoutexception, executionexception, connectionexception, ipmiexception, completion code, connection.properties +description: The overall and per-message timeouts, what happens when the BMC drops a UDP reply, the retries, and the exceptions thrown by IpmiClient. + +# Timeouts and Errors + + + +IPMI over LAN runs over **UDP**: a request or a reply can be lost, and nothing but a timeout +tells the client. BMCs do drop replies, especially when several sessions query them at the same +time. Two timeouts apply: + +| Timeout | Set with | Default | Scope | +| --- | --- | --- | --- | +| [Overall timeout](#overall-timeout) | `IpmiClientConfiguration.timeout` (seconds) | none, required | One `IpmiClient` call, from the first packet to the closed session | +| [Per-message timeout](#per-message-timeout-and-retries) | `IpmiConnector.setTimeout(handle, ms)`, or the `timeout` of [`connection.properties`](#library-wide-defaults) | 300 000 ms | Each request, including each step of the session handshake | + +## Overall timeout + +Each `IpmiClient` method runs its whole exchange — open the session, send the commands, close +the session — in a worker thread, and waits for it at most `timeout` seconds. When the deadline +expires, the worker is interrupted and the method throws `java.util.concurrent.TimeoutException`, +with nothing collected: there are no partial results. + +> [!WARNING] +> Interrupting the worker does not always stop it +> ([#79](https://github.com/metricshub/ipmi-java/issues/79)): a worker waiting for a reply may +> keep waiting, and the library's receiving and timer threads are not daemon threads. The calling +> thread gets its `TimeoutException` on time, but these threads can keep a short-lived JVM alive: +> end command-line programs with `System.exit()`. + +## Per-message timeout and retries + +Below the overall timeout, each message has its own timeout and is retried: + +1. The request is sent, and the client waits for the reply up to the **per-message timeout**. +2. Without a reply, the request is sent again, after a random pause of up to `idleTime` + (4 000 ms), up to `retries` (3) times. +3. When every try failed, the call fails with a `ConnectionException`: `Command timed out` during + the session handshake, `Message timed out` in the session. + +BMC replies with a *transient* completion code — node busy, out of resources, initialization in +progress, timeout — are retried the same way. Any other error completion code fails at once. + +> [!IMPORTANT] +> The per-message timeout is **5 minutes** by default, longer than any reasonable overall +> timeout, and `IpmiClientConfiguration` does not expose it +> ([#77](https://github.com/metricshub/ipmi-java/issues/77), +> [#101](https://github.com/metricshub/ipmi-java/issues/101)). With the defaults, **a single lost +> reply makes the whole call wait for the overall timeout** and throw `TimeoutException`. + +To recover from lost replies within the overall timeout, lower the per-message timeout to a few +seconds: + +* with the [low-level API](low-level-api.html#timeouts), call `setTimeout(handle, ms)` on the + connector right after `createConnection()`; +* with `IpmiClient`, change the [library-wide default](#library-wide-defaults) before the first + call. + +Two known defects limit what the retries achieve: a retried in-session message does not wait +for the reply to the resent request +([#78](https://github.com/metricshub/ipmi-java/issues/78)), and each handshake step waits longer +than its timeout because it counts its 1 ms sleeps rather than the elapsed time +([#79](https://github.com/metricshub/ipmi-java/issues/79)). A short per-message timeout still +turns a lost reply into a retry (or a fast failure) instead of a stall. + +## Library-wide defaults + +The defaults come from two properties files packaged in the jar, read through the +`org.metricshub.ipmi.core.common.PropertiesManager` singleton: + +| Property | Default | Meaning | Read | +| --- | --- | --- | --- | +| `timeout` | `300000` | Per-message timeout, in ms | When each connection is created | +| `retries` | `3` | How many times a failed message is sent again | When each `IpmiConnector` is created | +| `idleTime` | `4000` | Upper bound of the random pause before a retry, in ms | When each `IpmiConnector` is created | +| `pingPeriod` | `30000` | Keep-alive period, in ms, when the configuration's `pingPeriod` is `-1` | When each `IpmiConnector` is created | + +Override them at application startup, from a single thread, before the first IPMI call: the +values then apply to every connection created afterwards, in the whole JVM. + +```java +import org.metricshub.ipmi.core.common.PropertiesManager; + +PropertiesManager properties = PropertiesManager.getInstance(); +properties.setProperty("timeout", "5000"); // per-message timeout: 5 s instead of 5 min +properties.setProperty("retries", "3"); +``` + +`PropertiesManager` logs every lookup at the `INFO` level and its lazy initialization is not +synchronized ([#98](https://github.com/metricshub/ipmi-java/issues/98)), hence "from a single +thread, at startup". + +## Exceptions + +The `IpmiClient` methods declare three checked exceptions: + +| Exception | When | +| --- | --- | +| `TimeoutException` | The [overall timeout](#overall-timeout) expired. Also the usual symptom of a wrong host, a closed UDP port, IPMI over LAN disabled, or a lost reply with the default per-message timeout. | +| `ExecutionException` | The exchange failed. `getCause()` holds the actual exception (see below). | +| `InterruptedException` | The calling thread was interrupted while waiting. | + +Common causes wrapped in the `ExecutionException`: + +| Cause | Meaning | +| --- | --- | +| `ConnectionException: Illegal connection state: Rakp1Waiting` | The RAKP handshake failed: wrong user name or password, account not allowed over LAN or at the User level. The `ERROR` log shows the actual reason (`Authentication check failed`, ...), see [#109](https://github.com/metricshub/ipmi-java/issues/109). | +| `ConnectionException: Command timed out` / `Message timed out` | No reply after all the tries of a message (with a [shortened](#per-message-timeout-and-retries) per-message timeout). | +| `IPMIException` | The BMC answered with an error completion code. `getCompletionCode()` returns it, for example `InsufficientPrivilege` (`0xD4`). | +| `IllegalArgumentException: ... is not yet implemented.` | The chosen cipher suite uses an algorithm the client does not implement (xRC4, MD5-128). See [cipher suites](preparing-the-bmc.html#cipher-suites). | +| `Exception: Cannot get the available cipher suites.` | The BMC returned an empty cipher suite list. | +| `UnknownHostException` | The host name cannot be resolved. | + +[Troubleshooting](troubleshooting.html) maps these symptoms to their usual fixes. + +## Errors that do not fail the call + +Some problems are logged at the `WARN` level and the call goes on with what it could collect: + +* an **SDR record** that cannot be decoded — an OEM record type, a malformed record, an empty + reply — is skipped and the repository walk continues with the next record + ([Supported Commands](supported-commands.html#sdr-records)); +* a **FRU** that cannot be read (for example a FRU device that is not present) is reported + truncated or not at all ([FRU Inventory](fru-inventory.html)); +* a **sensor** whose reading is not available (completion code `DataNotPresent`) is returned + without reading data. + +Unknown values in a record (an entity ID, a sensor type or a unit the library does not know) are +logged at the `ERROR` level as `Invalid value: ...` and replaced with a default (`Other` for an +entity ID); the record is still decoded. diff --git a/src/site/markdown/troubleshooting.md b/src/site/markdown/troubleshooting.md new file mode 100644 index 0000000..527feae --- /dev/null +++ b/src/site/markdown/troubleshooting.md @@ -0,0 +1,121 @@ +keywords: troubleshooting, timeout, illegal connection state, rakp1waiting, authentication check failed, insufficient privilege, 0xd4, ipmitool, ipmiutil, debug +description: Diagnose the usual failures of the IPMI Java Client — timeouts, authentication errors, insufficient privilege, missing sensors or FRUs, a JVM that does not exit — and compare with ipmitool and ipmiutil. + +# Troubleshooting + + + +## First steps + +1. **Check the BMC from the same machine with another tool**, with the same account, privilege + level and cipher suite (see [below](#equivalent-ipmitool-and-ipmiutil-commands)). If + `ipmitool` or `ipmiutil` fails too, the problem is in the BMC configuration or the network: + see [Preparing the BMC](preparing-the-bmc.html). +2. **Set the `org.metricshub.ipmi` logger to `DEBUG`** ([Logging](installation.html#logging)): + the messages sent, the retries, the records skipped and the handshake errors are logged. +3. **Start with `getChassisStatus()`**: it is a single command, so it tests the network, the + credentials and the cipher suite in a few hundred milliseconds. + +## `TimeoutException`, nothing collected + +The BMC did not answer in time. With the default settings, this is the symptom of every network +or configuration problem, because the 5-minute per-message timeout is longer than the overall +timeout ([Timeouts and Errors](timeouts-and-errors.html)). + +| Cause | Check | +| --- | --- | +| Wrong address: the server's operating system instead of its BMC | The BMC has its own IP address (`ipmitool lan print 1` on the server). | +| IPMI over LAN disabled on the BMC | [Enabling IPMI over LAN](preparing-the-bmc.html#enabling-ipmi-over-lan) | +| UDP port 623 filtered | [Firewall](preparing-the-bmc.html#firewall) | +| An IPMI 1.5-only BMC | Such BMCs never answer the RMCP+ Open Session request ([#91](https://github.com/metricshub/ipmi-java/issues/91)). | +| A lost UDP reply | Run the call again. With a [shorter per-message timeout](timeouts-and-errors.html#library-wide-defaults), lost replies are retried instead. | +| Several sessions to the same BMC at the same time | BMCs drop replies under concurrent sessions: query each BMC [from one thread at a time](configuration.html#thread-safety). | +| A large SDR repository or many FRUs on a slow BMC | Raise the [timeout](configuration.html#timeout): 120 s is a safe value. | + +When the timeout expires, the interrupted session logs an `ERROR` with an `InterruptedException` +(`sleep interrupted`): its stack trace shows the step that was waiting. A wait in +`getAvailableCipherSuites` means the BMC never answered the very first request: the address, the +port or the firewall is wrong, or IPMI over LAN is disabled. + +## `Illegal connection state: Rakp1Waiting` + +The `ExecutionException` wraps +`ConnectionException: Illegal connection state: Rakp1Waiting`: the RAKP handshake (the login) +failed, and the session could not be opened. The actual reason is logged just before, at the +`ERROR` level ([#109](https://github.com/metricshub/ipmi-java/issues/109)): + +| Logged | Cause | +| --- | --- | +| `IllegalArgumentException: Authentication check failed` | The BMC's proof does not match the password: **wrong password** (or wrong [BMC key](configuration.html#bmc-key)). | +| `IPMIException: Unauthorized name.` | **Unknown user**, or a user not allowed to log in over the LAN channel. | +| Another `IPMIException` (`Invalid role.`, ...) | The account is not allowed the User privilege level, or the BMC refused the cipher suite. | + +Check the account with `ipmitool -I lanplus ... -L USER chassis status`: `ipmitool` reports +`RAKP 2 HMAC is invalid` for a wrong password and `unauthorized name` for an unknown user. + +## `IPMIException: Insufficient privilege level` (`0xD4`) + +The BMC refused a command at the session's privilege level. `IpmiClient` always uses the +**User** level, which the specification allows for every command it sends; if a BMC refuses +one, check: + +* the privilege of the account on the LAN channel (`ipmitool channel getaccess 1 `), +* the maximum privilege of the cipher suite in use (`Cipher Suite Priv Max` in + `ipmitool lan print 1`). + +With the [low-level API](low-level-api.html#privilege-level), open the session with the level the +command needs (Operator for Chassis Control, Administrator for configuration commands). + +## `... is not yet implemented.` + +`IllegalArgumentException: Confidentiality algorithm XRC4-128 is not yet implemented.` (or +MD5-128 integrity): the cipher suite chosen for the session uses an algorithm the client does not +implement. See [How `IpmiClient` chooses the suite](preparing-the-bmc.html#how-ipmiclient-chooses-the-suite) +to make it use suite 3 or 17. + +## Sensors or FRUs are missing + +| Symptom | Cause | +| --- | --- | +| `WARN Skipping SDR record ...` | A record that cannot be decoded is skipped; the other sensors are still returned. [SDR records](supported-commands.html#sdr-records) lists what is decoded. | +| `WARN Failed to read FRU at offset ... Requested Sensor, data, or record not present` | The FRU is declared in the SDR repository but not present, for example an empty power supply bay. Usually harmless. | +| `WARN Failed to decode FRU ` | The FRU data is not in the IPMI FRU format (for example the SPD data of a memory module, [#107](https://github.com/metricshub/ipmi-java/issues/107)). | +| A sensor known to `ipmitool` is not returned | Only Full and Compact sensor records of the BMC's own repository are read: sensors behind satellite controllers are not ([#84](https://github.com/metricshub/ipmi-java/issues/84)), and shared Compact records are not expanded ([#100](https://github.com/metricshub/ipmi-java/issues/100)). | +| A sensor reads `0.0` | The BMC flags the reading as unavailable, which is not checked yet ([#110](https://github.com/metricshub/ipmi-java/issues/110)). | +| Negative processor temperatures (`CPU1 DTS = -44.0`) | Not an error: Intel *Digital Thermal Sensor* readings are the margin below the maximum junction temperature. | + +## The JVM does not exit + +After a `TimeoutException`, some threads of the library may still run, and they are not daemon +threads ([#79](https://github.com/metricshub/ipmi-java/issues/79)). End command-line programs and +test harnesses with `System.exit()`. + +## Collecting is slow + +* **FRUs**: each FRU is read 16 bytes at a time, one round trip per chunk: a few seconds per FRU + on some BMCs ([#102](https://github.com/metricshub/ipmi-java/issues/102)). +* **`getFrusAndSensorsAsStringResult()`** opens two sessions and walks the SDR repository twice + ([#102](https://github.com/metricshub/ipmi-java/issues/102)). +* **Lost replies** stall a call until the per-message timeout: shorten it + ([Timeouts and Errors](timeouts-and-errors.html#library-wide-defaults)). + +## Equivalent `ipmitool` and `ipmiutil` commands + +With `ipmitool`, add `-I lanplus -H -U -P -L USER -C `; with +`ipmiutil`, add `-N -U -P -F lan2 -J -V 2`. + +| Library | `ipmitool` | `ipmiutil` | +| --- | --- | --- | +| `IpmiClient.getChassisStatus()` | `chassis status` | `health` | +| `IpmiClient.getFrus()` | `fru print` | `fru` | +| `IpmiClient.getSensors()` | `sdr elist` | `sensor` | +| Cipher suites offered by the BMC | `channel getciphers ipmi` | | +| Get SEL Info / Get SEL Entry | `sel info`, `sel elist` | `sel` | + +The device types and state descriptions of the [text output](sensors.html#text-output-format) +follow the wording of `ipmiutil`. + +> [!NOTE] +> `ipmiutil sensor` reads the sensors with Get Device SDR, which some BMCs refuse (`0xD4`) at any +> privilege level, while this library reads the SDR repository with Get SDR. A failure of +> `ipmiutil sensor` alone does not mean the library will fail. diff --git a/src/site/markdown/upgrading.md b/src/site/markdown/upgrading.md new file mode 100644 index 0000000..6fc0071 --- /dev/null +++ b/src/site/markdown/upgrading.md @@ -0,0 +1,101 @@ +keywords: upgrade, migration, release notes, breaking changes, protected fields, accessors, abstractsensorrecord, validateresponse, org.sentrysoftware +description: What changes when upgrading the IPMI Java Client — from 1.2.02, from 1.2.01, and from the org.sentrysoftware:ipmi artifact of 1.2.00 and earlier. + +# Upgrading + + + +## Upgrading from 1.2.02 + +The `IpmiClient` API is unchanged, and the client is more tolerant of real-world BMCs: + +* the SDR repository walk no longer aborts on a record type the library does not model: OEM + records (`C0h` – `FFh`) are decoded as `OemRecord`, and any record that cannot be decoded is + logged and skipped, where 1.2.02 returned no sensors and no FRUs at all + ([OEM and unknown records](supported-commands.html#oem-and-unknown-records)); +* a BMC that answers a whole-record Get SDR with a truncated record is read again in chunks. + +Code that **extends** the library's protocol classes needs the changes below. + +### Protected fields + +The `protected` fields of the protocol classes are now `private`. Replace direct access to them +with the new `protected` accessors: + +| Class | Former field | Accessor | +| --- | --- | --- | +| `AbstractIpmiRunner` | `ipmiConfiguration` | `getIpmiConfiguration()` | +| `AbstractIpmiRunner` | `connector` | `getConnector()` | +| `AbstractIpmiRunner` | `handle` | `getHandle()` | +| `AbstractIpmiRunner` | `nextRecId` | `getNextRecId()`, `setNextRecId(int)` | +| `MessageHandler` | `messageQueue` | `getMessageQueue()` | +| `MessageHandler` | `connection` | `getConnection()` | +| `MessageHandler` | `lastReceivedSequenceNumber` | `getLastReceivedSequenceNumber()`, `setLastReceivedSequenceNumber(int)` | +| `IpmiLanMessage` | `networkFunction` | `getNetworkFunctionCode()`, `setNetworkFunctionCode(byte)` | +| `ConfidentialityAlgorithm` | `sik` | `getSik()` | +| `IntegrityAlgorithm` | `sik` | `getSik()`, `setSik(byte[])` | + +`IpmiClient`, `IpmiResultConverter`, `Utils`, `DeviceDescription`, `ReadingTypeDescription` and +`MessageComposer` are now `final` (they only had private constructors, so they could not be +subclassed anyway). + +### Sensor records + +`FullSensorRecord`, `CompactSensorRecord` and `EventOnlyRecord` now extend the new +[`AbstractSensorRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/AbstractSensorRecord.html) +(itself a `SensorRecord`), which holds the fields the three record types share: sensor owner and +number, entity, sensor type, event/reading type, direction, name (ID string), capabilities, units +and record sharing. Their getters and setters keep the same signatures, so existing code compiles +unchanged, and code that handles several record types can use `AbstractSensorRecord` instead of +testing each type: + +```java +if (record instanceof AbstractSensorRecord) { + AbstractSensorRecord sensor = (AbstractSensorRecord) record; + System.out.println(sensor.getName() + ": " + sensor.getSensorType()); +} +``` + +A record type now also inherits the getters of fields it does not define, which return defaults: + +| Record | Field | Value | +| --- | --- | --- | +| `EventOnlyRecord` (no reading) | `getRateUnit()`, `getModifierUnitUsage()`, `getSensorBaseUnit()`, `getSensorModifierUnit()` | `null` | +| `EventOnlyRecord` (no reading) | `isHysteresisReadable()`, `isThresholdsReadable()` | `false` | +| `FullSensorRecord` (a single sensor) | `getShareCount()`, `getIdInstanceModifierOffset()` | `0` | +| `FullSensorRecord` (a single sensor) | `getIdInstanceModifierType()` | `null` | +| `FullSensorRecord` (a single sensor) | `isEntityInstanceIncrements()` | `false` | + +### Command responses + +Commands that extend +[`IpmiCommandCoder`](apidocs/org/metricshub/ipmi/core/coding/commands/IpmiCommandCoder.html) can +call the new `protected` method `validateResponse(IpmiMessage)`, which checks that a message is a +successful response to the command and returns its data: it throws `IllegalArgumentException` +for a response to another command or a payload that is not an IPMI LAN response, and +`IPMIException` for a completion code other than `Ok`. The message of the +`IllegalArgumentException` now names the command class (three commands used to name the wrong +command). See [Writing your own command](low-level-api.html#writing-your-own-command). + +## Upgrading from 1.2.01 + +Version 1.2.02 added the UDP port of the BMC to `IpmiClientConfiguration` (a constructor with a +`port` argument, and `setPort(int)`), 623 by default. No change is needed. + +## Upgrading from 1.2.00 and earlier + +Version 1.2.01 moved the project from Sentry Software to MetricsHub. The API is the same, under +new Maven coordinates and package names: + +| | 1.2.00 and earlier | 1.2.01 and later | +| --- | --- | --- | +| Maven coordinates | `org.sentrysoftware:ipmi` | `${project.groupId}:${project.artifactId}` | +| Client packages | `org.sentrysoftware.ipmi.client` | `org.metricshub.ipmi.client` | +| Protocol packages | `org.sentrysoftware.ipmi.core` | `org.metricshub.ipmi.core` | + +Replace the dependency, then the package prefix in the imports (`org.sentrysoftware.ipmi.` → +`org.metricshub.ipmi.`). + +Version 1.2.00 had added the RAKP-HMAC-SHA256 and RAKP-HMAC-MD5 authentication algorithms and +their integrity algorithms (cipher suites 6 to 8 and 15 to 17); 1.1.00 fixed a busy loop of the +connection thread. diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css index e85911b..7ac2751 100644 --- a/src/site/resources/css/site.css +++ b/src/site/resources/css/site.css @@ -64,7 +64,7 @@ --heading-font: "Poppins", sans-serif; --content-font: "Poppins", sans-serif; - --content-font-size: medium; + --content-font-size: 15px; --banner-font-size: 40px; --banner-font-weight: 800; @@ -83,4 +83,10 @@ body.dark { --link-color: #7cb6ff; --main-bgcolor: #262626; --main-fgcolor: #e9ecef; -} \ No newline at end of file +} +/* The skin inverts the alternate colors for IMPORTANT callouts in dark mode, which turns our white-on-blue + palette into a white box with light text */ +body.dark .callout.callout-important { + --callout-bg: var(--main-bgcolor); + --callout-fg: var(--link-color); +} diff --git a/src/site/resources/images/metricshub-logo.png b/src/site/resources/images/metricshub-logo-only.png similarity index 100% rename from src/site/resources/images/metricshub-logo.png rename to src/site/resources/images/metricshub-logo-only.png diff --git a/src/site/site.xml b/src/site/site.xml index dbea058..9be338e 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -1,35 +1,77 @@ - - + + org.sentrysoftware.maven sentry-maven-skin - 6.4.01 + 8.0.01 true - verax, vxipmi, ipmi + ipmi, bmc, rmcp+, sensors, fru, sdr, chassis, serial over lan, java, hardware monitoring + + + IPMI Java Client on GitHub + https://github.com/metricshub/ipmi-java + fa-brands fa-github + + + + + Issue Tracker + https://github.com/metricshub/ipmi-java/issues + + + Licenses + licenses.html + + + Releases + https://github.com/metricshub/ipmi-java/releases + + - - images/metricshub-logo.png - https://metricshub.org + + MetricsHub - + - + + + - - - + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + From bc8f017211600f944417dc314e381617e06feea4 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 11:01:08 +0200 Subject: [PATCH 02/13] Address the Codex review of the documentation (#113) - Low-Level API: the example falls back to suite 3 instead of the BMC's last suite, which can be an xRC4 or MD5-128 suite the library does not implement (verified on a Lenovo IMM: picks 17, opens the session). - FRU Inventory, Timeouts and Errors: a FRU read error does not fail the call, except Get FRU Inventory Area Info on FRU 0, which propagates. - Serial over LAN: warn about the data loss of writes longer than two packets until #123 is fixed. - System.exit(0), not System.exit(). Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/fru-inventory.md | 5 ++++- src/site/markdown/low-level-api.md | 16 +++++++++------- src/site/markdown/serial-over-lan.md | 10 +++++++++- src/site/markdown/timeouts-and-errors.md | 5 +++-- src/site/markdown/troubleshooting.md | 2 +- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index a0e59bf..01c48aa 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -30,7 +30,10 @@ but makes large FRUs slow to read: a few seconds per FRU on some BMCs ([#102](https://github.com/metricshub/ipmi-java/issues/102)). A FRU that cannot be read — not present, or answering with an error at some offset — is logged at -the `WARN` level and reported truncated, or not at all; it never fails the whole call. Physical +the `WARN` level and reported truncated, or not at all. The exception is **FRU 0**: if the BMC +rejects Get FRU Inventory Area Info for it, `getFrus()` (and therefore +`getFrusAndSensorsAsStringResult()`) fails with that error. As with any call, a request that gets +no reply also fails the call ([Timeouts and Errors](timeouts-and-errors.html)). Physical FRU devices (EEPROMs on a private I²C bus, read with Master Write-Read) are not read. ## The `Fru` object diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index a56db21..622794a 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -21,6 +21,7 @@ which `IpmiClient` itself uses: ```java import java.net.InetAddress; +import java.util.Comparator; import java.util.List; import org.metricshub.ipmi.core.api.async.ConnectionHandle; @@ -44,13 +45,13 @@ public class LowLevelExample { // Wait at most 5 s for each reply instead of 5 min connector.setTimeout(handle, 5000); - // Pick a cipher suite among those the BMC offers: 17 if available + // Pick a cipher suite among those the BMC offers: 17 if available, else 3 List suites = connector.getAvailableCipherSuites(handle); CipherSuite cipherSuite = suites .stream() - .filter(suite -> suite.getId() == 17) - .findFirst() - .orElse(suites.get(suites.size() - 1)); + .filter(suite -> suite.getId() == 17 || suite.getId() == 3) + .max(Comparator.comparingInt(CipherSuite::getId)) + .orElseThrow(() -> new IllegalStateException("The BMC offers neither cipher suite 17 nor 3")); // Declare the cipher suite and the privilege level, then log in (RAKP handshake) connector.getChannelAuthenticationCapabilities(handle, cipherSuite, PrivilegeLevel.User); @@ -94,9 +95,10 @@ port (or always pass `0`), and call `tearDown()` when you are done with it. ### Choosing the cipher suite -`getAvailableCipherSuites()` returns the suites the BMC offers, in the BMC's order. Choose one -the library implements: **3** or **17** in practice (see the -[table](preparing-the-bmc.html#cipher-suites)). To skip the discovery when you already know the +`getAvailableCipherSuites()` returns the suites the BMC offers, in the BMC's order, including +suites the library does not implement. Choose one it implements: **3** or **17** in practice (see +the [table](preparing-the-bmc.html#cipher-suites)); a suite with an xRC4 or MD5-128 algorithm +fails when the session is opened. To skip the discovery when you already know the suite, build it and pass it to `createConnection()`: ```java diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index b905caa..d3682ef 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -71,7 +71,15 @@ the payload cannot be activated (SOL disabled, no free payload instance, privile ## Reading and writing -Writes block until the BMC acknowledges the data, and return `false` when it is rejected: +Writes block until the BMC acknowledges the data, and return `false` when it is rejected. +Data longer than the BMC's SOL payload size (announced when the payload is activated) is sent in +several packets. + +> [!WARNING] +> Until [#123](https://github.com/metricshub/ipmi-java/issues/123) is fixed, a write that needs +> **more than two packets** loses the data of its second packet, or throws +> `IllegalArgumentException` beyond three packets. This affects `writeBytes()`, `writeString()` +> and `writeIntArray()`: send long data in several short writes. | Method | Writes | | --- | --- | diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index 6c898a1..c18508d 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -26,7 +26,7 @@ with nothing collected: there are no partial results. > ([#79](https://github.com/metricshub/ipmi-java/issues/79)): a worker waiting for a reply may > keep waiting, and the library's receiving and timer threads are not daemon threads. The calling > thread gets its `TimeoutException` on time, but these threads can keep a short-lived JVM alive: -> end command-line programs with `System.exit()`. +> end command-line programs with `System.exit(0)`. ## Per-message timeout and retries @@ -121,7 +121,8 @@ Some problems are logged at the `WARN` level and the call goes on with what it c reply — is skipped and the repository walk continues with the next record ([Supported Commands](supported-commands.html#sdr-records)); * a **FRU** that cannot be read (for example a FRU device that is not present) is reported - truncated or not at all ([FRU Inventory](fru-inventory.html)); + truncated or not at all — except the built-in FRU 0, whose inventory information must be + readable ([FRU Inventory](fru-inventory.html#how-the-frus-are-read)); * a **sensor** whose reading is not available (completion code `DataNotPresent`) is returned without reading data. diff --git a/src/site/markdown/troubleshooting.md b/src/site/markdown/troubleshooting.md index 527feae..d9e05e8 100644 --- a/src/site/markdown/troubleshooting.md +++ b/src/site/markdown/troubleshooting.md @@ -88,7 +88,7 @@ to make it use suite 3 or 17. After a `TimeoutException`, some threads of the library may still run, and they are not daemon threads ([#79](https://github.com/metricshub/ipmi-java/issues/79)). End command-line programs and -test harnesses with `System.exit()`. +test harnesses with `System.exit(0)`. ## Collecting is slow From 0fb47dcb43e813199988d2e33242d68f191c91dd Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 11:16:07 +0200 Subject: [PATCH 03/13] Address the second Codex review of the documentation (#113) - Serial over LAN: the example selects 17 or 3 from the suites the BMC offers (SpecificCipherSuiteSelector ignores them), and tears down the connector in a finally block, since a failed constructor closes nothing (a second tearDown() is harmless, checked). - Low-Level API: skip the SEL walk on an empty SEL, and warn that OEM SEL entries are decoded with the system event layout, types C0h and E0h throwing IllegalArgumentException. - Supported Commands: OemRecord has a manufacturer ID for type C0h only. - Overview, Sensors, README: the client reads the Full and Compact sensor records, not every sensor. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- src/site/markdown/index.md | 9 ++--- src/site/markdown/low-level-api.md | 36 ++++++++++++------- src/site/markdown/sensors.md | 4 +-- src/site/markdown/serial-over-lan.md | 46 ++++++++++++++----------- src/site/markdown/supported-commands.md | 6 ++-- 6 files changed, 60 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index fa10627..303cbe3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This project is a fork of the excellent [IPMI Library for Java by Verax Systems] See **[Project Documentation](https://metricshub.org/ipmi-java)** and the [Javadoc](https://metricshub.org/ipmi-java/apidocs) for more information on how to use this library in your code. -The IPMI Java Client talks to the Baseboard Management Controller (BMC) of a server over IPMI 2.0 over LAN (RMCP+): it reads the chassis status, the Field Replaceable Units (FRUs) and every sensor, as Java objects or as the text output that MetricsHub parses, and its low-level API sends any IPMI command (System Event Log, chassis control, Serial over LAN). It requires Java 8 or later. +The IPMI Java Client talks to the Baseboard Management Controller (BMC) of a server over IPMI 2.0 over LAN (RMCP+): it reads the chassis status, the Field Replaceable Units (FRUs) and the sensors of the SDR repository, as Java objects or as the text output that MetricsHub parses, and its low-level API sends any IPMI command (System Event Log, chassis control, Serial over LAN). It requires Java 8 or later. ```java IpmiClientConfiguration config = new IpmiClientConfiguration("bmc.example.com", "monitor", password, null, false, 120); diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index 3bb5020..558fa11 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,5 +1,5 @@ keywords: ipmi java client, ipmi 2.0, rmcp+, bmc, hardware monitoring, sensors, fru, overview -description: A Java client for IPMI 2.0 over LAN (RMCP+): read the chassis power state, the FRU inventory and every sensor of a server's BMC, or send any IPMI command yourself. +description: A Java client for IPMI 2.0 over LAN (RMCP+): read the chassis power state, the FRU inventory and the sensors of a server's BMC, or send any IPMI command yourself. # IPMI Java Client @@ -15,9 +15,10 @@ boards) over **IPMI 2.0 over LAN (RMCP+)**, on UDP port 623. It lets a Java appl intrusion ([Chassis Status](chassis-status.html)), * read the **FRU inventory**: manufacturer, product name, part and serial numbers of the chassis, boards, power supplies and other Field Replaceable Units ([FRU Inventory](fru-inventory.html)), -* read **every sensor** of the SDR repository: temperatures, voltages, fan speeds, currents, - power and energy readings with their thresholds, and the discrete states (presence, redundancy, - failure, ...) ([Sensors](sensors.html)), and +* read the **sensors** of the BMC's SDR repository (its Full and Compact sensor records): + temperatures, voltages, fan speeds, currents, power and energy readings with their thresholds, + and the discrete states (presence, redundancy, failure, ...); [Sensors](sensors.html) lists + what is not read, and * send **any IPMI command** through the low-level connector, including the System Event Log and chassis control commands, and open a **Serial over LAN** console ([Low-Level API](low-level-api.html), [Serial over LAN](serial-over-lan.html)). diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index 622794a..833b7e4 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -146,19 +146,21 @@ GetSelInfoResponseData info = (GetSelInfoResponseData) connector .sendMessage(handle, new GetSelInfo(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus)); System.out.println("SEL entries: " + info.getEntriesCount()); -int reservationId = ((ReserveSelResponseData) connector - .sendMessage(handle, new ReserveSel(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus))) - .getReservationId(); - -int recordId = 0; // 0: the first entry -while (recordId != 0xFFFF) { // 0xFFFF: no more entries - GetSelEntryResponseData entry = (GetSelEntryResponseData) connector - .sendMessage(handle, new GetSelEntry(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, - reservationId, recordId)); - SelRecord record = entry.getSelRecord(); - System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " - + record.getEventDirection()); - recordId = entry.getNextRecordId(); +if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL + int reservationId = ((ReserveSelResponseData) connector + .sendMessage(handle, new ReserveSel(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus))) + .getReservationId(); + + int recordId = 0; // 0: the first entry + while (recordId != 0xFFFF) { // 0xFFFF: no more entries + GetSelEntryResponseData entry = (GetSelEntryResponseData) connector + .sendMessage(handle, new GetSelEntry(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, + reservationId, recordId)); + SelRecord record = entry.getSelRecord(); + System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " + + record.getEventDirection()); + recordId = entry.getNextRecordId(); + } } ``` @@ -169,6 +171,14 @@ Wed May 15 11:16:11 CEST 2024 Voltage LimitNotExceeded Assertion Wed May 15 11:23:27 CEST 2024 PowerUnit PowerOffOrDown Assertion ``` +> [!WARNING] +> `GetSelEntry` decodes every entry with the layout of a *system event record* (type `02h`). +> OEM entries (types `C0h` to `FFh`) come back with meaningless sensor and event fields (and +> timestamp, for the non-timestamped types `E0h` to `FFh`); an entry of type exactly `C0h` or +> `E0h` makes `sendMessage()` throw `IllegalArgumentException` (`Invalid value: 192` or `224`), +> which ends the walk, since the ID of the next entry is lost with it. Check +> `record.getRecordType()` before using the decoded fields. + Exposing the SEL in `IpmiClient` is tracked in [#103](https://github.com/metricshub/ipmi-java/issues/103). diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index 26167f8..e408a9d 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -1,5 +1,5 @@ keywords: sensors, sdr, sensor data record, sensor reading, thresholds, temperature, voltage, fan, power, states, text output format, ipmiresultconverter -description: Read every sensor of a server through its BMC — how the SDR repository is walked, the Sensor object, readings and thresholds, discrete states, and the text output format of getFrusAndSensorsAsStringResult. +description: Read the sensors of a server through its BMC — how the SDR repository is walked, the Sensor object, readings and thresholds, discrete states, and the text output format of getFrusAndSensorsAsStringResult. # Sensors @@ -8,7 +8,7 @@ description: Read every sensor of a server through its BMC — how the SDR repos The BMC describes its sensors in the **Sensor Data Record (SDR) repository**: one record per sensor, with its name, what it measures (the *entity*: a processor, a power supply, the system board, ...), its unit, the formula that converts its raw reading, and its thresholds. The client -walks this repository and reads each sensor. +walks this repository and reads the sensors that have a reading. ```java List sensors = IpmiClient.getSensors(config); diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index d3682ef..765455a 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -23,41 +23,45 @@ on top of the [low-level API](low-level-api.html). ```java import java.nio.charset.StandardCharsets; +import java.util.Comparator; +import org.metricshub.ipmi.core.api.sol.CipherSuiteSelectionHandler; import org.metricshub.ipmi.core.api.sol.SerialOverLan; -import org.metricshub.ipmi.core.api.sol.SpecificCipherSuiteSelector; import org.metricshub.ipmi.core.api.sync.IpmiConnector; -import org.metricshub.ipmi.core.connection.Connection; +import org.metricshub.ipmi.core.coding.security.CipherSuite; -IpmiConnector connector = new IpmiConnector(0); - -try (SerialOverLan sol = new SerialOverLan(connector, "bmc.example.com", "admin", "the-password", - new SpecificCipherSuiteSelector(Connection.getDefaultCipherSuite()))) { +// Use cipher suite 17 if the BMC offers it, else 3 +CipherSuiteSelectionHandler selector = suites -> suites + .stream() + .filter(suite -> suite.getId() == 17 || suite.getId() == 3) + .max(Comparator.comparingInt(CipherSuite::getId)) + .orElseThrow(() -> new IllegalStateException("The BMC offers neither cipher suite 17 nor 3")); - sol.writeString("\r\n", StandardCharsets.US_ASCII); // wake the console up - Thread.sleep(1000); - System.out.print(sol.readString(StandardCharsets.US_ASCII, 4096, 2000)); +IpmiConnector connector = new IpmiConnector(0); +try { + try (SerialOverLan sol = new SerialOverLan(connector, "bmc.example.com", "admin", "the-password", selector)) { + sol.writeString("\r\n", StandardCharsets.US_ASCII); // wake the console up + Thread.sleep(1000); + System.out.print(sol.readString(StandardCharsets.US_ASCII, 4096, 2000)); + } +} finally { + // Also releases the connector when the console cannot be opened + connector.tearDown(); } ``` This constructor opens a dedicated session with the **Administrator** privilege, activates the SOL payload, and owns the session: **closing the `SerialOverLan` closes the session and tears -down the connector** passed to it. Use a new connector for each console opened this way. +down the connector** passed to it. Use a new connector for each console opened this way. When the +constructor fails after the session is open (SOL disabled, no free payload instance), nothing is +closed: hence the outer `finally`, as tearing down a connector twice is harmless. The cipher suite is chosen by a [`CipherSuiteSelectionHandler`](apidocs/org/metricshub/ipmi/core/api/sol/CipherSuiteSelectionHandler.html), -which receives the suites the BMC offers and returns the one to use. +which receives the suites the BMC offers and returns the one to use: the selector above picks 17, +else 3, and fails if the BMC offers neither. [`SpecificCipherSuiteSelector`](apidocs/org/metricshub/ipmi/core/api/sol/SpecificCipherSuiteSelector.html) -always returns the suite it was built with (suite 3 above); implement the interface to pick -one from the list, for example suite 17 when offered: - -```java -CipherSuiteSelectionHandler prefer17 = suites -> suites - .stream() - .filter(suite -> suite.getId() == 17) - .findFirst() - .orElse(Connection.getDefaultCipherSuite()); -``` +always returns the suite it was built with, whether the BMC offers it or not. | Constructor | Use | | --- | --- | diff --git a/src/site/markdown/supported-commands.md b/src/site/markdown/supported-commands.md index 4f4bbe5..54fc7c3 100644 --- a/src/site/markdown/supported-commands.md +++ b/src/site/markdown/supported-commands.md @@ -86,8 +86,10 @@ Full, Compact and Event-Only records share IPMI 2.0 reserves the record types `C0h` to `FFh` for OEM use, and vendors do use them: a GIGABYTE BMC tested with this library returns 19 records of type `D0h`, a Lenovo IMM one of type `C0h`. They are all decoded as an -[`OemRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/OemRecord.html), -which keeps the manufacturer ID and the whole vendor-defined payload as raw bytes. +[`OemRecord`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/OemRecord.html), which +keeps the whole vendor-defined payload as raw bytes. Only type `C0h` has a standard layout, with +a manufacturer ID: for the types `C1h` to `FFh`, `getManufacturerId()` returns `0` (unknown), not +the record's vendor. A record that cannot be decoded at all — a reserved type such as the deprecated BMC Message Channel Info record (`14h`), a record shorter than its header, an empty or truncated reply — is From 8be45cbb78217e94085114a4789cea110c9e6cbd Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 11:27:41 +0200 Subject: [PATCH 04/13] Link the SEL decoding issue from the SEL example (#125) Walking the whole SEL of the test BMCs showed OEM timestamped entries on both (358 of 643 on a Lenovo IMM), which the decoder reports with meaningless sensor and event fields: the example printed three of them as "Voltage LimitNotExceeded". It now prints the event fields of system event records only, its output is the real one, and the warning links #125. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/low-level-api.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index 833b7e4..fc0dcb9 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -157,8 +157,12 @@ if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL .sendMessage(handle, new GetSelEntry(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, reservationId, recordId)); SelRecord record = entry.getSelRecord(); - System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " - + record.getEventDirection()); + if (record.getRecordType() == SelRecordType.System) { + System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " + + record.getEventDirection()); + } else { + System.out.println("OEM entry " + record.getRecordId()); // fields not decoded, see #125 + } recordId = entry.getNextRecordId(); } } @@ -167,17 +171,21 @@ if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL ```text SEL entries: 643 Wed May 15 11:15:25 CEST 2024 EventLoggingDisabled LogAreaReset Assertion -Wed May 15 11:16:11 CEST 2024 Voltage LimitNotExceeded Assertion +OEM entry 2 +OEM entry 3 +OEM entry 4 Wed May 15 11:23:27 CEST 2024 PowerUnit PowerOffOrDown Assertion +Wed May 15 11:23:34 CEST 2024 PowerUnit PowerOffOrDown Deassertion ``` > [!WARNING] -> `GetSelEntry` decodes every entry with the layout of a *system event record* (type `02h`). -> OEM entries (types `C0h` to `FFh`) come back with meaningless sensor and event fields (and -> timestamp, for the non-timestamped types `E0h` to `FFh`); an entry of type exactly `C0h` or -> `E0h` makes `sendMessage()` throw `IllegalArgumentException` (`Invalid value: 192` or `224`), -> which ends the walk, since the ID of the next entry is lost with it. Check -> `record.getRecordType()` before using the decoded fields. +> `GetSelEntry` decodes every entry with the layout of a *system event record* (type `02h`), +> including the OEM entries (types `C0h` to `FFh`) that vendors log in large numbers (more than +> half of the entries on a Lenovo IMM): their sensor and event fields are meaningless (and their +> timestamp too, for the non-timestamped types `E0h` to `FFh`), so check `getRecordType()` first, +> as above. An entry of type exactly `C0h` or `E0h` makes `sendMessage()` throw +> `IllegalArgumentException` (`Invalid value: 192` or `224`), which ends the walk, since the ID of +> the next entry is lost with it. See [#125](https://github.com/metricshub/ipmi-java/issues/125). Exposing the SEL in `IpmiClient` is tracked in [#103](https://github.com/metricshub/ipmi-java/issues/103). From 3aaeb10d9ccee005cc7fcfed044e7abf8cf33b9e Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 11:29:15 +0200 Subject: [PATCH 05/13] Address the third Codex review of the documentation (#113) - Low-Level API: close the session in a finally block, since tearDown() does not log out and BMCs have few session slots. - Supported Commands: a Get SDR reply without any record byte is not skipped: the decoder rejects it and the walk fails. - FRU Inventory: FRU 0 is attached to the system board only when it has a Board Info area; the MultiRecord decoder drops the last record (#85). - Preparing the BMC: the firewall rules use the configured port, and Serial over LAN may use the payload port announced by the BMC. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/fru-inventory.md | 8 ++++++-- src/site/markdown/low-level-api.md | 20 ++++++++++++-------- src/site/markdown/preparing-the-bmc.md | 11 ++++++++--- src/site/markdown/supported-commands.md | 14 +++++++++----- 4 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index 01c48aa..518aa56 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -23,7 +23,9 @@ List frus = IpmiClient.getFrus(config); 2. walks the **SDR repository** and, for each **FRU Device Locator** record of a *logical* FRU device (one accessed with the FRU commands of the BMC), reads that FRU the same way; 3. attaches FRU 0 to the first **Compact Sensor** record of the system board entity, under the - name ` `. + name ` `. This needs a **Board Info** area in FRU 0 + (it gives the name) and such a Compact Sensor record: otherwise FRU 0 is returned only if a + FRU Device Locator record of the repository points to it. The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC but makes large FRUs slow to read: a few seconds per FRU on some BMCs @@ -54,7 +56,9 @@ Each [`Fru`](apidocs/org/metricshub/ipmi/client/model/Fru.html) holds: The MultiRecord area (power supply, DC output, management access records) is decoded by the library but not returned by `getFrus()`; read it with the [low-level API](low-level-api.html) and -`ReadFruData.decodeFruData()` if you need it. +`ReadFruData.decodeFruData()` if you need it. Note that the decoder drops the last record of the +area, which is often the only one, such as the Power Supply Information record of a power supply +([#85](https://github.com/metricshub/ipmi-java/issues/85)). ```java for (Fru fru : IpmiClient.getFrus(config)) { diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index fc0dcb9..0c2d536 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -56,13 +56,15 @@ public class LowLevelExample { // Declare the cipher suite and the privilege level, then log in (RAKP handshake) connector.getChannelAuthenticationCapabilities(handle, cipherSuite, PrivilegeLevel.User); connector.openSession(handle, "monitor", "the-password", null); - - // Send any command, and cast the response to the matching ResponseData class - GetChassisStatusResponseData status = (GetChassisStatusResponseData) connector - .sendMessage(handle, new GetChassisStatus(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus)); - System.out.println("Power is " + (status.isPowerOn() ? "on" : "off")); - - connector.closeSession(handle); + try { + // Send any command, and cast the response to the matching ResponseData class + GetChassisStatusResponseData status = (GetChassisStatusResponseData) connector + .sendMessage(handle, new GetChassisStatus(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus)); + System.out.println("Power is " + (status.isPowerOn() ? "on" : "off")); + } finally { + // Log out, even when a command failed: BMCs only have a few session slots + connector.closeSession(handle); + } } finally { // Close every connection and release the local UDP port connector.tearDown(); @@ -74,7 +76,9 @@ public class LowLevelExample { The four steps before the first command are mandatory and must come in this order: `createConnection()`, `getAvailableCipherSuites()`, `getChannelAuthenticationCapabilities()`, `openSession()`. Calling them out of order fails with -`ConnectionException: Illegal connection state: ...`. +`ConnectionException: Illegal connection state: ...`. Close the session in a `finally` block: +`tearDown()` only releases the local resources and does not log out, so a session left open +holds one of the BMC's few session slots until the BMC expires it. ## Connections and connectors diff --git a/src/site/markdown/preparing-the-bmc.md b/src/site/markdown/preparing-the-bmc.md index 458d008..3a20aab 100644 --- a/src/site/markdown/preparing-the-bmc.md +++ b/src/site/markdown/preparing-the-bmc.md @@ -124,11 +124,16 @@ then derived from this key instead of the user's password. When it is set, pass | From | To | Protocol / port | | --- | --- | --- | -| The machine running the client (any local port) | The BMC | **UDP 623** (RMCP / RMCP+) | -| The BMC (port 623) | The machine running the client (the same local port) | UDP replies | +| The machine running the client (any local port) | The BMC | **UDP 623** (RMCP / RMCP+), or the port set with [`setPort()`](configuration.html#host-and-port) | +| The BMC (the same port) | The machine running the client (the same local port) | UDP replies | A stateful firewall needs the outbound rule only. Each session binds its own ephemeral local UDP -port, so a stateless firewall must accept UDP replies from port 623 on the whole ephemeral range. +port, so a stateless firewall must accept UDP replies from the BMC's port on the whole ephemeral +range. + +[Serial over LAN](serial-over-lan.html) may use another UDP port: the BMC returns the port of the +SOL payload when it is activated, usually the same one, and the console then opens a second +session on that port. Allow it too if your BMC announces a different one. ## Checking access with `ipmitool` or `ipmiutil` diff --git a/src/site/markdown/supported-commands.md b/src/site/markdown/supported-commands.md index 54fc7c3..e5a12bf 100644 --- a/src/site/markdown/supported-commands.md +++ b/src/site/markdown/supported-commands.md @@ -91,11 +91,15 @@ keeps the whole vendor-defined payload as raw bytes. Only type `C0h` has a stand a manufacturer ID: for the types `C1h` to `FFh`, `getManufacturerId()` returns `0` (unknown), not the record's vendor. -A record that cannot be decoded at all — a reserved type such as the deprecated BMC Message -Channel Info record (`14h`), a record shorter than its header, an empty or truncated reply — is -**skipped**: the client logs it at the `WARN` level and goes on with the next record, so one -unexpected record never costs the whole sensor list. When a BMC answers a whole-record Get SDR -with fewer bytes than the record declares, the client reads the record again in chunks. +A record that cannot be decoded — a reserved type such as the deprecated BMC Message Channel +Info record (`14h`), a record shorter than its header — is **skipped**: the client logs it at +the `WARN` level and goes on with the next record, so one unexpected record does not cost the +whole sensor list. When a BMC answers a whole-record Get SDR with fewer bytes than the record +declares, the client reads the record again in chunks. + +A Get SDR reply that carries the next record ID but **no record byte at all** is not handled: +the Get SDR decoder rejects it (`IllegalArgumentException: Invalid response payload length`), +which ends the walk and fails `getSensors()` or `getFrus()`. ## FRU records From c187ecfc0fd20fbb2079ccd71b80b1632313365f Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 11:43:23 +0200 Subject: [PATCH 06/13] Address the fourth Codex review of the documentation (#113) - Low-Level API: DCMI, group extension and OEM commands need a request with a raw network function, since NetworkFunction only lists 00h-0Bh; the example (Get DCMI Capabilities Info) was run on a Lenovo IMM and a GIGABYTE BMC. - Serial over LAN: close() does not log out when deactivating the payload fails. - Sensors: an empty threshold also means a threshold of exactly 0, and every threshold is 0.0 on BMCs affected by #83. - Configuration: credentials and the BMC key go through the platform charset; non-ASCII Kg bytes break the login (#90). Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/configuration.md | 10 ++++++ src/site/markdown/low-level-api.md | 42 +++++++++++++++++++++++++ src/site/markdown/sensors.md | 11 +++++-- src/site/markdown/serial-over-lan.md | 4 +++ src/site/markdown/supported-commands.md | 4 ++- 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 9787595..c5e71ce 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -54,6 +54,10 @@ chosen by the operating system for each session. the library converts it to a `String` internally to open the session and does not clear the array, so clear it yourself once you no longer need the configuration. +The user name and the password are encoded with the **platform default charset**, and their +IPMI length limits (16 and 20 bytes) are not checked: stick to ASCII credentials of at most 16 +and 20 characters ([#90](https://github.com/metricshub/ipmi-java/issues/90)). + The client opens every session with the **User** privilege level, which is enough for every `IpmiClient` method. @@ -63,6 +67,12 @@ The client opens every session with the **User** privilege level, which is enoug logins. Leave it `null` (the default on virtually every BMC): the session keys are then derived from the password. See [Preparing the BMC](preparing-the-bmc.html#bmc-key-kg). +> [!WARNING] +> The library turns the key into a `String` and back with the platform default charset, which +> alters bytes that are not valid characters in that charset (any byte from `80h` with UTF-8): +> the session keys are then wrong and the login fails. Only keys made of ASCII bytes (`00h` to +> `7Fh`) work reliably ([#90](https://github.com/metricshub/ipmi-java/issues/90)). + ### skipAuth Despite its name, `skipAuth` does **not** skip authentication: the session is always diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index 0c2d536..6174237 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -246,6 +246,48 @@ public class GetDeviceId extends IpmiCommandCoder { } ``` +#### DCMI, group extension and OEM network functions + +The [`NetworkFunction`](apidocs/org/metricshub/ipmi/core/coding/payload/lan/NetworkFunction.html) +enum only lists the standard network functions (`00h` to `0Bh`). For a command of another +network function — DCMI (`2Ch`), the other group extensions (`2Eh` is OEM/Group), or a vendor's +OEM network function (`30h` to `3Fh`) — build the request with a subclass of `IpmiLanRequest` +that sets the raw code; `getNetworkFunction()` must still return a value, which is then unused. +Responses are matched by tag and command code, so nothing else changes. For example, Get DCMI +Capabilities Info: + +```java +/** An IPMI request with a raw network function code */ +class RawNetFnRequest extends IpmiLanRequest { + + RawNetFnRequest(int networkFunction, byte commandCode, byte[] requestData, int sequenceNumber) { + super(NetworkFunction.ApplicationRequest, commandCode, requestData, TypeConverter.intToByte(sequenceNumber)); + setNetworkFunctionCode(TypeConverter.intToByte(networkFunction)); + } +} + +public class GetDcmiCapabilities extends IpmiCommandCoder { + + // constructor, getResponseData() as above + + @Override + public NetworkFunction getNetworkFunction() { + return NetworkFunction.ApplicationRequest; // unused: see preparePayload() + } + + @Override + public byte getCommandCode() { + return 0x01; // Get DCMI Capabilities Info + } + + @Override + protected IpmiLanMessage preparePayload(int sequenceNumber) { + // NetFn 2Ch (DCMI); data: group extension ID DCh, parameter 1 (supported capabilities) + return new RawNetFnRequest(0x2C, getCommandCode(), new byte[] { (byte) 0xDC, 0x01 }, sequenceNumber); + } +} +``` + ## Asynchronous API [`IpmiAsyncConnector`](apidocs/org/metricshub/ipmi/core/api/async/IpmiAsyncConnector.html) has diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index e408a9d..3afe882 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -73,7 +73,10 @@ System 3.3V = 3.38 Volts (upper critical: 3.56) `getSensorBaseUnit()` returns a [`SensorUnit`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorUnit.html); the six thresholds (`getLowerNonCriticalThreshold()` to `getUpperNonRecoverableThreshold()`) -are converted with the same formula, and are `0.0` when the BMC does not define them. +are converted with the same formula. A threshold the BMC does not define, or does not make +readable, is left at `0.0`, the same value as a threshold that really is 0; on BMCs that do not +set the *init sensor type* bit of the record, every threshold is left at `0.0` +([#83](https://github.com/metricshub/ipmi-java/issues/83)). > [!WARNING] > Known limitations of the decoding, by the IPMI 2.0 specification @@ -176,8 +179,10 @@ Energy;$sensorId;$sensorName;$deviceUniqueId;$value * `$sensorId` is the SDR record ID, as 4 lowercase hexadecimal digits. * `$deviceUniqueId` is the entity of the sensor, as in the device state lines. -* Thresholds are rounded to integers, in the same unit as `$value`, and empty when the BMC does - not define them. +* Thresholds are rounded to integers, in the same unit as `$value`. A threshold is empty when + its decoded value is `0.0`: when the BMC does not define it or does not make it readable, but + also when it really is 0 (a lower fan threshold of 0 RPM, for example), and on BMCs affected by + [#83](https://github.com/metricshub/ipmi-java/issues/83). The chassis status has its own text form: [`getChassisStatusAsStringResult()`](chassis-status.html#as-text). diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index 765455a..794f3c7 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -56,6 +56,10 @@ down the connector** passed to it. Use a new connector for each console opened t constructor fails after the session is open (SOL disabled, no free payload instance), nothing is closed: hence the outer `finally`, as tearing down a connector twice is harmless. +`close()` deactivates the SOL payload first. If that fails (an error, or no reply), `close()` +throws `IOException` without logging out: the outer `finally` still releases the connector, but +the session stays open on the BMC until the BMC expires it. + The cipher suite is chosen by a [`CipherSuiteSelectionHandler`](apidocs/org/metricshub/ipmi/core/api/sol/CipherSuiteSelectionHandler.html), which receives the suites the BMC offers and returns the one to use: the selector above picks 17, diff --git a/src/site/markdown/supported-commands.md b/src/site/markdown/supported-commands.md index e5a12bf..4167d64 100644 --- a/src/site/markdown/supported-commands.md +++ b/src/site/markdown/supported-commands.md @@ -10,7 +10,9 @@ commands below. Each command is a class of [`org.metricshub.ipmi.core.coding.commands`](apidocs/org/metricshub/ipmi/core/coding/commands/package-summary.html), sent with the [low-level API](low-level-api.html#sending-commands); the last column shows which ones `IpmiClient` uses. A command not listed here can be added by -[extending `IpmiCommandCoder`](low-level-api.html#writing-your-own-command). +[extending `IpmiCommandCoder`](low-level-api.html#writing-your-own-command), with a +[raw network function](low-level-api.html#dcmi-group-extension-and-oem-network-functions) for +DCMI, group extension and OEM commands. ## Commands From 2702c69e7c0df77bda5764f0743b8e1e1d7ac51c Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 12:02:35 +0200 Subject: [PATCH 07/13] Address the fifth Codex review of the documentation (#113) - Timeouts and Errors: a Get SDR reply without any record byte fails the call, it is not skipped. - FRU Inventory: a Read FRU Data chunk without a reply only truncates the FRU, while Get FRU Inventory Area Info fails the call for FRU 0 on any error and for the other FRUs on no reply; word-addressed FRUs are read at wrong offsets (#85, by the spec, not seen on the test BMCs). - Serial over LAN: the constructors that open their own session take no BMC key; use a low-level session on two-key BMCs. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/fru-inventory.md | 24 ++++++++++++++++-------- src/site/markdown/serial-over-lan.md | 7 +++++++ src/site/markdown/timeouts-and-errors.md | 11 ++++++----- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index 518aa56..ac78000 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -29,14 +29,22 @@ List frus = IpmiClient.getFrus(config); The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC but makes large FRUs slow to read: a few seconds per FRU on some BMCs -([#102](https://github.com/metricshub/ipmi-java/issues/102)). - -A FRU that cannot be read — not present, or answering with an error at some offset — is logged at -the `WARN` level and reported truncated, or not at all. The exception is **FRU 0**: if the BMC -rejects Get FRU Inventory Area Info for it, `getFrus()` (and therefore -`getFrusAndSensorsAsStringResult()`) fails with that error. As with any call, a request that gets -no reply also fails the call ([Timeouts and Errors](timeouts-and-errors.html)). Physical -FRU devices (EEPROMs on a private I²C bus, read with Master Write-Read) are not read. +([#102](https://github.com/metricshub/ipmi-java/issues/102)). FRUs that the BMC addresses in +**words** rather than bytes (as reported by Get FRU Inventory Area Info) are read at the wrong +offsets and sizes, and come out garbled or missing: this is wrong by the specification, though +not seen on the BMCs tested ([#85](https://github.com/metricshub/ipmi-java/issues/85)). + +A FRU whose data cannot be read — not present, or answering with an error or not at all at some +offset — is logged at the `WARN` level and reported truncated, or not at all. Get FRU Inventory +Area Info, which starts the read of each FRU, is less forgiving: + +* for **FRU 0**, any failure (an error completion code or no reply) fails `getFrus()`, and + therefore `getFrusAndSensorsAsStringResult()`; +* for the other FRUs, an error completion code is logged and the FRU skipped, but no reply fails + the call, as does a Get SDR without a reply during the repository walk + ([Timeouts and Errors](timeouts-and-errors.html)). + +Physical FRU devices (EEPROMs on a private I²C bus, read with Master Write-Read) are not read. ## The `Fru` object diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index 794f3c7..70e523d 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -73,6 +73,13 @@ always returns the suite it was built with, whether the BMC offers it or not. | `SerialOverLan(connector, host, port, user, password, selector)` | New session on another port | | `SerialOverLan(connector, session)` | Reuse a session opened with the low-level API; closing the console leaves it, and the connector, open. If the BMC serves SOL on another UDP port, the console uses an existing session on that port, or opens one (which closing the console closes, with the connector). | +> [!NOTE] +> The constructors that open their own session take no [BMC key](configuration.html#bmc-key): +> they cannot log in to a BMC configured with *two-key* logins. On such a BMC, open the session +> with the low-level API, `connector.openSession(handle, user, password, bmcKey)`, and pass the +> `Session` it returns to `SerialOverLan(connector, session)`. The session that the console +> opens by itself on another UDP port has the same limitation. + If the session's privilege is too low to activate the payload, the client raises it to Administrator (Set Session Privilege Level) and tries again. The constructors throw `SOLException` when the payload cannot be activated (SOL disabled, no free payload instance, privilege refused). diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index c18508d..807de09 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -117,12 +117,13 @@ Common causes wrapped in the `ExecutionException`: Some problems are logged at the `WARN` level and the call goes on with what it could collect: -* an **SDR record** that cannot be decoded — an OEM record type, a malformed record, an empty - reply — is skipped and the repository walk continues with the next record - ([Supported Commands](supported-commands.html#sdr-records)); +* an **SDR record** that cannot be decoded — a reserved record type, a record shorter than its + header — is skipped and the repository walk continues with the next record (a Get SDR reply + without any record byte fails the call instead, see + [Supported Commands](supported-commands.html#oem-and-unknown-records)); * a **FRU** that cannot be read (for example a FRU device that is not present) is reported - truncated or not at all — except the built-in FRU 0, whose inventory information must be - readable ([FRU Inventory](fru-inventory.html#how-the-frus-are-read)); + truncated or not at all — except when Get FRU Inventory Area Info fails for FRU 0, or gets no + reply for any FRU ([FRU Inventory](fru-inventory.html#how-the-frus-are-read)); * a **sensor** whose reading is not available (completion code `DataNotPresent`) is returned without reading data. From 8d380347224f5d276b7cac5add4138781c8d2ac4 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 12:12:07 +0200 Subject: [PATCH 08/13] Address the sixth Codex review of the documentation (#113) Sensors and Troubleshooting no longer promise 0.0 for an unavailable reading: the library ignores the reading-unavailable and scanning-disabled flags and converts whatever raw byte the BMC returns (#110). The readings example now checks isSensorStateValid(), which flags the three unavailable sensors of the GIGABYTE test BMC and leaves the Lenovo sample output unchanged. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/sensors.md | 13 ++++++++++--- src/site/markdown/troubleshooting.md | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index 3afe882..5758e76 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -54,7 +54,8 @@ linear formula (`M`, `B` and the exponents of IPMI 2.0, section 36.3): ```java for (Sensor sensor : IpmiClient.getSensors(config)) { - if (sensor.isFull() && sensor.getData() != null) { + // isSensorStateValid() is false when the BMC flags the reading as unavailable + if (sensor.isFull() && sensor.getData() != null && sensor.getData().isSensorStateValid()) { FullSensorRecord record = (FullSensorRecord) sensor.getRecord(); double value = sensor.getData().getSensorReading(record); System.out.println(sensor.getName() + " = " + value + " " + record.getSensorBaseUnit() @@ -82,8 +83,12 @@ set the *init sensor type* bit of the record, every threshold is left at `0.0` > Known limitations of the decoding, by the IPMI 2.0 specification > (not all of them reproduced on real hardware): > -> * a sensor whose reading is flagged *unavailable* or whose scanning is disabled is reported -> with a reading of `0.0` ([#110](https://github.com/metricshub/ipmi-java/issues/110)); +> * the *reading unavailable* and *scanning disabled* flags of Get Sensor Reading are ignored +> by `getSensorReading()` and by the text output: such a sensor is reported with whatever raw +> value the BMC returns, converted (`0.0` for the three unavailable sensors of the GIGABYTE BMC +> tested, but any value is possible). `getData().isSensorStateValid()` is `false` when the +> reading is unavailable, so check it as above; the scanning flag is not exposed +> ([#110](https://github.com/metricshub/ipmi-java/issues/110)); > * the non-linear conversions and the readability of each threshold are not fully handled > ([#83](https://github.com/metricshub/ipmi-java/issues/83)); > * the threshold status bits of the reading are mis-mapped @@ -179,6 +184,8 @@ Energy;$sensorId;$sensorName;$deviceUniqueId;$value * `$sensorId` is the SDR record ID, as 4 lowercase hexadecimal digits. * `$deviceUniqueId` is the entity of the sensor, as in the device state lines. +* Sensors whose reading the BMC flags as unavailable are **not** left out: their line carries + whatever value the BMC returned ([#110](https://github.com/metricshub/ipmi-java/issues/110)). * Thresholds are rounded to integers, in the same unit as `$value`. A threshold is empty when its decoded value is `0.0`: when the BMC does not define it or does not make it readable, but also when it really is 0 (a lower fan threshold of 0 RPM, for example), and on BMCs affected by diff --git a/src/site/markdown/troubleshooting.md b/src/site/markdown/troubleshooting.md index d9e05e8..13d6b8e 100644 --- a/src/site/markdown/troubleshooting.md +++ b/src/site/markdown/troubleshooting.md @@ -81,7 +81,7 @@ to make it use suite 3 or 17. | `WARN Failed to read FRU at offset ... Requested Sensor, data, or record not present` | The FRU is declared in the SDR repository but not present, for example an empty power supply bay. Usually harmless. | | `WARN Failed to decode FRU ` | The FRU data is not in the IPMI FRU format (for example the SPD data of a memory module, [#107](https://github.com/metricshub/ipmi-java/issues/107)). | | A sensor known to `ipmitool` is not returned | Only Full and Compact sensor records of the BMC's own repository are read: sensors behind satellite controllers are not ([#84](https://github.com/metricshub/ipmi-java/issues/84)), and shared Compact records are not expanded ([#100](https://github.com/metricshub/ipmi-java/issues/100)). | -| A sensor reads `0.0` | The BMC flags the reading as unavailable, which is not checked yet ([#110](https://github.com/metricshub/ipmi-java/issues/110)). | +| A sensor reads `0.0`, or a value that makes no sense | The BMC may flag the reading as unavailable (or the sensor's scanning as disabled), which the library ignores: the value is whatever raw byte the BMC returned. `getData().isSensorStateValid()` is `false` for an unavailable reading; the text output does not check it ([#110](https://github.com/metricshub/ipmi-java/issues/110)). | | Negative processor temperatures (`CPU1 DTS = -44.0`) | Not an error: Intel *Digital Thermal Sensor* readings are the margin below the maximum junction temperature. | ## The JVM does not exit From 572731768f00eea5a8abb4f98aea242a925b9ada Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 12:22:41 +0200 Subject: [PATCH 09/13] Address the seventh Codex review of the documentation (#113) - Configuration, Timeouts and Errors, Low-Level API: the default pingPeriod of -1 disables the keep-alive. ConnectionManager(port, pingPeriod) reads the 30 s property in this(port), then overwrites it with -1, so only IpmiConnector(int) and IpmiConnector(int, InetAddress) get the 30 s period. Recommend setting pingPeriod explicitly. - Low-Level API: the SEL example uses reservation 0, since whole-entry reads need none (IPMI 2.0 section 31.5) and Reserve SEL is optional; checked on a Lenovo IMM and a GIGABYTE BMC, same output. - Serial over LAN: on a BMC that serves SOL on another port, close() leaves the first session open. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/configuration.md | 12 ++++++++---- src/site/markdown/low-level-api.md | 10 +++++----- src/site/markdown/serial-over-lan.md | 5 +++++ src/site/markdown/timeouts-and-errors.md | 2 +- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index c5e71ce..7484fca 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -36,7 +36,7 @@ one constructor can be combined with those of another. | `bmcKey` | `null` | [BMC key](#bmc-key) | | `skipAuth` | required | [skipAuth](#skipauth) | | `timeout` | required, in **seconds** | [Timeout](#timeout) | -| `pingPeriod` | `-1`: 30 000 ms | [Keep-alive](#keep-alive) | +| `pingPeriod` | `-1`: no keep-alive | [Keep-alive](#keep-alive) | ### Host and port @@ -108,13 +108,17 @@ inactivity during a long collection. | `pingPeriod` | Behavior | | --- | --- | -| `-1` (default) | The `pingPeriod` of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults): 30 000 ms | | `> 0` | One keep-alive message every `pingPeriod` ms | +| `-1` (default) | **No keep-alive messages** (see below) | | `0` (or any other negative value) | No keep-alive messages | -Each `IpmiClient` call opens its own session and closes it when it is done, so the keep-alive +`-1` is meant to use the `pingPeriod` of +[`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms), but the +connector that `IpmiClient` creates overwrites that value with `-1`, which disables the +keep-alive. Each `IpmiClient` call opens its own session and closes it when it is done, so this only matters for calls that last longer than the BMC's session inactivity timeout (typically -60 s). Disable it (`0`) to keep the traffic to the strict minimum. +60 s), such as a long SDR walk or FRU read on a slow BMC: for those, **set `pingPeriod` +explicitly**, for example to `30000`. ## Thread safety diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index 6174237..efb091e 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -89,8 +89,8 @@ port (or always pass `0`), and call `tearDown()` when you are done with it. | Method | Purpose | | --- | --- | -| `IpmiConnector(int port)`, `IpmiConnector(int port, InetAddress address)` | Bind the given local port (`0`: any free port), on all interfaces or on one. | -| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0`: none). | +| `IpmiConnector(int port)`, `IpmiConnector(int port, InetAddress address)` | Bind the given local port (`0`: any free port), on all interfaces or on one, with the keep-alive period of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms). | +| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0` or a negative value, including `-1`: none). | | `createConnection(InetAddress address[, int port])` | Register a connection to a BMC (port 623 by default). | | `createConnection(InetAddress address, [int port,] CipherSuite cipherSuite, PrivilegeLevel level)` | The same, skipping the cipher suite and capabilities steps: call `openSession()` next. | | `closeSession(handle)` | Log out (Close Session). | @@ -151,9 +151,9 @@ GetSelInfoResponseData info = (GetSelInfoResponseData) connector System.out.println("SEL entries: " + info.getEntriesCount()); if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL - int reservationId = ((ReserveSelResponseData) connector - .sendMessage(handle, new ReserveSel(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus))) - .getReservationId(); + // GetSelEntry reads whole entries, which need no reservation (IPMI 2.0, section 31.5): + // 0 works on every BMC, including those that do not implement Reserve SEL + int reservationId = 0; int recordId = 0; // 0: the first entry while (recordId != 0xFFFF) { // 0xFFFF: no more entries diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index 70e523d..b5fb818 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -80,6 +80,11 @@ always returns the suite it was built with, whether the BMC offers it or not. > `Session` it returns to `SerialOverLan(connector, session)`. The session that the console > opens by itself on another UDP port has the same limitation. +When the BMC serves SOL on another UDP port, a console opened with the host and password +constructors holds **two** sessions: the one it opened first, and the one on the SOL port. +`close()` only closes the second one, so the first stays open on the BMC until the BMC expires +it. On such a BMC, opening and closing consoles in a loop can use up its session slots. + If the session's privilege is too low to activate the payload, the client raises it to Administrator (Set Session Privilege Level) and tries again. The constructors throw `SOLException` when the payload cannot be activated (SOL disabled, no free payload instance, privilege refused). diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index 807de09..32deee1 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -73,7 +73,7 @@ The defaults come from two properties files packaged in the jar, read through th | `timeout` | `300000` | Per-message timeout, in ms | When each connection is created | | `retries` | `3` | How many times a failed message is sent again | When each `IpmiConnector` is created | | `idleTime` | `4000` | Upper bound of the random pause before a retry, in ms | When each `IpmiConnector` is created | -| `pingPeriod` | `30000` | Keep-alive period, in ms, when the configuration's `pingPeriod` is `-1` | When each `IpmiConnector` is created | +| `pingPeriod` | `30000` | Keep-alive period, in ms, of the connectors created with `IpmiConnector(int)` or `IpmiConnector(int, InetAddress)`; not applied to `IpmiClient` (see [Keep-alive](configuration.html#keep-alive)) | When each `IpmiConnector` is created | Override them at application startup, from a single thread, before the first IPMI call: the values then apply to every connection created afterwards, in the whole JVM. From 553e0b3db9144e9761972743f0862fe03d6e8d0f Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 12:41:30 +0200 Subject: [PATCH 10/13] Link the keep-alive issue from the documentation (#126) Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/configuration.md | 2 +- src/site/markdown/low-level-api.md | 2 +- src/site/markdown/timeouts-and-errors.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 7484fca..916d12a 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -115,7 +115,7 @@ inactivity during a long collection. `-1` is meant to use the `pingPeriod` of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms), but the connector that `IpmiClient` creates overwrites that value with `-1`, which disables the -keep-alive. Each `IpmiClient` call opens its own session and closes it when it is done, so this +keep-alive ([#126](https://github.com/metricshub/ipmi-java/issues/126)). Each `IpmiClient` call opens its own session and closes it when it is done, so this only matters for calls that last longer than the BMC's session inactivity timeout (typically 60 s), such as a long SDR walk or FRU read on a slow BMC: for those, **set `pingPeriod` explicitly**, for example to `30000`. diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index efb091e..3ad76e0 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -90,7 +90,7 @@ port (or always pass `0`), and call `tearDown()` when you are done with it. | Method | Purpose | | --- | --- | | `IpmiConnector(int port)`, `IpmiConnector(int port, InetAddress address)` | Bind the given local port (`0`: any free port), on all interfaces or on one, with the keep-alive period of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms). | -| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0` or a negative value, including `-1`: none). | +| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0` or a negative value, including `-1`: none, see [#126](https://github.com/metricshub/ipmi-java/issues/126)). | | `createConnection(InetAddress address[, int port])` | Register a connection to a BMC (port 623 by default). | | `createConnection(InetAddress address, [int port,] CipherSuite cipherSuite, PrivilegeLevel level)` | The same, skipping the cipher suite and capabilities steps: call `openSession()` next. | | `closeSession(handle)` | Log out (Close Session). | diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index 32deee1..af61e9b 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -73,7 +73,7 @@ The defaults come from two properties files packaged in the jar, read through th | `timeout` | `300000` | Per-message timeout, in ms | When each connection is created | | `retries` | `3` | How many times a failed message is sent again | When each `IpmiConnector` is created | | `idleTime` | `4000` | Upper bound of the random pause before a retry, in ms | When each `IpmiConnector` is created | -| `pingPeriod` | `30000` | Keep-alive period, in ms, of the connectors created with `IpmiConnector(int)` or `IpmiConnector(int, InetAddress)`; not applied to `IpmiClient` (see [Keep-alive](configuration.html#keep-alive)) | When each `IpmiConnector` is created | +| `pingPeriod` | `30000` | Keep-alive period, in ms, of the connectors created with `IpmiConnector(int)` or `IpmiConnector(int, InetAddress)`; not applied to `IpmiClient` ([Keep-alive](configuration.html#keep-alive), [#126](https://github.com/metricshub/ipmi-java/issues/126)) | When each `IpmiConnector` is created | Override them at application startup, from a single thread, before the first IPMI call: the values then apply to every connection created afterwards, in the whole JVM. From fa0809a64cb19fee6708222048a6d898436b0ba5 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 12:52:17 +0200 Subject: [PATCH 11/13] Address the eighth Codex review of the documentation (#113) Sensors: a record owned by a satellite controller or another LUN is read from the BMC by its number alone and can get an unrelated reading (#84); OEM sensors whose reply has a single state byte get no state; Full records with no analog reading still produce a reading line. FRU Inventory: FRU 0 can be returned twice (locator and system board), and a failed chunk in the middle of a FRU shifts the following data. All by the specification and the code, not seen on the test BMCs. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/fru-inventory.md | 10 +++++++--- src/site/markdown/sensors.md | 19 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index ac78000..3c06776 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -25,7 +25,8 @@ List frus = IpmiClient.getFrus(config); 3. attaches FRU 0 to the first **Compact Sensor** record of the system board entity, under the name ` `. This needs a **Board Info** area in FRU 0 (it gives the name) and such a Compact Sensor record: otherwise FRU 0 is returned only if a - FRU Device Locator record of the repository points to it. + FRU Device Locator record of the repository points to it. When both exist, FRU 0 is + returned **twice**, once for each. The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC but makes large FRUs slow to read: a few seconds per FRU on some BMCs @@ -35,8 +36,11 @@ offsets and sizes, and come out garbled or missing: this is wrong by the specifi not seen on the BMCs tested ([#85](https://github.com/metricshub/ipmi-java/issues/85)). A FRU whose data cannot be read — not present, or answering with an error or not at all at some -offset — is logged at the `WARN` level and reported truncated, or not at all. Get FRU Inventory -Area Info, which starts the read of each FRU, is less forgiving: +offset — is logged at the `WARN` level and reported truncated, or not at all. A chunk that fails +in the middle of a FRU is simply left out: the following chunks move up into its place, so the +fields after the gap can be decoded wrong (a plausible but incorrect serial number, for example) +rather than missing. Get FRU Inventory Area Info, which starts the read of each FRU, is less +forgiving: * for **FRU 0**, any failure (an error completion code or no reply) fails `getFrus()`, and therefore `getFrusAndSensorsAsStringResult()`; diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index 5758e76..3d7e7e2 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -29,10 +29,13 @@ OEM records are part of the walk but are not returned. A record the library cann logged and skipped; the walk goes on ([Supported Commands](supported-commands.html#sdr-records)). -The walk reads only the sensors of the **BMC's** SDR repository, through the BMC itself: sensors -owned by satellite controllers that the BMC does not bridge, and the Device SDRs of other -controllers, are not read ([#84](https://github.com/metricshub/ipmi-java/issues/84), -[#105](https://github.com/metricshub/ipmi-java/issues/105)). +The walk reads only the sensors of the **BMC's** SDR repository, and always asks the BMC itself +(LUN 0) for the reading: the Device SDRs of other controllers are not read +([#105](https://github.com/metricshub/ipmi-java/issues/105)), and a record owned by a satellite +controller or another LUN is read by its sensor number alone. Sensor numbers are only unique +per owner and LUN, so such a record can get the reading of an unrelated BMC sensor that has the +same number, or no reading at all ([#84](https://github.com/metricshub/ipmi-java/issues/84); by +the specification, not seen on the BMCs tested). ## The `Sensor` object @@ -112,7 +115,8 @@ The raw states are available as `getData().getStatesAsserted(record.getSensorType(), record.getEventReadingType())`, a list of [`ReadingType`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/ReadingType.html). For OEM sensors (event/reading type `0x7F`), whose states the specification does not define, -the state is the raw reading: `sensorName=0xHHLL`. +the state is the raw reading: `sensorName=0xHHLL`. A BMC that returns only the first state byte +(the second one is optional) gives such a sensor no state at all. ## Text output format @@ -162,7 +166,10 @@ Sensors that report `Device Absent` are left out. ### Reading lines One line per Full Sensor record with a reading, for the units below; sensors in other units are -not reported, and neither are sensors with no reading (raw value `0xFF`). +not reported, and neither are sensors with no reading (raw value `0xFF`). A Full Sensor record +whose data format says it has *no analog reading* is not left out: its line carries a value +computed from a byte that the BMC does not define as a reading (by the specification, not seen +on the BMCs tested). ```text Temperature;$sensorId;$sensorName;$deviceUniqueId;$value;$threshold1;$threshold2 From 69e7b7354ebda4277f702ffba6ac92196ce6df6e Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 13:03:18 +0200 Subject: [PATCH 12/13] Move the library bugs found by the Codex reviews to issues (#113) The pages document how the library is meant to work: the bugs that the Codex reviews of #124 found in the library are tracked as issues #127 to #132 (and #123, #125, #126) and are no longer described in the pages. The corrections of the documentation itself stay (cipher suite selection, closing the session in a finally block, the SEL example, the firewall port, System.exit(0), the scope of the sensors read). AGENTS.md: file library bugs found while writing documentation as issues instead of describing them in the pages. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- src/site/markdown/configuration.md | 22 ++-------- src/site/markdown/fru-inventory.md | 33 +++----------- src/site/markdown/low-level-api.md | 55 +----------------------- src/site/markdown/sensors.md | 39 +++++------------ src/site/markdown/serial-over-lan.md | 26 +---------- src/site/markdown/supported-commands.md | 8 +--- src/site/markdown/timeouts-and-errors.md | 10 ++--- src/site/markdown/troubleshooting.md | 2 +- 9 files changed, 32 insertions(+), 165 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1fdb4c0..ba8e193 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ Code quality reports (checkstyle, pmd/cpd, spotbugs) are generated by `mvn verif Always make sure that public API changes are properly documented in src/site/markdown/*.md and that README.md is always up-to-date. The published documentation is https://metricshub.org/ipmi-java. -The site is built with maven-site-plugin 4 and the Sentry Maven Skin (https://sentrysoftware.org/sentry-maven-skin/, see its "Vibe Writing" page for the syntax: callouts, tabs, `toc` macro). Every page starts with `keywords:` and `description:` headers, has a single H1 and the `toc` macro, and is listed in `src/site/site.xml` (Getting Started, Usage or Reference menu). Keep the pages true to the code: verify examples against the API (and, where they show output, against a real BMC), and link known limitations to their GitHub issue instead of hiding them. User-visible changes go to `upgrading.md`. +The site is built with maven-site-plugin 4 and the Sentry Maven Skin (https://sentrysoftware.org/sentry-maven-skin/, see its "Vibe Writing" page for the syntax: callouts, tabs, `toc` macro). Every page starts with `keywords:` and `description:` headers, has a single H1 and the `toc` macro, and is listed in `src/site/site.xml` (Getting Started, Usage or Reference menu). Keep the pages true to the code: verify examples against the API (and, where they show output, against a real BMC). File the library bugs found while writing documentation as GitHub issues instead of describing them in the pages, which document how the library is meant to work. User-visible changes go to `upgrading.md`. ## IPMI specifics diff --git a/src/site/markdown/configuration.md b/src/site/markdown/configuration.md index 916d12a..9787595 100644 --- a/src/site/markdown/configuration.md +++ b/src/site/markdown/configuration.md @@ -36,7 +36,7 @@ one constructor can be combined with those of another. | `bmcKey` | `null` | [BMC key](#bmc-key) | | `skipAuth` | required | [skipAuth](#skipauth) | | `timeout` | required, in **seconds** | [Timeout](#timeout) | -| `pingPeriod` | `-1`: no keep-alive | [Keep-alive](#keep-alive) | +| `pingPeriod` | `-1`: 30 000 ms | [Keep-alive](#keep-alive) | ### Host and port @@ -54,10 +54,6 @@ chosen by the operating system for each session. the library converts it to a `String` internally to open the session and does not clear the array, so clear it yourself once you no longer need the configuration. -The user name and the password are encoded with the **platform default charset**, and their -IPMI length limits (16 and 20 bytes) are not checked: stick to ASCII credentials of at most 16 -and 20 characters ([#90](https://github.com/metricshub/ipmi-java/issues/90)). - The client opens every session with the **User** privilege level, which is enough for every `IpmiClient` method. @@ -67,12 +63,6 @@ The client opens every session with the **User** privilege level, which is enoug logins. Leave it `null` (the default on virtually every BMC): the session keys are then derived from the password. See [Preparing the BMC](preparing-the-bmc.html#bmc-key-kg). -> [!WARNING] -> The library turns the key into a `String` and back with the platform default charset, which -> alters bytes that are not valid characters in that charset (any byte from `80h` with UTF-8): -> the session keys are then wrong and the login fails. Only keys made of ASCII bytes (`00h` to -> `7Fh`) work reliably ([#90](https://github.com/metricshub/ipmi-java/issues/90)). - ### skipAuth Despite its name, `skipAuth` does **not** skip authentication: the session is always @@ -108,17 +98,13 @@ inactivity during a long collection. | `pingPeriod` | Behavior | | --- | --- | +| `-1` (default) | The `pingPeriod` of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults): 30 000 ms | | `> 0` | One keep-alive message every `pingPeriod` ms | -| `-1` (default) | **No keep-alive messages** (see below) | | `0` (or any other negative value) | No keep-alive messages | -`-1` is meant to use the `pingPeriod` of -[`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms), but the -connector that `IpmiClient` creates overwrites that value with `-1`, which disables the -keep-alive ([#126](https://github.com/metricshub/ipmi-java/issues/126)). Each `IpmiClient` call opens its own session and closes it when it is done, so this +Each `IpmiClient` call opens its own session and closes it when it is done, so the keep-alive only matters for calls that last longer than the BMC's session inactivity timeout (typically -60 s), such as a long SDR walk or FRU read on a slow BMC: for those, **set `pingPeriod` -explicitly**, for example to `30000`. +60 s). Disable it (`0`) to keep the traffic to the strict minimum. ## Thread safety diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index 3c06776..440c831 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -23,32 +23,15 @@ List frus = IpmiClient.getFrus(config); 2. walks the **SDR repository** and, for each **FRU Device Locator** record of a *logical* FRU device (one accessed with the FRU commands of the BMC), reads that FRU the same way; 3. attaches FRU 0 to the first **Compact Sensor** record of the system board entity, under the - name ` `. This needs a **Board Info** area in FRU 0 - (it gives the name) and such a Compact Sensor record: otherwise FRU 0 is returned only if a - FRU Device Locator record of the repository points to it. When both exist, FRU 0 is - returned **twice**, once for each. + name ` `. The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC but makes large FRUs slow to read: a few seconds per FRU on some BMCs -([#102](https://github.com/metricshub/ipmi-java/issues/102)). FRUs that the BMC addresses in -**words** rather than bytes (as reported by Get FRU Inventory Area Info) are read at the wrong -offsets and sizes, and come out garbled or missing: this is wrong by the specification, though -not seen on the BMCs tested ([#85](https://github.com/metricshub/ipmi-java/issues/85)). - -A FRU whose data cannot be read — not present, or answering with an error or not at all at some -offset — is logged at the `WARN` level and reported truncated, or not at all. A chunk that fails -in the middle of a FRU is simply left out: the following chunks move up into its place, so the -fields after the gap can be decoded wrong (a plausible but incorrect serial number, for example) -rather than missing. Get FRU Inventory Area Info, which starts the read of each FRU, is less -forgiving: - -* for **FRU 0**, any failure (an error completion code or no reply) fails `getFrus()`, and - therefore `getFrusAndSensorsAsStringResult()`; -* for the other FRUs, an error completion code is logged and the FRU skipped, but no reply fails - the call, as does a Get SDR without a reply during the repository walk - ([Timeouts and Errors](timeouts-and-errors.html)). - -Physical FRU devices (EEPROMs on a private I²C bus, read with Master Write-Read) are not read. +([#102](https://github.com/metricshub/ipmi-java/issues/102)). + +A FRU whose data cannot be read — not present, or answering with an error at some offset — is +logged at the `WARN` level and reported truncated, or not at all. Physical FRU devices (EEPROMs on +a private I²C bus, read with Master Write-Read) are not read. ## The `Fru` object @@ -68,9 +51,7 @@ Each [`Fru`](apidocs/org/metricshub/ipmi/client/model/Fru.html) holds: The MultiRecord area (power supply, DC output, management access records) is decoded by the library but not returned by `getFrus()`; read it with the [low-level API](low-level-api.html) and -`ReadFruData.decodeFruData()` if you need it. Note that the decoder drops the last record of the -area, which is often the only one, such as the Power Supply Information record of a power supply -([#85](https://github.com/metricshub/ipmi-java/issues/85)). +`ReadFruData.decodeFruData()` if you need it. ```java for (Fru fru : IpmiClient.getFrus(config)) { diff --git a/src/site/markdown/low-level-api.md b/src/site/markdown/low-level-api.md index 3ad76e0..e775fae 100644 --- a/src/site/markdown/low-level-api.md +++ b/src/site/markdown/low-level-api.md @@ -90,7 +90,7 @@ port (or always pass `0`), and call `tearDown()` when you are done with it. | Method | Purpose | | --- | --- | | `IpmiConnector(int port)`, `IpmiConnector(int port, InetAddress address)` | Bind the given local port (`0`: any free port), on all interfaces or on one, with the keep-alive period of [`connection.properties`](timeouts-and-errors.html#library-wide-defaults) (30 000 ms). | -| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0` or a negative value, including `-1`: none, see [#126](https://github.com/metricshub/ipmi-java/issues/126)). | +| `IpmiConnector(int port, long pingPeriod)` | The same, with a [keep-alive period](configuration.html#keep-alive) in ms (`0`: none). | | `createConnection(InetAddress address[, int port])` | Register a connection to a BMC (port 623 by default). | | `createConnection(InetAddress address, [int port,] CipherSuite cipherSuite, PrivilegeLevel level)` | The same, skipping the cipher suite and capabilities steps: call `openSession()` next. | | `closeSession(handle)` | Log out (Close Session). | @@ -165,7 +165,7 @@ if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " + record.getEventDirection()); } else { - System.out.println("OEM entry " + record.getRecordId()); // fields not decoded, see #125 + System.out.println("OEM entry " + record.getRecordId()); // vendor-defined content } recordId = entry.getNextRecordId(); } @@ -182,15 +182,6 @@ Wed May 15 11:23:27 CEST 2024 PowerUnit PowerOffOrDown Assertion Wed May 15 11:23:34 CEST 2024 PowerUnit PowerOffOrDown Deassertion ``` -> [!WARNING] -> `GetSelEntry` decodes every entry with the layout of a *system event record* (type `02h`), -> including the OEM entries (types `C0h` to `FFh`) that vendors log in large numbers (more than -> half of the entries on a Lenovo IMM): their sensor and event fields are meaningless (and their -> timestamp too, for the non-timestamped types `E0h` to `FFh`), so check `getRecordType()` first, -> as above. An entry of type exactly `C0h` or `E0h` makes `sendMessage()` throw -> `IllegalArgumentException` (`Invalid value: 192` or `224`), which ends the walk, since the ID of -> the next entry is lost with it. See [#125](https://github.com/metricshub/ipmi-java/issues/125). - Exposing the SEL in `IpmiClient` is tracked in [#103](https://github.com/metricshub/ipmi-java/issues/103). @@ -246,48 +237,6 @@ public class GetDeviceId extends IpmiCommandCoder { } ``` -#### DCMI, group extension and OEM network functions - -The [`NetworkFunction`](apidocs/org/metricshub/ipmi/core/coding/payload/lan/NetworkFunction.html) -enum only lists the standard network functions (`00h` to `0Bh`). For a command of another -network function — DCMI (`2Ch`), the other group extensions (`2Eh` is OEM/Group), or a vendor's -OEM network function (`30h` to `3Fh`) — build the request with a subclass of `IpmiLanRequest` -that sets the raw code; `getNetworkFunction()` must still return a value, which is then unused. -Responses are matched by tag and command code, so nothing else changes. For example, Get DCMI -Capabilities Info: - -```java -/** An IPMI request with a raw network function code */ -class RawNetFnRequest extends IpmiLanRequest { - - RawNetFnRequest(int networkFunction, byte commandCode, byte[] requestData, int sequenceNumber) { - super(NetworkFunction.ApplicationRequest, commandCode, requestData, TypeConverter.intToByte(sequenceNumber)); - setNetworkFunctionCode(TypeConverter.intToByte(networkFunction)); - } -} - -public class GetDcmiCapabilities extends IpmiCommandCoder { - - // constructor, getResponseData() as above - - @Override - public NetworkFunction getNetworkFunction() { - return NetworkFunction.ApplicationRequest; // unused: see preparePayload() - } - - @Override - public byte getCommandCode() { - return 0x01; // Get DCMI Capabilities Info - } - - @Override - protected IpmiLanMessage preparePayload(int sequenceNumber) { - // NetFn 2Ch (DCMI); data: group extension ID DCh, parameter 1 (supported capabilities) - return new RawNetFnRequest(0x2C, getCommandCode(), new byte[] { (byte) 0xDC, 0x01 }, sequenceNumber); - } -} -``` - ## Asynchronous API [`IpmiAsyncConnector`](apidocs/org/metricshub/ipmi/core/api/async/IpmiAsyncConnector.html) has diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index 3d7e7e2..db60244 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -29,13 +29,10 @@ OEM records are part of the walk but are not returned. A record the library cann logged and skipped; the walk goes on ([Supported Commands](supported-commands.html#sdr-records)). -The walk reads only the sensors of the **BMC's** SDR repository, and always asks the BMC itself -(LUN 0) for the reading: the Device SDRs of other controllers are not read -([#105](https://github.com/metricshub/ipmi-java/issues/105)), and a record owned by a satellite -controller or another LUN is read by its sensor number alone. Sensor numbers are only unique -per owner and LUN, so such a record can get the reading of an unrelated BMC sensor that has the -same number, or no reading at all ([#84](https://github.com/metricshub/ipmi-java/issues/84); by -the specification, not seen on the BMCs tested). +The walk reads only the sensors of the **BMC's** SDR repository, through the BMC itself: sensors +owned by satellite controllers that the BMC does not bridge, and the Device SDRs of other +controllers, are not read ([#84](https://github.com/metricshub/ipmi-java/issues/84), +[#105](https://github.com/metricshub/ipmi-java/issues/105)). ## The `Sensor` object @@ -77,20 +74,14 @@ System 3.3V = 3.38 Volts (upper critical: 3.56) `getSensorBaseUnit()` returns a [`SensorUnit`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/SensorUnit.html); the six thresholds (`getLowerNonCriticalThreshold()` to `getUpperNonRecoverableThreshold()`) -are converted with the same formula. A threshold the BMC does not define, or does not make -readable, is left at `0.0`, the same value as a threshold that really is 0; on BMCs that do not -set the *init sensor type* bit of the record, every threshold is left at `0.0` -([#83](https://github.com/metricshub/ipmi-java/issues/83)). +are converted with the same formula, and are `0.0` when the BMC does not define them. > [!WARNING] > Known limitations of the decoding, by the IPMI 2.0 specification > (not all of them reproduced on real hardware): > -> * the *reading unavailable* and *scanning disabled* flags of Get Sensor Reading are ignored -> by `getSensorReading()` and by the text output: such a sensor is reported with whatever raw -> value the BMC returns, converted (`0.0` for the three unavailable sensors of the GIGABYTE BMC -> tested, but any value is possible). `getData().isSensorStateValid()` is `false` when the -> reading is unavailable, so check it as above; the scanning flag is not exposed +> * a sensor whose reading is flagged *unavailable* or whose scanning is disabled is still +> reported with a reading: check `getData().isSensorStateValid()` as above > ([#110](https://github.com/metricshub/ipmi-java/issues/110)); > * the non-linear conversions and the readability of each threshold are not fully handled > ([#83](https://github.com/metricshub/ipmi-java/issues/83)); @@ -115,8 +106,7 @@ The raw states are available as `getData().getStatesAsserted(record.getSensorType(), record.getEventReadingType())`, a list of [`ReadingType`](apidocs/org/metricshub/ipmi/core/coding/commands/sdr/record/ReadingType.html). For OEM sensors (event/reading type `0x7F`), whose states the specification does not define, -the state is the raw reading: `sensorName=0xHHLL`. A BMC that returns only the first state byte -(the second one is optional) gives such a sensor no state at all. +the state is the raw reading: `sensorName=0xHHLL`. ## Text output format @@ -166,10 +156,7 @@ Sensors that report `Device Absent` are left out. ### Reading lines One line per Full Sensor record with a reading, for the units below; sensors in other units are -not reported, and neither are sensors with no reading (raw value `0xFF`). A Full Sensor record -whose data format says it has *no analog reading* is not left out: its line carries a value -computed from a byte that the BMC does not define as a reading (by the specification, not seen -on the BMCs tested). +not reported, and neither are sensors with no reading (raw value `0xFF`). ```text Temperature;$sensorId;$sensorName;$deviceUniqueId;$value;$threshold1;$threshold2 @@ -191,12 +178,8 @@ Energy;$sensorId;$sensorName;$deviceUniqueId;$value * `$sensorId` is the SDR record ID, as 4 lowercase hexadecimal digits. * `$deviceUniqueId` is the entity of the sensor, as in the device state lines. -* Sensors whose reading the BMC flags as unavailable are **not** left out: their line carries - whatever value the BMC returned ([#110](https://github.com/metricshub/ipmi-java/issues/110)). -* Thresholds are rounded to integers, in the same unit as `$value`. A threshold is empty when - its decoded value is `0.0`: when the BMC does not define it or does not make it readable, but - also when it really is 0 (a lower fan threshold of 0 RPM, for example), and on BMCs affected by - [#83](https://github.com/metricshub/ipmi-java/issues/83). +* Thresholds are rounded to integers, in the same unit as `$value`, and empty when the BMC does + not define them. The chassis status has its own text form: [`getChassisStatusAsStringResult()`](chassis-status.html#as-text). diff --git a/src/site/markdown/serial-over-lan.md b/src/site/markdown/serial-over-lan.md index b5fb818..aac3cb7 100644 --- a/src/site/markdown/serial-over-lan.md +++ b/src/site/markdown/serial-over-lan.md @@ -52,13 +52,7 @@ try { This constructor opens a dedicated session with the **Administrator** privilege, activates the SOL payload, and owns the session: **closing the `SerialOverLan` closes the session and tears -down the connector** passed to it. Use a new connector for each console opened this way. When the -constructor fails after the session is open (SOL disabled, no free payload instance), nothing is -closed: hence the outer `finally`, as tearing down a connector twice is harmless. - -`close()` deactivates the SOL payload first. If that fails (an error, or no reply), `close()` -throws `IOException` without logging out: the outer `finally` still releases the connector, but -the session stays open on the BMC until the BMC expires it. +down the connector** passed to it. Use a new connector for each console opened this way. The cipher suite is chosen by a [`CipherSuiteSelectionHandler`](apidocs/org/metricshub/ipmi/core/api/sol/CipherSuiteSelectionHandler.html), @@ -73,18 +67,6 @@ always returns the suite it was built with, whether the BMC offers it or not. | `SerialOverLan(connector, host, port, user, password, selector)` | New session on another port | | `SerialOverLan(connector, session)` | Reuse a session opened with the low-level API; closing the console leaves it, and the connector, open. If the BMC serves SOL on another UDP port, the console uses an existing session on that port, or opens one (which closing the console closes, with the connector). | -> [!NOTE] -> The constructors that open their own session take no [BMC key](configuration.html#bmc-key): -> they cannot log in to a BMC configured with *two-key* logins. On such a BMC, open the session -> with the low-level API, `connector.openSession(handle, user, password, bmcKey)`, and pass the -> `Session` it returns to `SerialOverLan(connector, session)`. The session that the console -> opens by itself on another UDP port has the same limitation. - -When the BMC serves SOL on another UDP port, a console opened with the host and password -constructors holds **two** sessions: the one it opened first, and the one on the SOL port. -`close()` only closes the second one, so the first stays open on the BMC until the BMC expires -it. On such a BMC, opening and closing consoles in a loop can use up its session slots. - If the session's privilege is too low to activate the payload, the client raises it to Administrator (Set Session Privilege Level) and tries again. The constructors throw `SOLException` when the payload cannot be activated (SOL disabled, no free payload instance, privilege refused). @@ -95,12 +77,6 @@ Writes block until the BMC acknowledges the data, and return `false` when it is Data longer than the BMC's SOL payload size (announced when the payload is activated) is sent in several packets. -> [!WARNING] -> Until [#123](https://github.com/metricshub/ipmi-java/issues/123) is fixed, a write that needs -> **more than two packets** loses the data of its second packet, or throws -> `IllegalArgumentException` beyond three packets. This affects `writeBytes()`, `writeString()` -> and `writeIntArray()`: send long data in several short writes. - | Method | Writes | | --- | --- | | `writeBytes(byte[])`, `writeByte(byte)` | Raw bytes | diff --git a/src/site/markdown/supported-commands.md b/src/site/markdown/supported-commands.md index 4167d64..71359b4 100644 --- a/src/site/markdown/supported-commands.md +++ b/src/site/markdown/supported-commands.md @@ -10,9 +10,7 @@ commands below. Each command is a class of [`org.metricshub.ipmi.core.coding.commands`](apidocs/org/metricshub/ipmi/core/coding/commands/package-summary.html), sent with the [low-level API](low-level-api.html#sending-commands); the last column shows which ones `IpmiClient` uses. A command not listed here can be added by -[extending `IpmiCommandCoder`](low-level-api.html#writing-your-own-command), with a -[raw network function](low-level-api.html#dcmi-group-extension-and-oem-network-functions) for -DCMI, group extension and OEM commands. +[extending `IpmiCommandCoder`](low-level-api.html#writing-your-own-command). ## Commands @@ -99,10 +97,6 @@ the `WARN` level and goes on with the next record, so one unexpected record does whole sensor list. When a BMC answers a whole-record Get SDR with fewer bytes than the record declares, the client reads the record again in chunks. -A Get SDR reply that carries the next record ID but **no record byte at all** is not handled: -the Get SDR decoder rejects it (`IllegalArgumentException: Invalid response payload length`), -which ends the walk and fails `getSensors()` or `getFrus()`. - ## FRU records [`ReadFruData.decodeFruData()`](apidocs/org/metricshub/ipmi/core/coding/commands/fru/ReadFruData.html) diff --git a/src/site/markdown/timeouts-and-errors.md b/src/site/markdown/timeouts-and-errors.md index af61e9b..cd94f39 100644 --- a/src/site/markdown/timeouts-and-errors.md +++ b/src/site/markdown/timeouts-and-errors.md @@ -73,7 +73,7 @@ The defaults come from two properties files packaged in the jar, read through th | `timeout` | `300000` | Per-message timeout, in ms | When each connection is created | | `retries` | `3` | How many times a failed message is sent again | When each `IpmiConnector` is created | | `idleTime` | `4000` | Upper bound of the random pause before a retry, in ms | When each `IpmiConnector` is created | -| `pingPeriod` | `30000` | Keep-alive period, in ms, of the connectors created with `IpmiConnector(int)` or `IpmiConnector(int, InetAddress)`; not applied to `IpmiClient` ([Keep-alive](configuration.html#keep-alive), [#126](https://github.com/metricshub/ipmi-java/issues/126)) | When each `IpmiConnector` is created | +| `pingPeriod` | `30000` | Keep-alive period, in ms, when the configuration's `pingPeriod` is `-1` | When each `IpmiConnector` is created | Override them at application startup, from a single thread, before the first IPMI call: the values then apply to every connection created afterwards, in the whole JVM. @@ -118,12 +118,10 @@ Common causes wrapped in the `ExecutionException`: Some problems are logged at the `WARN` level and the call goes on with what it could collect: * an **SDR record** that cannot be decoded — a reserved record type, a record shorter than its - header — is skipped and the repository walk continues with the next record (a Get SDR reply - without any record byte fails the call instead, see - [Supported Commands](supported-commands.html#oem-and-unknown-records)); + header — is skipped and the repository walk continues with the next record + ([Supported Commands](supported-commands.html#oem-and-unknown-records)); * a **FRU** that cannot be read (for example a FRU device that is not present) is reported - truncated or not at all — except when Get FRU Inventory Area Info fails for FRU 0, or gets no - reply for any FRU ([FRU Inventory](fru-inventory.html#how-the-frus-are-read)); + truncated or not at all ([FRU Inventory](fru-inventory.html#how-the-frus-are-read)); * a **sensor** whose reading is not available (completion code `DataNotPresent`) is returned without reading data. diff --git a/src/site/markdown/troubleshooting.md b/src/site/markdown/troubleshooting.md index 13d6b8e..d9e05e8 100644 --- a/src/site/markdown/troubleshooting.md +++ b/src/site/markdown/troubleshooting.md @@ -81,7 +81,7 @@ to make it use suite 3 or 17. | `WARN Failed to read FRU at offset ... Requested Sensor, data, or record not present` | The FRU is declared in the SDR repository but not present, for example an empty power supply bay. Usually harmless. | | `WARN Failed to decode FRU ` | The FRU data is not in the IPMI FRU format (for example the SPD data of a memory module, [#107](https://github.com/metricshub/ipmi-java/issues/107)). | | A sensor known to `ipmitool` is not returned | Only Full and Compact sensor records of the BMC's own repository are read: sensors behind satellite controllers are not ([#84](https://github.com/metricshub/ipmi-java/issues/84)), and shared Compact records are not expanded ([#100](https://github.com/metricshub/ipmi-java/issues/100)). | -| A sensor reads `0.0`, or a value that makes no sense | The BMC may flag the reading as unavailable (or the sensor's scanning as disabled), which the library ignores: the value is whatever raw byte the BMC returned. `getData().isSensorStateValid()` is `false` for an unavailable reading; the text output does not check it ([#110](https://github.com/metricshub/ipmi-java/issues/110)). | +| A sensor reads `0.0` | The BMC flags the reading as unavailable, which is not checked yet ([#110](https://github.com/metricshub/ipmi-java/issues/110)). | | Negative processor temperatures (`CPU1 DTS = -44.0`) | Not an error: Intel *Digital Thermal Sensor* readings are the margin below the maximum junction temperature. | ## The JVM does not exit From 039cd2e19dc257fed9aa44eed70a21cf8d3c9232 Mon Sep 17 00:00:00 2001 From: Bertrand Martin Date: Thu, 8 Oct 2026 13:05:02 +0200 Subject: [PATCH 13/13] Do not over-promise FRU chunk sizes and sensor states (#113) The 16-byte FRU chunks are small, not guaranteed small enough for every BMC, and getStates() only returns the states that have an ipmiutil description. The library sides are added to #128 and #129. Co-Authored-By: Claude Opus 5.5 --- src/site/markdown/fru-inventory.md | 4 ++-- src/site/markdown/sensors.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/site/markdown/fru-inventory.md b/src/site/markdown/fru-inventory.md index 440c831..88b879c 100644 --- a/src/site/markdown/fru-inventory.md +++ b/src/site/markdown/fru-inventory.md @@ -25,8 +25,8 @@ List frus = IpmiClient.getFrus(config); 3. attaches FRU 0 to the first **Compact Sensor** record of the system board entity, under the name ` `. -The FRU data is read in chunks of 16 bytes, which keeps every request small enough for any BMC -but makes large FRUs slow to read: a few seconds per FRU on some BMCs +The FRU data is read in chunks of 16 bytes, which keeps the requests small but makes large FRUs +slow to read: a few seconds per FRU on some BMCs ([#102](https://github.com/metricshub/ipmi-java/issues/102)). A FRU whose data cannot be read — not present, or answering with an error at some offset — is diff --git a/src/site/markdown/sensors.md b/src/site/markdown/sensors.md index db60244..126c290 100644 --- a/src/site/markdown/sensors.md +++ b/src/site/markdown/sensors.md @@ -93,8 +93,8 @@ are converted with the same formula, and are `0.0` when the BMC does not define ### States Discrete sensors (presence, redundancy, power supply status, processor status, ...) report a set -of asserted **states** instead of a value. `getStates()` returns each asserted state with a -description, in the wording of `ipmiutil`, as `sensorName=state`, separated with `|`: +of asserted **states** instead of a value. `getStates()` returns the asserted states that have +a description in the wording of `ipmiutil`, as `sensorName=state`, separated with `|`: ```text PSU Redundancy=Fully Redundant