iscsi-client-rs
A pure‑Rust iSCSI initiator library (with example CLI) for interacting with iSCSI targets over TCP. Build/parse PDUs, perform login (plain or CHAP), and exchange SCSI commands asynchronously.
⚠️ Status: tested against Linux
tgtonly. Other targets may behave differently. Use with care.
Features
- iSCSI Login across Security → Operational → Full‑Feature phases
- CHAP (MD5) authentication (challenge parsing + response building)
- Plain login (no authentication)
- Async networking via Tokio
- High‑level state machines for:
- Login (plain & CHAP)
- NOP (Nop-Out / Nop-In)
- SCSI READ(10) (Data-In)
- SCSI WRITE(10) (Data-Out; ImmediateData path)
- Logout
- Zero C dependencies
Quick start
Install
# Cargo.toml
[]
= "*"
Connect + login (state machine)
async
Architecture overview
Connection & PDU framing
Connection wraps a Tokio TCP stream and frames iSCSI PDUs by their 48‑byte BHS. It:
- writes PDUs via
ToBytes - reads headers + payload with a timeout
- coalesces multi‑segment payloads using
Continue/Finalbits - completes a pending request by Initiator Task Tag (ITT) and delivers a fully reconstructed
PDUWithData<T>to the caller
Sequence & task numbers
- ITT (Initiator Task Tag): request correlation per command/flow
- CmdSN / ExpStatSN: maintained by caller; on responses we bump
ExpStatSN = stat_sn + 1
Utility builders produce PDUs with correct fields; state machines update counters for you at the right moments.
Login state machine
Two branches share a common context LoginCtx and output LoginStatus.
-
Plain: single step —
Operational → FullFeature+ login keys -
CHAP: four steps
Security → Security— advertise security keys (noCHAP_A)Security → Security— sendCHAP_A=5Security → Operational (Transit)— computeCHAP_RfromCHAP_I/CHAP_C, sendCHAP_N/CHAP_ROperational → FullFeature (Transit)— send operational keys
APIs:
pub async ;
CHAP details
- Parses challenge text for
CHAP_IandCHAP_C(accepts0x…or raw hex) - Computes
CHAP_R = MD5( one-octet CHAP_I || secret || challenge )as uppercase hex with0xprefix
NOP state machine
Ping the target and verify ExpStatSN handling.
let lun = ;
let ttt = DEFAULT_TAG;
let mut nctx = new;
// one round trip
let _st = run_nop.await?;
SCSI commands
READ(10) state machine (Data‑In)
ReadStart— sendsSCSI READ(10)(16‑byte padded CDB) and records countersReadWait— drains allScsiDataInfragments, appends data, updatesExpStatSNon the fragment that carriesstat_sn, and returns the assembled buffer
let mut cdb = ;
build_read10;
let mut rctx = ReadCtx ;
let rr = run_read.await?;
println!;
WRITE(10) state machine (Data-Out)
Both paths are supported:
-
ImmediateData: if
ImmediateData=Yesanddata_len ≤ FirstBurstLength, the payload is embedded in theScsiCommandRequest(one PDU). We then wait forScsiCommandResponseand validateResponseCode/Status. -
R2T (Ready-To-Transfer): if the payload can’t fit in ImmediateData (or the target requires R2T), we honor each R2T by sending one or more
Data-OutPDUs with the provided TTT and BufferOffset. Segmentation respects the negotiated MaxRecvDataSegmentLength (MRDSL) and MaxBurstLength; the last PDU in a burst has Final=1. (AssumesDataPDUInOrder=YesandDataSequenceInOrder=Yestoday.)
let mut cdb = ;
build_write10;
// The state machine will choose ImmediateData or R2T based on negotiation
// (ImmediateData, FirstBurstLength, MaxBurstLength, MRDSL).
let mut wctx = WriteCtx ;
let ws = run_write.await?;
println!;
Notes:
- If
ImmediateData=Noor the payload exceedsFirstBurstLength, the flow switches to R2T.- Each R2T defines the allowed window (offset/length);
Data-OutPDUs are sliced tomin(MRDSL, remaining_in_burst).
Testing via just
# List available libiscsi tests
# Bring up target + mapper and run a basic test (no CHAP)
# Run the destructive CompareAndWrite suite
# Run a test with CHAP enabled
# Follow mapper logs
# Tear everything down
&&
Tip: you can override addresses/ports and IQN via env vars used in your justfile (e.g., MAPPER_ADDR, MAPPER_PORT, ISCSI_ADDR, ISCSI_PORT, TGT_IQN, TGT_LUN).
CLI
An example CLI demonstrates discovery/login and simple I/O using the same library APIs. See examples/ (if enabled in this version).
Roadmap
A high-level plan, trimmed for GitHub readability. We track delivery as Now → Next → Later with checkboxes.
Now
-
Core protocol & plumbing
- CRC32C digests (Header/Data; opt-in)
- Unified state machine (Login, NOP, READ/WRITE)
- Discovery: SendTargets (Text)
-
Reliability & ergonomics
- Structured errors with retry hints
- Timeouts & cancellation tokens
- Back-pressure & graceful shutdown
-
Testing & CI
- Multi-target matrix: tgt, LIO/targetcli, SCST
- Byte-exact fixtures for each login hop
- Fuzzing (cargo-fuzz / proptest) for PDUs & text keys
Next
-
Sessions & recovery
- Multi-connection sessions (MC/S)
- Reinstatement & session recovery
- ERL1/ERL2: SNACKs, retransmit, CmdSN/StatSN windowing
-
Security
- Mutual CHAP (bi-dir), strict key parsing/normalization
- Optional TLS/TCP (when target supports it)
-
SCSI coverage
- REPORT LUNS, INQUIRY VPD, MODE SENSE/SELECT
- UNMAP, WRITE SAME, COMPARE-AND-WRITE
- TMFs: ABORT TASK, LUN RESET, CLEAR TASK SET
- AEN / Unit Attention flow
-
Performance
- Zero-copy build/parse; fewer allocs
- Pipelining & outstanding-cmd windows
- Auto-tune: MaxBurstLength, FirstBurstLength
- Scatter-gather for large Data-Out
- Benchmarks (throughput/latency) with reproducible profiles
How we track: create issues with labels epic, proto, perf, api, testing, docs. Link them here under the matching section as they’re planned.
Contributing
Issues and PRs are welcome. Please run:
Acknowledgments
Thanks to the iSCSI community and Linux tgt for a solid reference target to test against.