diff --git a/CMakeLists.txt b/CMakeLists.txt index e9edddb..1f394bd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,16 @@ if (ZTS_ENABLE_PINVOKE) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_PINVOKE=1") endif() +# Rust language bindings +if (ZTS_ENABLE_RUST) + # Features + set(BUILD_STATIC_LIB TRUE) + set(BUILD_SHARED_LIB FALSE) + set(BUILD_EXAMPLES FALSE) + set(ALLOW_INSTALL_TARGET FALSE) + set(BUILD_HOST_SELFTEST FALSE) +endif() + # Python language bindings (_libzt.so) if (ZTS_ENABLE_PYTHON) # Features diff --git a/build.sh b/build.sh index ef53f0f..dfd3bea 100755 --- a/build.sh +++ b/build.sh @@ -368,6 +368,22 @@ host-uninstall() cd - } +# Build rust crate, libzt.a, link and test +# +host-rust() +{ + BUILD_TYPE=${1:-debug} + + cd pkg/crate/libzt + cargo build # --verbose + + # Test Rust crate + if [[ $2 = *"test"* ]]; then + cargo run --example libzt-test-app -- server $alice_path $testnet 0.0.0.0 $port4 & + cargo run --example libzt-test-app -- client $bob_path $testnet $alice_ip4 $port4 & + fi +} + # Build C extension module (*.so), python module, package both into wheel # # ./build.sh host-python-wheel "release" @@ -467,7 +483,7 @@ host-pinvoke() cp $LIB_OUTPUT_DIR/* $BIN_OUTPUT_DIR # Start Alice as server MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" server $alice_path $testnet $port4 & - sleep 5 + sleep 3 # Start Bob as client MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" client $bob_path $testnet $alice_ip4 $port4 & fi @@ -663,6 +679,7 @@ format-code() then $PYTHON -m black src/bindings/python/libzt.py $PYTHON -m black src/bindings/python/node.py + $PYTHON -m black src/bindings/python/select.py $PYTHON -m black src/bindings/python/sockets.py $PYTHON -m black examples/python/ $PYTHON -m black test/selftest.py @@ -692,6 +709,8 @@ test-c() # Recursive deep clean clean() { + # Clean rust crate + (cd pkg/crate/libzt && cargo clean) # Finished artifacts rm -rf $BUILD_OUTPUT_DIR # CMake's build system cache diff --git a/examples/rust/README.md b/examples/rust/README.md new file mode 100644 index 0000000..115a770 --- /dev/null +++ b/examples/rust/README.md @@ -0,0 +1,11 @@ +# Rust example + +``` +cargo install libzt +``` + +Examples: [pkg/crate/libzt/src/examples](pkg/crate/libzt/src/examples) + +## Links + + - Getting Started: [docs.zerotier.com/sockets](https://docs.zerotier.com/sockets/tutorial.html) diff --git a/pkg/crate/libzt/Cargo.lock b/pkg/crate/libzt/Cargo.lock index ce4af25..9038bd7 100644 --- a/pkg/crate/libzt/Cargo.lock +++ b/pkg/crate/libzt/Cargo.lock @@ -58,6 +58,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "cc" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" + [[package]] name = "cexpr" version = "0.4.0" @@ -99,6 +105,15 @@ dependencies = [ "vec_map", ] +[[package]] +name = "cmake" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" +dependencies = [ + "cc", +] + [[package]] name = "env_logger" version = "0.8.3" @@ -147,9 +162,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.94" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36" [[package]] name = "libloading" @@ -166,6 +181,7 @@ name = "libzt" version = "0.1.0" dependencies = [ "bindgen", + "cmake", "libc", ] @@ -202,9 +218,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" dependencies = [ "unicode-xid", ] @@ -220,9 +236,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", diff --git a/pkg/crate/libzt/Cargo.toml b/pkg/crate/libzt/Cargo.toml index 5935e40..194f50d 100644 --- a/pkg/crate/libzt/Cargo.toml +++ b/pkg/crate/libzt/Cargo.toml @@ -16,12 +16,13 @@ categories = ["network-programming", "cryptography"] [build-dependencies] bindgen = "0.57" libc = "0.2" +cmake = "0.1" [lib] name = "libzt" path = "src/lib.rs" -[[bin]] +[[example]] name = "libzt-test-app" -path = "src/bin/libzt-test-app.rs" +path = "src/examples/libzt-test-app.rs" diff --git a/pkg/crate/libzt/README.md b/pkg/crate/libzt/README.md index 0602366..97f7f97 100644 --- a/pkg/crate/libzt/README.md +++ b/pkg/crate/libzt/README.md @@ -1,10 +1,19 @@ -[libzt](https://www.zerotier.com) -===== -Part of the ZeroTier SDK +# libzt - Sockets over ZeroTier + +`libzt` replicates the functionality of [std::net](https://doc.rust-lang.org/std/net/index.html) but uses [ZeroTier](https://www.zerotier.com) as its transport layer. Securely connect application instances, physical devices, and virtual devices as if everything is on a single LAN. ZeroTier brings your network into user-space. No root, and no host configuration requirements. -We've paired our network hyper-visor core with a TCP/UDP/IP stack [(lwIP)](https://en.wikipedia.org/wiki/LwIP) to provide your application with an exclusive and private virtual network layer. All traffic is end-to-end encrypted between each peer and we provide an easy-to-use socket interface similar to [std::net](https://doc.rust-lang.org/std/net/index.html). +## Usage + +Add the following to your `Cargo.toml`: + +```toml +[dependencies] +libzt = "0.1.0" +``` + +## Resources - Docs: [docs.zerotier.com](https://docs.zerotier.com/sockets/tutorial.html) - Repo: [github.com/zerotier/libzt](https://github.com/zerotier/libzt) diff --git a/pkg/crate/libzt/build.rs b/pkg/crate/libzt/build.rs index 7ac7e29..c22fd66 100644 --- a/pkg/crate/libzt/build.rs +++ b/pkg/crate/libzt/build.rs @@ -1,19 +1,36 @@ extern crate bindgen; -fn main() { - println!("cargo:rustc-link-lib=zt"); - println!("cargo:rustc-env=LLVM_CONFIG_PATH=/usr/local/opt/llvm/bin/llvm-config"); +use cmake::Config; +use std::env; +use std::path::PathBuf; - //println!("cargo:rerun-if-changed=../../../include/ZeroTierSockets.h"); - //println!("cargo:include=/usr/local/include"); +fn main() { + Config::new("src/native").build_target("zt-static").define("ZTS_ENABLE_RUST", "1").out_dir("target").build(); + + println!("cargo:rustc-link-search=target/build/lib"); + println!("cargo:rustc-link-lib=static=zt"); + + // See here for reasoning: https://flames-of-code.netlify.app/blog/rust-and-cmake-cplusplus/ + + let target = env::var("TARGET").unwrap(); + if target.contains("apple") { + println!("cargo:rustc-link-lib=dylib=c++"); + } else if target.contains("linux") { + println!("cargo:rustc-link-lib=dylib=stdc++"); + } else { + unimplemented!(); + } + + //println!("cargo:rustc-env=LLVM_CONFIG_PATH=/usr/local/opt/llvm/bin/llvm-config"); let bindings = bindgen::Builder::default() - .header("../../../include/ZeroTierSockets.h") + .header("src/native/include/ZeroTierSockets.h") .parse_callbacks(Box::new(bindgen::CargoCallbacks)) .generate() .expect("Unable to generate bindings"); + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings - .write_to_file("./src/libzt.rs") + .write_to_file(out_path.join("libzt.rs")) .expect("Couldn't write bindings!"); } diff --git a/pkg/crate/libzt/src/examples/libzt-test-app.rs b/pkg/crate/libzt/src/examples/libzt-test-app.rs new file mode 100644 index 0000000..8acd919 --- /dev/null +++ b/pkg/crate/libzt/src/examples/libzt-test-app.rs @@ -0,0 +1,146 @@ +use libzt; + +use std::env; + +use libzt::tcp::{TcpListener, TcpStream}; +use std::io::{Read, Write}; +use std::net::Shutdown; +use std::str::from_utf8; +use std::thread; + +// (Optional) Notify application of ZeroTier events, some with context +fn user_event_handler(event_code: i16) -> () { + println!("user_event {}", event_code); +} + +fn handle_client(mut stream: TcpStream) { + let mut data = [0 as u8; 50]; // using 50 byte buffer + while match stream.read(&mut data) { + Ok(size) => { + // echo everything! + stream.write(&data[0..size]).unwrap(); + true + } + Err(_) => { + println!( + "An error occurred, terminating connection with {}", + stream.peer_addr().unwrap() + ); + stream.shutdown(Shutdown::Both).unwrap(); + false + } + } {} +} + +fn main() -> std::io::Result<()> { + let args: Vec = env::args().collect(); + println!("{:?}", args); + + if args.len() != 5 && args.len() != 6 { + println!("Incorrect number of arguments."); + println!(" Usage: server "); + println!(" Usage: client "); + } + + let storage_path = &args[2]; + let net_id = u64::from_str_radix(&args[3], 16).unwrap(); + + println!("path = {}", storage_path); + println!("net_id = {:x}", net_id); + + // SET UP ZEROTIER + + let nn = libzt::node::ZeroTierNode {}; + // (Optional) initialization + nn.init_set_port(0); + nn.init_set_event_handler(user_event_handler); + nn.init_from_storage(&storage_path); + // Start the node + nn.start(); + println!("Waiting for node to come online..."); + while !nn.is_online() { + nn.delay(50); + } + println!("Node ID = {:#06x}", nn.id()); + println!("Joining network"); + nn.net_join(net_id); + println!("Waiting for network to assign addresses..."); + while !nn.net_transport_is_ready(net_id) { + nn.delay(50); + } + let addr = nn.addr_get(net_id).unwrap(); + println!("Assigned addr = {}", addr); + + // Server + + if &args[1] == "server" { + println!("server mode"); + let mut addr_str: String = "".to_owned(); + addr_str.push_str(&args[4]); + addr_str.push_str(":"); + addr_str.push_str(&args[5]); + + let server: std::net::SocketAddr = + addr_str.parse().expect("Unable to parse socket address"); + let listener = TcpListener::bind(&server).unwrap(); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + println!("New connection: {}", stream.peer_addr().unwrap()); + thread::spawn(move || { + // connection succeeded + handle_client(stream) + }); + } + Err(e) => { + println!("Error: {}", e); + // connection failed + } + } + } + drop(listener); + } + + // Client + + if &args[1] == "client" { + println!("client mode"); + let mut addr_str: String = "".to_owned(); + addr_str.push_str(&args[4]); + addr_str.push_str(":"); + addr_str.push_str(&args[5]); + match TcpStream::connect(addr_str) { + Ok(mut stream) => { + println!("Successfully connected to server"); + + let msg = b"Hello!"; + + stream.write(msg).unwrap(); + println!("Sent Hello, awaiting reply..."); + + let mut data = [0 as u8; 6]; + match stream.read_exact(&mut data) { + Ok(_) => { + if &data == msg { + println!("Reply is ok!"); + } else { + let text = from_utf8(&data).unwrap(); + println!("Unexpected reply: {}", text); + } + } + Err(e) => { + println!("Failed to receive data: {}", e); + } + } + } + Err(e) => { + println!("Failed to connect: {}", e); + } + } + println!("Terminated."); + } + + nn.stop(); + Ok(()) +} diff --git a/pkg/crate/libzt/src/lib.rs b/pkg/crate/libzt/src/lib.rs index 47d57bd..cb8927d 100644 --- a/pkg/crate/libzt/src/lib.rs +++ b/pkg/crate/libzt/src/lib.rs @@ -15,7 +15,8 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -pub mod libzt; +include!(concat!(env!("OUT_DIR"), "/libzt.rs")); + pub mod node; pub mod socket; pub mod tcp; diff --git a/pkg/crate/libzt/src/node.rs b/pkg/crate/libzt/src/node.rs index 4729e41..3c9df0b 100644 --- a/pkg/crate/libzt/src/node.rs +++ b/pkg/crate/libzt/src/node.rs @@ -14,16 +14,17 @@ #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] #![allow(non_snake_case)] +#![allow(unused_variables)] + +include!(concat!(env!("OUT_DIR"), "/libzt.rs")); -use crate::libzt::*; use std::ffi::{c_void, CStr, CString}; -use std::io; use std::net::{AddrParseError, IpAddr}; use std::str::FromStr; extern "C" fn native_event_handler(msg: *mut c_void) { let event: &mut zts_event_msg_t = unsafe { &mut *(msg as *mut zts_event_msg_t) }; - println!("event: {}", event.event_code); + //println!("event: {}", event.event_code); //user_event_handler(event.event_code); } diff --git a/pkg/crate/libzt/src/socket.rs b/pkg/crate/libzt/src/socket.rs index 8a38f6f..c0c3225 100644 --- a/pkg/crate/libzt/src/socket.rs +++ b/pkg/crate/libzt/src/socket.rs @@ -21,7 +21,6 @@ use std::{io, mem}; type time_t = i64; -use crate::libzt::*; use crate::utils::*; // Note: FileDesc and c_int in libc are private so we can't use that. Use i32 instead diff --git a/pkg/crate/libzt/src/tcp.rs b/pkg/crate/libzt/src/tcp.rs index a2fd4aa..804fc58 100644 --- a/pkg/crate/libzt/src/tcp.rs +++ b/pkg/crate/libzt/src/tcp.rs @@ -19,7 +19,6 @@ use std::os::raw::c_int; use std::time::Duration; use std::{cmp, mem}; -use crate::libzt::*; use crate::socket::Socket; use crate::utils::*; diff --git a/pkg/crate/libzt/src/udp.rs b/pkg/crate/libzt/src/udp.rs index 1d36a54..cb76dbd 100644 --- a/pkg/crate/libzt/src/udp.rs +++ b/pkg/crate/libzt/src/udp.rs @@ -13,13 +13,12 @@ use std::convert::TryInto; use std::ffi::{c_void, CString}; -use std::io::{self, Error, ErrorKind}; +use std::io::{self /*, Error, ErrorKind*/}; use std::net::{/*Ipv4Addr, Ipv6Addr,*/ SocketAddr, ToSocketAddrs}; use std::os::raw::c_int; use std::time::Duration; //use std::cmp; -use crate::libzt::*; use crate::socket::Socket; use crate::utils::*; @@ -82,24 +81,23 @@ impl UdpSocketImpl { self.inner.peek_from(buf) } - pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result { - /* - let len = cmp::min(buf.len(), ::MAX as usize) as size_t; - let (dstp, dstlen) = dst.into_inner(); - let ret = cvt(unsafe { - zts_bsd_sendto( - *self.inner.as_inner(), - buf.as_ptr() as *const c_void, - len, - ZTS_MSG_NOSIGNAL, - dstp, - dstlen, - ) - })?; - Ok(ret as usize) - */ - Ok(0) - } + /* + pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result { + let len = cmp::min(buf.len(), ::MAX as usize) as size_t; + let (dstp, dstlen) = dst.into_inner(); + let ret = cvt(unsafe { + zts_bsd_sendto( + *self.inner.as_inner(), + buf.as_ptr() as *const c_void, + len, + ZTS_MSG_NOSIGNAL, + dstp, + dstlen, + ) + })?; + Ok(ret as usize) + } + */ pub fn set_read_timeout(&self, dur: Option) -> io::Result<()> { self.inner.set_timeout(dur, ZTS_SO_RCVTIMEO as i32) @@ -317,17 +315,17 @@ impl UdpSocket { pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { self.0.peek_from(buf) } - - pub fn send_to(&self, buf: &[u8], addr: A) -> io::Result { - match addr.to_socket_addrs()?.next() { - Some(addr) => self.0.send_to(buf, &addr), - None => Err(Error::new( - ErrorKind::InvalidInput, - "No address to send data to", - )), + /* + pub fn send_to(&self, buf: &[u8], addr: A) -> io::Result { + match addr.to_socket_addrs()?.next() { + Some(addr) => self.0.send_to(buf, &addr), + None => Err(Error::new( + ErrorKind::InvalidInput, + "No address to send data to", + )), + } } - } - + */ pub fn peer_addr(&self) -> io::Result { self.0.peer_addr() } diff --git a/pkg/crate/libzt/src/utils.rs b/pkg/crate/libzt/src/utils.rs index 643f916..e33684e 100644 --- a/pkg/crate/libzt/src/utils.rs +++ b/pkg/crate/libzt/src/utils.rs @@ -15,7 +15,8 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] -use crate::libzt::*; +include!(concat!(env!("OUT_DIR"), "/libzt.rs")); + use std::ffi::c_void; use std::io::{Error, ErrorKind}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};