balena / elixir-sippet Goto Github PK
View Code? Open in Web Editor NEWAn Elixir library designed to be used as SIP protocol middleware.
License: BSD 3-Clause "New" or "Revised" License
An Elixir library designed to be used as SIP protocol middleware.
License: BSD 3-Clause "New" or "Revised" License
When I send an OPTION request, I get the following error message:
sipsak -vv -c sip:[email protected] -s sip:nobody@localhost
18:49:09.272 [error] GenStateMachine {Sippet.Transactions.Registry, ~K[z9hG4bK.74bac126|:options|127.0.0.1:62145]} terminating
** (ErlangError) Erlang error: {:"module could not be loaded", Sippet.Transaction}
(sippet) lib/sippet/transactions/server/non_invite.ex:12: Sippet.Transactions.Server.NonInvite.trying/3
(stdlib) gen_statem.erl:1240: :gen_statem.call_state_function/5
(stdlib) gen_statem.erl:1058: :gen_statem.loop_event_enter/11
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Here is my Core implementation:
defmodule MyCore do
use Sippet.Core
require Logger
alias Sippet.Transaction
alias Sippet.Message
def receive_request(request, server_key) do
Logger.info("logging request: #{inspect(request)}")
request |> Message.to_response(302) |> Transaction.send_response(server_key)
end
end
Maybe I don't understand exactly how to declare a Core module.
I am seeing some SIP traffic with two P-Asserted-Identity fields
...
P-Asserted-Identity: <sip:[email protected];user=phone;cpc=ordinary>
P-Asserted-Identity: <tel:5554443333;phone-context=+55;cpc=ordinary>
...
It looks like the RFC allows one SIP and one TEL P-Asserted-Identity.
https://www.ietf.org/rfc/rfc3325.txt
If there is no P-Asserted-Identity header field present, a proxy MAY
add one containing at most one SIP or SIPS URI, and at most one tel
URL.
Problem:
/home/your_mom/dev/my_bed/deps/sippet/c_src/utils.cc: In static member function ‘static bool {anonymous}::IteratorRangeToNumber<IteratorRangeToNumberTraits>::Invoke(const_iterator, const_iterator, value_type*)’:
/home/your_mom/dev/my_bed/deps/sippet/c_src/utils.cc:99:17: error: ‘numeric_limits’ is not a member of ‘std’
99 | if (!std::numeric_limits<value_type>::is_signed) {
Hack:
Edit the ./deps/sippet/c_src/utils.cc and add #include < limits >
#include <string>
#include <iostream>
#include <limits>
Fix:
Eh... I'm happy with the hack.
System:
Arch Linux
brett@beefstick: /tmp $ uname -a
Linux beefstick 6.1.15-1-lts #1 SMP PREEMPT_DYNAMIC Fri, 03 Mar 2023 12:22:08 +0000 x86_64 GNU/Linux
brett@beefstick: /tmp $ gcc --version
gcc (GCC) 12.2.1 20230201
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I need to parse a P-Asserted-Identity
header which as the same format as the From
header. I am wondering if there's a way to parse it. I am not familiar with nif extensions.
I am wondering if there's a way of running 2 instances of Sippet that listen on two different ports. But since I am fairly new to Elixir, I don't see if it's event possible with the current code base. Am I right?
Hi,
It appears that because of the structure of the Makefile, having LDFLAGS
set in the env makes the compilation of Sippet fail on Apple Silicon.
In our app, we're using Sippet as well as fast_tls
. The issue is, fast_tls
needs LDFLAGS
to contain the path to the OpenSSL library (i.e. LDFLAGS=-L/opt/homebrew/opt/openssl/lib
), and will not compile otherwise. When we try to compile everything at once, though, linking of Sippet fails because the Makefile uses conditional variable assignment ?=
and does not overwrite/append to LDFLAGS
if it is already set -- which is the case.
As far as I've tested, the only linker flag necessary for Sippet to compile successfully with this setup is -undefined suppress
.
The most straightforward way of alleviating this issue I can think of is to always append the -undefined suppress
flag, since it is necessary to link anyway. This is a simple fix, and doesn't change the general behaviour of the Makefile:
diff --git a/c_src/Makefile b/c_src/Makefile
index c20a402..a6dc5f1 100644
--- a/c_src/Makefile
+++ b/c_src/Makefile
@@ -73,7 +73,9 @@ endif
CC ?= cc
CFLAGS ?= -O3 -std=c11 $(ARCHFLAGS) -fstack-protector -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -std=c++11 $(ARCHFLAGS) -fstack-protector -Wall
- LDFLAGS ?= $(ARCHFLAGS) -flat_namespace -undefined suppress
+ LDFLAGS ?= $(ARCHFLAGS) -flat_namespace
+ # Necessary to link successfully on Apple Silicon
+ LDFLAGS += -undefined suppress
else ifeq ($(PLATFORM),freebsd)
CC ?= cc
CFLAGS ?= -O3 -std=c11 -finline-functions -fstack-protector -Wall -Wmissing-prototypes
Please let me know whether you're OK with this solution, and I'll be happy to make the relevant PR.
Logs from the failed linking for reference:
ld: Undefined symbols:
_enif_alloc_binary, referenced from:
(anonymous namespace)::ParseMultipleUriParams(enif_environment_t*, std::__1::__wrap_iter<char const*>, std::__1::__wrap_iter<char const*>) in parser.o
...
_enif_get_list_cell, referenced from:
parse_wrapper(enif_environment_t*, int, unsigned long const*) in parser.o
_enif_get_map_value, referenced from:
parse_wrapper(enif_environment_t*, int, unsigned long const*) in parser.o
[...] etc.
Hi,
Thank you for this library, I try to implement a scenario such as receive invite request, send 100 Trying, send 302 Moved Temporarily. Bu after returning 302 from my Sippet.Core module 100 Trying is not send because Sippet.Transactions.Server.Invite state is changed by outgoing response.
The another thing is after sending 302 outgoing response, Sippet.Transactions.Server.Invite retrying the send the the same response about 10 times within a timeout period.
Is this a normal behaviour?
My Sippet.Core receive_request function;
@spec receive_request(Message.request, Transactions.Server.t | nil) :: any
def receive_request(incoming_request, server_key) do
if incoming_request.start_line.method == :invite do
incoming_request |> Sippet.Message.to_response(302) |> Sippet.Transactions.send_response(server_key)
end
end
I am trying migrate my app to the latest version of Sippet and I don't understand how to send a response back to the caller.
When I read the README file, I see
Sippet.send(:sippet, request)
I don't understand what is the :sippet
atom. Should it be the stack name?
Prior to the migration my Core module add a function as such:
def receive_request(request, server_key) do
request
|> except_to_500(&Auth.auth(&1))
|> Transactions.send_response(server_key)
end
I am wondering how can I migrate this to the latest version of the library.
We will be using a sippet library, but noticed there is no tcp
transport. Is adding tcp
transport something hard to current implementation?
When I try to compile an app that uses the library I get the following error
could not compile dependency :sippet, "mix compile" failed. You can recompile this dependency with "mix deps.compile sippet", update it with "mix deps.update sippet" or clean it with "mix deps.clean sippet"
** (ArgumentError) all arguments for System.cmd/3 must be binaries
(elixir) lib/system.ex:625: System.cmd/3
/Users/mchabot/dev/ispt/test_sip/deps/sippet/mix.exs:85: Mix.Tasks.Compile.Make.run/1
I am on Elixir 1.8
Hello @balena, it would be great if you put up a simple ping pong example between two SIP user agents that talk to the SIP Server.
Is it possible to make a release with a bug fix from #34?
I see an constant increase of memory when running an app.
I have noticed that the sippet supervisor consumes more and more memory.
iex(as@stirsrv01)1> Supervisor.count_children(:as_stack_sup)
%{active: 47, specs: 173756, supervisors: 1, workers: 173755}
I think that the application uses a simple Supervisor and not a DynamicSupervisor.
This is very easy to reproduce, just let the application run while sending calls to it then call the Supervisor.count_children()
function.
When I send a request to my app, I get the following error:
07:59:58.395 [error] GenServer #PID<0.199.0> terminating
** (FunctionClauseError) no function clause matching in :inet_udp.getserv/1
(kernel) inet_udp.erl:38: :inet_udp.getserv({62421, ""})
(kernel) gen_udp.erl:127: :gen_udp.send/4
(sippet) lib/sippet/transports/udp/sender.ex:36: Sippet.Transports.UDP.Sender.handle_cast/2
(stdlib) gen_server.erl:616: :gen_server.try_dispatch/4
(stdlib) gen_server.erl:686: :gen_server.handle_msg/6
(stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
This is caused by this line:
https://github.com/balena/elixir-sippet/blob/master/lib/sippet/transports.ex#L85
The Integer.parse/1
function now returns a tuple.
https://hexdocs.pm/elixir/Integer.html#parse/2
I think the solution would be something like
%{"rport" => rport} -> Integer.parse(rport) |> elem(0)
When I send an OPTION
request to the server using sipsak
sipsak -vv -c sip:[email protected] -s sip:nobody@localhost
I get the following warning from the server:
discarded request, ":from does not have tag"
I looked at the source code and it seems that the tag is not even parsed. Do I miss something ?
I tried many other request and I had the same issue.
It was easy enough to fix though, I went into c_src/Makefile
and removed the -arch x86_64
s under ifeq ($(UNAME_SYS), Darwin)
, then did a make clean && make
. That seemed to get things working (I think, I haven't played around with this too much yet).
I am looking at this library as a relatively simple "client" or "phone side" SIP element for testing purposes. In my implementation every "User Agent" in this case is a GenServer that spawns it's own Sippet, and Transport, and I route to the different agents using a simple PID registry.
I have read all around these issues and the docs, but I seem to have missed something critical about Transaction handling.
My client and server Transactions (in this case a REGISTER attempt) time out in the :trying state.
def handle_call({:authenticate, response, _key}, _caller, agent) do
{:ok, new} = DigestAuth.make_request(List.first(agent.messages), response, fn _ ->
{:ok, agent.account.sip_user, agent.account.sip_password} end, [])
new_req =
new_req
|> Message.update_header(:cseq, fn {seq, method} ->
{seq + 1, method}
end)
|> Message.update_header_front(:via, fn {ver, proto, hostport, params} ->
{ver, proto, hostport, %{params | "branch" => Message.create_branch()}}
end)
|> Message.update_header(:from, fn {name, uri, params} ->
{name, uri, %{params | "tag" => Message.create_tag()}}
end)
Sippet.send(agent.registered_name, new_req)
......
end
here is my "Core":
def receive_request(%Message{start_line: %RequestLine{}} = request, nil) do
GenServer.call(route_agent(request), {request})
end
def receive_request(%Message{} = incoming_request, server_key) do
GenServer.call(route_agent(request), {request})
end
def receive_response(%Message{start_line: %StatusLine{status_code: status_code}} = incoming_response, client_key) when status_code in [401,407] do
GenServer.call(route_agent(incoming_response.headers.to), {:authenticate, incoming_response, client_key})
end
And In this case I get a "200 OK" from the proxy I'm registering through, but the transaction doesn't move beyond the :trying state.
I feel like I'm doing something wrong here. Can someone give me some pointers?
Here the error that I've got while running mix test
on the master branch:
== Compilation error in file test/transaction_client_invite_test.exs ==
** (CompileError) test/transaction_client_invite_test.exs:73: cannot invoke remote function :erlang.*/2 inside match
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1355: :lists.mapfoldl/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(elixir) src/elixir_fn.erl:14: anonymous fn/4 in :elixir_fn.expand/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(elixir) src/elixir_fn.erl:19: :elixir_fn.expand/3
(stdlib) lists.erl:1354: :lists.mapfoldl/3
(stdlib) lists.erl:1355: :lists.mapfoldl/3
(elixir) expanding macro: Kernel.|>/2
Could you please provide a sample MyCore implementation? Reason I'm asking is that I've done all other configuration/setup (as explained in the readme) and I'm able to see started plug 127.0.0.1:5060/udp
logged message when I start the application, however none of the log messages from my custom core implementation appear when I send a sample INVITE request.
For the reference - my custom core implementation:
defmodule ZCore do
require Logger
alias Sippet.Transactions
alias Sippet.Message
@behaviour Sippet.Core
require Logger
@impl Sippet.Core
def receive_request(req, serverTxKey) do
Logger.debug(fn -> "#{inspect self()} receive_request:: req => #{inspect req}; serverTxKey => #{inspect serverTxKey}" end)
req |> Message.to_response(100) |> Transactions.send_response(serverTxKey)
end
@impl Sippet.Core
def receive_response(resp, clientTxKey) do
Logger.debug(fn -> "#{inspect self()} receive_response:: resp => #{inspect resp}; clientTxKey => #{inspect clientTxKey}" end)
end
@impl Sippet.Core
def receive_error(reason, txKey) do
Logger.debug(fn -> "#{inspect self()} receive_error:: reason => #{inspect reason}; txKey => #{inspect txKey}" end)
end
end
As you might have guessed - an Elixir beginner here so any other relevant comments/suggestions are welcome.
Hello hello. First, thanks for implementing this!
I added config Sippet.Core and the receive_request callback got called but the client still timeouts. It is the Telephone macOS client. Could you give me some hint on what to do next?
Thank you!
cc /Users/krishna/projects/elixir-sippet/c_src/string_piece.o /Users/krishna/projects/elixir-sippet/c_src/utils.o /Users/krishna/projects/elixir-sippet/c_src/parser.o /Users/krishna/projects/elixir-sippet/c_src/tokenizer.o /Users/krishna/projects/elixir-sippet/c_src/prtime.o -arch x86_64 -flat_namespace -undefined suppress -shared -lstdc++ -L /usr/local/Cellar/erlang/23.1.1/lib/erlang/usr/lib -lerl_interface -lei -o /Users/krishna/projects/elixir-sippet/priv/sippet_nif.so
ld: library not found for -lerl_interface
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [/Users/krishna/projects/elixir-sippet/priv/sippet_nif.so] Error 1
First of all, this looks like a great application, still investigating, but thanks for making this available!
I have created a simple project using elixir-sippet 1.0.7.
My aim is to create a program that does (many) outbound registrations with authentication for different contacts (so one contact per registration).
I am able to send out a REGISTER request, by creating one as follows:
req = """
REGISTER sip:foo.com SIP/2.0
Via: SIP/2.0/UDP 192.168.64.1:5060;branch=#{Sippet.Message.create_branch()}
From: <sip:[email protected]>;tag=#{Sippet.Message.create_tag()}
To: <sip:[email protected]>
CSeq: 1 REGISTER
Call-ID: #{Sippet.Message.create_call_id()}
Contact: <sip:[email protected]:5060>
Expires: 300
""" |> Sippet.Message.parse!()
And then sending it out as follows:
Sippet.send :mystack, req
I see the request being sent out to the registrar and a 401 challenge being responded by it.
I have a simple Core registered that just prints out the arguments incoming_response and client_key passed to the receive_response function.
Now I'd like to send a second request, but with the proper Authorization header in it.
First, I tried to generate a new request based on the sent request and received response:
{:ok, auth_req} = Sippet.DigestAuth.make_request req, resp, fn(_realm) -> {:ok, "phone1", "topsecret"} end
Then incremented the cseq by one as follows:
auth_req_inc_cseq = put_in auth_req.headers.cseq, {2, :register}
But when then trying to send that request, it says the client transaction already exists.
I was thinking I may need to change the via branch, but still. If the response to a request is received, where does the transaction then remain ? Should I remove it myself ?
What would be the proper way to go for this ?
Thanks!
I am receiving request that has a Via
header that has a rport flag. Here is the original header
Via: SIP/2.0/UDP 205.205.74.6:5060;rport;branch=z9hG4bK-26320-1-0
When I return the response, the Via
header is serialized this way:
Via: SIP/2.0/UDP 205.205.74.6:5060;rport=;received=127.0.0.1;branch=z9hG4bK-26320-1-0
Note that the rport=
param is invalid. https://www.tech-invite.com/fo-abnf/tinv-fo-abnf-sip-h-via.html#via-params.
I think that the value of a parameter cannot be an empty string.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.