Git Product home page Git Product logo

sip-lab's Introduction

sip-lab

Overview

A nodejs module that helps to write functional tests for SIP systems (including media operations). It uses pjproject for SIP and media processing.

It permits to:

  • make audio calls using UDP, TCP and TLS transports
  • send and receive DTMF inband/RFC2833/INFO.
  • play/record wav file on a call
  • send/receive fax (T.30 only)
  • send/receive MRCPv2 messages (experimental)

TODO:

  • add suport for T.38 fax
  • add support for WebRTC
  • add support for video playing/recording from/to file

Installation

This is a node.js addon and it is known to work on Debian 11, Debian 10, Ubuntu 22.04 and Ubuntu 20.04. It is distributed with prebuild binaries for node.js 15.0.0 and above (but built for Debian 11. For other Debian versions or for Ubuntu a local built of the addon will be executed. Being the case, be patient as the build process will take several minutes to complete).

To install it, first install some dependencies (you might not need them if your are on Debian 11).

apt install build-essential automake autoconf libtool libspeex-dev libopus-dev libsdl2-dev libavdevice-dev libswscale-dev libv4l-dev libopencore-amrnb-dev libopencore-amrwb-dev libvo-amrwbenc-dev libvo-amrwbenc-dev libboost-dev libtiff-dev libpcap-dev libssl-dev uuid-dev cmake

Then install sip-lab (local build of the addon might be triggered here if this is not Debian 11):

npm install sip-lab

Then run some sample script from subfolder samples:

node samples/simple.js

The above script has detailed comments.

Please read it to undestand how to write your own test scripts.

About the code

Although the code in written in .cpp/.hpp named files, this is not actually a C++ project.

It is mostly written in C using some C++ facilities.

sip-lab's People

Contributors

dependabot[bot] avatar mayamat avatar mayamatakeshi avatar

Watchers

 avatar  avatar  avatar

sip-lab's Issues

Refactor packet dump to be done from packets received from pjsip

Currently we use libpcap to do this by updating a filter every time we create a SIP/RTP endpoint.
However there are problems with this approacy:

  • this doesn't permit to get all TCP/TLS traffic as if we connect to a remove SIP server, a random local port will be used (we will have only the traffic that reaches us with our listeing TCP/TLS ports).
  • in case of TLS we need to handle SSL keys to be able to decode the messages

Instead, let's dump the messages according to what is received from pjsip:
all SIP messages and RTP traffic would be dumped as UDP no matter how they were received/sent.
(obs: not sure if we can actually do this yet).

Occasional crash when executing samples/mrcp_and_audio.js

12:07:26.558  Match successful
12:07:26.558  
12:07:26.558  wait (mrcp_and_audio.js:224) got expected event:
12:07:26.558    {
    event: 'mrcp_msg',
    call_id: 0,
    msg: 'MRCP/2.0 87 SPEAK-COMPLETE 1 COMPLETE\r\n' +
  'channel-identifier: 32AECB234338@speechsynth\r\n' +
  '\r\n'
  }
12:07:26.558  All expected events received
12:07:26.558  wait (mrcp_and_audio.js:224) finished
12:07:26.558  All expected events received
12:07:26.558  wait (mrcp_and_audio.js:224) finished
pjw_call_reinvite call_id=0

process_media call_id=0
itr is object
itr is object
i=0 media found

i=1 media found

process_media call_id=0 call->local_sdp: v=0
o=- 3888011246 3888011246 IN IP4 0.0.0.0
s=pjmedia
t=0 0
m=application 9 TCP/MRCPv2 1
c=IN IP4 127.0.0.1
a=setup:active
a=connection:new
a=resource:speechsynth
a=cmid:1
m=audio 10000 RTP/AVP 0 8 3 96 97 98 99 9 18 120 121 122
c=IN IP4 127.0.0.1
b=TIAS:64000
a=rtcp:10001 IN IP4 127.0.0.1
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:3 GSM/8000
a=rtpmap:96 iLBC/8000
a=fmtp:96 mode=30
a=rtpmap:97 speex/32000
a=rtpmap:98 speex/16000
a=rtpmap:99 speex/8000
a=rtpmap:9 G722/8000
a=rtpmap:18 G729/8000
a=rtpmap:120 telephone-event/8000
a=fmtp:120 0-16
a=rtpmap:121 telephone-event/32000
a=fmtp:121 0-16
a=rtpmap:122 telephone-event/16000
a=fmtp:122 0-16
a=recvonly
a=mid:1


