implement a bunch of utility functions on tun device primitive
This commit is contained in:
parent
486fea2088
commit
d94f20d042
9 changed files with 248 additions and 82 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -407,7 +407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "p2pchat_transport_gen_tun"
|
name = "p2pchat_transport_prim_tun"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustler",
|
"rustler",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
members = ["native/p2pchat_transport_gen_tun"]
|
members = ["native/p2pchat_transport_prim_tun"]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
defmodule P2pChat do
|
defmodule P2pChat do
|
||||||
def hello do
|
def hello do
|
||||||
{:ok, pid1} = P2pChat.Transport.start_link(port: 12345)
|
{:ok, pid1} = P2pChat.Transport.start_link(port: 12345)
|
||||||
netid1 = P2pChat.Transport.get_netid(pid1)
|
_netid1 = P2pChat.Transport.get_netid(pid1)
|
||||||
|
|
||||||
{:ok, pid2} = P2pChat.Transport.start_link(port: 12346)
|
{:ok, pid2} = P2pChat.Transport.start_link(port: 12346)
|
||||||
netid2 = P2pChat.Transport.get_netid(pid2)
|
netid2 = P2pChat.Transport.get_netid(pid2)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
defmodule P2pChat.Transport.GenTun do
|
defmodule P2pChat.Transport.GenTun do
|
||||||
require Logger
|
require Logger
|
||||||
use Rustler, otp_app: :p2p_chat, crate: "p2pchat_transport_gen_tun"
|
require P2pChat.Transport.PrimTun, as: PrimTun
|
||||||
|
|
||||||
@behaviour GenServer
|
@behaviour GenServer
|
||||||
|
|
||||||
|
|
@ -10,15 +10,15 @@ defmodule P2pChat.Transport.GenTun do
|
||||||
# Server API
|
# Server API
|
||||||
#
|
#
|
||||||
@impl true
|
@impl true
|
||||||
def init(args) do
|
def init(_args) do
|
||||||
{:ok, tun_handle} = make_tun_device()
|
{:ok, tun_handle} = PrimTun.make_tun_device(true)
|
||||||
state = %__MODULE__{tun_handle: tun_handle}
|
state = %__MODULE__{tun_handle: tun_handle}
|
||||||
{ :ok, state, { :continue, :recv } }
|
{ :ok, state, { :continue, :recv } }
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_continue(:recv, state) do
|
def handle_continue(:recv, state) do
|
||||||
case tun_recv(state.tun_handle) do
|
case PrimTun.tun_recv(state.tun_handle, 2**16) do
|
||||||
{:ok, buf} ->
|
{:ok, buf} ->
|
||||||
Logger.debug("Received #{byte_size(buf)} bytes")
|
Logger.debug("Received #{byte_size(buf)} bytes")
|
||||||
{:error, :would_block} -> {}
|
{:error, :would_block} -> {}
|
||||||
|
|
@ -26,13 +26,13 @@ defmodule P2pChat.Transport.GenTun do
|
||||||
Logger.error("Error during receive: #{e}")
|
Logger.error("Error during receive: #{e}")
|
||||||
end
|
end
|
||||||
|
|
||||||
{:noreply, state, { :continue, :recv }}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_call({:recv}, _from, state) do
|
def handle_call({:get_addrs}, _from, state) do
|
||||||
data = tun_recv(state.tun_handle)
|
result = PrimTun.get_addrs(state.tun_handle)
|
||||||
{ :reply, data, state }
|
{:reply, result, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -42,11 +42,8 @@ defmodule P2pChat.Transport.GenTun do
|
||||||
GenServer.start_link(__MODULE__, nil)
|
GenServer.start_link(__MODULE__, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
def get_addrs(pid) do
|
||||||
# NIFs
|
GenServer.call(pid, {:get_addrs})
|
||||||
#
|
end
|
||||||
|
|
||||||
defp make_tun_device(), do: :erlang.nif_error(:nif_not_loaded)
|
|
||||||
defp tun_recv(_handle, _bufsize \\ 2*16), do: :erlang.nif_error(:nif_not_loaded)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
17
lib/p2p_chat/transport/prim_tun.ex
Normal file
17
lib/p2p_chat/transport/prim_tun.ex
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
defmodule P2pChat.Transport.PrimTun do
|
||||||
|
use Rustler, otp_app: :p2p_chat, crate: "p2pchat_transport_prim_tun"
|
||||||
|
|
||||||
|
def make_tun_device(_packet_info), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def recv(_handle, _bufsize), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def send(_handle, _buf), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def get_addrs(_handle), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def get_broadcast(_handle), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def get_name(_handle), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def set_name(_handle, _name), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def is_running(_handle), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def set_running(_handle, _should_run), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def add_address(_handle, _address, _prefix_length), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def remove_address(_handle, _address), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def get_mtu(_handle), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
def set_mtu(_handle, _mtu), do: :erlang.nif_error(:nif_not_loaded)
|
||||||
|
end
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
use std::io;
|
|
||||||
use std::net::Ipv6Addr;
|
|
||||||
|
|
||||||
use rustler::types::tuple::make_tuple;
|
|
||||||
use rustler::{resource_impl, Encoder, NifResult, OwnedBinary, Resource, ResourceArc, Term};
|
|
||||||
use tun_rs::{DeviceBuilder, SyncDevice};
|
|
||||||
|
|
||||||
mod atoms {
|
|
||||||
rustler::atoms! {
|
|
||||||
ok,
|
|
||||||
resource_busy,
|
|
||||||
permission_denied,
|
|
||||||
would_block,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TunHandle {
|
|
||||||
device: SyncDevice,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[resource_impl]
|
|
||||||
impl Resource for TunHandle {}
|
|
||||||
|
|
||||||
#[rustler::nif]
|
|
||||||
fn make_tun_device(env: rustler::Env) -> NifResult<Term> {
|
|
||||||
let device = DeviceBuilder::new()
|
|
||||||
.name("tunP2P")
|
|
||||||
.ipv6(Ipv6Addr::new(0x2001, 0x2f, 0, 0, 0, 0, 0, 1), 28)
|
|
||||||
.packet_information(true)
|
|
||||||
.build_sync()
|
|
||||||
.map_err(map_io_error)?;
|
|
||||||
device.set_nonblocking(true).map_err(map_io_error)?;
|
|
||||||
|
|
||||||
let handle = ResourceArc::new(TunHandle { device });
|
|
||||||
Ok(make_ok_tuple(env, handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rustler::nif]
|
|
||||||
fn tun_recv(env: rustler::Env, handle: ResourceArc<TunHandle>, bufsize: usize) -> NifResult<Term> {
|
|
||||||
let mut buf = OwnedBinary::new(bufsize).expect("Could not allocate receive buffer");
|
|
||||||
let n = handle.device.recv(&mut buf).map_err(map_io_error)?;
|
|
||||||
assert!(buf.realloc(n));
|
|
||||||
let erl_buf = buf.release(env);
|
|
||||||
|
|
||||||
Ok(make_ok_tuple(env, erl_buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn map_io_error(error: io::Error) -> rustler::Error {
|
|
||||||
rustler::Error::Term(match error.kind() {
|
|
||||||
io::ErrorKind::ResourceBusy => Box::new(atoms::resource_busy()),
|
|
||||||
io::ErrorKind::PermissionDenied => Box::new(atoms::permission_denied()),
|
|
||||||
io::ErrorKind::WouldBlock => Box::new(atoms::would_block()),
|
|
||||||
e => Box::new(e.to_string()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn make_ok_tuple<T: Encoder>(env: rustler::Env, value: T) -> Term {
|
|
||||||
make_tuple(env, &[atoms::ok().encode(env), value.encode(env)])
|
|
||||||
}
|
|
||||||
|
|
||||||
rustler::init!("Elixir.P2pChat.Transport.GenTun");
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "p2pchat_transport_gen_tun"
|
name = "p2pchat_transport_prim_tun"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = []
|
authors = []
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
215
native/p2pchat_transport_prim_tun/src/lib.rs
Normal file
215
native/p2pchat_transport_prim_tun/src/lib.rs
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
use std::io;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use rustler::types::tuple::{get_tuple, make_tuple};
|
||||||
|
use rustler::{
|
||||||
|
resource_impl, Binary, Decoder, Encoder, NifResult, OwnedBinary, Resource, ResourceArc, Term,
|
||||||
|
};
|
||||||
|
use tun_rs::{DeviceBuilder, SyncDevice};
|
||||||
|
|
||||||
|
mod atoms {
|
||||||
|
rustler::atoms! {
|
||||||
|
ok,
|
||||||
|
resource_busy,
|
||||||
|
permission_denied,
|
||||||
|
would_block,
|
||||||
|
address_not_available,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opaque handle which the BEAM an use to refer to an open TUN Device
|
||||||
|
pub struct TunHandle {
|
||||||
|
device: SyncDevice,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A utility wrapper around native IP addresses that has BEAM encoding/decoding rules implemented
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct WrappedIpAddr(IpAddr);
|
||||||
|
|
||||||
|
impl From<IpAddr> for WrappedIpAddr {
|
||||||
|
fn from(value: IpAddr) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for WrappedIpAddr {
|
||||||
|
type Target = IpAddr;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder for WrappedIpAddr {
|
||||||
|
fn encode<'a>(&self, env: rustler::Env<'a>) -> Term<'a> {
|
||||||
|
match self.0 {
|
||||||
|
IpAddr::V6(addr) => {
|
||||||
|
let octets = addr
|
||||||
|
.segments()
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| i.encode(env))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
make_tuple(env, &octets)
|
||||||
|
}
|
||||||
|
IpAddr::V4(addr) => {
|
||||||
|
let octets = addr.octets();
|
||||||
|
(octets[0], octets[1], octets[2], octets[3]).encode(env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Decoder<'a> for WrappedIpAddr {
|
||||||
|
fn decode(term: Term<'a>) -> NifResult<Self> {
|
||||||
|
let term_tuple = get_tuple(term)?;
|
||||||
|
match term_tuple.len() {
|
||||||
|
8 => {
|
||||||
|
let segments: [u16; 8] = [
|
||||||
|
term_tuple[0].decode()?,
|
||||||
|
term_tuple[1].decode()?,
|
||||||
|
term_tuple[2].decode()?,
|
||||||
|
term_tuple[3].decode()?,
|
||||||
|
term_tuple[4].decode()?,
|
||||||
|
term_tuple[5].decode()?,
|
||||||
|
term_tuple[6].decode()?,
|
||||||
|
term_tuple[7].decode()?,
|
||||||
|
];
|
||||||
|
Ok(IpAddr::V6(Ipv6Addr::from_segments(segments)).into())
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
let octets: [u8; 4] = [
|
||||||
|
term_tuple[0].decode()?,
|
||||||
|
term_tuple[1].decode()?,
|
||||||
|
term_tuple[2].decode()?,
|
||||||
|
term_tuple[3].decode()?,
|
||||||
|
];
|
||||||
|
Ok(IpAddr::V4(Ipv4Addr::from_octets(octets)).into())
|
||||||
|
}
|
||||||
|
_ => Err(rustler::Error::BadArg),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[resource_impl]
|
||||||
|
impl Resource for TunHandle {}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn make_tun_device(env: rustler::Env, packet_info: bool) -> NifResult<Term> {
|
||||||
|
let device = DeviceBuilder::new()
|
||||||
|
.name("tunP2P")
|
||||||
|
.packet_information(packet_info)
|
||||||
|
.build_sync()
|
||||||
|
.map_err(map_io_error)?;
|
||||||
|
device.set_nonblocking(true).map_err(map_io_error)?;
|
||||||
|
|
||||||
|
let handle = ResourceArc::new(TunHandle { device });
|
||||||
|
Ok(make_ok_tuple(env, handle))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn recv(env: rustler::Env, handle: ResourceArc<TunHandle>, bufsize: usize) -> NifResult<Term> {
|
||||||
|
handle.device.set_nonblocking(true).map_err(map_io_error)?;
|
||||||
|
|
||||||
|
let mut buf = OwnedBinary::new(bufsize).expect("Could not allocate receive buffer");
|
||||||
|
let n = handle.device.recv(&mut buf).map_err(map_io_error)?;
|
||||||
|
assert!(buf.realloc(n));
|
||||||
|
let erl_buf = buf.release(env);
|
||||||
|
|
||||||
|
Ok(make_ok_tuple(env, erl_buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn send(handle: ResourceArc<TunHandle>, buf: Binary) -> NifResult<()> {
|
||||||
|
handle.device.set_nonblocking(false).map_err(map_io_error)?;
|
||||||
|
|
||||||
|
let mut n = 0;
|
||||||
|
while n < buf.len() {
|
||||||
|
n += handle.device.send(&buf[n..]).map_err(map_io_error)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn get_addrs(handle: ResourceArc<TunHandle>) -> NifResult<Vec<WrappedIpAddr>> {
|
||||||
|
let addrs = handle.device.addresses().map_err(map_io_error)?;
|
||||||
|
Ok(addrs.into_iter().map(|i| i.into()).collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn get_broadcast(handle: ResourceArc<TunHandle>) -> NifResult<WrappedIpAddr> {
|
||||||
|
let broadcast = handle.device.broadcast().map_err(map_io_error)?;
|
||||||
|
Ok(broadcast.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn get_name(handle: ResourceArc<TunHandle>) -> NifResult<String> {
|
||||||
|
handle.device.name().map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn set_name(handle: ResourceArc<TunHandle>, value: &str) -> NifResult<()> {
|
||||||
|
handle.device.set_name(value).map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn is_running(handle: ResourceArc<TunHandle>) -> NifResult<bool> {
|
||||||
|
handle.device.is_running().map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn set_running(handle: ResourceArc<TunHandle>, should_run: bool) -> NifResult<()> {
|
||||||
|
handle.device.enabled(should_run).map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn add_address(
|
||||||
|
handle: ResourceArc<TunHandle>,
|
||||||
|
addr: WrappedIpAddr,
|
||||||
|
prefix_length: u8,
|
||||||
|
) -> NifResult<()> {
|
||||||
|
match addr.0 {
|
||||||
|
IpAddr::V6(addr) => handle
|
||||||
|
.device
|
||||||
|
.add_address_v6(addr, prefix_length)
|
||||||
|
.map_err(map_io_error)?,
|
||||||
|
IpAddr::V4(addr) => handle
|
||||||
|
.device
|
||||||
|
.add_address_v4(addr, prefix_length)
|
||||||
|
.map_err(map_io_error)?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn remove_address(handle: ResourceArc<TunHandle>, addr: WrappedIpAddr) -> NifResult<()> {
|
||||||
|
handle.device.remove_address(*addr).map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn get_mtu(handle: ResourceArc<TunHandle>) -> NifResult<u16> {
|
||||||
|
handle.device.mtu().map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rustler::nif]
|
||||||
|
fn set_mtu(handle: ResourceArc<TunHandle>, mtu: u16) -> NifResult<()> {
|
||||||
|
handle.device.set_mtu(mtu).map_err(map_io_error)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_io_error(error: io::Error) -> rustler::Error {
|
||||||
|
rustler::Error::Term(match error.kind() {
|
||||||
|
io::ErrorKind::ResourceBusy => Box::new(atoms::resource_busy()),
|
||||||
|
io::ErrorKind::PermissionDenied => Box::new(atoms::permission_denied()),
|
||||||
|
io::ErrorKind::WouldBlock => Box::new(atoms::would_block()),
|
||||||
|
io::ErrorKind::AddrNotAvailable => Box::new(atoms::address_not_available()),
|
||||||
|
e => Box::new(e.to_string()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_ok_tuple<T: Encoder>(env: rustler::Env, value: T) -> Term {
|
||||||
|
make_tuple(env, &[atoms::ok().encode(env), value.encode(env)])
|
||||||
|
}
|
||||||
|
|
||||||
|
rustler::init!("Elixir.P2pChat.Transport.PrimTun");
|
||||||
Loading…
Add table
Add a link
Reference in a new issue