From d94f20d04242009b521113e94f732270f5807b89 Mon Sep 17 00:00:00 2001
From: lilly
Date: Thu, 21 May 2026 16:44:01 +0200
Subject: [PATCH] implement a bunch of utility functions on tun device
primitive
---
Cargo.lock | 2 +-
Cargo.toml | 2 +-
lib/p2p_chat.ex | 2 +-
lib/p2p_chat/transport/gen_tun.ex | 27 +--
lib/p2p_chat/transport/prim_tun.ex | 17 ++
native/p2pchat_transport_gen_tun/src/lib.rs | 63 -----
.../Cargo.toml | 2 +-
.../README.md | 0
native/p2pchat_transport_prim_tun/src/lib.rs | 215 ++++++++++++++++++
9 files changed, 248 insertions(+), 82 deletions(-)
create mode 100644 lib/p2p_chat/transport/prim_tun.ex
delete mode 100644 native/p2pchat_transport_gen_tun/src/lib.rs
rename native/{p2pchat_transport_gen_tun => p2pchat_transport_prim_tun}/Cargo.toml (86%)
rename native/{p2pchat_transport_gen_tun => p2pchat_transport_prim_tun}/README.md (100%)
create mode 100644 native/p2pchat_transport_prim_tun/src/lib.rs
diff --git a/Cargo.lock b/Cargo.lock
index 796b5da..1495f33 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -407,7 +407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
-name = "p2pchat_transport_gen_tun"
+name = "p2pchat_transport_prim_tun"
version = "0.1.0"
dependencies = [
"rustler",
diff --git a/Cargo.toml b/Cargo.toml
index 28fe060..5d4615c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,4 @@
[workspace]
resolver = "2"
-members = ["native/p2pchat_transport_gen_tun"]
+members = ["native/p2pchat_transport_prim_tun"]
diff --git a/lib/p2p_chat.ex b/lib/p2p_chat.ex
index 17aae51..2d940a3 100644
--- a/lib/p2p_chat.ex
+++ b/lib/p2p_chat.ex
@@ -1,7 +1,7 @@
defmodule P2pChat do
def hello do
{: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)
netid2 = P2pChat.Transport.get_netid(pid2)
diff --git a/lib/p2p_chat/transport/gen_tun.ex b/lib/p2p_chat/transport/gen_tun.ex
index 98113a3..50137ae 100644
--- a/lib/p2p_chat/transport/gen_tun.ex
+++ b/lib/p2p_chat/transport/gen_tun.ex
@@ -1,6 +1,6 @@
defmodule P2pChat.Transport.GenTun do
require Logger
- use Rustler, otp_app: :p2p_chat, crate: "p2pchat_transport_gen_tun"
+ require P2pChat.Transport.PrimTun, as: PrimTun
@behaviour GenServer
@@ -10,15 +10,15 @@ defmodule P2pChat.Transport.GenTun do
# Server API
#
@impl true
- def init(args) do
- {:ok, tun_handle} = make_tun_device()
+ def init(_args) do
+ {:ok, tun_handle} = PrimTun.make_tun_device(true)
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
+ case PrimTun.tun_recv(state.tun_handle, 2**16) do
{:ok, buf} ->
Logger.debug("Received #{byte_size(buf)} bytes")
{:error, :would_block} -> {}
@@ -26,13 +26,13 @@ defmodule P2pChat.Transport.GenTun do
Logger.error("Error during receive: #{e}")
end
- {:noreply, state, { :continue, :recv }}
+ {:noreply, state}
end
@impl true
- def handle_call({:recv}, _from, state) do
- data = tun_recv(state.tun_handle)
- { :reply, data, state }
+ def handle_call({:get_addrs}, _from, state) do
+ result = PrimTun.get_addrs(state.tun_handle)
+ {:reply, result, state}
end
#
@@ -40,13 +40,10 @@ defmodule P2pChat.Transport.GenTun do
#
def open() do
GenServer.start_link(__MODULE__, nil)
- end
+ 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)
+ def get_addrs(pid) do
+ GenServer.call(pid, {:get_addrs})
+ end
end
diff --git a/lib/p2p_chat/transport/prim_tun.ex b/lib/p2p_chat/transport/prim_tun.ex
new file mode 100644
index 0000000..1510d37
--- /dev/null
+++ b/lib/p2p_chat/transport/prim_tun.ex
@@ -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
diff --git a/native/p2pchat_transport_gen_tun/src/lib.rs b/native/p2pchat_transport_gen_tun/src/lib.rs
deleted file mode 100644
index bcd3d31..0000000
--- a/native/p2pchat_transport_gen_tun/src/lib.rs
+++ /dev/null
@@ -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 {
- 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, bufsize: usize) -> NifResult {
- 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(env: rustler::Env, value: T) -> Term {
- make_tuple(env, &[atoms::ok().encode(env), value.encode(env)])
-}
-
-rustler::init!("Elixir.P2pChat.Transport.GenTun");
diff --git a/native/p2pchat_transport_gen_tun/Cargo.toml b/native/p2pchat_transport_prim_tun/Cargo.toml
similarity index 86%
rename from native/p2pchat_transport_gen_tun/Cargo.toml
rename to native/p2pchat_transport_prim_tun/Cargo.toml
index e7a6510..f043ec6 100644
--- a/native/p2pchat_transport_gen_tun/Cargo.toml
+++ b/native/p2pchat_transport_prim_tun/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "p2pchat_transport_gen_tun"
+name = "p2pchat_transport_prim_tun"
version = "0.1.0"
authors = []
edition = "2021"
diff --git a/native/p2pchat_transport_gen_tun/README.md b/native/p2pchat_transport_prim_tun/README.md
similarity index 100%
rename from native/p2pchat_transport_gen_tun/README.md
rename to native/p2pchat_transport_prim_tun/README.md
diff --git a/native/p2pchat_transport_prim_tun/src/lib.rs b/native/p2pchat_transport_prim_tun/src/lib.rs
new file mode 100644
index 0000000..7d13c82
--- /dev/null
+++ b/native/p2pchat_transport_prim_tun/src/lib.rs
@@ -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 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::>();
+ 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 {
+ 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 {
+ 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, bufsize: usize) -> NifResult {
+ 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, 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) -> NifResult> {
+ let addrs = handle.device.addresses().map_err(map_io_error)?;
+ Ok(addrs.into_iter().map(|i| i.into()).collect::>())
+}
+
+#[rustler::nif]
+fn get_broadcast(handle: ResourceArc) -> NifResult {
+ let broadcast = handle.device.broadcast().map_err(map_io_error)?;
+ Ok(broadcast.into())
+}
+
+#[rustler::nif]
+fn get_name(handle: ResourceArc) -> NifResult {
+ handle.device.name().map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn set_name(handle: ResourceArc, value: &str) -> NifResult<()> {
+ handle.device.set_name(value).map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn is_running(handle: ResourceArc) -> NifResult {
+ handle.device.is_running().map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn set_running(handle: ResourceArc, should_run: bool) -> NifResult<()> {
+ handle.device.enabled(should_run).map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn add_address(
+ handle: ResourceArc,
+ 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, addr: WrappedIpAddr) -> NifResult<()> {
+ handle.device.remove_address(*addr).map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn get_mtu(handle: ResourceArc) -> NifResult {
+ handle.device.mtu().map_err(map_io_error)
+}
+
+#[rustler::nif]
+fn set_mtu(handle: ResourceArc, 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(env: rustler::Env, value: T) -> Term {
+ make_tuple(env, &[atoms::ok().encode(env), value.encode(env)])
+}
+
+rustler::init!("Elixir.P2pChat.Transport.PrimTun");