corrupted size vs. prev_size
./runtests: line 3: 33334 Aborted                 (core dumped) node $i
samples/mrcp_and_audio.js failed
takeshi@takeshi-desktop:~/src/git/MayamaTakeshi/sip-lab$ 

Check TLS support

If I am not mistaken we can use TLS transport against SIP servers but we cannot create two TLS transports and make a call between them as we get:

09:37:56.749        ssl_sock_ossl.c  OpenSSL version : 30000020
09:37:56.799                    SSL  SSL_ERROR_SSL (Handshake): Level: 0 err: <337092801> <error:1417A0C1:SSL routines:tls_post_process_client_hello:no shared cipher> len: 0 peer: 127.0.0.1:52055
09:37:56.799           ssl0x6cbf860  Handshake failed in accepting 127.0.0.1:52055: Unknown error 2018481
09:37:56.849                    SSL  SSL_ERROR_SSL (Handshake): Level: 0 err: <336151568> <error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure> len: 0 peer: 127.0.0.1:6062
09:37:56.849          tlsc0x6cd2ea8  TLS connect() error: [code=1077248] peer: 127.0.0.1: Unknown error 1077248
09:37:56.849           tsx0x6cd10c8  Failed to send Request msg INVITE/cseq=18591 (tdta0x6c8b578)! err=1077248 (Unknown error 1077248)
09:37:56.849           tsx0x6cd10c8  State changed from Calling to Terminated, event=TRANSPORT_ERROR
09:37:56.849           dlg0x6b3b1f8  .Transaction tsx0x6cd10c8 state changed to Terminated

Googling i found a hint that the 'no shared cipher' might be misleading:

https://community.asterisk.org/t/pjsip-tls-trouble/73813/4

So let's try to solve this and add a TLS sample.

Segfault in Debian 10

sip-lab was failing to build on Debian 10.
We did the necessary adjustments but running

  node samples/simple.js

