Add Rust bindings (alpha)

This commit is contained in:
Joseph Henry
2021-05-30 19:51:26 -07:00
parent 3ea146fa5b
commit 8c275c4899
14 changed files with 285 additions and 57 deletions

View File

@@ -124,6 +124,16 @@ if (ZTS_ENABLE_PINVOKE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_PINVOKE=1") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DZTS_ENABLE_PINVOKE=1")
endif() 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) # Python language bindings (_libzt.so)
if (ZTS_ENABLE_PYTHON) if (ZTS_ENABLE_PYTHON)
# Features # Features

View File

@@ -368,6 +368,22 @@ host-uninstall()
cd - 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 C extension module (*.so), python module, package both into wheel
# #
# ./build.sh host-python-wheel "release" # ./build.sh host-python-wheel "release"
@@ -467,7 +483,7 @@ host-pinvoke()
cp $LIB_OUTPUT_DIR/* $BIN_OUTPUT_DIR cp $LIB_OUTPUT_DIR/* $BIN_OUTPUT_DIR
# Start Alice as server # Start Alice as server
MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" server $alice_path $testnet $port4 & MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" server $alice_path $testnet $port4 &
sleep 5 sleep 3
# Start Bob as client # Start Bob as client
MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" client $bob_path $testnet $alice_ip4 $port4 & MONO_THREADS_SUSPEND=preemptive; mono --debug "$BIN_OUTPUT_DIR/selftest.exe" client $bob_path $testnet $alice_ip4 $port4 &
fi fi
@@ -663,6 +679,7 @@ format-code()
then then
$PYTHON -m black src/bindings/python/libzt.py $PYTHON -m black src/bindings/python/libzt.py
$PYTHON -m black src/bindings/python/node.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 src/bindings/python/sockets.py
$PYTHON -m black examples/python/ $PYTHON -m black examples/python/
$PYTHON -m black test/selftest.py $PYTHON -m black test/selftest.py
@@ -692,6 +709,8 @@ test-c()
# Recursive deep clean # Recursive deep clean
clean() clean()
{ {
# Clean rust crate
(cd pkg/crate/libzt && cargo clean)
# Finished artifacts # Finished artifacts
rm -rf $BUILD_OUTPUT_DIR rm -rf $BUILD_OUTPUT_DIR
# CMake's build system cache # CMake's build system cache

11
examples/rust/README.md Normal file
View File

@@ -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)

View File

@@ -58,6 +58,12 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cc"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
[[package]] [[package]]
name = "cexpr" name = "cexpr"
version = "0.4.0" version = "0.4.0"
@@ -99,6 +105,15 @@ dependencies = [
"vec_map", "vec_map",
] ]
[[package]]
name = "cmake"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.8.3" version = "0.8.3"
@@ -147,9 +162,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.94" version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
[[package]] [[package]]
name = "libloading" name = "libloading"
@@ -166,6 +181,7 @@ name = "libzt"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cmake",
"libc", "libc",
] ]
@@ -202,9 +218,9 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@@ -220,9 +236,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.5.3" version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce5f1ceb7f74abbce32601642fcf8e8508a8a8991e0621c7d750295b9095702b" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",

View File

@@ -16,12 +16,13 @@ categories = ["network-programming", "cryptography"]
[build-dependencies] [build-dependencies]
bindgen = "0.57" bindgen = "0.57"
libc = "0.2" libc = "0.2"
cmake = "0.1"
[lib] [lib]
name = "libzt" name = "libzt"
path = "src/lib.rs" path = "src/lib.rs"
[[bin]] [[example]]
name = "libzt-test-app" name = "libzt-test-app"
path = "src/bin/libzt-test-app.rs" path = "src/examples/libzt-test-app.rs"

View File

@@ -1,10 +1,19 @@
[libzt](https://www.zerotier.com) # libzt - Sockets over ZeroTier
=====
Part of the ZeroTier SDK `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. 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) - Docs: [docs.zerotier.com](https://docs.zerotier.com/sockets/tutorial.html)
- Repo: [github.com/zerotier/libzt](https://github.com/zerotier/libzt) - Repo: [github.com/zerotier/libzt](https://github.com/zerotier/libzt)

View File

@@ -1,19 +1,36 @@
extern crate bindgen; extern crate bindgen;
fn main() { use cmake::Config;
println!("cargo:rustc-link-lib=zt"); use std::env;
println!("cargo:rustc-env=LLVM_CONFIG_PATH=/usr/local/opt/llvm/bin/llvm-config"); use std::path::PathBuf;
//println!("cargo:rerun-if-changed=../../../include/ZeroTierSockets.h"); fn main() {
//println!("cargo:include=/usr/local/include"); 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() let bindings = bindgen::Builder::default()
.header("../../../include/ZeroTierSockets.h") .header("src/native/include/ZeroTierSockets.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks)) .parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate() .generate()
.expect("Unable to generate bindings"); .expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings bindings
.write_to_file("./src/libzt.rs") .write_to_file(out_path.join("libzt.rs"))
.expect("Couldn't write bindings!"); .expect("Couldn't write bindings!");
} }

View File

@@ -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<String> = env::args().collect();
println!("{:?}", args);
if args.len() != 5 && args.len() != 6 {
println!("Incorrect number of arguments.");
println!(" Usage: server <storage_path> <net_id> <local_ip> <local_port>");
println!(" Usage: client <storage_path> <net_id> <remote_ip> <remote_port>");
}
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(())
}

View File

@@ -15,7 +15,8 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
pub mod libzt; include!(concat!(env!("OUT_DIR"), "/libzt.rs"));
pub mod node; pub mod node;
pub mod socket; pub mod socket;
pub mod tcp; pub mod tcp;

View File

@@ -14,16 +14,17 @@
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![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::ffi::{c_void, CStr, CString};
use std::io;
use std::net::{AddrParseError, IpAddr}; use std::net::{AddrParseError, IpAddr};
use std::str::FromStr; use std::str::FromStr;
extern "C" fn native_event_handler(msg: *mut c_void) { 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) }; 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); //user_event_handler(event.event_code);
} }

View File

@@ -21,7 +21,6 @@ use std::{io, mem};
type time_t = i64; type time_t = i64;
use crate::libzt::*;
use crate::utils::*; use crate::utils::*;
// Note: FileDesc and c_int in libc are private so we can't use that. Use i32 instead // Note: FileDesc and c_int in libc are private so we can't use that. Use i32 instead

View File

@@ -19,7 +19,6 @@ use std::os::raw::c_int;
use std::time::Duration; use std::time::Duration;
use std::{cmp, mem}; use std::{cmp, mem};
use crate::libzt::*;
use crate::socket::Socket; use crate::socket::Socket;
use crate::utils::*; use crate::utils::*;

View File

@@ -13,13 +13,12 @@
use std::convert::TryInto; use std::convert::TryInto;
use std::ffi::{c_void, CString}; 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::net::{/*Ipv4Addr, Ipv6Addr,*/ SocketAddr, ToSocketAddrs};
use std::os::raw::c_int; use std::os::raw::c_int;
use std::time::Duration; use std::time::Duration;
//use std::cmp; //use std::cmp;
use crate::libzt::*;
use crate::socket::Socket; use crate::socket::Socket;
use crate::utils::*; use crate::utils::*;
@@ -82,8 +81,8 @@ impl UdpSocketImpl {
self.inner.peek_from(buf) self.inner.peek_from(buf)
} }
pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result<usize> {
/* /*
pub fn send_to(&self, buf: &[u8], dst: &SocketAddr) -> io::Result<usize> {
let len = cmp::min(buf.len(), <size_t>::MAX as usize) as size_t; let len = cmp::min(buf.len(), <size_t>::MAX as usize) as size_t;
let (dstp, dstlen) = dst.into_inner(); let (dstp, dstlen) = dst.into_inner();
let ret = cvt(unsafe { let ret = cvt(unsafe {
@@ -97,9 +96,8 @@ impl UdpSocketImpl {
) )
})?; })?;
Ok(ret as usize) Ok(ret as usize)
*/
Ok(0)
} }
*/
pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> { pub fn set_read_timeout(&self, dur: Option<Duration>) -> io::Result<()> {
self.inner.set_timeout(dur, ZTS_SO_RCVTIMEO as i32) self.inner.set_timeout(dur, ZTS_SO_RCVTIMEO as i32)
@@ -317,7 +315,7 @@ impl UdpSocket {
pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> { pub fn peek_from(&self, buf: &mut [u8]) -> io::Result<(usize, SocketAddr)> {
self.0.peek_from(buf) self.0.peek_from(buf)
} }
/*
pub fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> { pub fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> {
match addr.to_socket_addrs()?.next() { match addr.to_socket_addrs()?.next() {
Some(addr) => self.0.send_to(buf, &addr), Some(addr) => self.0.send_to(buf, &addr),
@@ -327,7 +325,7 @@ impl UdpSocket {
)), )),
} }
} }
*/
pub fn peer_addr(&self) -> io::Result<SocketAddr> { pub fn peer_addr(&self) -> io::Result<SocketAddr> {
self.0.peer_addr() self.0.peer_addr()
} }

View File

@@ -15,7 +15,8 @@
#![allow(non_camel_case_types)] #![allow(non_camel_case_types)]
#![allow(non_snake_case)] #![allow(non_snake_case)]
use crate::libzt::*; include!(concat!(env!("OUT_DIR"), "/libzt.rs"));
use std::ffi::c_void; use std::ffi::c_void;
use std::io::{Error, ErrorKind}; use std::io::{Error, ErrorKind};
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};