| Crates.io | iscsi-client-rs |
| lib.rs | iscsi-client-rs |
| version | 0.0.8 |
| created_at | 2025-07-21 19:38:42.35068+00 |
| updated_at | 2025-10-27 09:51:26.004456+00 |
| description | A pure-Rust iSCSI initiator library and CLI |
| homepage | https://github.com/Masorubka1/iscsI-client-rs.git |
| repository | https://github.com/Masorubka1/iscsI-client-rs.git |
| max_upload_size | |
| id | 1762524 |
| size | 4,206,660 |
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
tgt/targetclionly. Other targets may behave differently. Use with care.
All SCSI commands must be executed via the session Pool.
Why this matters:
ITT, CmdSN, ExpStatSN for each task and binds them to the right connection (CID) in a session (TSIH).Do not call
ClientConnection::send_request/read_response*directly in application code. Always wrap your state machine intopool.execute_with(tsih, cid, |conn, itt, cmd_sn, exp_stat_sn| { … }).
use anyhow::Result;
use std::sync::Arc;
use tokio_util::sync::CancellationToken;
use iscsi_client_rs::{
cfg::config::Config,
client::pool_sessions::Pool,
};
#[tokio::main]
async fn main() -> Result<()> {
let cfg = Config::load_from_file("./config.yaml")?;
let cancel = CancellationToken::new();
// Create a Pool (session manager): owns connections and read loops.
let pool: Arc<Pool> = Pool::new(cfg.clone(), cancel.clone()).await?;
// Open a session (TSIH) + first connection (CID). Auth is taken from cfg.
let isid: [u8; 6] = [0x0d, 0x70, 0xbc, 0x71, 0xa1, 0x22];
let (tsih, cid) = pool.open_session_and_login(isid).await?;
// From now on, always use pool.execute_with(tsih, cid, …) to run I/O.
Ok(())
}
If you build sessions by hand, ensure the connection is bound:
ClientConnection::bind_pool_session(pool_weak, tsih, cid)before any I/O. This enables unsolicited NOP‑In auto‑replies and other internals.
use iscsi_client_rs::{
models::nop::request::NopOutRequest,
state_machine::nop_states::NopCtx,
};
let lun = 1u64 << 48;
pool.execute_with(tsih, cid, move |conn, itt, cmd_sn, exp_stat_sn| {
NopCtx::new(
conn,
lun,
itt,
cmd_sn,
exp_stat_sn,
NopOutRequest::DEFAULT_TAG,
)
})
.await?;
use iscsi_client_rs::{
control_block::read::build_read10,
// adjust the path to your crate's ReadCtx
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, /*lba=*/0, /*blocks=*/blocks, /*flags=*/0, /*control=*/0);
let read_outcome = pool.execute_with(tsih, cid, move |conn, itt, cmd_sn, exp_stat_sn| {
ReadCtx::new(
conn,
lun,
itt,
cmd_sn,
exp_stat_sn,
read_len,
cdb,
)
})
.await?;
// println!("read {} bytes", read_outcome.data.len());
use iscsi_client_rs::{
control_block::write::build_write10,
// adjust the path to your crate's WriteCtx
state_machine::write_states::WriteCtx,
};
let lun = 1u64 << 48;
let blocks = 64u32;
let block_size = 4096u32;
let bytes = (blocks * block_size) as usize;
let mut payload = vec![0u8; bytes];
// fill payload …
let mut cdb = [0u8; 16];
build_write10(&mut cdb, /*lba=*/0, /*blocks=*/blocks, /*flags=*/0, /*control=*/0);
pool.execute_with(tsih, cid, move |conn, itt, cmd_sn, exp_stat_sn| {
WriteCtx::new(
conn,
lun,
itt,
cmd_sn,
exp_stat_sn,
cdb,
payload,
)
})
.await?;
// println!("write ok");
Notes:
ImmediateData=Yes and len ≤ FirstBurstLength, WriteCtx may send the payload in the initial ScsiCommandRequest. Otherwise it honors R2T windows and segments Data‑Out by min(MRDSL, remaining_in_burst); the last PDU in a burst has F=1.ReadyToTransfer PDUs are never final; the Pool keeps the ITT open until the final ScsiCommandResponse.// ❌ Bypassing the Pool: sending requests and reading replies manually.
// This will desynchronize per‑ITT channels and leak in‑flight tasks.
let conn = /* … */;
conn.send_request(itt, pdu).await?;
let rsp = conn.read_response::<…>(itt).await?;
Always route through the Pool:
// ✅ Correct
pool.execute_with(tsih, cid, |conn, itt, cmd_sn, exp_stat_sn| {
/* build your state machine here */
})
.await?;
Want to parallelize I/O? Launch several execute_with calls (different ITTs or LBAs). The Pool handles sequencing and counters.
use futures::future::try_join_all;
let tasks = (0..8).map(|k| {
let pool = pool.clone();
async move {
pool.execute_with(tsih, cid, move |conn, itt, cmd_sn, exp_stat_sn| {
/* e.g., ReadCtx::new(... different LBA/len per k ...) */
})
.await
}
});
try_join_all(tasks).await?;
Stuck ITTs (e.g., left [ … ]). Ensure finality semantics are consistent in both the low‑level parser and SendingData impls:
F=1 && S=1 (status carried in Data‑In). If S=0, expect a separate ScsiCommandResponse and keep the channel open.parse::Pdu::get_final_bit() and ScsiDataIn’s SendingData::get_final_bit().Unsolicited NOP‑In (TTT ≠ 0xffffffff) needs auto‑reply. Make sure the connection is bound to the Pool (done automatically by open_session_and_login).
Connection wraps a Tokio TCP stream and frames iSCSI PDUs by their 48‑byte BHS. It:
ToBytesContinue/Final bitsPDUWithData<T> to the callerExpStatSN = stat_sn + 1Utility builders produce PDUs with correct fields; state machines update counters for you at the right moments.
An example CLI demonstrates discovery/login and simple I/O using the same library APIs. See examples/ (if enabled in this version).
A high‑level plan, tracked as Now → Next → Later with checkboxes. Pool‑first API is the baseline assumption.
Core protocol & plumbing
Reliability & ergonomics
Testing & CI
How we track: create issues with labels epic, proto, perf, api, testing, docs. Link them here under the matching section.
We use DCO (Signed-off-by on each commit) and require a CLA (individual/entity) before the first PR. This allows us to keep the project AGPL-only today and offer a commercial license later without recontacting contributors.
CONTRIBUTING.md, CLA-INDIVIDUAL.md, CLA-ENTITY.md.Issues and PRs are welcome. Please run:
cargo fmt --all
cargo clippy --tests --benches -- -D warnings
cargo test
AGPL-3.0-or-later. See LICENSE-AGPL-3.0.md.
© 2012-2025 Andrei Maltsev
Commercial licensing: not available yet; if you need a proprietary license, contact u7743837492@gmail.com to be notified when dual licensing launches.