openbmc_docs/designs/virtual-media.md
2024-12-23 14:53:31 +08:00

437 lines
29 KiB
Markdown

# Virtual Media (a.k.a. Remote Media)
Author: Pawel Rapkiewicz <pawel.rapkiewicz@intel.com> <pawelr>
Other contributors: Przemyslaw Czarnowski
<przemyslaw.hawrylewicz.czarnowski@intel.com> Anna Platash
<anna.platash@intel.com>
Created: 6/4/2019
## Problem Description
Virtual Media allows users to remotely mount given ISO/IMG drive images through
BMC to Server HOST. The Remote drive is visible in HOST as USB storage device,
and operates in RO mode, or RW mode (keeping in mind container limitations, and
write protection switches). This can even be used to install OS on bare metal
system. This document focuses on few redirection options, like in-browser
ISO/IMG image mounting, and remote CIFS/HTTPS image mounting.
## References
- Virtual Media is going to use Network Block Device as primary disk image
forwarder.
- NBDkit is being used, to serve images from remote storages over HTTPS/CIFS.
USBGadget as way to expose Media Storage Device to HOST.
## Requirements
None
## Proposed Design
Virtual Media splits into two modes of operation, lets call it Proxy, and
Legacy.
- Proxy mode - works directly from browser and uses JavaScript/HTML5 to
communicate over Secure WebSockets directly to HTTPS endpoint hosted by bmcweb
on BMC.
- Legacy mode - is initiated from browser using Redfish defined VirtualMedia
schemas, then BMC process connects to external CIFS/HTTPS image pointed during
initialization.
Both methods inherit from default Redfish/BMCWeb authentication and privileges
mechanisms.
The component diagram below shows Virtual Media high-level overview
```ascii
+------------------+ +----------------------------------+ +-----------------------+
|Remote Device| | |BMC| +------------+ | |HOST| |
+-------------/ | +---/ +--Dbus----->+VirtualMedia| | +----/ |
| | | v +------------+ | | |
| +------------+ | | +-+--------+ | | |
| |WebBrowser +<----HTTPS------->+BMCWeb | +---------+ | | +----------+ |
| +------------+ | | +-+--------+ |USBGadget+<--------->+USB Device| |
+------------------+ | ^ +----+----+ | | +----------+ |
| | ^ | | |
| | +------+ v | | |
| +--->+UNIX | +----+----+ | | |
| |SOCKET+<->+NBDClient| | | |
+------------------+ | +--->+ | +---------+ | | |
|Remote Storage| | | | +------+ | | |
+--------------/ | | | | | |
| | | v | | |
| +-----------+ | | +-+-------+ | | |
| |ISO/IMG +<---CIFS/HTTPS+-->+NBDkit | | | |
| +-----------+ | | +---------+ | +-----------------------+
| | | |
+------------------+ +----------------------------------+
```
Virtual Media feature supports multiple, simultaneous connections in both modes.
### Asynchronicity
Mounting and unmounting of remote device could be time consuming. Virtual Media
shall support asynchronicity at the level of DBus and optionally Redfish API.
Default timeouts for DBus (25 seconds) and for Redfish (60 seconds) may be
insufficient to perform mounting and unmounting in some cases.
Asynchronous responses will be described later on in appropriate sections.
### Network Block Device (NBD)
Reader can notice that most connections on diagram are based on Network Block
Device. After Sourceforge project description:
> With this compiled into your kernel, Linux can use a remote server as one of
> its block devices. Every time the client computer wants to read /dev/nbd0, it
> will send a request to the server via TCP, which will reply with the data
> requested. This can be used for stations with low disk space (or even
> diskless - if you use an initrd) to borrow disk space from other computers.
> Unlike NFS, it is possible to put any file system on it. But (also unlike
> NFS), if someone has mounted NBD read/write, you must assure that no one else
> will have it mounted.
>
> -- [https://nbd.sourceforge.io/](https://nbd.sourceforge.io/)
In Virtual Media use case, it's being used to pull data from remote client, and
present it into non BMC mounted `/dev/nbdXX` device. Then the block device is
being provided to Host through USB Gadget.
### USB Gadget
Part of Linux kernel that makes _emulation_ of certain USB device classes
possible through USB "On-The-Go", if connect appropriately to Host. In Virtual
Media case it emulates USB mass storage connected to HOST. The source or
redirection is block device created by nbd-client `/dev/nbdXX`
### Proxy Mode
Proxy Mode uses browser JavaScript and WebSockets support, to create JS NBD
Server. Browser is responsible for create HTTPS session, authenticate user, and
receive given privileges, then upgrade HTTPS session to WSS, through mechanisms
described by [RFC6455](HTTPS://tools.ietf.org/html/rfc6455). Since WSS upgrade,
JS application is responsible for handling all required by specification NBD
Server commands.
Multiple, simultaneous connections are supported per opening connections on
different URIs in HTTPS server. Number of available simultaneous connections is
being defined in configuration file described in next chapter.
Encryption for proxy is supported through HTTPS/WSS channel and inherits
encryption mechanisms directly from HTTPS server, all data transactions go
through bmcweb.
The initialization of connection will look as on diagram:
```ascii
┌───────┐ ┌──────┐ ┌────────────┐ ┌─────────┐ ┌────┐ ┌─────────┐
│Browser│ │bmcweb│ │VirtualMedia│ │NBDClient│ │uDEV│ │USBGadget│
└───┬───┘ └──┬───┘ └─────┬──────┘ └────┬────┘ └─┬──┘ └────┬────┘
│ establish HTTPS session │ │ │ │ │
│─────────────────────────> │ │ │ │
│ │ │ │ │ │
│ upgrade to WSS on │ │ │ │ │
│ /nbd/X endpoint │ ╔══════════════╧════╗ │ │ │
│─────────────────────────> ║* bmcweb creates ░║ │ │ │
│ │ ║ /tmp/nbd.X.sock ║ │ │ │
│ │ ║* bmcweb locks new ║ │ │ │
│ │ ║ connections on ║ │ │ │
│ │ ║ endpoint /nbd/X ║ │ │ │
│ │ ╚══════════════╤════╝ │ │ │
│ │ Mount from: │ │ │ │
│ │ /tmp/nbd.X.sock │ │ │ │
│ │ ────────────────> │ │ │
│ │ │ │ │ │
│ │ │ Spawn NBDClient from │ │ │
│ │ │ /tmp/nbd.x.sock │ │ │
│ │ │ to /dev/nbdX ┌┴┐ │ │
│ │ │ ──────────────────> │ │ │ │
│ │ │ │ │ │ │
│ │ │ Block Device │ │ │ │
│ │ │ properties changed │ │ │ │
│ │ │ <───────────────────────────────│ │
│ │ │ │ │ │ │
│ │ │ Configure USB mass │ │
│ │ │ storage from /dev/nbd/X │ │
│ │ │ ─────────────────────────────────────────>│
│ │ │ │ │ │ │
│ │ Completion │ | | │ │
│ │ signal │ | | │ │
│ │ <───────────────│ │ │ │ │
│ │ │ │ │ │ │
│ │ │ Data │ │ │ │
│<─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─>│
│ │ │ │ │ │ │
```
### Legacy Mode
Legacy Mode uses VirtualMedia schema, defined by DMTF, for mounting external
CIFS/HTTPS images. The current implementation supports only stream mounting as
for now. In this case Redfish is only used as mechanism for Virtual Media
initialization, and is not responsible for data transmission. For data there is
a separate component responsible for handling CIFS/HTTPS traffic called NBDkit.
Multiple, simultaneous connections are supported through spawning additional
nbkit instances, the number of available instances for CIFS/HTTPS is configured
and described in details in next chapter.
Encryption is based on remote storage connection, and follows Intel's Best
security practices, as remote server support such (i.e. HTTPS requires SSL, and
pure HTTP is not supported, for CIFS protocol version 3.0 allows enabling
encryption and that will be provided).
The flow looks like below:
```ascii
┌───────┐ ┌──────────┐ ┌──────┐ ┌────────────┐ ┌──────┐┌─────────┐┌────┐ ┌─────────┐
│Browser│ │CIFS/HTTPS│ │bmcweb│ │VirtualMedia│ │NBDkit││NBDClient││uDEV│ │USBGadget│
└───┬───┘ └────┬─────┘ └──┬───┘ └─────┬──────┘ └──┬───┘└────┬────┘└─┬──┘ └────┬────┘
│establish HTTPS session│ │ │ │ │ │
│───────────────────────> │ │ │ │ │
│ │ │ │ │ │ │ │
│Create new VirtualMedia│ │ │ │ │ │
│ mountpoint via POST │ │ │ │ │ │
│───────────────────────> │ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ Mount from │ │ │ │ │
│ │ │ CIFS/HTTPS │ │ │ │ │
│ │ │ location │ │ │ │ │
│ │ │────────────> │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │Spawn NBDKit mounting│ │ │ │
│ │ │ │ given location │ │ │ │
│ │ │ │ appropriate │ │ │ │
│ │ │ │ /tmp/nbd.X.sock ┌┴┐ │ │ │
│ │ │ │ ──────────────────>│ │ │ │ │
│ │ │ │ │ │ │ │ │
│ │ │ │ Spawn NBDClient from │ │ │
│ │ │ │ /tmp/nbd.X.sock to /dev/nbdX ┌┴┐ │ │
│ │ │ │ ────────────────────────────>│ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ Block Device properties changed │ │
│ │ │ │ <────────────────────────────────────── │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ Configure USB mass storage from /dev/nbd/X │
│ │ │ │ ───────────────────────────────────────────────>│
│ │ │ │ │ │ │ │ │ │
│ │ │ Completion │ │ │ │ │ │ │
│ │ │ signal │ │ │ │ │ │ │
│ │ |<───────────│ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ Data │ │ │ │ │ │
│ │ <─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─>│
```
### Redfish support
Virtual Media Service will be exposed as Redfish VirtualMedia endpoint as
defined by DMTF. Here are some examples.
#### Virtual Media Collection schema
Members in collection will be defined based on configuration file described in
next sections. And will be visible despite media is inserted or not.
```json
{
"@odata.type": "#VirtualMediaCollection.VirtualMediaCollection",
"Name": "Virtual Media Services",
"Description": "Redfish-BMC Virtual Media Service Settings",
"Members@odata.count": 2,
"Members": [
{
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia/ISO0"
},
{
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia/1"
}
],
"@odata.context": "/redfish/v1/$metadata#VirtualMediaCollection.VirtualMediaCollection",
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia"
}
```
#### Virtual Media schema
```json
{
"@odata.type": "#VirtualMedia.v1_1_0.VirtualMedia",
"Id": "ISO0",
"Name": "Virtual Removable Media",
"Actions": {
"#VirtualMedia.InsertMedia": {
"target": "/redfish/v1/Managers/bmc/VirtualMedia/ISO0/Actions/VirtualMedia.InsertMedia"
},
"#VirtualMedia.EjectMedia": {
"target": "/redfish/v1/Managers/bmc/VirtualMedia/ISO0/Actions/VirtualMedia.EjectMedia"
}
},
"MediaTypes": ["CD", "USBStick"],
"Image": "https://192.168.0.1/Images/os.iso",
"ImageName": "Os",
"ConnectedVia": "URI",
"Inserted": true,
"WriteProtected": false,
"@odata.context": "/redfish/v1/$metadata#VirtualMedia.VirtualMedia",
"@odata.id": "/redfish/v1/Managers/BMC/VirtualMedia/ISO0",
"Oem": {
"OpenBMC": {
"@odata.type": "#OemVirtualMedia.v1_0_0.VirtualMedia",
"WebSocketEndpoint": "/nbd/0"
}
}
}
```
Schema will look similar for both Proxy and Legacy Mode. Some key differences as
follows:
| Field Name | Proxy Mode | Legacy Mode | Comment |
| -------------------- | ---------- | -------------------------------- | --------------------------------------------------------------- |
| InsertMedia | N/A | action as described by DMTF spec | Action can return Task object if<br> process is time consumming |
| Image | N/A | image location | |
| ImageName | N/A | image name | |
| ConnectedVia | "Applet" | as described by DMTF spec | applies only for connected media |
| TransferMethod | "Stream" | "Stream" | "upload" is not supported by design |
| TransferProtocolType | "OEM" | as described by DMTF spec | |
#### Virtual Media OEM Extension
Virtual Media schema is adapted to Legacy Mode where image is given by user
directly via Redfish action and whole connection is processed between service
and web server.
For [Proxy Mode](#Proxy-Mode) nbd data is served by client web browser. Having
multiple connections, in order to setup connection, client needs the information
about the location of websocket created by web server. This value is exposed as
OEM `WebSocketEndpoint` property for each item.
### Inactivity timeout
Virtual Media supports inactivity timeout, which will break Virtual Media
connection after certain number of seconds of inactivity. Because nbdclient has
mechanism for caching image, also kernel has home buffer mechanisms for block
device, the idea is to prepare a patch on USBGadget driver, that will write USB
gadget statistics under /proc/USBGadget/lun.X file. Those statistics will be
observed by Virtual Media application.
### Virtual Media Service
Virtual Media Service is separate application that will coexist on DBus. It will
be initialized from configuration json file, and expose all available for
provisioning VirtualMedia objects. Virtual Media is responsible for:
- Exposing current Virtual Media configuration to DBus user.
- Spawning nbdclient, and monitor its lifetime, for Proxy connections.
- Spawning nbdkit, and monitor its lifetime, for CIFS/HTTPS connections.
- Monitoring uDEV for all NBD related block device changes, and
configure/de-configure USB Gadget accordingly.
- Monitor NBD device inactivity period to support inactivity timeout.
### Configuration
Upon process startup, Virtual Media reads its config file, with the following
structure:
```json
"InactivityTimeout": 1800, # Timeout of inactivity on device in seconds, that will lead to automatic disconnection
"MountPoints": {
"ISO0": {
"EndpointId": "/nbd/0", # bmcweb endpoint (URL) configured for this type of connection
"Mode": 0, # 0 - Proxy Mode, 1 - Legacy Mode
"NBDDevice": "/dev/nbd0", # nbd endpoint on device usually matches numeric value with EndpointId
"UnixSocket": "/tmp/nbd.sock", # defines which Unix socket will be occupied by connection
"Timeout": 30, # timeout in seconds passed to nbdclient
"BlockSize": 512, # Block size passed to nbdclient
}
},
```
### DBus Interface
Virtual Media will expose the following object structure. All object paths are
representation of configuration file described above
```ascii
/xyz/openbmc_project/VirtualMedia/Proxy/ISO0
/xyz/openbmc_project/VirtualMedia/Proxy/1
/xyz/openbmc_project/VirtualMedia/Legacy/0
/xyz/openbmc_project/VirtualMedia/Legacy/1
```
Each of object will implement `xyz.openbmc_project.VirtualMedia.Process`
interface, which will be defined as follow:
| Name | type | input | return | description |
| -------- | -------- | ----- | ------- | ------------------------------------------------------------------- |
| Active | Property | - | BOOLEAN | `True`, if object is occupied by active process, `False` otherwise |
| ExitCode | Property | - | INT32 | If process terminates this property will contain returned exit code |
Each object will also expose configuration of its own under
`xyz.openbmc_project.VirtualMedia.MountPoint` (all properties are RO)
| Name | type | input | return | description |
| -------------------------- | -------- | ----- | ------- | ------------------------------------------------------------------------- |
| EndPointId | Property | - | STRING | As per configuration |
| Mode | Property | - | BYTE | As per configuration |
| Device | Property | - | STRING | As per configuration |
| Socket | Property | - | STRING | As per configuration |
| Timeout | Property | - | UINT16 | As per configuration |
| BlockSize | Property | - | UINT16 | As per configuration |
| RemainingInactivityTimeout | Property | - | UINT16 | Seconds to drop connection by server, for activated endpoint, 0 otherwise |
| ImageURL | Property | - | STRING | URL to mounted image |
| WriteProtected | Property | - | BOOLEAN | 'True', if the image is mounted as read only, 'False' otherwise |
Another interface exposed by each object are stats under
`xyz.openbmc_project.VirtualMedia.Stats` (all properties are RO):
| Name | type | input | return | description |
| ------- | -------- | ----- | ------ | ---------------------------------------- |
| ReadIO | Property | - | UINT64 | Number of read IOs since image mounting |
| WriteIO | Property | - | UINT64 | Number of write IOs since image mounting |
Depends on object path, object will expose different interface for mounting
image.
Mounting can be a time consuming task, so event driven mechanism has to be
introduced. Mount and Unmount calls will trigger asynchronous action and will
end immediately, giving appropriate signal containing status on task completion.
For Proxy its `xyz.openbmc_project.VirtualMedia.Proxy`, defined as follow:
| Name | type | input | return | description |
| ---------- | ------ | ----- | ------- | ------------------------------------------------------------------------------ |
| Mount | Method | - | BOOLEAN | Perform an asynchronous operation of mounting to HOST on given object. |
| Unmount | Method | - | BOOLEAN | Perform an asynchronous operation of unmount from HOST on given object |
| Completion | Signal | - | INT32 | Returns 0 for success or errno on failure after background operation completes |
For Legacy its `xyz.openbmc_project.VirtualMedia.Legacy`, defined as follow:
| Name | type | input | return | description |
| ---------- | ------ | ----------------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Mount | Method | STRING | BOOLEAN | Perform an asynchronous operation of mounting to HOST on given object, with location given as STRING parameter |
| Mount | Method | STRING<br>BOOLEAN<br>VARIANT<UNIX_FD,INT> | BOOLEAN | Perform an asynchronous operation of mounting to HOST on given object, with parameters:<br><br>`STRING` : url to image. It should start with either `smb://` or `https://` prefix<br>`BOOLEAN` : RW flag for mounted gadget (should be consistent with remote image capabilities)<br>`VARIANT<UNIX_FD,INT>` : file descriptor of named pipe used for passing null-delimited secret data (username and password). When there is no data to pass `-1` should be passed as `INT` |
| Unmount | Method | - | BOOLEAN | Perform an asynchronous operation of unmounting from HOST on given object |
| Completion | Signal | - | INT32 | Returns 0 for success or errno on failure after background operation completes |
Mount and unmount operation return true if async operation is started and false
when preliminary check encountered an error. They may also indicate appropriate
dbus error.
## Alternatives Considered
Existing implementation in OpenBMC
## Impact
Shall not affect usability of current Virtual Media implementation
## Testing
TBD