Pure-Rust iSCSI initiator library for TCP targets. It builds/parses PDUs, performs login, and runs SCSI commands asynchronously.
Status: tested in CI against
tgt,LIO/targetcli, andTrueNAS SCALE.
- Login: plain and CHAP
- Pool-based session/connection management
- State machines for login, NOP, READ, WRITE, TUR, MODE SENSE, REPORT LUNS, REQUEST SENSE, INQUIRY, logout
- CRC32C header/data digests
- Multi-connection sessions and connection recovery
- No C dependencies
All SCSI I/O must go through Pool::execute_with_ctx(...). The raw
ClientConnection send/receive methods are internal because the pool owns:
ITT,CmdSN,ExpStatSN- per-ITT response channels
- unsolicited
NOP-Inauto-replies - graceful shutdown and poisoned-connection recovery
runtime.ResponseQueueCapacity controls the buffered response PDUs per
in-flight command. runtime.MaxConnectionRecoveryAttempts controls retries
after a poisoned connection fails; 0 disables retries. Both are required.
use anyhow::Result;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use iscsi_client_rs::{
cfg::config::Config,
client::{client::ClientConnection, pool_sessions::Pool},
models::identifiers::{Cid, Isid},
};
#[tokio::main]
async fn main() -> Result<()> {
let cfg = Config::load_from_file("./config.yaml")?;
let cancel = CancellationToken::new();
let pool = Pool::with_cancel(&cfg, cancel.clone());
let (isid, _) = Isid::generate();
let cid = Cid::ZERO;
let conn = ClientConnection::connect(cfg.clone(), pool.cancel_token().child_token()).await?;
let target_name: Arc<str> = Arc::from(cfg.login.identity.target_name.clone());
let tsih = pool.login_and_insert(target_name, isid, cid, conn).await?;
let _ = (tsih, cid);
Ok(())
}use iscsi_client_rs::{
models::nop::request::NopOutRequest,
state_machine::nop_states::NopCtx,
};
let lun = 1u64 << 48;
pool.execute_with_ctx(tsih, cid, move |env| {
NopCtx::from_execute_env(env, lun, NopOutRequest::DEFAULT_TAG)
})
.await?;use iscsi_client_rs::{
control_block::read::build_read10,
state_machine::read_states::ReadCtx,
};
let lun = 1u64 << 48;
let blocks = 64u32;
let block_size = 4096u32;
let read_len = blocks * block_size;
let mut cdb = [0u8; 16];
build_read10(&mut cdb, 0, blocks, 0, 0);
let out = pool.execute_with_ctx(tsih, cid, move |env| {
ReadCtx::from_execute_env(env, lun, read_len, cdb)
})
.await?;
let _data = out.data;use iscsi_client_rs::{
control_block::write::build_write10,
state_machine::write_states::WriteCtx,
};
let lun = 1u64 << 48;
let blocks = 64u32;
let block_size = 4096u32;
let mut payload = vec![0u8; (blocks * block_size) as usize];
let mut cdb = [0u8; 16];
build_write10(&mut cdb, 0, blocks, 0, 0);
pool.execute_with_ctx(tsih, cid, move |env| {
WriteCtx::from_execute_env(env, lun, cdb, payload)
})
.await?;Parallel I/O is just parallel execute_with_ctx calls. The pool wires sequence numbers and per-request routing.
use futures::future::try_join_all;
let jobs = (0..8).map(|_| {
let pool = pool.clone();
async move {
pool.execute_with_ctx(tsih, cid, move |env| {
/* build state machine */
})
.await
}
});
try_join_all(jobs).await?;- Stuck ITTs usually mean broken finality rules.
ScsiDataInis final only whenF=1 && S=1;ScsiCommandResponseis always final;R2Tis never final. - Unsolicited
NOP-Inrequires a pool-bound connection. WRITEmay use ImmediateData for small payloads and R2T windows for the rest.
- Unit tests
- Integration matrix for
tgt,LIO/targetcli, andTrueNAS SCALE - Integration test binary is built once and reused across target jobs
Done:
- CRC32C digests
- Pool-first API
- SendTargets discovery
- REPORT LUNS, INQUIRY VPD, MODE SENSE
- MC/S and basic connection recovery
Next:
- ERL1/ERL2 and SNACKs
- Mutual CHAP
- TLS/TCP when target supports it
- UNMAP / WRITE SAME / TMFs
- Fuzzing and benchmarks
We use DCO and require a CLA before the first PR. See CONTRIBUTING.md, legal/CLA-INDIVIDUAL.md, and legal/CLA-ENTITY.md.
Before sending changes:
cargo fmt --all
cargo clippy --tests --benches -- -D warnings
cargo testAGPL-3.0-or-later. See LICENSE-AGPL-3.0.md.
Commercial licensing is not available yet. For future proprietary licensing, contact u7743837492@gmail.com.