Packages
sippet
1.0.1
1.0.16
1.0.15
1.0.14
1.0.13
1.0.12
1.0.11
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.9
0.2.8
0.2.7
0.2.6
0.2.5
0.2.4
0.2.3
0.2.2
0.2.1
0.2.0
0.1.9
0.1.8
0.1.7
0.1.6
0.1.5
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
An Elixir Session Initiation Protocol (SIP) stack.
Current section
Files
Jump to
Current section
Files
lib/sippet/transports/udp.ex
defmodule Sippet.Transports.UDP do
@moduledoc """
Implements an UDP transport.
The UDP transport consists basically in a single listening and sending
process, this implementation itself.
This process creates an UDP socket and keeps listening for datagrams in
active mode. Its job is to forward the datagrams to the processing receiver
defined in `Sippet.Transports.Receiver`.
"""
use GenServer
alias Sippet.Message
require Logger
defstruct socket: nil,
family: :inet,
sippet: nil
@doc """
Starts the UDP transport.
"""
def start_link(options) when is_list(options) do
name =
case Keyword.fetch(options, :name) do
{:ok, name} when is_atom(name) ->
name
{:ok, other} ->
raise ArgumentError, "expected :name to be an atom, got: #{inspect(other)}"
:error ->
raise ArgumentError, "expected :name option to be present"
end
port =
case Keyword.fetch(options, :port) do
{:ok, port} when is_integer(port) and port > 0 and port < 65536 ->
port
{:ok, other} ->
raise ArgumentError,
"expected :port to be an integer between 1 and 65535, got: #{inspect(other)}"
:error ->
5060
end
{address, family} =
case Keyword.fetch(options, :address) do
{:ok, {address, family}} when family in [:inet, :inet6] and is_binary(address) ->
{address, family}
{:ok, address} when is_binary(address) ->
{address, :inet}
{:ok, other} ->
raise ArgumentError,
"expected :address to be an address or {address, family} tuple, got: " <>
"#{inspect(other)}"
:error ->
{"0.0.0.0", :inet}
end
ip =
case resolve_name(address, family) do
{:ok, ip} ->
ip
{:error, reason} ->
raise ArgumentError,
":address contains an invalid IP or DNS name, got: #{inspect(reason)}"
end
GenServer.start_link(__MODULE__, {name, ip, port, family}, name: __MODULE__)
end
@impl true
def init({name, ip, port, family}) do
Sippet.register_transport(name, :udp, false)
{:ok, nil, {:continue, {name, ip, port, family}}}
end
@impl true
def handle_continue({name, ip, port, family}, nil) do
case :gen_udp.open(port, [:binary, {:active, true}, {:ip, ip}, family]) do
{:ok, socket} ->
Logger.debug(
"#{inspect(self())} started transport " <>
"#{stringify_sockname(socket)}/udp"
)
state = %__MODULE__{
socket: socket,
family: family,
sippet: name
}
{:noreply, state}
{:error, reason} ->
Logger.error(
"#{inspect(self())} port #{port}/udp " <>
"#{inspect(reason)}, retrying in 10s..."
)
Process.sleep(10_000)
{:noreply, nil, {:continue, {name, ip, port, family}}}
end
end
@impl true
def handle_info({:udp, _socket, from_ip, from_port, packet}, %{sippet: sippet} = state) do
Sippet.Router.handle_transport_message(sippet, packet, {:udp, from_ip, from_port})
{:noreply, state}
end
@impl true
def handle_call(
{:send_message, message, to_host, to_port, key},
_from,
%{socket: socket, family: family, sippet: sippet} = state
) do
Logger.debug([
"sending message to #{stringify_hostport(to_host, to_port)}/udp",
", #{inspect(key)}"
])
with {:ok, to_ip} <- resolve_name(to_host, family),
iodata <- Message.to_iodata(message),
:ok <- :gen_udp.send(socket, {to_ip, to_port}, iodata) do
:ok
else
{:error, reason} ->
Logger.warn("udp transport error for #{to_host}:#{to_port}: #{inspect(reason)}")
if key != nil do
Sippet.Router.receive_transport_error(sippet, key, reason)
end
end
{:reply, :ok, state}
end
@impl true
def terminate(reason, %{socket: socket}) do
Logger.debug(
"stopped transport #{stringify_sockname(socket)}/udp, reason: #{inspect(reason)}"
)
:gen_udp.close(socket)
end
defp resolve_name(host, family) do
host
|> String.to_charlist()
|> :inet.getaddr(family)
end
defp stringify_sockname(socket) do
{:ok, {ip, port}} = :inet.sockname(socket)
address =
ip
|> :inet_parse.ntoa()
|> to_string()
"#{address}:#{port}"
end
defp stringify_hostport(host, port) do
"#{host}:#{port}"
end
end