Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

42 changes: 35 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,16 @@ impl Device {
.map_err(Into::into)
}

/// Get our node info.
pub async fn self_node(&self) -> Result<NodeInfo, Error> {
self.runtime
.control
.ask(ts_runtime::control_runner::SelfNode)
.await
.map_err(ts_runtime::Error::from)?
.ok_or(Error::RuntimeDegraded)
}

/// Look up a peer by name.
pub async fn peer_by_name(&self, name: &str) -> Result<Option<NodeInfo>, Error> {
let pt = self
Expand All @@ -251,14 +261,32 @@ impl Device {
.map_err(Into::into)
}

/// Get our node info.
pub async fn self_node(&self) -> Result<NodeInfo, Error> {
self.runtime
.control
.ask(ts_runtime::control_runner::SelfNode)
/// Look up a peer by ip.
pub async fn peer_by_tailnet_ip(&self, ip: IpAddr) -> Result<Option<NodeInfo>, Error> {
let pt = self
.runtime
.peer_tracker
.upgrade()
.ok_or(Error::RuntimeDegraded)?;

pt.ask(ts_runtime::peer_tracker::PeerByTailnetIp { ip })
.await
.map_err(ts_runtime::Error::from)?
.ok_or(Error::RuntimeDegraded)
.map_err(ts_runtime::Error::from)
.map_err(Into::into)
}

/// Look up the peer(s) with the most-specific route matches for `ip`.
pub async fn peers_with_route(&self, ip: IpAddr) -> Result<Vec<NodeInfo>, Error> {
let pt = self
.runtime
.peer_tracker
.upgrade()
.ok_or(Error::RuntimeDegraded)?;

pt.ask(ts_runtime::peer_tracker::PeerByAcceptedRoute { ip })
.await
.map_err(ts_runtime::Error::from)
.map_err(Into::into)
}

/// Attempt to gracefully shut down this device's runtime.
Expand Down
22 changes: 19 additions & 3 deletions ts_elixir/lib/tailscale.ex
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,12 @@ defmodule Tailscale do
"""
def ipv6_addr(dev), do: Tailscale.Native.ipv6_addr(dev)

@spec self_node(t()) :: {:ok, Tailscale.NodeInfo.t()} | {:error, any()}
@doc """
Get this node's `m:Tailscale.NodeInfo`.
"""
defdelegate self_node(dev), to: Tailscale.Native

@spec peer_by_name(t(), String.t()) :: {:ok, Tailscale.NodeInfo.t() | nil} | {:error, any()}
@doc """
Look up a peer by name.
Expand All @@ -119,9 +125,19 @@ defmodule Tailscale do
"""
def peer_by_name(dev, name), do: Tailscale.Native.peer_by_name(dev, name)

@spec self_node(t()) :: {:ok, Tailscale.NodeInfo.t()} | {:error, any()}
@spec peer_by_tailnet_ip(t(), Tailscale.ip_addr()) ::
{:ok, Tailscale.NodeInfo.t() | nil} | {:error, any()}
@doc """
Get this node's `m:Tailscale.NodeInfo`.
Look up the peer with the given tailnet IP address.

Returns `{:ok, nil}` if there was no such peer. `:error` if the lookup encountered an error.
"""
defdelegate self_node(dev), to: Tailscale.Native
defdelegate peer_by_tailnet_ip(dev, ip), to: Tailscale.Native

@spec peers_with_route(t(), Tailscale.ip_addr()) ::
{:ok, [Tailscale.NodeInfo.t()]} | {:error, any()}
@doc """
Retrieve the most narrow set of peers that accept packets for the specified IP.
"""
defdelegate peers_with_route(dev, ip), to: Tailscale.Native
end
12 changes: 12 additions & 0 deletions ts_elixir/lib/tailscale/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ defmodule Tailscale.Native do
@spec self_node(device()) :: {:ok, %{}} | {:error, any()}
def self_node(_dev), do: err()

@doc """
Retrieve a peer by its tailnet IP.
"""
@spec peer_by_tailnet_ip(device(), Tailscale.ip_addr()) :: {:ok, %{} | nil} | {:error, any()}
def peer_by_tailnet_ip(_dev, _ip), do: err()

@doc """
Retrieve the most narrow set of peers that accept packets for the specified IP.
"""
@spec peers_with_route(device(), Tailscale.ip_addr()) :: {:ok, [%{}]} | {:error, any()}
def peers_with_route(_dev, _ip), do: err()

@doc """
Load key state from the specified path, generating a new state if the file doesn't exist.
"""
Expand Down
34 changes: 34 additions & 0 deletions ts_elixir/native/ts_elixir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,40 @@ fn self_node(env: rustler::Env<'_>, dev: ResourceArc<Device>) -> impl Encoder {
}
}

#[rustler::nif(schedule = "DirtyIo")]
fn peer_by_tailnet_ip(env: rustler::Env<'_>, dev: ResourceArc<Device>, ip: Term) -> impl Encoder {
let dev = dev.inner.clone();
let Some(ip) = ip_from_erl(ip) else {
return env.error_tuple("invalid ip");
};

match TOKIO_RUNTIME.block_on(async move { dev.peer_by_tailnet_ip(ip).await }) {
Err(e) => (atoms::error(), e.to_string()).encode(env),
Ok(None) => (atoms::ok(), Option::<()>::None).encode(env),
Ok(Some(peer)) => (atoms::ok(), NodeInfo::from_node(env, peer)).encode(env),
}
}

#[rustler::nif(schedule = "DirtyIo")]
fn peers_with_route(env: rustler::Env<'_>, dev: ResourceArc<Device>, ip: Term) -> impl Encoder {
let dev = dev.inner.clone();
let Some(ip) = ip_from_erl(ip) else {
return env.error_tuple("invalid ip");
};

match TOKIO_RUNTIME.block_on(async move { dev.peers_with_route(ip).await }) {
Err(e) => (atoms::error(), e.to_string()).encode(env),
Ok(peers) => (
atoms::ok(),
peers
.into_iter()
.map(|x| NodeInfo::from_node(env, x))
.collect::<Vec<_>>(),
)
.encode(env),
}
}

fn ip_to_erl(env: rustler::Env, ip: impl Into<IpAddr>) -> Term {
match ip.into() {
IpAddr::V4(ip) => {
Expand Down
30 changes: 30 additions & 0 deletions ts_python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,36 @@ impl Device {
Ok(NodeInfo::from(&node))
})
}

/// Look up a peer by its tailnet IP address.
pub fn peer_by_tailnet_ip<'p>(&self, py: Python<'p>, ip: IpRepr) -> PyFut<'p> {
let dev = self.dev.clone();

future_into_py(py, async move {
let ip = ip.try_into().map_err(py_value_err)?;
let node = dev.peer_by_tailnet_ip(ip).await.map_err(py_value_err)?;

Ok(node.map(|node| NodeInfo::from(&node)))
})
}

/// Look up peer(s) with the most specific route match for the given address.
///
/// If more than one peer has the same route covering the same address, more than one
/// result may be returned.
pub fn peers_with_route<'p>(&self, py: Python<'p>, ip: IpRepr) -> PyFut<'p> {
let dev = self.dev.clone();

future_into_py(py, async move {
let ip = ip.try_into().map_err(py_value_err)?;
let nodes = dev.peers_with_route(ip).await.map_err(py_value_err)?;

Ok(nodes
.into_iter()
.map(|node| NodeInfo::from(&node))
.collect::<Vec<_>>())
})
}
}

fn sockaddr_as_tuple(s: SocketAddr) -> (IpAddr, u16) {
Expand Down
1 change: 1 addition & 0 deletions ts_runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ ts_transport_derp.workspace = true

# Unconditionally required dependencies.
futures.workspace = true
ipnet.workspace = true
kameo = "0.19"
kameo_actors = "0.4"
thiserror.workspace = true
Expand Down
Loading