forked from NetComposer/nksip
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
160 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,85 +13,96 @@ Firt of all install last stable version of NkSIP: | |
> make tutorial | ||
``` | ||
|
||
Now you can start a simple SipApp proxy server using the included [server callback module](../samples/nksip_tutorial/src/nksip_tutorial_sipapp_server.erl). 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](../nksip/src/nksip_sipapp.erl)): | ||
Now you can start a simple SipApp proxy server using the included [server callback module](../samples/nksip_tutorial/src/nksip_tutorial_sipapp_server.erl). We will listen on all interfaces, port `5060` for udp and tcp and `5061` for tls. We also activate the [nksip_registrar](../plugins/registrar.md) plugin to tell NkSIP to process registrations: | ||
```erlang | ||
1> nksip:start(server, nksip_tutorial_sipapp_server, [server], | ||
[ | ||
registrar, | ||
{transport, {udp, any, 5060}}, | ||
{transport, {tls, any, 5061}} | ||
]). | ||
ok | ||
1> nksip:start(server, nksip_tutorial_sipapp_server, [], | ||
[ | ||
{plugins, [nksip_registrar]}, | ||
{transports, [{udp, all, 5060}, {tls, all, 5061}]} | ||
]), | ||
{ok,armejl7} | ||
``` | ||
|
||
Now we can start two clients, using the included [client callback module](../samples/nksip_tutorial/src/nksip_tutorial_sipapp_client.erl). 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`: | ||
NkSip returns the _internal name_ of the application, which is alwats an `atom()`, calculated as a hash over the name. | ||
|
||
Now we can start two clients, using the included [client callback module](../samples/nksip_tutorial/src/nksip_tutorial_sipapp_client.erl). 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`. | ||
|
||
We also activate the [nksip_uac_auto_auth](../plugins/auto_auth.md) plugin to generate automatic digest authentications: | ||
|
||
```erlang | ||
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 | ||
2> nksip:start(client1, nksip_tutorial_sipapp_client, [], | ||
[ | ||
{plugins, [nksip_uac_auto_auth]}, | ||
{from, "sip:client1@nksip"}, | ||
{transports, [{udp, {127,0,0,1}, 5070}, {tls, {127,0,0,1}, 5071}]} | ||
]). | ||
{ok,b3finlv} | ||
3> nksip:start(client2, nksip_tutorial_sipapp_client, [], | ||
[ | ||
{plugins, [nksip_uac_auto_auth]}, | ||
{from, "sips:client2@nksip"}, | ||
{transports, [{udp, {127,0,0,1}, 5080}, {tls, {127,0,0,1}, 5081}]} | ||
]). | ||
{ok,ayr8ncm} | ||
|
||
``` | ||
|
||
From now on, you could start tracing to see all SIP messages on the console, using `nksip_trace:start(true).` | ||
From now on, you could start tracing to see all SIP messages on the console, using `nksip_trace:start().` | ||
|
||
Let's try now to send an _OPTIONS_ from `client2` to `client1` and from `client1` to the `server`: | ||
```erlang | ||
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">>}]} | ||
5> nksip_uac:options(client1, "sip:127.0.0.1", [{meta,[reason_phrase]}]). | ||
{ok,407,[{reason_phrase, <<"Proxy Authentication Required">>}]} | ||
``` | ||
|
||
Oops, the `server` didn't accept the request (we have used the `fields` option to | ||
Oops, the `server` didn't accept the request (we have used the `meta` 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: | ||
|
||
```erlang | ||
%% @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}. | ||
%% | ||
sip_get_user_pass(_User, <<"nksip">>, _Req, _Call) -> | ||
<<"1234">>; | ||
sip_get_user_pass(_User, _Realm, _Req, _Call) -> | ||
false. | ||
|
||
|
||
%% @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 | ||
%% "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 | ||
%% 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 | ||
%% | ||
sip_authorize(AuthList, _Req, _Call) -> | ||
case lists:member(dialog, AuthList) orelse lists:member(register, AuthList) of | ||
true -> | ||
{reply, true, State}; | ||
ok; | ||
false -> | ||
case nksip_lib:get_value({digest, <<"nksip">>}, Auth) of | ||
case proplists:get_value({digest, <<"nksip">>}, AuthList) of | ||
true -> | ||
{reply, true, State}; % Password is valid | ||
ok; % Password is valid | ||
false -> | ||
{reply, false, State}; % User has failed authentication | ||
forbidden; % User has failed authentication | ||
undefined -> | ||
{reply, {proxy_authenticate, <<"nksip">>}, State} | ||
{proxy_authenticate, <<"nksip">>} | ||
|
||
end | ||
end. | ||
|
@@ -106,20 +117,22 @@ use `tls` transport. Note we must use `<` and `>` if including `uri` parameters | |
{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: | ||
Both requests receive a 407 response, but the nksip_uac_auto_auth plugin takes the included password and generates a new request with the correct headers, that are accepted at the server. | ||
|
||
Let's register now both clients with the server. We use the option `contact` to tell NkSIP to include a valid _Contact_ header in the request, and the `mtea` option to get the _Contact_ header from the response, to be sure the server has stored the contact: | ||
|
||
```erlang | ||
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]). | ||
[{pass, "1234"}, contact, {meta, [<<"contact">>]}]). | ||
{ok,200,[{<<"contact">>, [<<"<sip:[email protected]:5070>...">>]}]} | ||
10> nksip_uac:register(client2, "sips:127.0.0.1", [{pass, "1234"}, 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: | ||
|
||
```erlang | ||
12> nksip_uac:register(client2, "sips:127.0.0.1", [{pass, "1234"}, {fields, [all_headers]}]). | ||
12> nksip_uac:register(client2, "sips:127.0.0.1", [{pass, "1234"}, {meta, [all_headers]}]). | ||
{ok,200,[{all_headers, [{<<"CallId">>, ...}]}]} | ||
``` | ||
|
||
|
@@ -139,56 +152,58 @@ The first request is not authorized. The reason is that we are using a `sips` ur | |
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">>]}]} | ||
[{route, "<sip:127.0.0.1;lr>"}, {pass, "1234"}, | ||
{meta, [<<"x-nk-id">>]}]). | ||
{ok,200,[{<<"x-nk-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: | ||
In the second case we want to get the _X-Nk-Id header from the response (in NkSIP, all headers must be spelled lowercase). | ||
Our callback `options/2` is called for every received options request, which includes the custom header: | ||
|
||
```erlang | ||
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}. | ||
sip_options(Req, _Call) -> | ||
AppName = nksip_request:app_name(Req), | ||
{reply, {ok, [{add, "x-nk-id", AppName}, contact, allow, accept, supported]}}. | ||
``` | ||
|
||
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: | ||
Now let's try a _INVITE_ from `client2` to `client1` through the proxy. NkSIP will call the callback `invite/2` in `client1`'s callback module: | ||
|
||
```erlang | ||
invite(Req, Call) -> | ||
SDP = nksip_request:body(ReqId), | ||
case nksip_sdp:is_sdp(SDP) of | ||
sip_invite(Req, _Call) -> | ||
Body = nksip_request:body(Req), | ||
case nksip_sdp:is_sdp(Body) of | ||
true -> | ||
ReqId = nksip_request:get_id(Req), | ||
Fun = fun() -> | ||
nksip_request:reply(ringing, ReqId), | ||
timer:sleep(2000), | ||
nksip_request:reply({answer, SDP}, ReqId) | ||
nksip_request:reply({answer, Body}, ReqId) | ||
end, | ||
spawn(Fun), | ||
{noreply, State}; | ||
noreply; | ||
false -> | ||
{reply, {not_acceptable, <<"Invalid SDP">>}, State} | ||
{reply, {not_acceptable, <<"Invalid SDP">>}} | ||
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: | ||
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 2xx _INVITE_ responses, NkSIP will allways include the `dialog_id` value, After receiving each `2xx` response to an _INVITE_, we must send an _ACK_ inmediatly: | ||
|
||
```erlang | ||
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,488,[]} | ||
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, []). | ||
19> nksip_uac:ack(DlgId, []), | ||
ok | ||
``` | ||
|
||
The call is accepted and we have started a _dialog_: | ||
```erlang | ||
19> nksip_dialog:field(client2, DlgId, status). | ||
19> nksip_dialog:meta(invite_status, DlgId). | ||
confirmed | ||
``` | ||
|
||
|
@@ -199,7 +214,7 @@ You can _print_ all dialogs in the console. We see dialogs at `client1`, `client | |
|
||
Ok, let's stop the call, the dialogs and the SipApps: | ||
```erlang | ||
27> nksip_uac:bye(client2, DlgId, []). | ||
27> nksip_uac:bye(DlgId, []). | ||
{ok,200,[]} | ||
28> nksip:stop_all(). | ||
ok | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,32 +28,35 @@ | |
|
||
%% @doc Launches the full tutorial. | ||
launch() -> | ||
ok = nksip:start(server, nksip_tutorial_sipapp_server, [server], | ||
{ok, _} = nksip:start(server, nksip_tutorial_sipapp_server, [], | ||
[ | ||
registrar, | ||
{transport, {udp, any, 5060}}, | ||
{transport, {tls, any, 5061}} | ||
{plugins, [nksip_registrar]}, | ||
{transports, [{udp, all, 5060}, {tls, all, 5061}]} | ||
]), | ||
ok = nksip:start(client1, nksip_tutorial_sipapp_client, [client1], | ||
{ok, _} = nksip:start(client1, nksip_tutorial_sipapp_client, [], | ||
[ | ||
{plugins, [nksip_uac_auto_auth]}, | ||
{from, "sip:client1@nksip"}, | ||
{transport, {udp, {127,0,0,1}, 5070}}, | ||
{transport, {tls, {127,0,0,1}, 5071}} | ||
{transports, [{udp, {127,0,0,1}, 5070}, {tls, {127,0,0,1}, 5071}]} | ||
]), | ||
ok = 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, _} = nksip:start(client2, nksip_tutorial_sipapp_client, [], | ||
[ | ||
{plugins, [nksip_uac_auto_auth]}, | ||
{from, "sips:client2@nksip"}, | ||
{transports, [{udp, {127,0,0,1}, 5080}, {tls, {127,0,0,1}, 5081}]} | ||
]), | ||
|
||
nksip_registrar_util:clear(), | ||
|
||
|
||
{ok,200,[]} = nksip_uac:options(client2, "sip:127.0.0.1:5070", []), | ||
{ok,407,[{reason_phrase, <<"Proxy Authentication Required">>}]} = | ||
nksip_uac:options(client1, "sip:127.0.0.1", [{meta,[reason_phrase]}]), | ||
|
||
{ok,200,[]} = nksip_uac:options(client1, "sip:127.0.0.1", [{pass, "1234"}]), | ||
{ok,200,[]} = nksip_uac:options(client2, "<sip:127.0.0.1;transport=tls>", [{pass, "1234"}]), | ||
|
||
{ok,200,[{<<"contact">>, [<<"<sip:[email protected]:5070>;expires=3600">>]}]} = | ||
{ok,200,[{<<"contact">>, [<<"<sip:[email protected]:5070>", _/binary>>]}]} = | ||
nksip_uac:register(client1, "sip:127.0.0.1", | ||
[{pass, "1234"}, contact, {meta, [<<"contact">>]}]), | ||
|
||
|
@@ -66,20 +69,20 @@ launch() -> | |
{ok,200,[]} = nksip_uac:options(client2, "sips:127.0.0.1", []), | ||
|
||
{ok,407,[]} = nksip_uac:options(client1, "sips:client2@nksip", [{route, "<sip:127.0.0.1;lr>"}]), | ||
{ok,200,[{<<"nksip-id">>, [<<"client2">>]}]} = | ||
{ok,200,[{<<"x-nk-id">>, [<<"client2">>]}]} = | ||
nksip_uac:options(client1, "sips:client2@nksip", | ||
[{route, "<sip:127.0.0.1;lr>"}, {pass, "1234"}, | ||
{meta, [<<"nksip-id">>]}]), | ||
{meta, [<<"x-nk-id">>]}]), | ||
|
||
{ok,488,[{dialog_id, _}]} = | ||
{ok,488,[]} = | ||
nksip_uac:invite(client2, "sip:client1@nksip", [{route, "<sips:127.0.0.1;lr>"}]), | ||
|
||
{ok,200,[{dialog_id, DlgId}]}= | ||
nksip_uac:invite(client2, "sip:client1@nksip", | ||
[{route, "<sips:127.0.0.1;lr>"}, {body, nksip_sdp:new()}]), | ||
ok = nksip_uac:ack(DlgId, []), | ||
|
||
confirmed = nksip_dialog:meta(DlgId, status, client2), | ||
confirmed = nksip_dialog:meta(invite_status, DlgId), | ||
[_, _, _] = nksip_dialog:get_all_data(), | ||
|
||
{ok,200,[]} = nksip_uac:bye(DlgId, []), | ||
|
@@ -92,14 +95,27 @@ launch() -> | |
%% =================================================================== | ||
|
||
%% @doc Enables SIP trace messages to console. | ||
-spec trace(Start::boolean()) -> ok. | ||
-spec trace(Start::boolean()) -> | ||
ok. | ||
|
||
trace(true) -> nksip_trace:start(); | ||
trace(false) -> nksip_trace:stop(). | ||
trace(true) -> | ||
nksip_trace:start(), | ||
ok; | ||
trace(false) -> | ||
nksip_trace:stop(), | ||
ok. | ||
|
||
|
||
%% @doc Changes console log level. | ||
%% Availanle options are `debug' (maximum), `info' (medium) and `notice' (minimum). | ||
-spec loglevel(debug|info|notice) -> ok. | ||
-spec loglevel(debug|info|notice) -> | ||
ok. | ||
|
||
loglevel(Level) -> | ||
lager:set_loglevel(lager_console_backend, Level), | ||
{ok, _} = nksip:update(server, [{log_level, Level}]), | ||
{ok, _} = nksip:update(client1, [{log_level, Level}]), | ||
{ok, _} = nksip:update(client2, [{log_level, Level}]), | ||
ok. | ||
|
||
|
||
loglevel(Level) -> lager:set_loglevel(lager_console_backend, Level). |
Oops, something went wrong.