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

View File

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

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"
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",

View File

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

View File

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

View File

@@ -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!");
}

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_snake_case)]
pub mod libzt;
include!(concat!(env!("OUT_DIR"), "/libzt.rs"));
pub mod node;
pub mod socket;
pub mod tcp;

View File

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

View File

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

View File

@@ -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::*;

View File

@@ -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<usize> {
/*
let len = cmp::min(buf.len(), <size_t>::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<usize> {
let len = cmp::min(buf.len(), <size_t>::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<Duration>) -> 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<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> {
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<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> io::Result<usize> {
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<SocketAddr> {
self.0.peer_addr()
}

View File

@@ -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};