Skip to content

Latest commit

 

History

History
226 lines (175 loc) · 8.9 KB

TUTORIAL.md

File metadata and controls

226 lines (175 loc) · 8.9 KB

Tutorial

This a simple tutorial covering the basic aspects of NkSIP.

Firt of all install last stable version of NkSIP:

> git clone https://github.com/kalta/nksip
> cd nksip
> git checkout v0.3.0 -q
> make
> make tutorial

Now you can start a simple SipApp proxy server using the included server callback module. We will listen on all interfaces, port 5060 for udp and tcp and 5061 for tls. We also include the registrar option to tell NkSIP to process registrations (without having to implement register/3 callback function, see nksip_siapp.erl):

1> nksip:start(server, nksip_tutorial_sipapp_server, [server], 
		[
			registrar, 
		 	{transport, {udp, any, 5060}}, 
		 	{transport, {tls, any, 5061}}
		 ]).
ok

Now we can start two clients, using the included client callback module. The first is called client1 and listens on 127.0.0.1 ports 5070 for udp and tcp and 5071 for tls, and the second is called client2 and listens on all interfaces, random ports. We also configure the From header to be used on each one, the second one using sips:

2> nksip:start(client1, nksip_tutorial_sipapp_client, [client1], 
		[
			{from, "sip:client1@nksip"},
		 	{transport, {udp, {127,0,0,1}, 5070}}, 
		 	{transport, {tls, {127,0,0,1}, 5071}}
		]).
ok
3> nksip:start(client2, nksip_tutorial_sipapp_client, [client2], 
		[	{from, "sips:client2@nksip"},
		 	{transport, {udp, {127,0,0,1}, 5080}}, 
		 	{transport, {tls, {127,0,0,1}, 5081}}
		]).
ok

From now on, you could start tracing to see all SIP messages on the console, using nksip_trace:start(true).

Let's try now to send an OPTIONS from client2 to client1 and from client1 to the server:

4> nksip_uac:options(client2, "sip:127.0.0.1:5070", []).
{ok,200,[]}
5> nksip_uac:options(client1, "sip:127.0.0.1", [{fields, [reason]}]).
{ok,407,[{reason,<<"Proxy Authentication Required">>}]}

Oops, the server didn't accept the request (we have used the fields option to order NkSIP to return the reason phrase).

In the client callback module there is no authentication related callback function implemented, so every request is accepted. But server callback module is different:

%% @doc Called to check user's password.
%% If the incoming user's realm is "nksip", the password for any user is "1234". 
%% For other realms, no password is valid.
get_user_pass(_User, <<"nksip">>, _From, State) -> 
    {reply, <<"1234">>, State};
get_user_pass(_User, _Realm, _From, State) -> 
    {reply, false, State}.


%% @doc Called to check if a request should be authorized.
%%
%% 1) We first check to see if the request is an in-dialog request, coming from 
%%    the same ip and port of a previously authorized request.
%% 2) If not, we check if we have a previous authorized REGISTER request from 
%%    the same ip and port.
%% 3) Next, we check if the request has a valid authentication header with realm 
%%    "nksip". If '{{digest, <<"nksip">>}, true}' is present, the user has 
%%    provided a valid password and it is authorized. 
%%    If '{{digest, <<"nksip">>}, false}' is present, we have presented 
%%    a challenge, but the user has failed it. We send 403.
%% 4) If no digest header is present, reply with a 407 response sending 
%%    a challenge to the user.
authorize(Auth, _ReqId, _From, State) ->
    case lists:member(dialog, Auth) orelse lists:member(register, Auth) of
        true -> 
            {reply, true, State};
        false ->
            case nksip_lib:get_value({digest, <<"nksip">>}, Auth) of
                true -> 
                    {reply, true, State};       % Password is valid
                false -> 
                    {reply, false, State};      % User has failed authentication
                undefined -> 
                    {reply, {proxy_authenticate, <<"nksip">>}, State}
                    
            end
    end.

We try again with a correct password. In the second case we are telling NkSIP to use tls transport. Note we must use < and > if including uri parameters like transport.

6> nksip_uac:options(client1, "sip:127.0.0.1", [{pass, "1234"}]).
{ok,200,[]}
7> nksip_uac:options(client2, "<sip:127.0.0.1;transport=tls>", [{pass, "1234"}]).
{ok,200,[]}