is resulting in crash.
Running under gdb we see:

                              
Thread 1 "node" received signal SIGSEGV, Segmentation fault.
__strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:120
120     ../sysdeps/x86_64/multiarch/../strlen.S: No such file or directory.
(gdb) bt                         
#0  0x00007ffff7b40206 in __strlen_sse2 () at ../sysdeps/x86_64/multiarch/../strlen.S:120
#1  0x00007ffff7af99ef in _IO_vfprintf_internal (s=0x7fffffff6af0, format=0x7ffff52533ec "on_rx_request %.*s\n\n", ap=0x7fffffff91b0) at vfprintf.c:1638
#2  0x00007ffff7afa866 in buffered_vfprintf (s=s@entry=0x7ffff7c64760 <_IO_2_1_stdout_>, format=format@entry=0x7ffff52533ec "on_rx_request %.*s\n\n", args=args@entry=0x7fffffff91b0) at vfprintf.c:2322   
#3  0x00007ffff7af7eb2 in _IO_vfprintf_internal (s=0x7ffff7c64760 <_IO_2_1_stdout_>, format=0x7ffff52533ec "on_rx_request %.*s\n\n", ap=ap@entry=0x7fffffff91b0) at vfprintf.c:1296
#4  0x00007ffff7b00606 in __printf (format=<optimized out>) at printf.c:33
#5  0x00007ffff51ccbe6 in _addon_log(int, char const*, ...) () at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node
#6  0x00007ffff51d81a9 in on_rx_request(pjsip_rx_data*) () at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node
#7  0x00007ffff520b94b in pjsip_endpt_process_rx_data (p_handled=0x7fffffffa75c, p=0x7fffffffa780, rdata=0x266f488, endpt=0x261e7a8) at ../src/pjsip/sip_endpoint.c:887
#8  0x00007ffff520b94b in pjsip_endpt_process_rx_data (endpt=0x261e7a8, rdata=0x266f488, p=<optimized out>, p_handled=0x7fffffffa75c) at ../src/pjsip/sip_endpoint.c:824
#9  0x00007ffff520bb56 in endpt_on_rx_msg (endpt=0x261e7a8, status=<optimized out>, rdata=0x266f488) at ../src/pjsip/sip_endpoint.c:1037
#10 0x00007ffff52117a6 in pjsip_tpmgr_receive_packet (mgr=<optimized out>, rdata=rdata@entry=0x266f488) at ../src/pjsip/sip_transport.c:1938
#11 0x00007ffff5213a4f in udp_on_read_complete (key=0x26257e8, op_key=<optimized out>, bytes_read=<optimized out>) at ../src/pjsip/sip_transport_udp.c:170
#12 0x00007ffff5236e28 in ioqueue_dispatch_read_event (h=0x26257e8, ioqueue=0x2629600) at ../src/pj/ioqueue_common_abs.c:605
#13 0x00007ffff5236e28 in ioqueue_dispatch_read_event (ioqueue=0x2629600, h=0x26257e8) at ../src/pj/ioqueue_common_abs.c:433
#14 0x00007ffff52386cf in pj_ioqueue_poll (ioqueue=0x2629600, timeout=timeout@entry=0x7fffffffb190) at ../src/pj/ioqueue_select.c:981
#15 0x00007ffff520b70a in pjsip_endpt_handle_events2 (endpt=0x261e7a8, max_timeout=0x7fffffffb1d0, p_count=0x0) at ../src/pjsip/sip_endpoint.c:742
#16 0x00007ffff51cf824 in handle_events() () at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node
#17 0x00007ffff51d00a4 in __pjw_poll(char*) () at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node                                                                                 
#18 0x00007ffff51dab71 in do_poll(Napi::CallbackInfo const&) () at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node
#19 0x00007ffff51e4923 in Napi::details::CallbackData<Napi::Value (*)(Napi::CallbackInfo const&), Napi::Value>::Wrapper(napi_env__*, napi_callback_info__*) ()
    at /usr/local/src/git/github_MayamaTakeshi/sip-lab/build/Release/addon.node
#20 0x00000000008e85b5 in (anonymous namespace)::v8impl::FunctionCallbackWrapper::Invoke(v8::FunctionCallbackInfo<v8::Value> const&) ()
#21 0x0000000000b62a3f in v8::internal::MaybeHandle<v8::internal::Object> v8::internal::(anonymous namespace)::HandleApiCallHelper<false>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::HeapObj
ect>, v8::internal::Handle<v8::internal::HeapObject>, v8::internal::Handle<v8::internal::FunctionTemplateInfo>, v8::internal::Handle<v8::internal::Object>, v8::internal::BuiltinArguments) ()
#22 0x0000000000b635a9 in v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) ()
#23 0x0000309d62fdbe1d in  ()           
#24 0x0000309d62fdbd81 in  ()                              
#25 0x00007fffffffc550 in  ()         
#26 0x0000000000000006 in  ()                                        
#27 0x00007fffffffc5f8 in  ()              
#28 0x0000309d62f918d5 in  ()        
#29 0x00003673ac6826f1 in  ()              
#30 0x000012d9e81e70c9 in  ()
#31 0x0000000500000000 in  ()                     
#32 0x00003673ac682801 in  ()

Permit to enable/disable auto-cleanup upon shutdown

Permit to specify on sip-lab startup if auto-clean up upon shutdown must be done.
Auto-clean up was disabled by #11.

However, some case will need it.
For example, in a test environment, it is easy to as for example for freeswitch/asterisk/yate to terminate all calls. Or to clean up all registrations etc.
However, the scripts could be used against a production system (to check for example if services are OK after system update) and in this case this would not be possible.
So auto-clean up is still necessary.

However, let's try to make it more efficient and make sip-lab shutdown fast.

Segfault on Debian 11

All samples fail like this:

$ node node_modules/sip-lab/samples/delayed_media.js ^C
... ABRIDGED ...

