本文档目的意在说明,如何利用openimscore的特定版本,配置一个注册服务器,能够正常运行起来,并对程序的原理进行简要的说明。最终目的是为用户完全配置,再开发openimscore打基础。
说明
软件版本与运行环境
安装与配置
数据库配置
数据库安装
域信息配置
Sip终端用户信息配置
模块说明
注册服务器的主要moduel的结构:
说明
本文档目的意在说明,如何利用openimscore的特定版本,配置一个注册服务器,能够正常运行起来,并对程序的原理进行简要的说明。最终目的是为用户完全配置,再开发openimscore打基础。
本文设定用户有以下条件:
l 对开源项目Ser和openimscore的背景有充足的了解,但对其程序的原理了解不作要求。
l 有Linxu下c语言开发经验
这个注册服务器具有下列功能:
l 对前来注册的用户进行验证,验证的方式采用WWW-Authorizer的方式,详情请参考rfc217。
l 利用mysql数据库对用户信息进行持久保存。
软件版本与运行环境
软件版本:
Openimscore svn 版本号663
Svn路径: http://195.37.77.137/svnroot/repos/openimscore/ser_ims/trunk
操作系统:
Ubuntu8.04 LTS
安装与配置
在trunk目录,执行如下命令:
Make include_group=”standard mysql” all
Make include_group=”standard mysql” install
在执行上述make 命令时如果编译出错,多数原因是因为openimscore的依赖项没有安装,可以依据出错信息,查找到依赖项,并安进行安装。
这些依赖项可能是库文件(*.s0),也可能是库文件的头文件,也可以是其它组件程序,比如mysql。
在ubuntu下可以采用 apt-get install 命令进行安装eg:
Apt-get install libc6-dev
Apt-get install mysql
如果make include_group=”standard mysql” install 被正确安装,那么文件会被安装在下列位置:
/usr/local/etc/ser/ser.cfg
核心配置文件
/usr/local/lib/ser/module/*.so
module文件,openimscore是采用plus-in的方式构建成的,在运行时会依据核心配置文件来动态的加载这些库文件。
/usr/bin/ser
到配置文件目录/usr/local/etc/ser/下把 ser.cfg换成附件中的ser.cfg。
见附件。 Ser.cfg
如果以上操顺利,在命令行下运行 ./ser。 注册服务器就可以运行起来。 如果不能正常运行,请确定所有的module是否全部被安装到/usr/local/lib/ser/module目录,如果没有请手动从编译目录下边考过去。
虽然能够正常的跑起来,但并不意味着sip终端可以前来成功的注册,因为数据库中没有注册服务器的域信息和终端用户信息。所以sip终端要想成功注册,还得先把数据库给配置完整。
数据库配置
数据库安装
在trunk/script/mysql下
运行 ./ser_mysql.sh 然后按提示添加参数,进行数据库安装。
域信息配置
注册服务器必须要有自己的域信息,它是在数据表domain和domain_attrs表中存放。对于domain表中的记录字段,一定要在domain_attrs中体现出来。这一点非常重要,如果在domain中加一条域记录,ser是不认的,因为它只读domain_attrs中的数据。
Domain(did, domain, flags)
1 192.168.7.103 1
Domain_attrs(did, name , type,value, flags)
1, digest_realm , 2, 192.168.7.103 1
上述两条记录指明:本地注册服务器的域是192.168.7.103. 它有一个属性名字叫:digest_realm, 类型是2,值是192.168.7.103 ,flags为1是指明要把这个属性加载到AVP中。
什么是AVP? 正如其名 attribute-value pair 。 实际上就是一个key-value。它把一个个的key-value加载到内存中,以便快速查找引用。 在ser中,有个了Moudle叫做AVP,这个module是专门负责avp工作的。 在配置文件ser.cfg中有很多以$开前的变量,在ser内部的路由Engine中,这些变量会被转换成实际值,这个变量的实际值就来自于AVP。 如果你把上边Domain_attrs中的flags设为0,就表明它不会加载到avp中,那么你在ser.cfg中使用$digest_realm就不会被转换为 192.168.7.103。*@192.168.7.103的用户前来注册,注册服务器就不会理会。 扯远了,先回来。
Sip终端用户信息配置
Sip终端用户前来注册,如果它的信息在注册服务器的数据库中查不到,注册就会失败。要让一个用户注册成功,数据库中得有如下信息。
ACC 保存用户的基本信息,没有什么要说的,直接添加的的sip 终端信息就是,
Credentials 保存用户的认证信息。
Uri 定义用户的通信范围,也就是说所有前来注册的sip head中to字段和form字段的uri都要在些定义,如果没有则会引起注册失败。 此处要留意flags的设置,8表示本条uri只可用来to, 16表示只可用来from ,1,表示有效。 如果你定义一个url想让它用业当to,也用来当from ,并且启用,那么它的flag要设为 8+16+1=25
模块说明
注册服务器的主要moduel的结构:
一个sip msg进来,主要用到了三个module:
Sl所有msg进来的必经之中。它的导出函数有:
sl_filter_ACK(struct sip_msg *msg, void *bar )用来过滤ACK。
sl_send_reply(struct sip_msg *msg , int code, char* reason)用来回发响应包。
Auth_db 进行用户信息验证。
Registear 注册操作。 比如把在线的用户状态保存到数据库,超时时把用户的状态信息从数据库中删除。
那么mysql module用在哪呢? 实际上mysql module 是一个公共服务组件:
它可以被其它moduel调用。但有可能不直接参与消息处理。
配置文件:

Code
#
# $Id: ser.cfg 166 2007-03-02 19:28:23Z vingarzan $
#
# First start SER sample config script with:
# database, accounting, authentication, multi-domain support
# PSTN GW section, named flags, named routes, global-,
# domain- and user-preferences with AVPs
# Several of these features are only here for demonstration purpose
# what can be achieved with the SER config script language.
#
# If you look for a simpler version with a lot less dependencies
# please refer to the ser-basic.cfg file in your SER distribution.
# To get this config running you need to execute the following commands
# with the new serctl (the capital word are just place holders)
# - ser_ctl domain add DOMAINNAME
# - ser_ctl user add USERNAME@DOMAINNAME -p PASSWORD
# If you want to have PID header for your user
# - ser_attr add uid=UID asserted_id="PID"
# If you want to have gateway support
# - ser_db add attr_types name=gw_ip rich_type=string raw_type=2 description="The gateway IP for the default ser.cfg" default_flags=33
# - ser_attr add global gw_ip=GATEWAY-IP
### xxk 2009-06-12 this cfg is a default configuration for openimscore
# ----------- global configuration parameters ------------------------
debug=1 # debug level (cmd line: -dddddddddd)
#memdbg=10 # memory debug log level
#memlog=10 # memory statistics log level
#log_facility=LOG_LOCAL0 # sets the facility used for logging (see syslog(3))
/* Uncomment these lines to enter debugging mode */
fork=no
log_stderror=yes
check_via=no # (cmd. line: -v)
dns=no # (cmd. line: -r)
rev_dns=no # (cmd. line: -R)
port=5060
listen=192.168.7.103
#children=4
#user=ser
#group=ser
#disable_core=yes #disables core dumping
#open_fd_limit=1024 # sets the open file descriptors limit
#mhomed=yes # usefull for multihomed hosts, small performance penalty
#disable_tcp=yes
#tcp_accept_aliases=yes # accepts the tcp alias via option (see NEWS)
#
# ------------------ module loading ----------------------------------
# load a SQL database for authentication, domains, user AVPs etc.
loadmodule "/usr/local/lib/ser/modules/mysql.so"
loadmodule "/usr/local/lib/ser/modules/sl.so"
loadmodule "/usr/local/lib/ser/modules/tm.so"
loadmodule "/usr/local/lib/ser/modules/rr.so"
loadmodule "/usr/local/lib/ser/modules/maxfwd.so"
loadmodule "/usr/local/lib/ser/modules/usrloc.so"
loadmodule "/usr/local/lib/ser/modules/registrar.so"
loadmodule "/usr/local/lib/ser/modules/xlog.so"
loadmodule "/usr/local/lib/ser/modules/textops.so"
loadmodule "/usr/local/lib/ser/modules/ctl.so"
loadmodule "/usr/local/lib/ser/modules/fifo.so"
loadmodule "/usr/local/lib/ser/modules/auth.so"
loadmodule "/usr/local/lib/ser/modules/auth_db.so"
loadmodule "/usr/local/lib/ser/modules/gflags.so"
loadmodule "/usr/local/lib/ser/modules/domain.so"
loadmodule "/usr/local/lib/ser/modules/uri_db.so"
loadmodule "/usr/local/lib/ser/modules/avp.so"
loadmodule "/usr/local/lib/ser/modules/avp_db.so"
loadmodule "/usr/local/lib/ser/modules/acc_db.so"
loadmodule "/usr/local/lib/ser/modules/xmlrpc.so"
# ----------------- setting script FLAGS -----------------------------
flags
FLAG_ACC : 1 # include message in accouting
avpflags
dialog_cookie; # handled by rr module
# ----------------- setting module-specific parameters ---------------
# specify the path to you database here
modparam("acc_db|auth_db|avp_db|domain|gflags|usrloc|uri_db", "db_url", "mysql://ser:heslo@192.168.7.103/ser")
# -- usrloc params --
# as we use the database anyway we will use it for usrloc as well
modparam("usrloc", "db_mode", 1)
# -- auth params --
modparam("auth_db", "calculate_ha1", yes)
modparam("auth_db", "password_column", "password")
# -- rr params --
# add value to ;lr param to make some broken UAs happy
modparam("rr", "enable_full_lr", 1)
#
# limit the length of the AVP cookie to only necessary ones
modparam("rr", "cookie_filter", "(account)")
#
# you probably do not want that someone can simply read and change
# the AVP cookie in your Routes, thus should really change this
# secret value below
modparam("rr", "cookie_secret", "MyRRAVPcookiesecret")
# -- gflags params --
# load the global AVPs
modparam("gflags", "load_global_attrs", 1)
# -- domain params --
# load the domain AVPs
modparam("domain", "load_domain_attrs", 1)
# -- ctl params --
# by default ctl listens on unixs:/tmp/ser_ctl if no other address is
# specified in modparams; this is also the default for sercmd
modparam("ctl", "binrpc", "unixs:/tmp/ser_ctl")
# listen on the "standard" fifo for backward compatibility
modparam("ctl", "fifo", "fifo:/tmp/ser_fifo")
# listen on tcp, localhost
#modparam("ctl", "binrpc", "tcp:192.168.7.103:2046")
# -- acc_db params --
# failed transactions (=negative responses) should be logged to
modparam("acc_db", "failed_transactions", 1)
# comment the next line if you dont want to have accouting to DB
modparam("acc_db", "log_flag", "FLAG_ACC")
# -- tm params --
# uncomment the following line if you want to avoid that each new reply
# restarts the resend timer (see INBOUND route below)
#modparam("tm", "restart_fr_on_each_reply", "0")
# ------------------------- request routing logic -------------------
# main routing logic
route{
# if you have a PSTN gateway just un-comment the follwoing line and
# specify the IP address of it to route calls to it
#$gw_ip = "1.2.3.4"
# first do some initial sanity checks
route(INIT);
# check if the request is routed via Route header or
# needs a Record-Route header
route(RR);
# check if the request belongs to our proxy
route(DOMAIN);
# handle REGISTER requests
route(REGISTRAR);
# from here on we want to know you is calling
route(AUTHENTICATION);
# check if we should be outbound proxy for a local user
route(OUTBOUND);
# check if the request is for a local user
route(INBOUND);
# here you coud for example try to an ENUM lookup before
# the call gets routed to the PSTN
#route(ENUM);
# lets see if someone wants to call a PSTN number
######route(PSTN);
# nothing matched, reject it finally
sl_reply("404", "No route matched");
}
route[FORWARD]
{
# here you could decide wether this call needs a RTP relay or not
# send it out now; use stateful forwarding as it works reliably
# even for UDP2TCP
if (!t_relay()) {
sl_reply_error();
}
drop;
}
route[INIT]
{
# as soon as it is save to distinguish HTTP from SIP
# we can un-comment the next line
route(RPC);
# initial sanity checks -- messages with
# max_forwards==0, or excessively long requests
if (!mf_process_maxfwd_header("10")) {
sl_reply("483","Too Many Hops");
drop;
}
if (msg:len >= max_len )
{
sl_reply("513", "Message too big");
drop;
}
# you could add some NAT detection here for example
# or you cuold call here some of the check from the sanity module
# lets account all initial INVITEs and the BYEs
# if (method=="INVITE" && @to.tag=="" || method=="BYE") {
if (method=="INVITE" && @to.tag=="") {
setflag(FLAG_ACC);
}
}
route[RPC]
{
# allow XMLRPC from localhost
if ((method=="POST" || method=="GET") &&
src_ip==127.0.0.1) {
if (msg:len >= 8192) {
sl_reply("513", "Request to big");
drop;
}
# lets see if a module want to answer this
dispatch_rpc();
drop;
}
}
route[RR]
{
# subsequent messages withing a dialog should take the
# path determined by record-routing
if (loose_route()) {
# mark routing logic in request
append_hf("P-hint: rr-enforced\r\n");
# if the Route contained the accounting AVP cookie we
# set the accounting flag for the acc_db module.
# this is more for demonstration purpose as this could
# also be solved without RR cookies.
# Note: this means all in-dialog request will show up in the
# accouting tables, so prepare your accounting software for this ;-)
if ($account == "yes") {
setflag(FLAG_ACC);
}
# for broken devices which overwrite their Route's with each
# (not present) RR from within dialog requests it is better
# to repeat the RRing
# and if we call rr after loose_route the AVP cookies are restored
# automatically :)
record_route();
route(FORWARD);
}
else if (!method=="REGISTER")
{
# we record-route all messages -- to make sure that
# subsequent messages will go through our proxy; that's
# particularly good if upstream and downstream entities
# use different transport protocol
# if the inital INVITE got the ACC flag store this in
# an RR AVP cookie. this is more for demonstration purpose
if (isflagset(FLAG_ACC))
{
$account = "yes";
setavpflag($account, "dialog_cookie");
}
record_route();
}
}
route[DOMAIN]
{
# check if the caller is from a local domain
lookup_domain("$fd", "@from.uri.host");
# call function lookup_domain_fixup() in module domain //xxk
# check if the callee is at a local domain
lookup_domain("$td", "@ruri.host");
# we dont know the domain of the caller and also not
# the domain of the callee -> somone uses our proxy as
# a relay
if (!$t.did && !$f.did) {
sl_reply("403", "Relaying Forbidden");
drop;
}
}
route[REGISTRAR]
{
# if the request is a REGISTER lets take care of it
if (method=="REGISTER")
{
# check if the REGISTER if for one of our local domains
#if (!$t.did)
#{
# sl_reply("403", "Register forwarding forbidden");
# drop;
#}
# we want only authenticated users to be registered
if (!www_authenticate("$fd.digest_realm", "credentials"))
{
if ($? == -2)
{
sl_reply("500", "Internal Server Error");
}
else if ($? == -3)
{
sl_reply("400", "Bad Request");
}
else
{
if ($digest_challenge)
{
append_to_reply("%$digest_challenge");
}
sl_reply("401", "Unauthorized");
}
drop;
}
# check if the authenticated user is the same as the target user
if (!lookup_user("$tu.uid", "@to.uri")) {
sl_reply("404", "Unknown user in To");
drop;
}
# why to do this action? xxk
#if ($f.uid != $t.uid) {
# sl_reply("403", "Authentication and To-Header mismatch");
# drop;
#}
# check if the authenticated user is the same as the request originator
# you may uncomment it if you care, what uri is in From header
if (!lookup_user("$fu.uid", "@from.uri")) {
sl_send_reply("404", "Unknown user in From");
drop;
}
#if ($fu.uid != $tu.uid) {
# sl_send_reply("403", "Authentication and From-Header mismatch");
# drop;
#}
# everyhting is fine so lets store the binding
if (!save_contacts("location"))
{
sl_reply("400", "Invalid REGISTER Request");
drop;
}
drop;
}
}
route[AUTHENTICATION]
{
if (method=="CANCEL" || method=="ACK") {
# you are not allowed to challenge these methods
break;
}
# requests from non-local to local domains should be permitted
# remove this if you want a walled garden
if (! $f.did) {
break;
}
# as gateways are usually not able to authenticate for their
# requests you will have trust them base on some other information
# like the source IP address. WARNING: if at all this is only safe
# in a local network!!!
#if (src_ip==a.b.c.d) {
# break;
#}
if (!proxy_authenticate("$fd.digest_realm", "credentials")) {
if ($? == -2) {
sl_reply("500", "Internal Server Error");
} else if ($? == -3) {
sl_reply("400", "Bad Request");
} else {
if ($digest_challenge) {
append_to_reply("%$digest_challenge");
}
sl_reply("407", "Proxy Authentication Required");
}
drop;
}
# check if the UID from the authentication meets the From header
$authuid = $uid;
if (!lookup_user("$fu.uid", "@from.uri")) {
del_attr("$uid");
}
if ($fu.uid != $fr.authuid) {
sl_reply("403", "Fake Identity");
drop;
}
# load the user AVPs (preferences) of the caller, e.g. for RPID header
load_attrs("$fu", "$f.uid");
}
route[OUTBOUND]
{
# if a local user calls to a foreign domain we play outbound proxy for him
# comment this out if you want a walled garden
if ($f.did && ! $t.did) {
append_hf("P-hint: outbound\r\n");
route(FORWARD);
}
}
route[INBOUND]
{
# lets see if know the callee
if (lookup_user("$tu.uid", "@ruri")) {
# load the preferences of the callee to have his timeout values loaded
load_attrs("$tu", "$t.uid");
# if you want to know if the callee username was an alias
# check it like this
#if (! $tu.uri_canonical) {
# if the alias URI has different AVPs/preferences
# you can load them into the URI track like this
#load_attrs("$tr", "@ruri");
#}
# native SIP destinations are handled using our USRLOC DB
if (lookup_contacts("location")) {
append_hf("P-hint: usrloc applied\r\n");
# we set the TM module timers according to the prefences
# of the callee (avoid too long ringing of his phones)
# Note1: timer values have to be in ms now!
# Note2: this makes even more sense if you switch to a voicemail
# in a FAILURE route
if ($t.fr_inv_timer) {
if ($t.fr_timer) {
t_set_fr("$t.fr_inv_timer", "$t.fr_timer");
} else {
t_set_fr("$t.fr_inv_timer");
}
}
route(FORWARD);
} else {
sl_reply("480", "User temporarily not available");
drop;
}
}
}
#route[PSTN]
#{
# # Only if the AVP 'gw_ip' is set and the request URI contains
# # only a number we consider sending this to the PSTN GW.
# # Only users from a local domain are permitted to make calls.
# # Additionally you might want to check the acl AVP to verify
#
# # that the user is allowed to make such expensives calls.
# if ($f.did && $gw_ip &&
# uri=~"sips?:\+?[0-9]{3,18}@.*")
# {
# # probably you need to convert the number in the request
# # URI according to the requirements of your gateway here
#
# # if an AVP 'asserted_id' is set we insert an RPID header
# if ($asserted_id) {
# xlset_attr("$rpidheader", "<sip:%$asserted_id@%@ruri.host>;screen=yes");
# replace_attr_hf("Remote-Party-ID", "$rpidheader");
# }
#
# # just replace the domain part of the RURI with the
# # value from the AVP and send it out
# attr2uri("$gw_ip", "domain");
# route(FORWARD);
# }
# }