Let's register now both clients with the server. We use the option make_contact to tell NkSIP to include a valid Contact header in the request, and the fields option to get the Contact header from the response, to be sure the server has stored the contact:

8> nksip_uac:register(client1, "sip:127.0.0.1", 
                      [{pass, "1234"}, make_contact, {fields, [<<"Contact">>]}]).
{ok,200,[{<<"Contact">>, [<<"<sip:[email protected]:5070>;expires=3600">>]}]}
10> nksip_uac:register(client2, "sips:127.0.0.1", [{pass, "1234"}, make_contact]).
{ok,200,[]}

We can check this second registration has worked. If we send a REGISTER request with no Contact header, the server will include one for each stored registration. This time, lets get all the header from the response using all_headers as field specification:

12> nksip_uac:register(client2, "sips:127.0.0.1", [{pass, "1234"}, {fields, [all_headers]}]).
{ok,200,[{all_headers, [{<<"CallId">>, ...}]}]}

Now, if we want to send the same OPTIONS again, we don't need to include the authentication, because the origins of the requests are already registered:

13> nksip_uac:options(client1, "sip:127.0.0.1", []).
{ok,200,[]}
14> nksip_uac:options(client2, "sips:127.0.0.1", []).
{ok,200,[]}

Now let's send an OPTIONS from client1 to client2 through the proxy. As they are already registered, we can use their registered names or address-of-record. We use the option route to send the request to the proxy (you usually include this option in the call to nksip:start/4, to send every request to the proxy automatically).

The first request is not authorized. The reason is that we are using a sips uri as a target, so NkSIP must use tls. But the origin port is then different from the one we registered, so we must authenticate again:

15> nksip_uac:options(client1, "sips:client2@nksip", [{route, "<sip:127.0.0.1;lr>"}]).
{ok,407,[]}
16> nksip_uac:options(client1, "sips:client2@nksip", 
                				[{route, "<sip:127.0.0.1;lr>"}, {pass, "1234"},
                                 {fields, [<<"Nksip-Id">>]}]).
{ok,200,[{<<"Nksip-Id">>, [<<"client2">>]}]}

In the second case we want to get the Nksip-Id header from the response. Our callback options/3 is called for every received options request, which includes the custom header:

options(_ReqId, _From, #state{id=Id}=State) ->
    Headers = [{"Nksip-Id", Id}],
    Opts = [make_contact, make_allow, make_accept, make_supported],
    {reply, {ok, Headers, <<>>, Opts}, State}.

Now let's try a INVITE from client2 to client1 through the proxy. NkSIP will call the callback invite/3 in client1's callback module:

invite(ReqId, From, State) ->
    SDP = nksip_request:body(ReqId),
    case nksip_sdp:is_sdp(SDP) of
        true ->
            Fun = fun() ->
                nksip_request:reply(ReqId, ringing),
                timer:sleep(2000),
                nksip:reply(From, {ok, [], SDP})
            end,
            spawn(Fun),
            {noreply, State};
        false ->
            {reply, {not_acceptable, <<"Invalid SDP">>}, State}
    end.

In the first call, since we don't include a body, client1 will reply not_acceptable (code 488). In the second, we spawn a new process, reply a provisional 180 Ringing, wait two seconds and reply a final 200 Ok with the same body. For INVITE responses, NkSIP will allways include the dialog_id value, After receiving each 2xx response to an INVITE, we must send an ACK inmediatly:

17> nksip_uac:invite(client2, "sip:client1@nksip", [{route, "<sips:127.0.0.1;lr>"}]).
{ok,488,[{dialog_id, ...}]}
18> {ok,200,[{dialog_id, DlgId}]}= nksip_uac:invite(client2, "sip:client1@nksip", 
					   [{route, "<sips:127.0.0.1;lr>"}, {body, nksip_sdp:new()}]).
{ok,200,[{dialog_id, <<"...">>}]}					   
19> nksip_uac:ack(client2, DlgId, []).
ok

The call is accepted and we have started a dialog:

19> nksip_dialog:field(client2, DlgId, status).
confirmed

You can print all dialogs in the console. We see dialogs at client1, client2 and at server. The three dialogs are the same actually, but in different SipApps (do not use this command in production with many thousands of dialogs):

26> nksip_dialog:get_all_data().

Ok, let's stop the call, the dialogs and the SipApps:

27> nksip_uac:bye(client2, DlgId, []).
{ok,200,[]}
28> nksip:stop_all().
ok

The full code for this tutorial is available here.