Skip to content

Commit

Permalink
Merge branch 'master' of github.com:kalta/nksip
Browse files Browse the repository at this point in the history
  • Loading branch information
kalta committed Jul 8, 2013
2 parents 73f10a9 + 277cc99 commit 30a602e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 86 deletions.
64 changes: 26 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ NkSIP is an Erlang SIP framework or _application server_, which greatly facilita

**This software is still alpha quality!! Do not use it in production!!**

SIP is the standard protocol related to IP voice, video and remote sessions, supported by thousands of devices, softphones and network operators. It is the basic building block for most current voice or video enabled networks and it is the core protocol of the IP Multimedia Subsytem [IMS](https://en.wikipedia.org/wiki/IP_Multimedia_Subsystem). SIP is powerful and flexible, but also very complex to work with. SIP basic concepts are easy to understand, but developing robust, scalable, highly available applications is usually quite hard and time consuming, because of the many details you have to take into account.
SIP is the standard protocol related to IP voice, video and remote sessions, supported by thousands of devices, softphones and network operators. It is the basic building block for most current voice or video enabled networks and it is the core protocol of the IP Multimedia Subsytem ([IMS](https://en.wikipedia.org/wiki/IP_Multimedia_Subsystem)). SIP is powerful and flexible, but also very complex to work with. SIP basic concepts are easy to understand, but developing robust, scalable, highly available applications is usually quite hard and time consuming, because of the many details you have to take into account.

NkSIP takes care of much of the SIP complexity, while allowing full access to requests and responses.

NkSIP allows you to run any number of **SipApps**. To start a SipApp, you define a _name_, a set of _transports_ to start listening on and a **callback module**. Currently the only way to develop NkSIP applications is using [Erlang]("http://www.erlang.org") (a new, language-independent way of developing SipApps is in the works). You can now start sending SIP requests, and when your application starts receiving requests, specific functions in the callback module will be called. Each defined callback function has a _sane_ default functionality, so you only have to implement the functions you need to customize. You don't have to deal with transports, retransmissions, authentications or dialog management. All of those aspects are managed by NkSIP in a standard way. In case you need to, you can implement the related callback functions, or even process the request by yourself using the powerful NkSIP Erlang functions.

NkSIP has a clean, written from scratch, [OTP compliant](http://www.erlang.org/doc/design_principles/users_guide.html) and [fully typed](http://www.erlang.org/doc/reference_manual/typespec.html) pure Erlang code. New RFCs and features can be implemented securely and quickly. The codebase includes currently more than 50 unit tests. If you want to customize the way NkSIP behaves beyond what the callback mechanism offers, it should be easy to understand the code and use it as a powerful base for your specific application server.

NkSIP is currently alpha quality, it probably has important bugs and is not **not yet production-ready**, but it is already very robust, thanks to its OTP design. Also thanks to its Erlang roots it can perform many actions while running: starting and stopping SipApps, hot code upgrades, configuration changes and even changing your application behavior and function callbacks on the fly.
NkSIP is currently alpha quality, it probably has important bugs and is **not yet production-ready**, but it is already very robust, thanks to its OTP design. Also thanks to its Erlang roots it can perform many actions while running: starting and stopping SipApps, hot code upgrades, configuration changes and even updating your application behavior and function callbacks on the fly.

NkSIP scales automatically using all of the available cores on the machine. Without any serious optimization done yet, and using common hardware (4-core i7 MacMini), it is easy to get more than 1.000 call cycles (INVITE-ACK-BYE) or 8.000 stateless registrations per second. On the roadmap there is a **fully distributed version**, based on [Riak Core](https://github.com/basho/riak_core), that will allow you to add and remove nodes while running, scale as much as needed and offer a very high availability, all of it without changing your application.

Expand All @@ -37,20 +37,19 @@ Current Features
* Simple STUN server (for future SIP Outbound support).
* Robust and highly scalable, using all available processor cores.

See [FEATURES.md](../nksip/FEATURES.md) for up to date RFC support.
See [FEATURES.md](docs/FEATURES.md) for up to date RFC support and the [ROADMAP.md](docs/ROADMAP.md).



Documentation
-------------
Full documentation is available [here](http://kalta.github.io/nksip/doc/0.1.0/core/doc/index.html).
=============
Full documentation is available [here](http://kalta.github.io/nksip).

There are currently **three sample applications** included with NkSIP:
* [Simple PBX](http://kalta.github.io/nksip/docs/v0.1.0/nksip_pbx/index.html): Registrar server and forking proxy with endpoints monitoring.
* [LoadTest](http://kalta.github.io/nksip/docs/v0.1.0/nksip_loadtest/index.html): Heavy-load NkSIP testing.
* [Tutorial](docs/TUTORIAL.md): Code base for the included tutorial.

**Sample Applications**

There are currently two samples applications included with NkSIP:
* NkPBX. Implements a SIP registrar and forking proxy, with endpoints monitoring. See [docs](http:kalta.github.io/nksip/doc/0.1.0/nksip_pbx/doc/index.html).
* NkLoadTest. Heavy-load NkSIP testing. See [docs](http:kalta.github.io/nksip/doc/0.1.0/nksip_loadtest/doc/index.html).


Quick Start
Expand All @@ -63,42 +62,31 @@ Quick Start
> make tests
```

Now you can start a simple SipApp using default `nksip_sipapp.erl` callback module:
You could also perform a heavy load test using the included application [nksip_loadtest](http://kalta.github.io/nksip/docs/v0.1.0/nksip_loadtest/index.html):
```erlang
> make loadtest
1> nksip_loadtest:full().
```

Now you can start a simple SipApp using the [client callback module](samples/nksip_tutorial/src/nksip_tutorial_sipapp_client.erl) included in the tutorial:
```erlang
> make shell
1> nksip:start(test1, nksip_sipapp, [], [])
2> nksip_uac:options(test1, "sip:sip2sip.info", [])
> make tutorial
1> nksip:start(test1, nksip_sipapp, [], []).
2> nksip_uac:options(test1, "sip:sip2sip.info", []).
{ok, 200}
```

You could also start the sample application `NkPBX` to have a target SIP server to test:
```erlang
3> nksip_pbx:start()
4> nksip_uac:register(test1, "sip:127.0.0.1;transport=tls", [])
{ok, 407}
```

We need a password. We use now `make_contact` option to generate a valid _Contact_ header:
From this point you can read the [tutorial](docs/TUTORIAL.md) or start hacking with the included [nksip_pbx](http://kalta.github.io/nksip/docs/v0.1.0/nksip_pbx/index.html) application:
```erlang
5> nksip_uac:register(test1, "sip:127.0.0.1;transport=tls", [{pass, "1234"}, make_contact])
{ok, 200}
6> {reply, Reply1} = nksip_uac:register(test1, "sip:127.0.0.1;transport=tls", [{pass, "1234"}, full_response]).
7> nksip_response:headers(<<Contact>>, Reply1)
> make pbx
1> nksip_pbx:start().
```

We have now registered. With the option `full_response` we get a full resonse object instead of simple the code. The server replies with the stored _Contact_.

Roadmap
-------
See [ROADMAP.md](../nksip/ROADMAP.md)







Contributing
============

Please contribute with code, bug fixes, documentation fixes, testing with SIP devices or any other form. Use
GitHub Issues and Pull Requests, forking this repository.

Just make sure your code is dialyzer-friendly before submitting!!

7 changes: 4 additions & 3 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ RFC|Description|Coverage


NkSIP features also:
* SIP Registrar (RAM storage only).
* A written from scratch, fully typed Erlang code easy to understand and extend. More than 50 unit tests.
* Hot code loading.
* Full RFC3261 coverage, including SIP Registrar (RAM storage only).
* A written from scratch, fully typed Erlang code easy to understand and extend, with more than 50 unit tests.
* Hot core and application code upgrade.
* Very few external dependencies: [Lager](https://github.com/basho/lager) for error logging and [Cowboy](http://ninenines.eu") as TCP/SSL acceptor and Websocket server.
* UDP, TCP and TLS transports, capable of handling thousands of simultaneous sessions.
* Stateful proxy servers with serial and parallel forking.
Expand All @@ -25,3 +25,4 @@ NkSIP features also:
* SDP processing utilities.
* Simple STUN server (for future SIP Outbound support).
* Robust and highly scalable, using all available processor cores.

3 changes: 2 additions & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ ROADMAP

In no special order:

* More application examples
* SIP message processing engine rework.
* _Bridge_ support for B2BUA.
* _IPv6_.
* _SIP Websockets_ support.
* External control, to be able to use NkSIP without having to use Erlang or outside of the NkSIP Erlang VM.
* Fully distributed, highly available version based on [Riak Core](https://github.com/basho/riak_core).
* IMS and RCS extensions.
* More application examples.


Many common SIP featured are still missing and will be addressed in following versions, including:
Expand Down
88 changes: 44 additions & 44 deletions docs/TUTORIAL.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
Tutorial
========

Firt of all install NkSIP
This a simple tutorial covering the basic aspects of NkSIP.


Firt of all install NkSIP:
```
> git clone https://github.com/kalta/nksip
> cd nksip
> make
> make tutorial
```

Now you can start a simple SipApp proxy server using the included `nksip_tutorial_sipapp_server.erl` 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 the `register/3' callback function):
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)):
```erlang
1> nksip:start(server, nksip_tutorial_sipapp_server, [server],
[
Expand All @@ -20,7 +23,7 @@ Now you can start a simple SipApp proxy server using the included `nksip_tutoria
ok
```

Now we can start two clients, the first `client1` listens on `127.0.0.1` ports `5070` for udp and tcp and `5071` for tls, and the second `client2` listens on all interfaces, random ports. We also configure the _From_ to be used on each one, the second using `sips`:
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`:

```erlang
2> nksip:start(client1, nksip_tutorial_sipapp_client, [client1],
Expand All @@ -30,34 +33,27 @@ Now we can start two clients, the first `client1` listens on `127.0.0.1` ports `
{transport, {tls, {127,0,0,1}, 5071}}
]).
ok
3> nksip:start(client2, nksip_tutorial_sipapp_client, [client2],
[{from, "sips:client2@nksip"}]),1> nksip:start(server, nksip_sipapp_sample, [server], [registrar, {transport, {udp, {0,0,0,0}, 5060}}, {transport, {tls, {0,0,0,0}, 5061}}]).
3> nksip:start(client2, nksip_tutorial_sipapp_client, [client2],
[{from, "sips:client2@nksip"}]).
ok
```

server's callback module includes a synchronous call, let's test it:
```erlang
nksip:call(server, get_started).
{ok, ...}
```

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

Let's try now to ping from client1 to client1 and to the server:
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", []).
{ok, 407}
```

Ops, the server didn't accept the request. In the callback module for the client, there is no
authentication related callback function implemented, so every request is accepted. For the server, in its callback module, requests must be authenticated:

Oops, the `server` didn't accept the request. 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) ->
%% 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}.
Expand All @@ -66,14 +62,14 @@ get_user_pass(_User, _Realm, _From, 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.</li>
%% 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
%% a challenge, but the user has failed it. We send 403.</li>
%% 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) ->
Expand All @@ -93,7 +89,7 @@ authorize(Auth, _ReqId, _From, State) ->
end.
```

Lets try again with a password:
If we try again with a correct password:
```erlang
6> nksip_uac:options(client1, "sip:127.0.0.1", [{pass, "1234"}]).
{ok, 200}
Expand All @@ -110,7 +106,7 @@ Let's register now both clients with the server. We use the option `make_contact
{ok, 200}
```

Lets check the server has stored the registration. If we send a _REGISTER_ request with no _Contact_ header, the server will include one for each stored registration:
We can check that the server has stored the registration. If we send a _REGISTER_ request with no _Contact_ header, the server will include one for each stored registration. We usedthe option `full_response` to tell NkSIP to send the full response as the return, instead of only the code.

```erlang
10> {reply, Resp1} = nksip_uac:register(client2, "sip:127.0.0.1;transport=tls", [{pass, "1234"}, full_response]).
Expand All @@ -121,42 +117,37 @@ Lets check the server has stored the registration. If we send a _REGISTER_ reque
[<<"<sips:client2@...">>]
```

We have used the option `full_response` to tell NkSIP to send as the full response in the return, instead of only the code.

Now we can send the same _OPTIONS_ as before without the authentication, because the origin of the requests are already registered:

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:
```erlang
13> nksip_uac:options(client1, "sip:127.0.0.1", []).
{ok, 200}
14> nksip_uac:options(client2, "sip:127.0.0.1;transport=tls", []).
{ok, 200}
```

Now let's send and _OPTIONS_ from client1 to client2 through the proxy. As it is already registered, we can use it registered name. 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.

Now let's send and _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:
```erlang
15> nksip_uac:options(client1, "sips:client2@nksip", [{route, "sip:127.0.0.1;lr"}]).
{ok, 407}
16> {reply, Resp2} = nksip_uac:options(client1, "sips:client2@nksip",
[{route, "sip:127.0.0.1;lr"}, full_response,
{pass, "1234"}]).
[{route, "sip:127.0.0.1;lr"}, full_response,{pass, "1234"}]).
{reply, ...}
17> nksip_response:code(Resp2).
200
18> nksip_response:headers(<<"Nksip-Id">>, Resp2).
[<<"client2">>]
19> {reply, Resp3} = nksip_uac:options(client2, "sip:client1@nksip",
[{route, "sips:127.0.0.1;lr"}, full_response]),
[{route, "sips:127.0.0.1;lr"}, full_response]).
{reply, ...}
20> nksip_response:code(Resp3).
200
21> nksip_response:headers(<<"Nksip-Id">>, Resp3),
21> nksip_response:headers(<<"Nksip-Id">>, Resp3).
[<<"client1">>]
```

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.


The callback `options/3` is called for every client, which includes the custom header:

Expand All @@ -167,7 +158,7 @@ options(_ReqId, _From, #state{id=Id}=State) ->
{reply, {ok, Headers, <<>>, Opts}, State}.
```

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

```erlang
invite(_DialogId, ReqId, From, State) ->
Expand All @@ -186,27 +177,36 @@ invite(_DialogId, ReqId, From, 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 200 OK with the same body. After receiving each 2xx response to an _INVITE_, we must send an _ACK_ inmediatly.

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. After receiving each `2xx` response to an _INVITE_, we must send an _ACK_ inmediatly:
```erlang
22> nksip_uac:invite(client2, "sip:client1@nksip", [{route, "sips:127.0.0.1;lr"}]).
{ok, 488, ...}
23> {ok, 200, Dialog1} = nksip_uac:invite(client2, "sip:client1@nksip",
[{route, "sips:127.0.0.1;lr"}, {body, nksip_sdp:new()}]),
[{route, "sips:127.0.0.1;lr"}, {body, nksip_sdp:new()}]),
ok = nksip_uac:ack(Dialog1, []).
ok
```

We have started a dialog:
The call is accepted and we have started a _dialog_:
```erlang
24> nksip_dialog:field(local_sdp, Dialog1).
...
25> nksip_uac:bye(Dialog1).
```

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:
```erlang
25> nksip_dialog:get_all().
```

Ok, let's stop the call, the dialogs and the SipApps:
```erlang
26> nksip_uac:bye(Dialog1, []).
ok
26> nksip:stop_all().
27> nksip:stop_all().
ok
```

The full code for this tutorial is available [here](../samples/nksip_tutorial/src/nksip_tutorial.erl).



Expand Down

0 comments on commit 30a602e

Please sign in to comment.