diff --git a/AGENTS.md b/AGENTS.md index 78010e7..ba8e193 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). 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/README.md b/README.md index f6afafd..303cbe3 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 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); +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..88b879c --- /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 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 +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 + +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..558fa11 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -1,109 +1,130 @@ +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 the sensors 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 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)). -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..e775fae --- /dev/null +++ b/src/site/markdown/low-level-api.md @@ -0,0 +1,263 @@ +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.Comparator; +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, else 3 + List suites = connector.getAvailableCipherSuites(handle); + CipherSuite cipherSuite = 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")); + + // 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); + 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(); + } + } +} +``` + +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: ...`. 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 + +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, 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`: 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, 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 +// 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()); + +if (info.getEntriesCount() > 0) { // Get SEL Entry fails on an empty SEL + // 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 + GetSelEntryResponseData entry = (GetSelEntryResponseData) connector + .sendMessage(handle, new GetSelEntry(IpmiVersion.V20, cipherSuite, AuthenticationType.RMCPPlus, + reservationId, recordId)); + SelRecord record = entry.getSelRecord(); + if (record.getRecordType() == SelRecordType.System) { + System.out.println(record.getTimestamp() + " " + record.getSensorType() + " " + record.getEvent() + " " + + record.getEventDirection()); + } else { + System.out.println("OEM entry " + record.getRecordId()); // vendor-defined content + } + recordId = entry.getNextRecordId(); + } +} +``` + +```text +SEL entries: 643 +Wed May 15 11:15:25 CEST 2024 EventLoggingDisabled LogAreaReset 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 +``` + +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..3a20aab --- /dev/null +++ b/src/site/markdown/preparing-the-bmc.md @@ -0,0 +1,153 @@ +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+), 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 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` + +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..126c290 --- /dev/null +++ b/src/site/markdown/sensors.md @@ -0,0 +1,185 @@ +keywords: sensors, sdr, sensor data record, sensor reading, thresholds, temperature, voltage, fan, power, states, text output format, ipmiresultconverter +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 + + + +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 the sensors that have a reading. + +```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)) { + // 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() + + " (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 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)); +> * 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 the asserted states that have +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..aac3cb7 --- /dev/null +++ b/src/site/markdown/serial-over-lan.md @@ -0,0 +1,117 @@ +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 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.sync.IpmiConnector; +import org.metricshub.ipmi.core.coding.security.CipherSuite; + +// 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")); + +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. + +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, +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, whether the BMC offers it or not. + +| 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. +Data longer than the BMC's SOL payload size (announced when the payload is activated) is sent in +several packets. + +| 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..71359b4 --- /dev/null +++ b/src/site/markdown/supported-commands.md @@ -0,0 +1,120 @@ +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 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 — 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. + +## 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..cd94f39 --- /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(0)`. + +## 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 — a reserved record type, a record shorter than its + 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 ([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. + +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..d9e05e8 --- /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(0)`. + +## 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 +