implement automatic receiving of tun messages from elixir code

This commit is contained in:
lilly 2026-05-21 13:59:10 +02:00
commit 486fea2088
Signed by: lilly
SSH key fingerprint: SHA256:y9T5GFw2A20WVklhetIxG1+kcg/Ce0shnQmbu1LQ37g
10 changed files with 119 additions and 106 deletions

2
Cargo.lock generated
View file

@ -407,7 +407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]] [[package]]
name = "p2pchat_transport_tuntapport" name = "p2pchat_transport_gen_tun"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"rustler", "rustler",

View file

@ -1,4 +1,4 @@
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["native/p2pchat_transport_tuntapport"] members = ["native/p2pchat_transport_gen_tun"]

View file

@ -27,6 +27,7 @@
elixir elixir
elixir-ls elixir-ls
cargo cargo
rustfmt
rust-analyzer rust-analyzer
rustc rustc
]; ];

View file

@ -0,0 +1,52 @@
defmodule P2pChat.Transport.GenTun do
require Logger
use Rustler, otp_app: :p2p_chat, crate: "p2pchat_transport_gen_tun"
@behaviour GenServer
defstruct [:tun_handle]
#
# Server API
#
@impl true
def init(args) do
{:ok, tun_handle} = make_tun_device()
state = %__MODULE__{tun_handle: tun_handle}
{ :ok, state, { :continue, :recv } }
end
@impl true
def handle_continue(:recv, state) do
case tun_recv(state.tun_handle) do
{:ok, buf} ->
Logger.debug("Received #{byte_size(buf)} bytes")
{:error, :would_block} -> {}
{:error, e} ->
Logger.error("Error during receive: #{e}")
end
{:noreply, state, { :continue, :recv }}
end
@impl true
def handle_call({:recv}, _from, state) do
data = tun_recv(state.tun_handle)
{ :reply, data, state }
end
#
# Client API
#
def open() do
GenServer.start_link(__MODULE__, nil)
end
#
# NIFs
#
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

View file

@ -1,33 +0,0 @@
defmodule P2pChat.Transport.TunTap do
use GenServer
defstruct [:tun_fd]
@impl true
def init(args) do
{:ok, io_device} = setup_tap_device()
{:ok, {}}
end
@impl true
def terminate(_reason, state) do
end
def setup_tap_device(name \\ "tunP2P") do
{:ok, io_device} = :file.open(~c"/dev/net/tun", [:read, :write, :raw])
:ok = :file.close(io_device)
:ok
end
def teardown_tap_device(io_device) do
end
#
# Client API
#
def start_link(args) do
GenServer.start_link(__MODULE__, args)
end
end

View file

@ -1,8 +0,0 @@
defmodule P2pChat.Transport.TunTapPort do
use Rustler, otp_app: :p2p_chat, crate: "p2pchat_transport_tuntapport"
def make_tun_device(), do: :erlang.nif_error(:nif_not_loaded)
def read(_handle, _length \\ 2**16), do: :erlang.nif_error(:nif_not_loaded)
end

View file

@ -1,5 +1,5 @@
[package] [package]
name = "p2pchat_transport_tuntapport" name = "p2pchat_transport_gen_tun"
version = "0.1.0" version = "0.1.0"
authors = [] authors = []
edition = "2021" edition = "2021"

View file

@ -0,0 +1,63 @@
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");

View file

@ -1,62 +0,0 @@
use std::io;
use std::net::Ipv6Addr;
use rustler::Binary;
use rustler::NifResult;
use rustler::OwnedBinary;
use rustler::Resource;
use rustler::ResourceArc;
use rustler::Error;
use tun_rs::DeviceBuilder;
use tun_rs::SyncDevice;
mod atoms {
rustler::atoms! {
ok,
would_block,
other_error,
}
}
pub struct TunHandle {
device: SyncDevice,
}
#[rustler::resource_impl]
impl Resource for TunHandle {}
#[rustler::nif]
fn make_tun_device() -> NifResult<ResourceArc<TunHandle>> {
let device = DeviceBuilder::new()
.name("tunP2P")
.ipv6(Ipv6Addr::new(0x2001, 0x2f, 0, 0, 0, 0, 0, 1), 28)
.build_sync()
.expect("Could not create tun device");
device.set_nonblocking(true).expect("Could not make TUN device nonblocking");
println!("TUN device {} created", device.name().unwrap());
let handle = ResourceArc::new(TunHandle { device });
Ok(handle)
}
#[rustler::nif]
fn read(env: rustler::Env, handle: ResourceArc<TunHandle>, length: usize) -> NifResult<Binary> {
let mut buf = OwnedBinary::new(length).expect("Could not allocate a buffer from the BEAM");
let n = match handle.device.recv(&mut buf) {
Ok(n) => n,
Err(e) => return match e.kind() {
io::ErrorKind::WouldBlock => Err(Error::Term(Box::new(atoms::would_block()))),
_ => Err(Error::Term(Box::new(atoms::other_error()))),
},
};
println!("Have read {n} bytes");
let erl_buf = Binary::from_owned(buf, env);
Ok(erl_buf)
}
rustler::init!("Elixir.P2pChat.Transport.TunTapPort");