nbd-0.3.0/.cargo_vcs_info.json0000644000000001360000000000100115770ustar { "git": { "sha1": "8102ee8a3aa238219c5b27b9c29b4f69da24de95" }, "path_in_vcs": "" }nbd-0.3.0/.gitignore000064400000000000000000000000221046102023000123510ustar 00000000000000target Cargo.lock nbd-0.3.0/.travis.yml000064400000000000000000000002621046102023000125000ustar 00000000000000os: - linux language: rust rust: - 1.23.0 - stable script: - cargo build --verbose --all - if [ $TRAVIS_RUST_VERSION != "1.23.0" ]; then cargo test --verbose --all; fi nbd-0.3.0/Cargo.lock0000644000000152570000000000100075640ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "byteorder" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" dependencies = [ "bitflags", ] [[package]] name = "fastrand" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fuchsia-cprng" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "nbd" version = "0.3.0" dependencies = [ "byteorder", "pipe", "proptest", "rand", "readwrite", ] [[package]] name = "num-traits" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", ] [[package]] name = "pipe" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cc8bd4a29fc75fb70561e09cd26cd67bc8a75df0777a86247e7540e231f30d9" [[package]] name = "proptest" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "926d0604475349f463fe44130aae73f2294b5309ab2ca0310b998bd334ef191f" dependencies = [ "bit-set", "bitflags", "byteorder", "lazy_static", "num-traits", "quick-error", "rand", "regex-syntax", "rusty-fork", "tempfile", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "rand" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" dependencies = [ "cloudabi", "fuchsia-cprng", "libc", "rand_core 0.3.1", "winapi", ] [[package]] name = "rand_core" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" dependencies = [ "rand_core 0.4.2", ] [[package]] name = "rand_core" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" [[package]] name = "readwrite" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73891b98dabbe836d23a094941e6ec891bc4880e771faea98813f2ff27ede473" [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "regex-syntax" version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] [[package]] name = "rusty-fork" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "tempfile" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", "fastrand", "libc", "redox_syscall", "remove_dir_all", "winapi", ] [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" nbd-0.3.0/Cargo.toml0000644000000020350000000000100075750ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] name = "nbd" version = "0.3.0" authors = ["Vitaly \"_Vi\" Shukela "] description = "Rust library for NBD (network block device) servers and clients." readme = "README.md" keywords = [ "nbd", "network-block-device", ] categories = ["network-programming"] license = "MIT/Apache-2.0" repository = "https://github.com/vi/rust-nbd" [dependencies.byteorder] version = "1.0" [dev-dependencies.pipe] version = "0.0.3" [dev-dependencies.proptest] version = "0.8.4" [dev-dependencies.rand] version = "0.5.5" [dev-dependencies.readwrite] version = "0.1.0" nbd-0.3.0/Cargo.toml.orig000064400000000000000000000007341046102023000132620ustar 00000000000000[package] name = "nbd" version = "0.3.0" authors = ["Vitaly \"_Vi\" Shukela "] license = "MIT/Apache-2.0" repository = "https://github.com/vi/rust-nbd" description = "Rust library for NBD (network block device) servers and clients." keywords = ["nbd", "network-block-device"] categories = ["network-programming"] readme = "README.md" [dependencies] byteorder = "1.0" [dev-dependencies] proptest = "0.8.4" rand = "0.5.5" readwrite = "0.1.0" pipe = "0.0.3" nbd-0.3.0/README.md000064400000000000000000000011761046102023000116530ustar 00000000000000rust-nbd --- [Network block device](https://en.wikipedia.org/wiki/Network_block_device) protocol implementation in Rust. Not all features are currently supported in server. Accepts a `Read`+`Write`+`Seek` as a data to be exposed in server mode. Provides `Read`+`Write`+`Seek` in client mode. Underlying connection is `Read`+`Write`, usage of `bufstream` crate is recommended. This library is IO-agnostic, but async is not supported. See [server example](https://github.com/vi/rust-nbd/blob/master/examples/server.rs) or [client example](https://github.com/vi/rust-nbd/blob/master/examples/client.rs). This is a rather early version. nbd-0.3.0/examples/client.rs000064400000000000000000000011251046102023000140300ustar 00000000000000extern crate nbd; use std::io::{Read, Result, Seek, SeekFrom, Write}; use std::net::TcpStream; use nbd::client::{handshake, NbdClient}; /// Read second 1024-byte block from an NBD export named "sda1" fn run() -> Result<()> { let mut buf = vec![0; 1024]; let mut tcp = TcpStream::connect("127.0.0.1:10809")?; let export = handshake(&mut tcp, b"sda1")?; let mut client = NbdClient::new(&mut tcp, &export); client.seek(SeekFrom::Start(1024))?; client.read_exact(&mut buf[..])?; std::io::stdout().write_all(&buf[..])?; Ok(()) } fn main() { run().unwrap(); } nbd-0.3.0/examples/server.rs000064400000000000000000000022331046102023000140610ustar 00000000000000extern crate nbd; use std::io::{Cursor, Result}; use std::net::{TcpListener, TcpStream}; use nbd::server::{handshake, transmission, Export}; fn handle_client(mut stream: TcpStream) -> Result<()> { let data = handshake(&mut stream, |name| { println!("requested export: {name}"); let mut data = vec![0; 1_474_560]; let signature = format!("Name of the export requested by client: `{}`.", name).into_bytes(); data[0..signature.len()].copy_from_slice(&signature); Ok(Export { size: data.len() as u64, data, readonly: false, ..Default::default() }) })?; let pseudofile = Cursor::new(data); transmission(&mut stream, pseudofile)?; Ok(()) } fn main() { let listener = TcpListener::bind("127.0.0.1:10809").unwrap(); for stream in listener.incoming() { match stream { Ok(stream) => match handle_client(stream) { Ok(_) => {} Err(e) => { eprintln!("error: {}", e); } }, Err(_) => { println!("Error"); } } } } nbd-0.3.0/src/lib.rs000064400000000000000000000631241046102023000123000ustar 00000000000000//! First sketch of NBD (Network block device) protocol support in Rust //! API is not stable yet, obviously //! //! https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md #![deny(missing_docs)] #![forbid(unsafe_code)] // Let's support legacy rustc #![allow(bare_trait_objects)] extern crate byteorder; /// Information about an export (without name) #[derive(Debug, Default, Hash, Eq, PartialEq, Ord, PartialOrd)] pub struct Export { /// Size of the underlying data, in bytes pub size: u64, /// Tell client it's readonly pub readonly: bool, /// Tell that NBD_CMD_RESIZE should be supported. Not implemented in this library currently pub resizeable: bool, /// Tell that the exposed device has slow seeks, hence clients should use elevator algorithm pub rotational: bool, /// Tell that NBD_CMD_TRIM operation is supported. Not implemented in this library currently pub send_trim: bool, /// Tell that NBD_CMD_FLUSH may be sent pub send_flush: bool, /// Associated data for the export pub data: Data, } fn strerror(s: &'static str) -> std::io::Result<()> { Err(std::io::Error::new(std::io::ErrorKind::InvalidData, s)) } // based on https://doc.rust-lang.org/src/std/io/util.rs.html#48 fn mycopy( reader: &mut R, mut writer: &mut W, buf: &mut [u8], mut limit: usize, before_first_write: UWO, ) -> ::std::io::Result where R: ::std::io::Read, W: ::std::io::Write, UWO: FnOnce(&mut /*dyn*/ ::std::io::Write) -> ::std::io::Result<()>, { let mut before_first_write = Some(before_first_write); let mut written = 0; loop { let to_read = buf.len().min(limit); let len = match reader.read(&mut buf[0..to_read]) { Ok(0) => return Ok(written), Ok(len) => len, Err(ref e) if e.kind() == ::std::io::ErrorKind::Interrupted => continue, Err(e) => return Err(e), }; if let Some(bfw) = before_first_write.take() { bfw(&mut writer)?; } writer.write_all(&buf[..len])?; written += len as u64; //eprintln!("written={} limit={} len={}", written, limit, len); limit -= len; if limit == 0 { return Ok(written); } } } /// Items for implementing NBD server /// /// "Serialize" your Read+Write+Seek into a Read+Write socket using standard protocol. pub mod server { use super::consts::*; use super::{mycopy, strerror}; use byteorder::{BigEndian as BE, ReadBytesExt, WriteBytesExt}; use std::io::{Error, Read, Result, Seek, SeekFrom, Write}; #[doc(hidden)] pub fn oldstyle_header(mut c: W, size: u64, flags: u32) -> Result<()> { c.write_all(b"NBDMAGIC")?; c.write_all(b"\x00\x00\x42\x02\x81\x86\x12\x53")?; c.write_u64::(size)?; c.write_u32::(flags)?; c.write_all(&[0; 124])?; c.flush()?; Ok(()) } fn reply(mut c: IO, clopt: u32, rtype: u32, data: &[u8]) -> Result<()> { c.write_u64::(0x3e889045565a9)?; c.write_u32::(clopt)?; c.write_u32::(rtype)?; c.write_u32::(data.len() as u32)?; c.write_all(data)?; c.flush()?; Ok(()) } pub use super::Export; /// Passes the requested export name to the provided callback to get the requested export pub fn handshake Result>>(mut c: IO, exports: F) -> Result { //let hs_flags = NBD_FLAG_FIXED_NEWSTYLE; let hs_flags = NBD_FLAG_FIXED_NEWSTYLE | NBD_FLAG_NO_ZEROES; c.write_all(b"NBDMAGIC")?; c.write_all(b"IHAVEOPT")?; c.write_u16::(hs_flags)?; c.flush()?; let client_flags = c.read_u32::()?; if client_flags != NBD_FLAG_C_FIXED_NEWSTYLE && client_flags != (NBD_FLAG_C_FIXED_NEWSTYLE | NBD_FLAG_C_NO_ZEROES) { strerror("Invalid client flag")?; } loop { let client_optmagic = c.read_u64::()?; if client_optmagic != 0x49484156454F5054 { // IHAVEOPT strerror("Invalid client optmagic")?; } let clopt = c.read_u32::()?; let optlen = c.read_u32::()?; if optlen > 100000 { strerror("Suspiciously big option length")?; } let mut opt = vec![0; optlen as usize]; c.read_exact(&mut opt)?; match clopt { NBD_OPT_EXPORT_NAME => { let export_name = std::str::from_utf8(&opt).map_err(|_| strerror("Non-UTF8 export name requested").unwrap_err())?; let export = exports(export_name)?; c.write_u64::(export.size)?; let mut flags = NBD_FLAG_HAS_FLAGS; if export.readonly { flags |= NBD_FLAG_READ_ONLY } else { flags |= NBD_FLAG_SEND_FLUSH }; if export.resizeable { flags |= NBD_FLAG_SEND_RESIZE }; if export.rotational { flags |= NBD_FLAG_ROTATIONAL }; if export.send_trim { flags |= NBD_FLAG_SEND_TRIM }; c.write_u16::(flags)?; if client_flags & NBD_FLAG_C_NO_ZEROES == 0 { c.write_all(&[0; 124])?; } c.flush()?; return Ok(export.data); } NBD_OPT_ABORT => { reply(&mut c, clopt, NBD_REP_ACK, b"")?; strerror("Client abort")?; } NBD_OPT_LIST => { if optlen != 0 { strerror("NBD_OPT_LIST with content")?; } reply(&mut c, clopt, NBD_REP_SERVER, b"\x00\x00\x00\x07rustnbd")?; reply(&mut c, clopt, NBD_REP_ACK, b"")?; } NBD_OPT_STARTTLS => { strerror("TLS not supported")?; } NBD_OPT_INFO => { reply(&mut c, clopt, NBD_REP_ERR_UNSUP, b"")?; } NBD_OPT_GO => { reply(&mut c, clopt, NBD_REP_ERR_UNSUP, b"")?; } NBD_OPT_STRUCTURED_REPLY => { reply(&mut c, clopt, NBD_REP_ERR_UNSUP, b"")?; } _ => { strerror("Invalid client option type")?; } } } } fn replyt(mut c: IO, error: u32, handle: u64) -> Result<()> { c.write_u32::(0x67446698)?; c.write_u32::(error)?; c.write_u64::(handle)?; Ok(()) } fn replyte(mut c: IO, error: Error, handle: u64) -> Result<()> { let ec = if let Some(x) = error.raw_os_error() { if (x as u32) != 0 { x as u32 } else { 5 } } else { 5 }; replyt(&mut c, ec, handle) } /// Serve given data. If readonly, use a dummy `Write` implementation. /// /// Should be used after `handshake` pub fn transmission(mut c: IO, mut data: D) -> Result<()> where IO: Read + Write, D: Read + Write + Seek, { let mut buf = vec![0; 65536]; loop { let magic = c.read_u32::()?; if magic != 0x25609513 { strerror("Invalid request magic")?; } let _flags = c.read_u16::()?; let typ = c.read_u16::()?; let handle = c.read_u64::()?; let offset = c.read_u64::()?; let length = c.read_u32::()?; //eprintln!("typ={} handle={} off={} len={}", typ, handle, offset, length); match typ { NBD_CMD_READ => { if let Err(e) = data.seek(SeekFrom::Start(offset)) { replyte(&mut c, e, handle)?; } else { let mut writing_in_progress = false; let ret; { // a hello from old borrowck let on_first_chunk = |c:&mut /*dyn*/ Write| { replyt(c, 0, handle)?; writing_in_progress = true; Ok(()) }; ret = mycopy( &mut data, &mut c, &mut buf, length as usize, on_first_chunk, ); } match ret { Err(e) => { if writing_in_progress { // Reading errors after already copying first chunk // cannot be really handled, so aborting the entire connection return Err(e); } else { // Errors in the very first chunk can be non-fatal replyte(&mut c, e, handle)? } } Ok(x) if x == (length as u64) => {} Ok(_) => { strerror("sudden EOF")?; } } } } NBD_CMD_WRITE => { if let Err(e) = data.seek(SeekFrom::Start(offset)) { replyte(&mut c, e, handle)?; } else { let ret = mycopy(&mut c, &mut data, &mut buf, length as usize, |_| Ok(())); match ret { Err(e) => replyte(&mut c, e, handle)?, Ok(x) if x == (length as u64) => { replyt(&mut c, 0, handle)?; } Ok(_) => { strerror("sudden EOF")?; } } } } NBD_CMD_DISC => { return Ok(()); } NBD_CMD_FLUSH => { data.flush()?; replyt(&mut c, 0, handle)?; } NBD_CMD_TRIM => { replyt(&mut c, 38, handle)?; } NBD_CMD_WRITE_ZEROES => { replyt(&mut c, 38, handle)?; } _ => strerror("Unknown command from client")?, } c.flush()?; } } /// Recommended port for NBD servers, especially with new handshake format. /// There is some untested, doc-hidden old handshake support in this library. pub const DEFAULT_TCP_PORT: u16 = 10809; } // mod server /// Items for implementing NBD client. /// /// Turn Read+Write into a Read+Write+Seek using a standard protocol. pub mod client { use super::consts::*; use super::{strerror, CheckedAddI64, ClampToU32}; use byteorder::{BigEndian as BE, ReadBytesExt, WriteBytesExt}; use std::io::{Error, ErrorKind, Read, Result, Seek, SeekFrom, Write}; pub use super::Export; fn fill_in_flags(export: &mut Export, flags: u16) { if flags & NBD_FLAG_HAS_FLAGS != 0 { if flags & NBD_FLAG_READ_ONLY != 0 { export.readonly = true; } if flags & NBD_FLAG_SEND_RESIZE != 0 { export.resizeable = true; } if flags & NBD_FLAG_ROTATIONAL != 0 { export.rotational = true; } if flags & NBD_FLAG_SEND_TRIM != 0 { export.send_trim = true; } if flags & NBD_FLAG_SEND_FLUSH != 0 { export.send_flush = true; } } } /// Negotiate with a server, use before creating the actual client pub fn handshake(mut c: IO, name: &[u8]) -> Result { let mut signature = [0; 8]; c.read_exact(&mut signature)?; if signature != *b"NBDMAGIC" { strerror("Invalid magic1")?; } c.read_exact(&mut signature)?; let (size, flags) = if signature == *b"IHAVEOPT" { // newstyle let _hs_flags = c.read_u16::()?; c.write_u32::(NBD_FLAG_C_FIXED_NEWSTYLE)?; // optmagic c.write_u64::(0x49484156454F5054)?; c.write_u32::(NBD_OPT_EXPORT_NAME)?; c.write_u32::(name.len() as u32)?; c.write_all(name)?; c.flush()?; let size = c.read_u64::()?; let flags = c.read_u16::()?; let mut z = [0; 124]; c.read_exact(&mut z)?; if z[..] != [0; 124][..] { strerror("Expected 124 bytes of zeroes are not zeroes")?; } (size, flags) } else if signature == *b"\x00\x00\x42\x02\x81\x86\x12\x53" { // oldstyle. // Note: not tested at all if name != b"" { strerror("Old style server does not support named exports")?; }; let size = c.read_u64::()?; let flags = c.read_u32::()?; let mut z = [0; 124]; c.read_exact(&mut z)?; if z[..] != [0; 124][..] { strerror("Expected 124 bytes of zeroes are not zeroes")?; } // Is it those flags or some other flags? // Too lazy to actually look into NBD implementation. let flags = flags as u16; (size, flags) } else { strerror("Invalid magic2")?; unreachable!() }; let mut e = Export::default(); e.size = size; fill_in_flags(&mut e, flags); Ok(e) } /// Represents NBD client. Use `Read`,`Write` and `Seek` trait methods, /// but make sure those are block-aligned pub struct NbdClient { c: IO, seek_pos: u64, size: u64, } impl NbdClient { /// Create new NbdClient from `Export` returned from `handshake`. /// Obviously, the `c` connection should be the same as in `handshake`. pub fn new(c: IO, export: &Export) -> Self { NbdClient { c, seek_pos: 0, size: export.size, } } } impl Seek for NbdClient { fn seek(&mut self, sf: SeekFrom) -> Result { match sf { SeekFrom::Start(x) => { self.seek_pos = x; } SeekFrom::Current(x) => { if let Some(xx) = self.seek_pos.checked_add_i64(x) { self.seek_pos = xx; } else { strerror("Invalid seek")?; } } SeekFrom::End(x) => { if let Some(xx) = self.size.checked_add_i64(x) { self.seek_pos = xx; } else { strerror("Invalid seek")?; } } } Ok(self.seek_pos) } } fn check_err(error: u32) -> Result<()> { match error { 1 => Err(Error::new(ErrorKind::PermissionDenied, "from device")), 5 => Err(Error::new(ErrorKind::Other, "EIO")), 12 => Err(Error::new(ErrorKind::Other, "ENOMEM")), 22 => Err(Error::new(ErrorKind::Other, "EINVAL")), 28 => Err(Error::new(ErrorKind::Other, "ENOSPC")), 0 => Ok(()), _ => Err(Error::new(ErrorKind::Other, "other error from device")), } } fn getreply(mut c: IO) -> Result<()> { let signature = c.read_u32::()?; let error = c.read_u32::()?; let handle = c.read_u64::()?; if signature != 0x67446698 { strerror("Invalid signature for incoming reply")?; } if handle != 0 { strerror("Unexpected handle")?; }; check_err(error)?; Ok(()) } fn sendrequest(mut c: IO, cmd: u16, offset: u64, len: u32) -> Result<()> { c.write_u32::(0x25609513)?; c.write_u16::(0)?; // flags c.write_u16::(cmd)?; c.write_u64::(0)?; // handle c.write_u64::(offset)?; c.write_u32::(len)?; c.flush()?; Ok(()) } impl NbdClient { fn get_effective_len(&self, buflen: usize) -> Result { if self.seek_pos == self.size { return Ok(0); } if self.seek_pos > self.size { strerror("Trying to read or write past the end of the device")?; } let maxlen = (self.size - self.seek_pos).clamp_to_u32(); let len = buflen.clamp_to_u32().min(maxlen); Ok(len) } } impl Read for NbdClient { fn read(&mut self, buf: &mut [u8]) -> Result { let len = self.get_effective_len(buf.len())?; if len == 0 { return Ok(0); } sendrequest(&mut self.c, NBD_CMD_READ, self.seek_pos, len)?; getreply(&mut self.c)?; self.c.read_exact(&mut buf[0..(len as usize)])?; self.seek_pos += len as u64; Ok(len as usize) } } impl Write for NbdClient { fn write(&mut self, buf: &[u8]) -> Result { let len = self.get_effective_len(buf.len())?; if len == 0 { return Ok(0); } sendrequest(&mut self.c, NBD_CMD_WRITE, self.seek_pos, len)?; self.c.write_all(&buf[0..(len as usize)])?; self.c.flush()?; getreply(&mut self.c)?; self.seek_pos += len as u64; Ok(len as usize) } fn flush(&mut self) -> Result<()> { sendrequest(&mut self.c, NBD_CMD_FLUSH, 0, 0)?; getreply(&mut self.c)?; Ok(()) } } /// Additional operations (apart from reading and writing) supported by NBD extensions /// Not tested yet. pub trait NbdExt { /// Discard this data, starting from current seek offset up to specified length fn trim(&mut self, length: usize) -> Result<()>; /// Change size of the device fn resize(&mut self, newsize: u64) -> Result<()>; } impl NbdExt for NbdClient { fn trim(&mut self, length: usize) -> Result<()> { let len = self.get_effective_len(length)?; if len == 0 { return Ok(()); } sendrequest(&mut self.c, NBD_CMD_TRIM, self.seek_pos, len)?; getreply(&mut self.c)?; Ok(()) } fn resize(&mut self, newsize: u64) -> Result<()> { sendrequest(&mut self.c, NBD_CMD_RESIZE, newsize, 0)?; getreply(&mut self.c)?; self.size = newsize; Ok(()) } } } #[allow(dead_code)] mod consts { pub const NBD_OPT_EXPORT_NAME: u32 = 1; pub const NBD_OPT_ABORT: u32 = 2; pub const NBD_OPT_LIST: u32 = 3; pub const NBD_OPT_STARTTLS: u32 = 5; pub const NBD_OPT_INFO: u32 = 6; pub const NBD_OPT_GO: u32 = 7; pub const NBD_OPT_STRUCTURED_REPLY: u32 = 8; pub const NBD_REP_ACK: u32 = 1; pub const NBD_REP_SERVER: u32 = 2; pub const NBD_REP_INFO: u32 = 3; pub const NBD_REP_FLAG_ERROR: u32 = 1 << 31; pub const NBD_REP_ERR_UNSUP: u32 = 1 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_POLICY: u32 = 2 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_INVALID: u32 = 3 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_PLATFORM: u32 = 4 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_TLS_REQD: u32 = 5 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_UNKNOWN: u32 = 6 | NBD_REP_FLAG_ERROR; pub const NBD_REP_ERR_BLOCK_SIZE_REQD: u32 = 8 | NBD_REP_FLAG_ERROR; pub const NBD_FLAG_FIXED_NEWSTYLE: u16 = 1 << 0; pub const NBD_FLAG_NO_ZEROES: u16 = 1 << 1; pub const NBD_FLAG_C_FIXED_NEWSTYLE: u32 = NBD_FLAG_FIXED_NEWSTYLE as u32; pub const NBD_FLAG_C_NO_ZEROES: u32 = NBD_FLAG_NO_ZEROES as u32; pub const NBD_INFO_EXPORT: u16 = 0; pub const NBD_INFO_NAME: u16 = 1; pub const NBD_INFO_DESCRIPTION: u16 = 2; pub const NBD_INFO_BLOCK_SIZE: u16 = 3; pub const NBD_FLAG_HAS_FLAGS: u16 = 1 << 0; pub const NBD_FLAG_READ_ONLY: u16 = 1 << 1; pub const NBD_FLAG_SEND_FLUSH: u16 = 1 << 2; pub const NBD_FLAG_SEND_FUA: u16 = 1 << 3; pub const NBD_FLAG_ROTATIONAL: u16 = 1 << 4; pub const NBD_FLAG_SEND_TRIM: u16 = 1 << 5; pub const NBD_FLAG_SEND_WRITE_ZEROES: u16 = 1 << 6; pub const NBD_FLAG_CAN_MULTI_CONN: u16 = 1 << 8; pub const NBD_FLAG_SEND_RESIZE: u16 = 1 << 9; pub const NBD_CMD_READ: u16 = 0; pub const NBD_CMD_WRITE: u16 = 1; pub const NBD_CMD_DISC: u16 = 2; pub const NBD_CMD_FLUSH: u16 = 3; pub const NBD_CMD_TRIM: u16 = 4; pub const NBD_CMD_WRITE_ZEROES: u16 = 6; pub const NBD_CMD_RESIZE: u16 = 8; } trait CheckedAddI64 where Self: Sized, { fn checked_add_i64(self, rhs: i64) -> Option; } impl CheckedAddI64 for u64 { fn checked_add_i64(self, rhs: i64) -> Option { if rhs >= 0 { self.checked_add(rhs as u64) } else { if let Some(x) = rhs.checked_neg() { self.checked_sub(x as u64) } else { None } } } } trait ClampToU32 where Self: Sized, { fn clamp_to_u32(self) -> u32; } impl ClampToU32 for usize { fn clamp_to_u32(self) -> u32 { if self > u32::max_value() as usize { u32::max_value() } else { self as u32 } } } impl ClampToU32 for u64 { fn clamp_to_u32(self) -> u32 { if self > u32::max_value() as u64 { u32::max_value() } else { self as u32 } } } /* // Options that the client can select to the server #define NBD_OPT_EXPORT_NAME (1) // Client wants to select a named export (is followed by name of export) #define NBD_OPT_ABORT (2) // Client wishes to abort negotiation #define NBD_OPT_LIST (3) // Client request list of supported exports (not followed by data) #define NBD_OPT_STARTTLS (5) // Client wishes to initiate TLS #define NBD_OPT_INFO (6) // Client wants information about the given export #define NBD_OPT_GO (7) // Client wants to select the given and move to the transmission phase // Replies the server can send during negotiation #define NBD_REP_ACK (1) // ACK a request. Data: option number to be acked #define NBD_REP_SERVER (2) // Reply to NBD_OPT_LIST (one of these per server; must be followed by NBD_REP_ACK to signal the end of the list #define NBD_REP_INFO (3) // Reply to NBD_OPT_INFO #define NBD_REP_FLAG_ERROR (1 << 31) // If the high bit is set, the reply is an error #define NBD_REP_ERR_UNSUP (1 | NBD_REP_FLAG_ERROR) // Client requested an option not understood by this version of the server #define NBD_REP_ERR_POLICY (2 | NBD_REP_FLAG_ERROR) // Client requested an option not allowed by server configuration. (e.g., the option was disabled) #define NBD_REP_ERR_INVALID (3 | NBD_REP_FLAG_ERROR) // Client issued an invalid request #define NBD_REP_ERR_PLATFORM (4 | NBD_REP_FLAG_ERROR) // Option not supported on this platform #define NBD_REP_ERR_TLS_REQD (5 | NBD_REP_FLAG_ERROR) // TLS required #define NBD_REP_ERR_UNKNOWN (6 | NBD_REP_FLAG_ERROR) // NBD_OPT_INFO or ..._GO requested on unknown export #define NBD_REP_ERR_BLOCK_SIZE_REQD (8 | NBD_REP_FLAG_ERROR) // Server is not willing to serve the export without the block size being negotiated // Global flags #define NBD_FLAG_FIXED_NEWSTYLE (1 << 0) // new-style export that actually supports extending #define NBD_FLAG_NO_ZEROES (1 << 1) // we won't send the 128 bits of zeroes if the client sends NBD_FLAG_C_NO_ZEROES // Flags from client to server. #define NBD_FLAG_C_FIXED_NEWSTYLE NBD_FLAG_FIXED_NEWSTYLE #define NBD_FLAG_C_NO_ZEROES NBD_FLAG_NO_ZEROES // Info types #define NBD_INFO_EXPORT (0) #define NBD_INFO_NAME (1) #define NBD_INFO_DESCRIPTION (2) #define NBD_INFO_BLOCK_SIZE (3) // values for flags field #define NBD_FLAG_HAS_FLAGS (1 << 0) // Flags are there #define NBD_FLAG_READ_ONLY (1 << 1) // Device is read-only #define NBD_FLAG_SEND_FLUSH (1 << 2) // Send FLUSH #define NBD_FLAG_SEND_FUA (1 << 3) // Send FUA (Force Unit Access) #define NBD_FLAG_ROTATIONAL (1 << 4) // Use elevator algorithm - rotational media #define NBD_FLAG_SEND_TRIM (1 << 5) // Send TRIM (discard) #define NBD_FLAG_SEND_WRITE_ZEROES (1 << 6) // Send NBD_CMD_WRITE_ZEROES #define NBD_FLAG_CAN_MULTI_CONN (1 << 8) // multiple connections are okay */ nbd-0.3.0/tests/fuzz_hs.rs000064400000000000000000000037211046102023000135720ustar 00000000000000#[macro_use] extern crate proptest; extern crate nbd; extern crate pipe; extern crate readwrite; use proptest::prelude::{prop, Just, ProptestConfig, Strategy}; use proptest::string::bytes_regex; use std::io::{Cursor, Read, Write}; use readwrite::ReadWrite; fn gen_chunk() -> impl Strategy> { prop_oneof! { Just(b"IHAVEOPT".to_vec()), Just(b"NBDMAGIC".to_vec()), Just(b"\x00\x00".to_vec()), bytes_regex("\x00\x00\x00\x00\x00\x00..").unwrap(), bytes_regex("\x00\x00\x00.").unwrap(), bytes_regex("\x00.").unwrap(), bytes_regex("\x00[\x00-\x0F]").unwrap(), bytes_regex(".").unwrap(), Just(vec![0; 124]), Just(b"\x00\x00\x00\x01".to_vec()), Just(b"\x00\x00\x42\x02\x81\x86\x12\x53".to_vec()), Just(b"\x00\x03\xe8\x89\x04\x55\x65\xa9".to_vec()), Just(b"\x49\x48\x41\x56\x45\x4F\x50\x54".to_vec()), } } fn get_random_socket(chunks: Vec>) -> impl Read + Write { let input: Vec = chunks.iter().flatten().map(|x| *x).collect(); let socket = ReadWrite::new(Cursor::new(input), ::std::io::sink()); socket } proptest! { #![proptest_config(ProptestConfig { cases: 10_000, .. ProptestConfig::default() })] #[test] fn fuzz_client_hs(chunks in prop::collection::vec(gen_chunk(),3..12)) { let s = get_random_socket(chunks); let ret = nbd::client::handshake(s, b""); if let Ok(x) = ret { eprintln!("Happy case {:?}", x); } else { // Error, but not panic is fine too } } #[test] fn fuzz_server_hs(chunks in prop::collection::vec(gen_chunk(),3..12)) { let s = get_random_socket(chunks); let exp = nbd::server::Export::default(); let ret = nbd::server::handshake(s, &exp); if let Ok(x) = ret { eprintln!("Happy case {:?}", x); } else { // Error, but not panic is fine too } } } nbd-0.3.0/tests/fuzz_roundtrip.proptest-regressions000064400000000000000000000006101046102023000207750ustar 00000000000000# Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. xs 1929752010 3976052170 2958470491 1302865311 # shrinks to script = [Seek(0), Seek(0), Seek(0)] nbd-0.3.0/tests/fuzz_roundtrip.rs000064400000000000000000000061241046102023000152060ustar 00000000000000#[macro_use] extern crate proptest; extern crate nbd; extern crate pipe; extern crate rand; extern crate readwrite; use rand::prng::XorShiftRng; use rand::{RngCore, SeedableRng}; use proptest::prelude::{prop, ProptestConfig, Strategy}; use std::io::{Cursor, Read, Seek, SeekFrom, Write}; use readwrite::ReadWrite; #[derive(Debug, Eq, PartialEq)] enum Action { Seek(u64), Write(usize), ReadAndCheck(usize), } const SS: u64 = 1024 * 1024; prop_compose! { fn biased_size()(x in 0..65536usize, y in 0..3u8) -> usize { if y == 0 { x } else { x % 2048 } } } fn gen_action() -> impl Strategy { prop_oneof! { (0..SS).prop_map(Action::Seek), biased_size().prop_map(Action::Write), biased_size().prop_map(Action::ReadAndCheck), } } proptest! { #![proptest_config(ProptestConfig { cases: 500, .. ProptestConfig::default() })] #[test] fn fuzz_roundtrip(script in prop::collection::vec(gen_action(),3..12)) { let seed = [4u8;16]; let mut r = XorShiftRng::from_seed(seed); let mut buf = vec![0;65536]; let mut buf2 = vec![0;65536]; let backing_storage1 = vec![0u8;SS as usize]; let backing_storage2 = vec![0u8;SS as usize]; let mut c1 = Cursor::new(backing_storage1); let mut c2 = Cursor::new(backing_storage2); let (r1,w1) = pipe::pipe(); let (r2,w2) = pipe::pipe(); let (s1,s2) = (ReadWrite::new(r1,w2), ReadWrite::new(r2,w1)); let h = std::thread::spawn(move || { let _ = nbd::server::transmission(s2, &mut c2); c2 }); let mut c2 = nbd::client::NbdClient::new(s1, &nbd::Export{size : SS,..Default::default()}); for i in script { match i { Action::Seek(pos) => { c1.seek(SeekFrom::Start(pos)).unwrap(); c2.seek(SeekFrom::Start(pos)).unwrap(); }, Action::Write(mut sz) => { if sz > (SS - c1.position()) as usize { sz = (SS - c1.position()) as usize; } let bufview = &mut buf[0..sz]; r.fill_bytes(bufview); c1.write_all(bufview).unwrap(); c2.write_all(bufview).unwrap(); }, Action::ReadAndCheck(sz) => { let bufview = &mut buf[0..sz]; let bufview2 = &mut buf2[0..sz]; let ret1 = c1.read(bufview).unwrap(); let ret2 = c2.read(bufview2).unwrap(); assert!(ret1 == ret2); assert!(bufview[0..ret1] == bufview2[0..ret2]); }, } } drop(c2); let backing_storage1 = c1.into_inner(); let backing_storage2 = h.join().unwrap().into_inner(); //eprintln!("{:?}", &backing_storage1[0..16]); //eprintln!("{:?}", &backing_storage2[0..16]); assert!(backing_storage1 == backing_storage2); } } nbd-0.3.0/tests/fuzz_transmission.proptest-regressions000064400000000000000000000010241046102023000215000ustar 00000000000000# Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. xs 3460930584 2624636413 495172453 1693689206 # shrinks to chunks = [[37, 96, 149, 19], [0, 0, 0], [4], [37, 96, 149, 19], [37, 96, 149, 19], [37, 96, 149, 19], [37, 96, 149, 19], [37, 96, 149, 19], [37, 96, 149, 19], [37, 96, 149, 19]] nbd-0.3.0/tests/fuzz_transmission.rs000064400000000000000000000064371046102023000157200ustar 00000000000000#[macro_use] extern crate proptest; extern crate nbd; extern crate pipe; extern crate readwrite; use proptest::prelude::{prop, Just, ProptestConfig, Strategy}; use proptest::string::bytes_regex; use std::io::{Cursor, Read, Result, Seek, SeekFrom, Write}; use readwrite::ReadWrite; #[derive(Debug, Eq, PartialEq)] enum Action { Seek(u64), Write(usize), Read(usize), } const SS: u64 = 1024 * 1024; prop_compose! { fn biased_size()(x in 0..32usize, y in 0..3u8) -> usize { if y == 0 { x } else { x % 6 } } } fn gen_action() -> impl Strategy { prop_oneof! { (0..SS).prop_map(Action::Seek), biased_size().prop_map(Action::Write), biased_size().prop_map(Action::Read), } } fn gen_chunk() -> impl Strategy> { prop_oneof! { Just(b"\x25\x60\x95\x13".to_vec()), Just(b"\x67\x44\x66\x98".to_vec()), Just(b"\x00\x00\x00".to_vec()), Just(b"\x00\x00\x00\x00\x00\x00\x00[\x00-\x1F]".to_vec()), Just(b"\x00\x00".to_vec()), Just(b"\xFF\xFF".to_vec()), bytes_regex(".").unwrap(), bytes_regex("\x00[\x01-\x09]").unwrap(), } } fn get_random_socket(chunks: Vec>) -> impl Read + Write { let input: Vec = chunks.iter().flatten().map(|x| *x).collect(); let socket = ReadWrite::new(Cursor::new(input), ::std::io::sink()); socket } struct FakeStorage; impl Read for FakeStorage { fn read(&mut self, buf: &mut [u8]) -> Result { Ok(buf.len()) } } impl Write for FakeStorage { fn write(&mut self, buf: &[u8]) -> Result { Ok(buf.len()) } fn flush(&mut self) -> Result<()> { Ok(()) } } impl Seek for FakeStorage { fn seek(&mut self, _: SeekFrom) -> Result { Ok(1024_000) } } proptest! { #![proptest_config(ProptestConfig { cases: 20_000, .. ProptestConfig::default() })] #[test] fn fuzz_client_transmission(chunks in prop::collection::vec(gen_chunk(),3..30), script in prop::collection::vec(gen_action(),3..12), ) { let s = get_random_socket(chunks); let mut exp = nbd::client::Export::default(); exp.size = SS; let mut client = nbd::client::NbdClient::new(s, &exp); let mut buf = vec![0;4096]; let mut succ = 0; for i in script { match i { Action::Seek(pos) => { client.seek(SeekFrom::Start(pos)).unwrap(); }, Action::Write(sz) => { if sz > 0 { if client.write(&buf[0..sz]).is_ok() { succ += 1; } } }, Action::Read(sz) => { if sz > 0 { if client.read(&mut buf[0..sz]).is_ok() { succ += 1; } } }, } } if succ > 1 { eprintln!("succ={}", succ); } } #[test] fn fuzz_server_transmission(chunks in prop::collection::vec(gen_chunk(),3..12)) { let s = get_random_socket(chunks); let ret = nbd::server::transmission(s, FakeStorage); if ret.is_ok() { eprintln!("Happy"); } } }