Packages

This is an implementation of Mark Qvist's Reticulum Network Stack, a 'cryptography-based networking stack for building local and wide-area networks with readily available hardware.'

Current section

Files

Jump to
reticulum lib rns interface tcp_client.ex
Raw

lib/rns/interface/tcp_client.ex

defmodule RNS.Interface.TCPClient do
@moduledoc """
TCP client interface.
This is a simple tcp client interface, that will connect to a TCP server.
"""
@type params() :: map()
@type state() :: {RNS.Interface.t(), :inet.socket(), RNS.Config.transport()}
require Logger
use GenServer
@spec start(params(), RNS.Config.transport()) :: {:ok, pid()}
def start(params, transport) do
GenServer.start(__MODULE__, {params, transport})
end
# Callbacks
@impl true
@spec init({params(), RNS.Config.transport()}) :: {:ok, state()} | {:stop, atom()}
def init({%{"host" => host, "port" => port} = params, transport}) do
family =
case params["ipv6"] do
true ->
:inet6
false ->
:inet
nil ->
:inet
ipv6 ->
Logger.warning(
"TCP client interface on #{host}:#{port} will use ipv4 because an invalid value has been given for the parameter 'ipv6': #{inspect(ipv6)}. A boolean was expected, we will default to false(ipv4)."
)
:inet
end
case :gen_tcp.connect(String.to_charlist(host), port, [:binary, family]) do
{:ok, socket} ->
interface = interface(host, port, params)
# Tell the interface manager the interface has started and send the pid and interface struct.
RNS.InterfaceManager.interface_started(interface)
{:ok, {interface, socket, transport}}
{:error, error} ->
Logger.warning(
"TCP client interface failed to connect to #{host} on port #{port}: #{inspect(error)}."
)
{:stop, error}
end
end
def init({_, _}) do
Logger.error(
"TCP client interface failed to start: the parameters 'host' and 'port' are required, one or all of them was not provided."
)
{:stop, :not_all_params}
end
@impl true
@spec handle_info({:inbound, RNS.Packet.t(), RNS.Interface.t()}, state()) :: {:noreply, state()}
def handle_info({:inbound, packet, _}, state) do
socket = elem(state, 1)
framed_raw = RNS.Packet.raw(packet)
|> RNS.HDLC.frame()
:gen_tcp.send(socket, framed_raw)
{:noreply, state}
end
@impl true
@spec handle_info({:tcp, :inet.socket(), binary()}, state()) :: {:noreply, state()}
def handle_info({:tcp, _socket, packet}, {interface, _, transport} = state) do
RNS.PacketHandler.start(fn -> RNS.HDLC.unframe(packet) end, interface, transport)
{:noreply, state}
end
@impl true
@spec handle_info({:tcp_closed, :inet.socket()}, state()) :: {:stop, :tcp_closed}
def handle_info({:tcp_closed, _socket}, _state) do
{:stop, :tcp_closed}
end
@impl true
@spec handle_info({:tcp_closed, :inet.socket()}, state()) :: {:stop, atom()}
def handle_info({:tcp_error, _socket, error}, _state) do
{:stop, error}
end
@impl true
@spec handle_info({:transport_change, RNS.Config.transport()}, state()) :: {:noreply, state()}
def handle_info({:transport_change, transport}, {interface, socket, _old_transport}) do
new_state = {interface, socket, transport}
{:noreply, new_state}
end
@spec interface(String.t(), pos_integer(), map()) :: RNS.Interface.t()
defp interface(host, port, params) do
id = "TCPClient@#{host}:#{port}"
mode =
case params["mode"] do
nil ->
:full
"full" ->
:full
"access_point" ->
:access_point
"gateway" ->
:gateway
"roaming" ->
:roaming
"boundary" ->
:boundary
mode ->
Logger.warning("""
Unknown interface mode '#{mode}' on interface #{id}, 'full' will be used instead!
The interface mode can be 'full', 'access_point', 'gateway', 'roaming' or 'boundary'. You can read more about interface modes at https://reticulum.network/manual/interfaces.html#interface-modes.
""")
:full
end
RNS.Interface.new(id, self(), mode: mode)
end
end