Seas Module
Elias Baixas
VozTelecom Sistemas
www.wesip.eu
Ronda Can Fatjo, 9, 1p Parc Tecnologic del Valles Cerdanyola,
08520 (SPAIN)
Phone:+34 933968800
www.voztele.com
<elias.baixas@voztele.com>
Copyright � 2006 VozTelecom Sistemas
_________________________________________________________
Table of Contents
1. The Sip Express Application Server User's Guide
1.1. Application Servers
1.1.1. Sip Express Application Server module
overview
1.1.2. Application Servers
1.1.3. Dependencies
1.1.4. Exported Parameters
1.1.5. Exported Functions
1.2. WeSIP Application Server
1.2.1. The Servlet programming paradigm: Sip/Http
Servlets
1.2.2. Configuring WeSIP to work with SEAS
1.2.3. Configuration Examples
2. Developer's Guide
2.1. Internals
2.2. SEAS Protocol
2.2.1. The SEAS protocol
2.2.2. General codification of a header
3. Frequently Asked Questions
List of Figures
1-1. SipServlet UML diagram
2-1. Overview of Seas Event Dispatcher process operation
2-2. SIP Messages and control flow within SER
2-3. General codification of a SIP header in SEAS protocol
2-4. Example of a from header SEAS-protocol codification
2-5. SEAS-codification of a SIP URI (byte meanings are shown)
2-6. Example of a SEAS SIP URI codification
2-7. SEAS codification of From and To headers
2-8. SEAS codification of a Contact header
2-9. SEAS codification of a Route Header
2-10. SEAS codification of Authentication/Authorization
headers
2-11. SEAS codification of a SIP First Line
2-12. SEAS Headers Index section overview
2-13. SEAS SIP-Message codification
2-14. Different kinds of SEAS codified Events and Actions
List of Examples
1-1. Set listen_sockets parameter
1-2. as_relay_t usage
1-3. Typical example of an HttpServlet
1-4. Typical Sip Servlet Example
1-5. Server
1-6. Service
_________________________________________________________
Chapter 1. The Sip Express Application Server User's Guide
1.1. Application Servers
1.1.1. Sip Express Application Server module overview
SEAS module enables OpenSER to transfer the execution logic
control of a sip message to a given external entity, called
the Application Server. When the OpenSER script is being
executed on an incoming SIP message, invocation of the
as_relay_t() function makes this module send the message along
with some transaction information to the specified Application
Server. The Application Server then executes some call-control
logic code, and tells OpenSER to take some actions, ie.
forward the message downstream, or respond to the message with
a SIP repy, etc.
The module acts implements a network protocol acting as the
interface between OpenSER internal API and the external
Application Server entity.
There's only one relevant function, as_relay_t, exported by
this module. This function receives as a parameter the name of
the application server to which the message should be relaied.
Every message relaied to an Application Server is
automatically associated to a SIP transaction (a transaction
is created for it). Just after the message is relaied to the
Application Server, the script stops its execution on the
message, because the control of message-processing is now in
the Application Server.
In the context of SEAS module, relaying a message to an App
Server, is _not_ done in SIP protocol, but in a special
protocol by means of which the SEAS module and the Application
Server comunicate efficiently and seamlessly. This procotol is
specially designed so that a message doesn't need to be parsed
again once it arrives at the Application Server. This protocol
carries information regarding the internal structure of the
SIP message (to avoid reparsing) and also information about
the associated transaction (recall that invoking as_relay_t
indirectly calls t_newtran). This way, all the SIP-Transaction
machinery, and the SIP-Message parsing, is handled at the
OpenSER core, while the execution of the Application Logic is
carried in the Application Server.
The SEAS module and protocol provide a means by which an
external entity can utilize OpenSER as a transaction-stateful
SIP-stack to act on behalf of it. This means that this
external entity (which we call the Application Server) is
notified whenever a SIP-Request enters OpenSER, and this
external entity can then order OpenSER to execute some
actions, either replying the request, or generating new UAC
transactions.
This version of SEAS works with VozTelecom's WeSIP Application
Server. This Application Server is a SipServlet JAVA
Container.
_________________________________________________________
1.1.2. Application Servers
When OpenSER starts and SEAS module is loaded, a new process
is spawn which listens on a server-socket (IP and port are
specified as a parameter in the config script). From then on,
the Application Servers can connect to that socket so that
OpenSER can relay messages to them. When an Application Server
connects to the socket, it sends its name through the socket,
so every App Server is identified with a name. Within the
OpenSER script, invoking as_relay_t() receives a string as a
parameter, which specifies the name of an application server
to which the message has to be sent. If that concrete
application server hasn't already connected to the module, the
function returns a negative value, otherwise (the Application
Server is connected), the message is relaied to it.
_________________________________________________________
1.1.3. Dependencies
1.1.3.1. OpenSER Modules
SEAS module relies on the Transaction Module (TM module) for
operation.
_________________________________________________________
1.1.3.2. External Applications
Using the SEAS module requires to have an Application Server
running and connected to a particular instance of OpenSER.
At the moment, the only Application Server that works with
SEAS is WeSIP Application Server, which can be downloaded from
www.wesip.eu, and used freely for non-comercial purposes.
_________________________________________________________
1.1.4. Exported Parameters
1.1.4.1. listen_sockets (string)
The listen_sockets string tells SEAS where to listen for
incoming connections of Application Servers. It has the form:
"ip:port". SEAS will open two server-sockets on that IP, at
the specified port, and another at port+1. Application Servers
must be configured to connect to that port.
In case this parameter is ommited, SEAS listens on the default
IP which OpenSER is using, and opens the ports 5080 and 5081
to listen for Application Servers.
Example 1-1. Set listen_sockets parameter
...
modparam("seas", "listen_sockets","127.0.0.1:5080")
...
_________________________________________________________
1.1.5. Exported Functions
1.1.5.1. as_relay_t(String name)
Creates a new transaction (if it isn't already created) and
sends the SIP Request and transaction information to the
Application Server specified in the parameter. Every
Application Server connected to OpenSER through the SEAS
module, must be identified with a different name.
This function can be used within REQUEST_ROUTE.
Example 1-2. as_relay_t usage
...
if (!as_relay_t("app_server_1")) {
log("Error sending to app server");
t_reply("500","App Server not connected");
}
...
_________________________________________________________
1.1.5.1.1. Return value
In case the Application Server is connected to OpenSER, the
function does _not_ return, the Application Server is now in
charge of processing the request, and it may then reply to the
request, initiate new transactions, or whatever the
application being executed wants.
In case the Application Server identified by the string
parameter passed to as_relay_t() is not connected to OpenSER,
the function returns 0, so that the script can continue
processing the request.
_________________________________________________________
1.2. WeSIP Application Server
At the moment, the only Application Server known to work with
SEAS is WeSIP. You can download a copy from www.wesip.eu.
WeSIP is a converged Sip/Http Servlet Container.
_________________________________________________________
1.2.1. The Servlet programming paradigm: Sip/Http Servlets
Servlets are pieces of code that encapsulate the logic of an
application. Servlets are deployed into an Application Server.
Whenever a user requests service, the Application Server
processes the request, and passes control to the servlet. The
servlet then executes some logic, may it be a query to a
database, the execution of a business process, the creation of
customized content for the user, or whatever the service
programmer could imagine. When the servlet finishes the
execution, it creates a response and gives it back to the
Application Server, which is in charge of making it reach back
to the user. The Application Server implements the network
protocol, it takes care of everything needed for a proper
communication between user and server, so the servlet doesn't
have to care about these things. The servlet uses a set of
resources from the Application Server, such as Session
management, service routing or chaining, and request/response
header composition.
In HttpServlets, a service programmer has to implement a
method in a JAVA class, which could be called doGet() or
doPost(). Whenever an HTTP request arrived at the server, one
of these functions was called with the request as a parameter,
so the logic of the application was executed over that
particular request.
HttpServlet has been extensively used over the past years, in
all kinds of business and web services.
This is how a typical HttpServlet looks like:
Example 1-3. Typical example of an HttpServlet
public final class Hello extends HttpServlet {
protected void doGet(HttpServletRequest request,HttpServletResponse res
ponse)
throws IOException, ServletException
{
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.println("<html>");
writer.println("<head>");
writer.println("<title>Sample Application Servlet</title>");
writer.println("</head>");
writer.println("<body bgcolor=white>");
writer.println("<table border=\"0\" width=\"100%\">");
Enumeration names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
writer.println("<tr>");
writer.println("<th align=\"right\">"+name+":</th>");
writer.println("<td>"+request.getHeader(name)+"</td>");
writer.println("</tr>");
}
writer.println("</table>");
writer.println("</body>");
writer.println("</html>");
}
}
The successor of HttpServlet for SIP networks, is the
SipServlet API. Making most of the success of HttpServlet, the
SipServlet API follows the same programming paradigm, so that
SIP application programmers can reuse their knowledge in the
field.
SipServlet API works the same way as HttpServlet: an
Application Server implements a SIP Stack and executes all the
complex protocol logic. It receives and pre-processes the
requests from the network, and at the right moment, passes
control to the servlet doXxx() method, where the programmer
implemented the application logic. Depending on what kind of
SIP Message it was, a method or another will be executed. For
example, if an INVITE is received, the doInvite() method will
be invoked in the servlet.
The application can then access all the parts of the request
and do its work. When the service has been executed, it passes
control back to the Application Server with a response, so
that it can be forwarded to the user, and the service be
satisfied.
Sip Servlets can be used to implement basic SIP network
functionalities (such as Proxy or Registrar servers), but
their true power emerges in the implementation of value-added
services, which greatly surpasses the basic service
functionality of plain SIP servers.
Examples of value-added services, are Virtual PBX or
IPCentrex, Attended call forwarding, Instant Messaging, etc.
This is the appearance a typical SipServlet:
Example 1-4. Typical Sip Servlet Example
public class ProxyServlet extends SipServlet {
protected void doInvite(SipServletRequest req) throws
ServletException, IOException
{
if (req.isInitial()) {
Proxy proxy = req.getProxy();
proxy.setRecordRoute(false);
proxy.setParallel(parallel);
proxy.setSupervised(supervised);
SipURI rrURI = proxy.getRecordRouteURI();
rrURI.setParameter("foo", "bar");
req.setContent("Method is INVITE", "text/plain");
proxy.proxyTo(uris);
} else {
log("re-INVITE");
}
}
protected void doAck(SipServletRequest req) throws
ServletException, IOException
{
log("doAck " + req.getRequestURI());
if (req.isInitial()) {
throw new ServletException("unexpectedly got initial ACK");
The servlet programming API is event-ridden: every time a
request comes into the Application Server (may it be an Http
or SIP one), the specific servlet is executed and the service
provided within it.
It is a very straightforward way of programming services, and
the Servlet API provides very easy and powerful means to
access information about the SIP-session or Http-session,
about the request or response, about the state of the dialog,
or whatever it is needed.
The application programmer has a rich framework of resources
that allow him to focus only on the service logic, without
having to worry about the underlying protocol specifics (SIP
or HTTP).
Figure 1-1. SipServlet UML diagram
[image024.png]
The Servlet programming language is JAVA, which offers a wide
spectrum of programming API's dealing with all kinds of
techniques, tools and resources, which also are available
seamlessly from the Servlet context.
This makes the SipServlet API very desirable for all kinds
application developers.
SipServlet allows a rapid SIP application development and
deployment, and also provides a reliable and secure framework
of service execution (the JAVA sandbox and the Application
Server execution environment).
_________________________________________________________
1.2.1.1. Converged Http/Sip Servlet Containers
SipServlets achieve the most of it when they can be deployed
along with HttpServlets, in the same Application Server (also
known as Servlet Container). This environment truly realizes
the power of converged voice/data networks: Http protocol
represents one of the most powerful data transmission
protocols used in modern networks (think of the SOAP
web-services protocol), and SIP is the protocol of choice in
most of the modern and future voice over IP (VoIP) networks
for the signaling part. So an Application Server capable of
combining and leveraging the power of these two APIs will be
the most successful.
Convergence of SIP and HTTP protocols into the same
Application Server offers, amongst others, the following key
advantages:
-It doesn't require to have 2 different servers (Http and Sip)
so it relieves from maintenance problems, and eases user and
configuration provisioning.
-It offers great convenience to the application programmer to
have all the classes related to the different protocols
handled within the same code.
-As it eases development of interactive and multimedia
services, realizing the power of well-known web-services and
intermixing them with new voice services.
These are some simple, but suggestive examples of services
that could be developed within a converged Http/Sip servlet:
-IP Centrex: through the use of the Web-interface, users could
have a layout of the office in a web page, and see what phones
were ringing at a given moment, so they could pick-up a call
ringing in another phone in their own desktop. Or they could
forward a call to another party by clicking on the web page
and selecting which of the office phones it had to be
transferred to.
-Voicemail: users could upload an audio file to the server
through a web-page, to be used as the automatic answering
message, and then also download their voicemail through the
web-page, or organize the messages and remove the old ones.
-Instant Messaging: users could continue a voice call by
starting or joining a new Instant Messaging session carried
over a web-page.
-Click-to-dial: users could initiate SIP sessions only by
clicking a link on a web page, without the need of the
Web-Browser being SIP-aware nor needing even a SIP phone: the
server could handle all the logic so the user who clicked
could receive a call from the server's SIP network.
_________________________________________________________
1.2.2. Configuring WeSIP to work with SEAS
The WeSIP Application Server configuration file is based on
the Apache Tomcat configuration system: It is an XML-formatted
file, in which the different components of the server are
specified.
The default config file that comes with the WeSIP distribution
package should be suitable for most of the deployment
configurations.
>
_________________________________________________________
1.2.2.1. Server
The topmost element in the XML configuration file is the
"server" which has 2 xml attributes, called "port" and
"shutdown". The former specifies a port on which the WeSIP AS
will listen for the shutdown command, and the latter is the
magic word that will make the server shutdown.
Example 1-5. Server
<Server port="8005" shutdown="SHUTDOWN" >
if you send the magic word "SHUTDOWN" to the port 8005 of the
localhost, the server will stop cleanly.
>
_________________________________________________________
1.2.2.2. Service
Nested within the Server element, must be a "Service" element,
with an attribute called "name" which specifies the name for
the service. This attribute is not very relevant, you can call
it whatever you like.
Example 1-6. Service
<Service name="WeSIP-Standalone">
Within the Service element must be two or more elements: the
connectors and the engines. A connector is the instance that
will receive messages from the network. You can specify HTTP
connectors and/or SIP connectors. Every connector needs an
attribute called "className" which specifies which class will
be responsible for receiving the messages from the network.
For HTTP connectors, the classname must be
"org.apache.catalina.connector.http.HttpConnector" and for SIP
connectors "com.voztele.sipservlet.connector.SipConnector".
_________________________________________________________
1.2.2.3. Connector
The SIP Connector uses 4 attributes:
className="com.voztele.sipservlet.connector.SipConnector"
specifies the classname of the connector.
minProcessors="5"
specifies the minimum number of SIPprocessor instances (and
threads in the pool) to process incoming SIP messages. More
processors should allow more load to be processed. This is the
minimum number of instances, even if they are spare and not
working.
maxProcessors="10"
specifies the maximum number of SIP processors used (a
negative value specifies that there is no limit).
addresses="localhost:5060"
Specifies the SIP address and port in which the Application
Server from which the Application Server will process the SIP
messages. This Addres is where OpenSER listens for the
messages, so in fact, OpenSER is listening on them, but
OpenSER passes the messages to WeSIP, so WeSIP must be aware
of this IP/port.
Warning
this attribute MUST match one of the listening points declared
within OpenSER in the "listen" parameters.
For example in openser.cfg:
listen = tcp:localhost:5060
listen = udp:localhost:5060
Within the SIP Connector element there must be an
ExtraProperties element, containing nestes Property elements.
Each property element specifies a parameter for the SIP Stack.
Each property is specified by a key and a value. The most
significant keys are:
* com.voztele.javax.sip.SER_ADDRESS
This specifies the IP and port in which the OpenSER is
listening for Application Servers to connect and
register.This specifies the IP and port in which the
OpenSER is listening for Application Servers to connect
and register.
Warning
This needs to match the listen_sockets seas module parameter
within the OpenSER configuration file. Ie.:
modparam("seas", "listen_sockets","127.0.0.1:5080")
* javax.sip.STACK_NAME
Specifies the name identifying this instance of the
Application Server.
Warning
This is the name you will set in the OpenSER configuration
script when you invoke the WeSIP Application Server, by
calling the as_relay_t function. This is the name you pass as
the parameter of the function. If you have different WeSIP
instances all connecting to the same OpenSER, they must each
one have a different STACK_NAME", and within OpenSER you can
call each of them by invoking as_relay_t() with a different
name. Example:
<Property key="javax.sip.STACK_NAME" value="app_server_one" />
* com.voztele.javax.sip.THREAD_POOL_SIZE (integer)
Specifies the number of threads there must be in the pool
to process incoming SIP messages. If unspecificed, the
default is "infinity".
* com.voztele.javax.sip.SPIRAL_HDR
This property tells WeSIP and SEAS that every SipRequest
and UAC transaction generated from WeSIP, must spiral
through SER, and will be added a special Header called
"X-WeSIP-SPIRAL: true" this will make all the outgoing
messages pass again through the OpenSER script, so that
they can be accounted or whatever the configurator wants.
For example, the configuration script could go:
route{
if(is_present_hf("X-WeSIP-SPIRAL")){
/* account, log, register, or whatever */
t_relay();
}else{
as_relay_t("app_server_1");
}
}
>
_________________________________________________________
1.2.2.4. Engine
The Engine must also be nested within the Server element,
along with the Connectors. It must have a "name" attribute
with whatever name you feel like. It needs to have another
attribute called "defaultHost" which will be the default host
to which to pass the incoming request (in HTTP/1.0 the
requests dont have a Host header, so they will be passed to
this default host, in SIP, this attribute doesn't have a
meaning.). In order to have this Engine handling also SIP
messages, the "className" attribute of the Engine must be
"com.voztele.sipservlet.core.ConvergedEngine".
Within the Engine, there can be one or more Hosts, each one
specified within a "Host" element nested in the engine.
_________________________________________________________
1.2.2.5. Mapper
A mapper is used to map an incoming request to one or another
SIP or HTTP host. In case it is a SIP request, the mapping is
done based on the sip.xml deployment descriptor rules. The
classname of the SIP mapper MUST BE
"com.voztele.sipservlet.core.EngineSipMapper". The "mapper"
element must also have a "protocol" attribute, specifying
which protocol this mapper handles. In case of the SIP mapper
it must be "SIP/2.0". The HTTP mapper's classname must be
"org.apache.catalina.core.StandardEngineMapper" and the
protocol attribute "HTTP/1.1"
_________________________________________________________
1.2.2.6. Realm
The authentication in HTTP is performed in Apache-Tomcat
through Realms. The memory realm is (textual copy from the
Apache-Tomcat javadoc"): "Simple implementation of Realm that
reads an XML file to configure the valid users, passwords, and
roles."
The classname must be "org.apache.catalina.realm.MemoryRealm"
A "pathname" attribute can be specified to tell the Realm
which file contains the usernames, passwords and roles. If not
specified, it is "conf/wesip-users.xml"
_________________________________________________________
1.2.2.7. Host
A Host represents a VirtualHost in HTTP/1.1 servers, so the
requests will be dispatched to one or another virtual host
depending on the Host: header. In SIP this doesn't make much
sense, because there's no such Host: header, and virtual
hosting is not done in this way. Every host must have a "name"
attribute which specifies the name of the virtual host, it
must also have a "nameSip" attribute which MUST MATCH the IP
or hostname _and_ port" specified in OpenSER listen parameters
and in the Sip Connector the hostname and the port must be
separated with an underscore. for example:
nameSip="localhost_5060" or nameSip="192.168.1.1_5060" The
next important attribute that must have the Host element is
"appBase" which declares the directory where the WEB and SIP
applications reside. It usually is a directory called apps in
the directory from which the server runs. The attribute
"unpackWARs" says the WeSIP Application Server to unpack the
Web or Sip Application Archives (.war or .sar extensions)
found inside the appBase directory. It should usually be set
to "true". The "port" attribute specifies the port where this
host is going to receive SIP messages . This only has to do
with the SIP protocol, not with HTTP. It must be the same as
the port specified in OpenSER parameter "listen_sockets" (for
the seas module). The "autoDeploy" attribute tells the host to
monitor the "appBase" directory for new application archives
(.sar or .war) so they can automatically be deployed. This
parameter should be set to "true". The "className" used for
the Host _must_be_ "com.voztele.sipservlet.core.ConvergedHost"
_________________________________________________________
1.2.2.8. Mapper
Hosts must also have a nested Mapper element, but when the
mapper is inside a Host (and not in an Engine) the classnames
must be "com.voztele.sipservlet.core.SipHostMapper" for the
"SIP/2.0" protocol and
"org.apache.catalina.core.HttpHostMapper" for the "HTTP/1.1"
protocol. (2 mappers must be nested inside the Host).
_________________________________________________________
1.2.3. Configuration Examples
In general, you can configure WeSIP to work with your OpenSER
in two ways: have 2 OpenSER instances, the first acting as
Proxy/Registrar/Redirect and the second cooperating with WeSIP
to act as the Application Server. This is the preferred
deployment layout, as the first OpenSER works as usual, and
the requests that need special services are relaied to another
OpenSER which acts on behalf of the WeSIP AS. This
configuration profile distributes load (call-routing logic in
one instance, and Application Services in the other), and is
also more fault-tolerant. On the other hand, you can have all
your call-routing logic and Application Server on the same
OpenSER, having one script handle all the logic, and then
invoking the App Server at any point.
>
_________________________________________________________
1.2.3.1. Openser.cfg in standalone
debug=3 # debug level (cmd line: -dddddddddd)
fork=yes
log_stderror=no # (cmd line: -E)
check_via=no # (cmd. line: -v)
dns=no # (cmd. line: -r)
rev_dns=no # (cmd. line: -R)
port=5060
children=4
loadmodule "/usr/local/lib/openser/modules/sl.so"
loadmodule "/usr/local/lib/openser/modules/tm.so"
loadmodule "/usr/local/lib/openser/modules/rr.so"
loadmodule "/usr/local/lib/openser/modules/maxfwd.so"
loadmodule "/usr/local/lib/openser/modules/usrloc.so"
loadmodule "/usr/local/lib/openser/modules/registrar.so"
loadmodule "/usr/local/lib/openser/modules/textops.so"
loadmodule "/usr/local/lib/openser/modules/seas.so"
loadmodule "/usr/local/lib/openser/modules/mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/openser_fifo")
modparam("usrloc", "db_mode", 0)
modparam("rr", "enable_full_lr", 1)
modparam("seas", "listen_sockets", "127.0.0.1:5080");
route{
if (!mf_process_maxfwd_header("10")) {
sl_send_reply("483","Too Many Hops");
exit;
};
if (msg:len >= 2048 ) {
sl_send_reply("513", "Message too big");
exit;
};
if (!method=="REGISTER")
record_route();
if (loose_route()) {
append_hf("P-hint: rr-enforced\r\n");
route(1);
};
if (uri==myself) {
if (method=="REGISTER") {
save("location");
exit;
};
lookup("aliases");
if (!uri==myself) {
append_hf("P-hint: outbound alias\r\n");
route(1);
};
if (!lookup("location")) {
sl_send_reply("404", "Not Found");
exit;
};
append_hf("P-hint: usrloc applied\r\n");
};
route(1);
}
route[1] {
if(!as_relay_t("app_server_one")){
t_reply("500","Application Server error");
}
}
_________________________________________________________
1.2.3.2. Openser.cfg working as WeSIP front-end
debug=9 # debug level (cmd line: -dddddddddd)
fork=yes
log_stderror=yes # (cmd line: -E)
check_via=no # (cmd. line: -v)
dns=no # (cmd. line: -r)
rev_dns=no # (cmd. line: -R)
port=5060
children=4
reply_to_via=1
listen = tcp:localhost:5060
listen = udp:localhost:5060
mpath="/home/elias/src/sipservlet/seas"
loadmodule "modules/tm/tm.so"
loadmodule "modules/seas/seas.so"
loadmodule "modules/mi_fifo/mi_fifo.so"
modparam("mi_fifo", "fifo_name", "/tmp/openser_fifo")
modparam("seas", "listen_sockets","127.0.0.1:5080")
route{
if(!as_relay_t("app_server_1")){
t_reply("500","Application Server error");
}
}
_________________________________________________________
1.2.3.3. Server.xml
<Server port="8005" shutdown="SHUTDOWN" debug="0">
<Service name="WeSIP-Standalone">
<Connector className="org.apache.catalina.connector.http.HttpConnec
tor"
port="8080" minProcessors="5" maxProcessors="75"
enableLookups="true" address="localhost" acceptCount="10" debug
="10" />
<Connector className="com.voztele.sipservlet.connector.SipConne
ctor"
minProcessors="5" maxProcessors="75"
addresses="localhost:5060" >
<ExtraProperties>
<Property key="com.voztele.javax.sip.SER_ADDRESS
" value="127.0.0.1:5080" />
<Property key="javax.sip.STACK_NAME" value="app_
server_one" />
<Property key="com.voztele.javax.sip.THREAD_POOL
_SIZE" value="10" />
</ExtraProperties>
</Connector>
<Engine name="Standalone" defaultHost="localhost" debug="10"
className="com.voztele.sipservlet.core.ConvergedEngine">
<Logger className="org.apache.catalina.logger.SystemOutLogger"
timestamp="true"/>
<Mapper className="org.apache.catalina.core.StandardEngineMappe
r" protocol="HTTP/1.1"/>
<Mapper className="com.voztele.sipservlet.core.EngineSipMapper"
protocol="SIP/2.0"/>
<Realm className="org.apache.catalina.realm.MemoryRealm" />
<Host name="localhost" nameSip="localhost_5060" debug="10" appB
ase="webapps" unpackWARs="true"
port="5060" autoDeploy="true" className="com.voztele.sipservlet
.core.ConvergedHost">
<Mapper className="com.voztele.sipservlet.core.SipHostMa
pper" protocol="SIP/2.0"/>
<Mapper className="org.apache.catalina.core.HttpHostMapp
er" protocol="HTTP/1.1"/>
</Host>
</Engine>
</Service>
</Server>
_________________________________________________________
Chapter 2. Developer's Guide
2.1. Internals
The SEAS module runs within the Open Sip Express Router aka.
OpenSER. OpenSER uses a pool of processes to execute the
script logic on every new message received. These are called
the worker processes. One of these processes will be selected
to process the script, and at some point it will find a
function invoking the relay of the SIP message to one of the
Application Servers registered. This function has been called
as_relay_t, which stands for Application Server relay (the _t
stands for TransactionStatefully), and receives as the only
parameter the name of the application server to be invoked.
The process will execute the as_relay_t function, which looks
up in a table if there is a registered Application Server with
that name. If there is one, the process will craft the SEAS
header for the SIP message being handled, put it in a shared
memory segment, and write the address of that segment to a
pipe (4 bytes pointer in IA32).
This way, we will have all the OpenSER processes composing the
SEAS header along with the SIP message, and putting its shared
memory address into that pipe. This technique of inter-process
communication avoids race conditions because writing to a pipe
is granted to be an atomic operation if the data to write is
less than _POSIX_PIPE_BUF, which usually is 512 bytes.
At the initialization of OpenSER, the SEAS module creates the
discussed pipe, so that all the OpenSER worker processes
inherit the file descriptor associated to the pipe. Then it
spawns a new process, which will be the one to open two server
sockets, and wait for the Application Servers to connect and
register.
Each Application Server wishing to receive events from
OpenSER, will have to open a socket to the module (the port
and IP of the socket are defined at start time in the script).
After connection, it has to print its identification name. The
SEAS process (from now on, called event dispatcher) will then
register it in its internal structures, so that the OpenSER
processes can push events for it. The following picture, shows
the internals of the SEAS Event dispatcher process:
Figure 2-1. Overview of Seas Event Dispatcher process
operation
[image056.gif]
Within the SER server, the flowing of SIP Messages and control
flow, is depicted in the following diagram:
Figure 2-2. SIP Messages and control flow within SER
[image058.gif]
_________________________________________________________
2.2. SEAS Protocol
SIP is a very flexible protocol. It can be very easily
extended with new features, and SIP entities have a high level
of freedom in composing the SIP messages, for example setting
IPs or hostnames in URIs, reordering header fields, folding
headers, aggregating/scattering headers, etc.
This flexibility, though, makes it difficult to implement
efficiently, because parsing of text headers requires a lot of
state.
OpenSER implements a very efficient parsing mechanism and
SIP-transaction machinery. The goal of the SEAS protocol is to
keep all this information that has been already extracted at
OpenSER, so that it can be reused at the Application Server.
_________________________________________________________
2.2.1. The SEAS protocol
The SEAS protocol is a layer of information regarding the
internal structure of a SIP message that is added whenever
SEAS sends a SIP event to the Application Servers. The
protocol is used for communication between OpenSER and the
Application Servers.
Once an incoming SIP message has reached the worker process
within OpenSER, it copies its content into a private memory
area (which is, a memory chunk not shared across processes).
In this point, the message first line is parsed to know
whether it is a SIP request or response.
OpenSER uses a technique called lazy-parsing, which consists
in delaying the parse of headers until some piece of the code
requires it.
As the SIP message goes traversing functions and the script
code, a function called parse_msg() gets called again and
again, and the SIP message gets parsed further and further.
Each call to parse_msg passes an integer value argument (32
bits) in which every bit signals a header to be parsed, if
they are already parsed (because a previous invocation of
parse_msg), the function returns immediately, otherwise, the
SIP message is scanned and parsed until all the headers
requested get parsed.
In each call to parse_msg, different parts of the message are
analyzed, and different SIP header-specific structures get
filled. Each one of this structures, give quick access to each
of the parts of a SIP message header.
For example, a Via header struct is called via_body, and has
these members: name, version, transport, host, proto, port,
port_str, params, comment, received, rport, etc. each of these
members gives quick access to each of the parts of the header.
For example, a via header like this: "Via: SIP/2.0/UDP
192.168.1.64:5070;branch=z9hG4bK-c02c60cc" would have the
member proto pointing to the "U" of "UDP", and a length of 3,
the host member would be pointing to "192.168.1.64" and have a
length of 12, the branch member would be pointing to
"z9hG4bK-c02c60cc" and a length of 16, and so on.
This structure is the result of the parsing. All this
meta-information regarding the SIP message structure, is
stored in a sip_msg structure, using dynamically-allocated
memory segments.
OpenSER defines different structure types describing different
SIP headers, such as via_body, to_body, cseq_body, via_param,
and so on. These structures are generally composed of another
kind of structure called str.
The str structure is a key component of OpenSER's high
performance. In the C programming language, a string's length
is known because a '0' (null-character) is found at the end of
it. This forces each of the string manipulation functions to
keep looking for a '0' in the byte stream, which is quite
processor consuming. Instead of this, OpenSER defines a
structure composed of a char pointer and an integer. The char
points to the start of a string, and the integer gives its
length, thus avoiding the '0' lookup problem, and giving a
significant performance boost.
This structure has been quite useful to the design of the SEAS
protocol, because it enables the description of the SIP
message anatomy by giving pointers to each of its fields, and
integers describing each of its lengths.
Knowing that a SIP header does not usually occupy more than a
few characters (always less than 256), the pointer in the
structure has been relativized to the beginning of the SIP
message or the beginning of the SIP header, and the integer
giving the length, has been casted to an unsigned byte (256
values, so 256 characters maximum length).
When messages get transferred from OpenSER to the Application
Server, it is optimum to keep this worthy meta-information
regarding the SIP message, so that it can be used at the AS
part. For this to be possible, it is needed to store the
pointers to each of the syntactic structures and their length.
In general, pointers are variables that point to a region in
the memory of a computer. The region of the memory is counted
from the 0x00000000 address in IA32 architectures (from the
beginning).
C provides functionality to do any kind of arithmetic
operations over pointers (add, subtract, multiply and divide),
so that the euclidean distance over the one-dimension address
space can be calculated just by subtracting a base address
from another pointer.
These pointers will have to be transmitted through the
network, along with the SIP message, so for the pointers to
keep their meaning, they need to be relativized to a known
point, and the most meaningful known point in a SIP message is
its start.
So making the pointers relative to the message start, gives
two important features: first, it makes the pointers still
valid when they arrive at another computer (because they are
relative to the beginning of the message), and they occupy far
less memory, because from a 4-byte pointer (in IA32) it gets
translated to a 1 or 2 byte index, because an important amount
of redundant information is elicited (we already know that
each of the parts of the message belong to the message, so why
carry the message begin address in each of the pointers ?).
The SIP messages are composed of protocol headers and a
payload. The headers section don't usually surpass the 1500
byte limit, amongst other reasons, because the usual Maximum
Transmission Unit in Ethernet networks is 1500 bytes and the
protocol was initially designed to work on UDP. For that
reason, 11 bits should be enough to address a particular
region within the SIP message, because it yields 2048
positions. The closest greater value to 11 bits multiple of a
byte (the basic TCP network transport unit) is 16 bits, or 2
bytes, which makes it possible to address 65536 positions from
the beginning.
For the SEAS protocol to be extensible and
platform-independent, all the 2-byte pointers or indexes to
each of the message regions are sent in network-byte-order, or
big endian. This is also useful in the JAVA part to retrieve
the indexes, because the JAVA natively uses a big-endian
representation of integers, regardless the architecture on
which it runs.
For each kind of standard SIP header (this is, the headers
referred to in the SIP specification) there is a code
specification, regarding the composition of the header. Each
one of its parts points to one the several components of the
header. For example, a From header always has a SipURI and may
have several parameters, amongst others, a tag. Then, the From
header code has a field indicating where the URI starts, a
codification of the URI, and several pointers that point to
each one of the parameter names and values. This is the
codification of the From header. All the other headers have a
similar codification.
_________________________________________________________
2.2.2. General codification of a header
Every header codification, regardless it is known to the
server or not, begins with a 2-byte unsigned integer, which
points to the beginning of that header counted from the SIP
message begin (a SIP message start based pointer to the
header). Following these two bytes is another byte giving the
length of the name, and another byte giving the length of the
entire header (including name and value).
Figure 2-3. General codification of a SIP header in SEAS
protocol
[image100.gif]
For example:
Figure 2-4. Example of a from header SEAS-protocol
codification
[image101.gif]
_________________________________________________________
2.2.2.1. Codification of a generic URI
As the SIP URI is one of the most used types in a SIP message,
a special structure has been defined to describe the contents
of it. A URI is always included inside a SIP header, or may be
in the first line of a SIP Request (as the request URI).
The codification of any URI is as follows:
Figure 2-5. SEAS-codification of a SIP URI (byte meanings are
shown)
[image102.gif]
What follows is an example of a SIP URI codification with the
SEAS protocol.
Figure 2-6. Example of a SEAS SIP URI codification
[image103.gif]
The first byte in the encoded-URI structure, gives the index
where the URI starts, counting from the beginning of the SIP
header where it appears. The next two bytes are flags
indicating known fields present in the URI (such as port,
host, user, etc.).
All the following bytes are uri-start based pointers to the
fields that are present in the URI, as specified by the flags.
They must appear in the same order shown in the flags, and
only appear if the flag was set to 1.
The end of the field, will be the place where the following
pointer points to, minus one (note that all the fields present
in a URI are preceded by 1 character, ie
sip[:user][:passwod][@host][:port][;param1=x][;param2=y][?hdr1
=a][&hdr2=b]) it will also be necessary to have a pointer at
the end, pointing two past the end of the URI, so that the
length of the last header can be computed.
The reason to have the "other parameters" and headers flags at
the beginning (just after the strictly URI stuff), is that it
will be necessary to know the length of the parameters section
and the headers section. The parameters can appear in an
arbitrary order, they won't be following the convention of
transport-ttl-user-method-maddr-lr, so we can't rely on the
next pointer to compute the length of the previous pointer
field, as the ttl parameter can appear before the transport
parameter. So the parameter pointers must have 2 bytes:
pointer+length.
_________________________________________________________
2.2.2.2. Codification of To and From headers
To and From headers follow the same structure, so the same
codification structure has been used to describe both. The
structure is depicted in the drawing:
Figure 2-7. SEAS codification of From and To headers
[image104.gif]
_________________________________________________________
2.2.2.3. Codification of Contact
The contact header is one of those SIP headers that can be
combined, which means that if several headers of the same type
are present in the message, they can be aggregated in a single
header, having the header values separated by a comma. Thus, a
single Contact header can contain more than one contact-value.
For this reason, the Contact codification is composed of a
several Contact codifications concatenated, and a byte at the
beginning telling how much Contact codifications are present.
The code is depicted in the following drawing:
Figure 2-8. SEAS codification of a Contact header
[image105.gif]
_________________________________________________________
2.2.2.4. Codification of Route and Record Route headers
Both Route and Record-Route headers follow an identical
structure, and it is also permitted to combine several headers
into one, with their bodies (or header values) separated by
commas. In this case, both kinds of headers follow the same
structure, defined as follows:
Figure 2-9. SEAS codification of a Route Header
[image106.gif]
_________________________________________________________
2.2.2.5. Codification of Accept and Content-Type headers
These two kinds of headers carry mime type and subtype
definitions in the form "type/subtype" (ie. text/xml,
application/sdp or whatever). For internal handling of this
headers, SER codifies the known types and subtypes into a
single 32 bit integer, with the highest two bytes giving the
mime type, and the lowest two bytes giving the subtype.
The difference is that Accept header can also be combined,
carrying more than one header value in a single header row.
Thus the Accept header has a leading byte giving the number of
mime type/subtype integers present, while the Content-Type
only uses 4 bytes (a 32-bit integer) giving the type/subtype.
_________________________________________________________
2.2.2.6. Codification of Authorization headers
SIP has inherited the authentication scheme from HTTP, which
is based on a digest scheme. There are several headers
regarding these authorization scheme, namely
Proxy-Authenticate, WWW-Authenticate, Authorization and
Proxy-Authorization. All of them can be codified using the
same schema, which is as follows:
Figure 2-10. SEAS codification of Authentication/Authorization
headers
[image107.gif]
For each field present, there are 2 bytes, one pointing the
place where it starts, the next giving how long this field is.
The URI is a special case, and is composed of 1 byte telling
how long is the URI structure, and then the encoded URI
structure.
_________________________________________________________
2.2.2.7. Codification of Allow headers
Allow headers carry request methods that a user agent or proxy
understands or is willing to accept. In SER, request methods
are codified into a 32-bit integer, each of its bits signals a
different kind of header. The Allow header is codified copying
that integer into the payload of the header.
_________________________________________________________
2.2.2.8. Codification of Content-Disposition headers
The content-disposition is encoded within 2 bytes: the first
is a header-start based pointer to where the
content-disposition value starts, and the second is its
length. If there are parameters present, each of them uses 1
byte pointing to where the parameter name starts, and 1 byte
pointing to where the parameter value starts. From these two
values, the parameter name and value lengths can be inferred.
_________________________________________________________
2.2.2.9. Codification of Content-Length header
The content length header is codified as a 4-byte unsigned
integer, in network byte order.
_________________________________________________________
2.2.2.10. Codification of Cseq header
The Cseq header is codified using 9 bytes. The first one is a
number corresponding to the internal value that SER assigns to
that request method (the method ID). The following 4 bytes are
an unsigned 32-bit integer according to the Cseq number. The
next two bytes are the header based pointer to the beginning
of the Cseq number and its length, and two more bytes pointing
to the beginning of the method name and its length.
_________________________________________________________
2.2.2.11. Codification of Expires header
The expires header is composed of 6 bytes. The first four
bytes are an unsigned 32-bit integer with the parsed value of
the header (which is the number of seconds before a request
expires). Then follows 1 byte pointing to the beginning of the
header value (the expires value as a string) and a byte giving
the length of the value.
_________________________________________________________
2.2.2.12. Codification of a SIP message
2.2.2.12.1. The general message information section
In SER, not only the headers are parsed with a high degree of
optimization, but also the first line is. So for the SEAS
protocol to realize this improvement, a codification for the
first line of every SIP messages has also been defined.
The first two bytes of the codification are a 2-byte unsigned
integer. If its value is equal or greater than 100, then this
is a response, and the integer represents its status code. If
its value is smaller than 100, then it is a request, and the
integer represents the method of the request being
transported.
Figure 2-11. SEAS codification of a SIP First Line
[image108.gif]
The next two bytes are an unsigned integer which is a pointer
to where the actual SIP message starts, beginning from the
start of the codified payload.
The next two bytes are also an unsigned integer giving the SIP
message length.
The next bytes differ on the meaning depending on whether the
message is a SIP Request or Response.
In case it is a Request:
The next two bytes, are a SIP-message-start based pointer to
where the method begins, and the method length.
The next two bytes, are a SIP-message-start based pointer to
where the Request URI begins, and the request URI length.
The next two bytes, are a SIP-message-start based pointer to
where the version identifier begins, and the version
identifier length.
In case it was a Response:
The next two bytes, are a SIP-message-start based pointer to
where the response code begins, and the response code length.
The next two bytes, are a SIP-message-start based pointer to
where the reason phrase begins, and the reason phrase length.
The next two bytes, are a SIP-message-start based pointer to
where the version identifier begins, and the version
identifier length.
In case the message is a SIP response, the following bytes
correspond to the Request URI codification. The first byte is
the length of the URI codification, followed by the URI code.
The last byte in this set, is the number of headers present in
the SIP message. After this byte, goes a section, called the
Message Headers Index, which gives quick access to each of the
headers and their codifications present in the message.
_________________________________________________________
2.2.2.12.2. The headers index section
As it has been already discussed, the aim of SEAS project is
to achieve as high a performance as possible. One of the
techniques enabling high performance in text-based servers is
the so called lazy parsing. To enable the laziest possible
parsing at the Application Server endpoint, a mechanism has
been used so that access to a requested SIP header can be
delayed until the application requests it, and the access can
be direct to that header, without parsing the former headers
present in the SIP message. Recall that one of the performance
drawbacks of the SIP protocol is that headers of any type can
be spread all along the header section, not having the
constraint of putting the most critical sip-specific headers
at the beginning and ordered (which would be, in fact, very
desirable).
For this to be possible, there is a section right after the
beginning of the payload (the general message information
section) which is a kind of hash table, giving quick access to
the codes (as explained in the previous sections) of each of
the headers present in the message.
This sort of hash table, is composed of triplets of bytes. The
first byte of each three is a code indicating which kind of
header it points to (whether it is a From, To, Call-ID, Route
header, etc). Then follows a 2 byte network-byte-order integer
that points to a section in the codified-header where the body
of this header is more specifically described.
This gives really fast access to any of the headers. For
example, if all the Route Headers were requested by the
application, then a lookup in this table would be necessary,
looking for the value '9' (corresponding to the Route header)
in each of the positions multiple of 3 (0,3,6,9,12, etc). This
can be done in a extremely fast and easy way, as this snipped
of pseudo code explains:
for(int j=0,int i=0;i<table_length;i+=3){
if(payload[i]==9)
results[j++]=i;
}
this would let in the "results" array all the indexes in the
headers table that refer to a Route header. Then, the Route
codification for each of the headers could be reached thanks
to the two-byte unsigned integer that follows each of the
header identifiers.
Figure 2-12. SEAS Headers Index section overview
[image109.gif]
So a SIP message codified by the SEAS protocol, has the
following layout:
Figure 2-13. SEAS SIP-Message codification
[image110.gif]
SIP Messages are a fundamental part of the protocol, but they
are not the only one. Transaction play a very important role
in the SIP protocol, within SER and in any JAIN-SIP
implementation. For this reason, the SEAS protocol also needs
to define and implement some semantics regarding transaction
handling. The events related to a transaction are: Incoming
Request, Outgoing Request, Incoming Response, Outgoing
Response, Timeout and Transport Error.
So the SEAS protocol defines a specific format for each one of
these events. Internally, SER stores the transactions in a
hash table. This hash table generates an integer for each
transaction applying a hash function to its Via branch
parameter, this integer is the hash index, and it identifies
in which slot within the hash table the transaction is stored.
The transaction table usually uses 65536 entries, so the hash
collision is pretty unlikely. Anyway, every hash entry is in
reality a linked list of transactions, so in the case a hash
collision (two transactions being assigned to the same hash
slot) the transactions are added to the same slot, each one
being identified by another integer called the label. The
label within a hash slot, is initially generated randomly, and
then increased by one each time a transaction falls in the
same slot. So every transaction is identified by a hash index
and a label.
For incoming SIP requests, a transaction is generated at SER,
and the SEAS module gets that transaction identifier (hash
index + label), then grabs the source and destination IP, port
and transport from every message, and crafts a SEAS RequestIn
event. This kind of event carries all this information within
it.
In order to send Responses out for the Server Transactions,
JAIN can send a type of Action messages, that order SER to
send them to the network. These messages follow a structure
very similar to that of RequestIn events: they start with the
Action length in bytes, then follows a byte giving the type of
action, then follows the Hash Index and the Label associated
with the transaction that is being replied, and finally the
SIP Message in raw format. It doesn't use the SEAS
codification described above, because SER can easily parse the
JAIN provided Response to process it and send it out, so the
pre-parsing is not needed in that direction.
In order to generate Client Transactions, that is, sending SIP
Requests out, JAIN utilizes another kind of action called Seas
Request Action. In this case, when JAIN generates the Request
to be sent out, it doesn't have any means to know the
transaction identifier (hash index and label) that will be
assigned to it by SER, so a new mechanism has bee implemented
to correlate JAIN requests to SER transactions. Basically,
JAIN-SIP assigns a unique identifier (an integer) that is
incremented by one for each new Client Transaction generated.
This identifier is passed to SER along with the SIP Request,
so when a SIP Response arrives to SER regarding that
transaction, SER sends a ResponseIn event to the JAIN stack,
containing both the initial integer identifying the
transaction at JAIN and the hash index and label that have
been assigned to the transaction. This way, JAIN can correlate
its own identifiers with the identifiers used within SER.
Figure 2-14. Different kinds of SEAS codified Events and
Actions
[image111.gif]
In case there is a Transaction Timeout, it is notified to the
JAIN SIP Stack by passing it a Seas Incoming Response with a
flag called Faked Reply, and a Response code number 408
(Request Timeout).
_________________________________________________________
Chapter 3. Frequently Asked Questions
3.1. Where can I find more about OpenSER?
3.2. Where can I find more about WeSIP?
3.3. Where can I post a question about this module?
3.4. How can I report a bug?
3.1. Where can I find more about OpenSER?
Take a look at http://openser.org/
3.2. Where can I find more about WeSIP?
Take a look at http://www.wesip.eu/
3.3. Where can I post a question about this module?
First at all check if your question was already answered on
one of our mailing lists:
* User Mailing List -
http://openser.org/cgi-bin/mailman/listinfo/users
* Developer Mailing List -
http://openser.org/cgi-bin/mailman/listinfo/devel
E-mails regarding any stable OpenSER release should be sent to
<users@openser.org> and e-mails regarding development versions
should be sent to <devel@openser.org>.
If you want to keep the mail private, send it to
<team@openser.org>.
3.4. How can I report a bug?
Please follow the guidelines provided at:
http://sourceforge.net/tracker/?group_id=139143.