status=-397952776

17:04:52.759                        !: Success
17:04:52.768  wait (delayed_media.js:26) started. Waiting for expected_events:
17:04:52.778  [
  partial_match({
    event: 'incoming_call',
    call_id: collect['call_id']()
  }),
  partial_match({
    event: 'response',
    call_id: 0,
    method: 'INVITE',
    msg: sip_msg({
      $rs: '100',
      $rr: 'Trying',
      $(hdrcnt(via)): 1,
      $hdr(call-id): collect['sip_call_id'](),
      $fU: 'alice',
      $fd: 'test.com',
      $tU: 'bob',
      $hdr(l): '0'
    })
  })
]
Segmentation fault

OS:

$ cat /etc/issue
Debian GNU/Linux 11 \n \l

Small error in subscription_subscribe()

The function only requires 2 parameters and not 5.

Napi::Value subscription_subscribe(const Napi::CallbackInfo& info) {
  Napi::Env env = info.Env();

  if (info.Length() < 5) {
    Napi::Error::New(env, "Wrong number of arguments. Expected: subscription_id, expires [, additional_headers]").ThrowAsJavaScriptException();
    return env.Null();
  }

Debug occasional crash when running samples/sip_cancel.js

The script samples/sip_cancel.js frequently crashes with:

pjproject/src/pjsip/pjsip/sip_transaction.c:3267: pj_status_t tsx_on_state_completed_uas(pjsip_transaction *, pjsip_event *): Assertion `event->type == PJSIP_EVENT_TX_MSG && event->body.tx_msg.tdata == tsx->last_tx' failed.

Add support for MRCPv2

MRCPv2 media can be negotiated in the SDP in addition to audio RTP media.
Upon SDP answer, we should create TCP socket to exchange MRCPv2 messages as part of the call interface:
call.send_mrcp_msg()

Error in Debian 10

When using 'npm install sip-lab' we get:

$ node g729.js 
pjw_init thread_id=1062153640

18:23:54.392         os_core_unix.c !pjlib 2.9-svn for POSIX initialized
node: symbol lookup error: /root/tmp/t1/node_modules/sip-lab/build/Release/addon.node: undefined symbol: napi_create_threadsafe_function

Error in static link of bcg729

Doing a

npx node-gyp clean

to do a build from a clean slate permits to successfully build addon.node.
However when trying to use it, the loader cannot find pjmedia_codec_bcg729_init.
So the static linking from #26 failed.

Permit to reply to requests in dialogs

Currently, our addon automatically replies to requests inside a dialog with '200 OK'.
However, we should be able to control this and set for example T.38 SDP (#32) and select codecs (audio/video) in the answer and even refuse the request.

Disable auto-clean upon shutdown

When a test finishes we currently check if there are active calls/registrations/subscriptions and terminate them.
This was done as a convenience for the developer as the next test would not be affected by an unexpected state in the remote sip server under test.
However this causes some issues:

  • it make the test scripts to take longer to finish
  • we actually eventually might want to see the current state of the remote sip server and doing this shutdown would change it

So we will disable this.
Then the writing of test scripts using sip-lab would be:

  • if a test fails, the state of the remote sip servers will be preserved
  • it is the responsibility of the test script to clear the remote sip servers state before starting the test

This way, debugging/troubleshooting will be eased.

Then, if in the future this becomes somehow necessary, we can add a flag to be used upon sip-lab initialization to permit to enable/disable it.

Refactor stop_play_wav/stop_record_wav

Later we will have stop_fax.
All of them would use identical code except for the port to be stopped.
So it should be passed as an argument for a common function.

Refactor to permit proper control of multiple media offer/answer

Precedes #22, #32 and #40.

Currently, we handle SDP with only one media offer and it is always audio.
However, to add support for video, MRCPv2, MSRP, T.38 etc, we will need to change the code to handle multiple media offer.
Also, we could have multiple audio streams, multiple video streams, no audio/video in some MSRP scenarios etc.
So, the current Call struct:

struct Call {
    int id;
    pjsip_inv_session *inv;
    pjmedia_transport *med_transport;
    pjmedia_stream *med_stream;
    pj_bool_t local_hold;
    pj_bool_t remote_hold;
    pjmedia_master_port *master_port;
    pjmedia_port *media_port; //will contain Null Port, WAV File Player etc.

    pjmedia_port *null_port;
    chainlink *wav_writer;
    chainlink *wav_player;
    chainlink *tonegen;
    chainlink *dtmfdet;
    chainlink *fax;

    Transport *transport;

    bool outgoing;

    char DigitBuffers[2][MAXDIGITS + 1];
    int DigitBufferLength[2];
    int last_digit_timestamp[2];

    pjsip_evsub *xfer_sub; // Xfer server subscription, if this call was triggered by xfer.

    pjsip_rx_data *initial_invite_rdata;
};

must be refactored to have an array/list of media elements and this array/list might grow/shrink in case of reinvite.

Occasional error when executing samples/register_subscribe.js

12:05:45.647  wait (register_subscribe.js:41) got expected event:
12:05:45.647    {
    event: 'non_dialog_request',
    request_id: 0,
    transport_id: 1,
    msg: 'REGISTER sip:127.0.0.1:5092 SIP/2.0\r\n' +
  'Via: SIP/2.0/UDP 127.0.0.1:5090;rport;branch=z9hG4bKPja29e5137-1b8d-4b2e-b8e0-51d881221c86\r\n' +
  'Max-Forwards: 70\r\n' +
  'From: <sip:[email protected]>;tag=94956e6d-7daa-47b9-bb7d-516fcb66abe7\r\n' +
  'To: <sip:[email protected]>\r\n' +
  'Call-ID: 2f7ee9da-a639-486d-88f6-cb50f8734bb9\r\n' +
  'CSeq: 7310 REGISTER\r\n' +
  'X-MyHeader1: aaa\r\n' +
  'X-MyHeader2: bbb\r\n' +
  'Contact: <sip:[email protected]:5090>\r\n' +
  'Expires: 60\r\n' +
  'Allow: MESSAGE, SUBSCRIBE, NOTIFY, REFER, INVITE, ACK, BYE, CANCEL, UPDATE, PRACK\r\n' +
  'Content-Length:  0\r\n' +
  '\r\n'
  }
12:05:45.648  All expected events received
12:05:45.648  wait (register_subscribe.js:41) finished
12:05:45.648  All expected events received
12:05:45.648  wait (register_subscribe.js:41) finished
pjw_request_respond: request_id=0 json={"code":200,"reason":"OK","headers":{"Expires":"60"}}

Error: Cannot respond to our own request
    at Object.respond (/home/takeshi/src/git/MayamaTakeshi/sip-lab/index.js:53:45)
    at test (/home/takeshi/src/git/MayamaTakeshi/sip-lab/samples/register_subscribe.js:57:17)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
samples/register_subscribe.js failed

Implement processing of parameters as JSON string

Rationale:

Some new commands like start_fax will require a multitude of options.
Also, the way we set SIP headers in messages is not very good (single parameter as a string with headers separated with ‘\n’)
So let’s use JSON to send parameters to the underlying pj controlling code.

This will simplify updating the interface with javascript:
basically at javascript side each method should require only the entity id (call_id, account_id, transport_id etc) and an object with parameters.
So we would not need to update those interfaces anymore and all non-object-creation methods would be like this:

  call.start_play_wav(call_id, params)
  call.start_fax(call_id, params)
  call.reivinte(call_id, params)
  account.register(acc_id, params)

We would stringify the params object and pass it as a string parameter to the methods.
Then at the c side we would just need to parse the string using some library like rapidjson.
Obs: note that we could use NAPI to interface passing the object data.
However, we might eventually need to reuse the core c code in another project/language and so it is better have the processing of JSON at the core.

JSON libraries to check:
https://rapidjson.org/
https://github.com/Tencent/rapidjson/blob/master/example/simplereader/simplereader.cpp

https://zserge.com/jsmn/

https://gitee.com/xuzd/cJSON

https://github.com/skeeto/pdjson

https://jansson.readthedocs.io/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.