美文网首页后端砖头freeswitch
WebRTC+Kamamilio+rtpengine+Webso

WebRTC+Kamamilio+rtpengine+Webso

作者: 全栈码农 | 来源:发表于2022-05-17 18:45 被阅读0次

    发现国内使用SIP的资料真的很少,由于公司需要使用语音视频电话,在网上找了一大圈也没找到相关案例,最后自己折腾出一个方案,故此分享出来。
    开始之前不得不介绍一下这五个东西。

    Kamamilio 是 OpenSER 的前身,是C语言写的一个Sip服务,支持在 在Linux/UNIX系统上运行,优点就是快,可配置性强,可直接写C语言配置,其它的我就不多做介绍了,可自行google

    FreeSWITCH 也是sip服务器,只是相对于Kamamilio 我感觉要复杂,而且它的信令用户管理我感觉不友好,每个用户就得搞一个配置文件,那100000个用户不得崩溃,这个看个人需求吧,我觉得,也许是我理解不够深入,它也有它的优势。

    RTPProxy 就是流媒体(Stream) NAT穿透的一个代理组件
    Rtpengine 这个和RTPProxy 其实是一个东西,只是 Rtpengine 加入了更多东西,可以说是一个RTPProxy 的升级版这么理解吧。
    Websocket 这个不需要解释了吧,http/s 的长连接服务

    为什么要写这篇文章呢?需求是这样的,这里先列出几大sip的使用方案吧

    1、SDP -> Kamamilio/FreeSWITCH -> RTPProxy
    这种方案适用于 android/ios/pc 的基于SDP协议的方案,支持的框架有,linphone,MicroSIP 等
    2、Websocket -> WebRTC -> Kamamilio/FreeSWITCH -> WebsocketServer -> Rtpengine
    这种方案适用于 JsSIP、Flutter、react-native、VUE 等前端框架使用
    3、SDP/Websocket -> server -> 硬件交换机
    这个没啥不好的,就是成本高一点
    4、SRS to WebRTC
    这个也没啥不好的,但是毕竟是做直播推流的,很多通话逻辑处理还得自己实现,比如信令、计费、分组、对接运营商等。

    有关直播推流拉流可以关注我后续文章,会有讲到。

    1 2
    3 4
    --- ---
    5 6

    我使用的系统是 debian 10,之前用的是Centos 7,但是由于 Rtpengine 引擎在 Centos 不好装,直接给我把系统玩坏了,嗯,还是少用root为妙,所以换成 debian
    所以如果你的系统是 Centos 那就使用方案1会少折腾一点。

    如果你使用的是阿里、百度、腾讯云,那么估计我这个配置还不一定行,因为我这个服务器是直接绑定公网IP的,而这些大平台都内置了一个NAT路由,把外网IP隔离了,所以如果NAT是内网IP,请开放网络安全组/防火墙的端口。

    而我需要的就是第二种,好了,废话不多说了,直接上代码,注意Kamamilio 依赖MYSQL或其它数据库服务,使用前请确认你的数据库类型,我这里以mysql服务为例。

    之前有玩过第一种方案的并且也用的挺好,最近又在玩flutter,发现这个方案玩的人还是比较少的,但是web玩的人却是很多,而这个方案就比较适合web了,所以出一篇文章记录一下,也好让喜欢折腾的道友有个参照。

    安装 Kamamilio

    # 添加 Kamamilio 源公钥
    wget http://deb.kamailio.org/kamailiodebkey.gpg
    sudo apt-key add kamailiodebkey.gpg
    sudo vim /etc/apt/sources.list
    

    编辑 /etc/apt/sources.list 添加如下 源地址,这里我直接贴上我的配置信息

    # deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 DVD Binary-1 20210814-10:04]/ bullseye contrib main
    
    # deb cdrom:[Debian GNU/Linux 11.0.0 _Bullseye_ - Official amd64 DVD Binary-1 20210814-10:04]/ bullseye contrib main
    
    deb http://security.debian.org/debian-security bullseye-security main contrib
    deb-src http://security.debian.org/debian-security bullseye-security main contrib
    
    # bullseye-updates, to get updates before a point release is made;
    # see https://www.debian.org/doc/manuals/debian-reference/ch02.en.html#_updates_and_backports
    # A network mirror was not selected during install.  The following entries
    # are provided as examples, but you should amend them as appropriate
    # for your mirror of choice.
    #
    # deb http://deb.debian.org/debian/ bullseye-updates main contrib
    # deb-src http://deb.debian.org/debian/ bullseye-updates main contrib
    
    # aliyun
    deb https://mirrors.aliyun.com/debian/ bullseye main non-free contrib
    deb-src https://mirrors.aliyun.com/debian/ bullseye main non-free contrib
    deb https://mirrors.aliyun.com/debian-security/ bullseye-security main
    deb-src https://mirrors.aliyun.com/debian-security/ bullseye-security main
    deb https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib
    deb-src https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib
    deb https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib
    deb-src https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib
    
    # kamailio
    deb     http://deb.kamailio.org/kamailio55 jessie  main
    deb-src http://deb.kamailio.org/kamailio55 jessie  main
    deb     http://deb.kamailio.org/kamailio55 stretch main
    deb-src http://deb.kamailio.org/kamailio55 stretch main
    deb     http://deb.kamailio.org/kamailio55 buster main
    deb-src http://deb.kamailio.org/kamailio55 buster main
    deb     http://deb.kamailio.org/kamailio55 bullseye main
    deb-src http://deb.kamailio.org/kamailio55 bullseye main
    deb     http://deb.kamailio.org/kamailio55 trusty main
    deb-src http://deb.kamailio.org/kamailio55 trusty main
    deb     http://deb.kamailio.org/kamailio55 xenial main
    deb-src http://deb.kamailio.org/kamailio55 xenial main
    deb     http://deb.kamailio.org/kamailio55 bionic main
    deb-src http://deb.kamailio.org/kamailio55 bionic main
    deb     http://deb.kamailio.org/kamailio55 focal main
    deb-src http://deb.kamailio.org/kamailio55 focal main
    

    更新源和安装,kamailio-mysql-modules kamailio-websocket-modules这两个模块是必须装的,不然会找不到so文件

    sudo apt update
    sudo apt-get install vim kamailio kamailio-mysql-modules kamailio-websocket-modules
    

    如果kamailio-mysql-modules报错Unable to locate package libmysql dev,请安装mysql服务和客户端,至于mysql服务这个不用我教了吧,玩服务器这个基本少不了的,不想折腾就直接安装个宝塔,或单独安装 libmysqlclient21

    sudo apt-get install libmysqlclient21
    # 如果没有找到包,请到官网单独下载 然后使用
    # https://packages.debian.org/sid/amd64/libmysqlclient21/download
    # sudo apt-get install ./libmysqlclient21_8.0.23-3+b1_amd64.deb
    

    安装完成 kamailio 后就是配置文件了,整个kamailio服务器只需要关注这两个文件就可以了

    root@debian:~# ls -l /etc/kamailio/
    total 44
    -rw-r--r-- 1 root root 35805 Apr 30 00:09 kamailio.cfg
    -rw-r--r-- 1 root root  4254 Apr 22 17:45 kamctlrc
    
    vim /etc/kamailio/kamctlrc
    

    配置 kamctlrc

    下面直接贴出我的配置,这个文件只要是配置 kamctl 命令行工具使用的数据库

    ## The Kamailio configuration file for the control tools.
    ##
    ## Here you can set variables used in the kamctl and kamdbctl setup
    ## scripts. Per default all variables here are commented out, the control tools
    ## will use their internal default values.
    
    ## the SIP domain
    SIP_DOMAIN=你的域名或IP
    
    ## chrooted directory
    # CHROOT_DIR="/path/to/chrooted/directory"
    
    ## database type: MYSQL, PGSQL, ORACLE, DB_BERKELEY, DBTEXT, or SQLITE
    ## by default none is loaded
    ##
    ## If you want to setup a database with kamdbctl, you must at least specify
    ## this parameter.
    DBENGINE=MYSQL
    
    ## database host
    DBHOST= localhost #你数据库的ip或域名一般 localhost|127.0.0.1
    
    ## database port
    DBPORT=3306 #你数据库的端口
    
    ## database name (for ORACLE this is TNS name)
    DBNAME=kamailio #数据库的名称
    
    ## database path used by dbtext, db_berkeley or sqlite
    # DB_PATH="/usr/local/etc/kamailio/dbtext"
    
    ## database read/write user
    DBRWUSER="kamailio" #数据库的用户名
    
    ## password for database read/write user
    DBRWPW="xxx" #数据库的密码
    
    ## database read only user
    # DBROUSER="kamailioro"
    
    ## password for database read only user
    # DBROPW="kamailioro"
    
    ## database access host (from where is kamctl used)
    # DBACCESSHOST=192.168.0.1
    
    ## database super user (for ORACLE this is 'scheme-creator' user)
    DBROOTUSER="root" #数据库root用名名
    
    ## password for database super user
    ## - important: this is insecure, targeting the use only for automatic testing
    ## - known to work for: mysql
    DBROOTPW="xxx" #数据库root密码
    
    ## option to ask confirmation for all database creation steps
    # DBINITASK=yes
    
    ## database character set (used by MySQL when creating database)
    CHARSET="utf8" #数据库字符集
    
    ## user name column
    # USERCOL="username"
    
    
    ## SQL definitions
    ## If you change this definitions here, then you must change them
    ## in db/schema/entities.xml too.
    ## FIXME
    
    # FOREVER="2030-05-28 21:32:15"
    # DEFAULT_Q="1.0"
    
    
    ## Program to calculate a message-digest fingerprint
    # MD5="md5sum"
    
    ## awk tool
    # AWK="awk"
    
    ## gdb tool
    # GDB="gdb"
    
    ## If you use a system with a grep and egrep that is not 100% gnu grep compatible,
    ## e.g. solaris, install the gnu grep (ggrep) and specify this below.
    ##
    ## grep tool
    # GREP="grep"
    
    ## egrep tool
    # EGREP="egrep"
    
    ## sed tool
    # SED="sed"
    
    ## tail tool
    # LAST_LINE="tail -n 1"
    
    ## expr tool
    # EXPR="expr"
    
    
    ## Describe what additional tables to install. Valid values for the variables
    ## below are yes/no/ask. With ask (default) it will interactively ask the user
    ## for an answer, while yes/no allow for automated, unassisted installs.
    
    ## If to install tables for the modules in the EXTRA_MODULES variable.
    # INSTALL_EXTRA_TABLES=ask
    
    ## If to install presence related tables.
    # INSTALL_PRESENCE_TABLES=ask
    
    ## If to install uid modules related tables.
    # INSTALL_DBUID_TABLES=ask
    
    ## Define what module tables should be installed.
    ## If you use the postgres database and want to change the installed tables, then you
    ## must also adjust the STANDARD_TABLES or EXTRA_TABLES variable accordingly in the
    ## kamdbctl.base script.
    
    ## Kamailio standard modules
    # STANDARD_MODULES="standard acc lcr domain group permissions registrar usrloc msilo
    #                   alias_db uri_db speeddial avpops auth_db pdt dialog dispatcher
    #                   dialplan"
    
    ## Kamailio extra modules
    # EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute userblocklist htable purple sca"
    
    
    ## type of aliases used: DB - database aliases; UL - usrloc aliases
    ## - default: none
    # ALIASES_TYPE="DB"
    
    ## control engine: RPCFIFO
    ## - default RPCFIFO
    # CTLENGINE="RPCFIFO"
    
    ## path to FIFO file for engine RPCFIFO
    # RPCFIFOPATH="/run/kamailio/kamailio_rpc.fifo"
    
    ## check ACL names; default on (1); off (0)
    # VERIFY_ACL=1
    
    ## ACL names - if VERIFY_ACL is set, only the ACL names from below list
    ## are accepted
    # ACL_GROUPS="local ld int voicemail free-pstn"
    
    ## check if user exists (used by some commands such as acl);
    ## - default on (1); off (0)
    # VERIFY_USER=1
    
    ## verbose - debug purposes - default '0'
    # VERBOSE=1
    
    ## do (1) or don't (0) store plaintext passwords
    ## in the subscriber table - default '1'
    # STORE_PLAINTEXT_PW=0
    
    ## Kamailio START Options
    ## PID file path - default is: /run/kamailio/kamailio.pid
    # PID_FILE=/run/kamailio/kamailio.pid
    
    ## Extra start options - default is: not set
    ## example: start Kamailio with 64MB share memory: STARTOPTIONS="-m 64"
    # STARTOPTIONS=
    

    配置 kamailio.cfg

    接下来才是重点 kamailio.cfg 这个文件才是最复杂,里面包含很多逻辑处理,如果你只是单纯的使用,不想去研究太多逻辑的,可以直接把我这个配置把IP地址改掉换成你的就好,我也就不做过多解释了(开玩笑我解释个锤子我解释,1500行配置,解释起来天都黑了,我是花了整整两天时间配出来的),有兴趣的可以自己一行行代码去解读研究。

    找到 #!substdef "!MY_IP_ADDR! 这一行,把IP地址改掉
    找到 #!define DBURL 这一行,把xxx改成你的root密码或数据库密码

    #!KAMAILIO
    #
    # Kamailio SIP Server v5.5 - default configuration script
    #     - web: https://www.kamailio.org
    #     - git: https://github.com/kamailio/kamailio
    #
    # Direct your questions about this file to: <sr-users@lists.kamailio.org>
    #
    # Refer to the Core CookBook at https://www.kamailio.org/wiki/
    # for an explanation of possible statements, functions and parameters.
    #
    # Note: the comments can be:
    #     - lines starting with #, but not the pre-processor directives,
    #       which start with #!, like #!define, #!ifdef, #!endif, #!else, #!trydef,
    #       #!subst, #!substdef, ...
    #     - lines starting with //
    #     - blocks enclosed in between /* */
    # Note: the config performs symmetric SIP signaling
    #     - it sends the reply to the source address of the request
    #     - remove the use of force_rport() for asymmetric SIP signaling
    #
    # Several features can be enabled using '#!define WITH_FEATURE' directives:
    #
    # *** To run in debug mode:
    #     - define WITH_DEBUG
    #     - debug level increased to 3, logs still sent to syslog
    #     - debugger module loaded with cfgtrace endabled
    #
    # *** To enable mysql:
    #     - define WITH_MYSQL
    #
    # *** To enable authentication execute:
    #     - enable mysql
    #     - define WITH_AUTH
    #     - add users using 'kamctl' or 'kamcli'
    #
    # *** To enable IP authentication execute:
    #     - enable mysql
    #     - enable authentication
    #     - define WITH_IPAUTH
    #     - add IP addresses with group id '1' to 'address' table
    #
    # *** To enable persistent user location execute:
    #     - enable mysql
    #     - define WITH_USRLOCDB
    #
    # *** To enable presence server execute:
    #     - enable mysql
    #     - define WITH_PRESENCE
    #     - if modified headers or body in config must be used by presence handling:
    #     - define WITH_MSGREBUILD
    #
    # *** To enable nat traversal execute:
    #     - define WITH_NAT
    #     - option for NAT SIP OPTIONS keepalives: WITH_NATSIPPING
    #     - install RTPProxy: http://www.rtpproxy.org
    #     - start RTPProxy:
    #        rtpproxy -l _your_public_ip_ -s udp:localhost:7722
    #
    # *** To use RTPEngine (instead of RTPProxy) for nat traversal execute:
    #     - define WITH_RTPENGINE
    #     - install RTPEngine: https://github.com/sipwise/rtpengine
    #     - start RTPEngine:
    #        rtpengine --listen-ng=127.0.0.1:2223 ...
    #
    # *** To enable PSTN gateway routing execute:
    #     - define WITH_PSTN
    #     - set the value of pstn.gw_ip
    #     - check route[PSTN] for regexp routing condition
    #
    # *** To enable database aliases lookup execute:
    #     - enable mysql
    #     - define WITH_ALIASDB
    #
    # *** To enable speed dial lookup execute:
    #     - enable mysql
    #     - define WITH_SPEEDDIAL
    #
    # *** To enable multi-domain support execute:
    #     - enable mysql
    #     - define WITH_MULTIDOMAIN
    #
    # *** To enable TLS support execute:
    #     - adjust CFGDIR/tls.cfg as needed
    #     - define WITH_TLS
    #
    # *** To enable JSONRPC over HTTP(S) support execute:
    #     - define WITH_JSONRPC
    #     - adjust event_route[xhttp:request] for access policy
    #
    # *** To enable anti-flood detection execute:
    #     - adjust pike and htable=>ipban settings as needed (default is
    #       block if more than 16 requests in 2 seconds and ban for 300 seconds)
    #     - define WITH_ANTIFLOOD
    #
    # *** To block 3XX redirect replies execute:
    #     - define WITH_BLOCK3XX
    #
    # *** To block 401 and 407 authentication replies execute:
    #     - define WITH_BLOCK401407
    #
    # *** To enable VoiceMail routing execute:
    #     - define WITH_VOICEMAIL
    #     - set the value of voicemail.srv_ip
    #     - adjust the value of voicemail.srv_port
    #
    # *** To enhance accounting execute:
    #     - enable mysql
    #     - define WITH_ACCDB
    #     - add following columns to database
    #!ifdef ACCDB_COMMENT
      ALTER TABLE acc ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE acc ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT '';
      ALTER TABLE acc ADD COLUMN src_ip varchar(64) NOT NULL default '';
      ALTER TABLE acc ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE acc ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE acc ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT '';
      ALTER TABLE missed_calls ADD COLUMN src_user VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE missed_calls ADD COLUMN src_domain VARCHAR(128) NOT NULL DEFAULT '';
      ALTER TABLE missed_calls ADD COLUMN src_ip varchar(64) NOT NULL default '';
      ALTER TABLE missed_calls ADD COLUMN dst_ouser VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE missed_calls ADD COLUMN dst_user VARCHAR(64) NOT NULL DEFAULT '';
      ALTER TABLE missed_calls ADD COLUMN dst_domain VARCHAR(128) NOT NULL DEFAULT '';
    #!endif
    
    ####### Include Local Config If Exists #########
    import_file "kamailio-local.cfg"
    
    ####### Defined Values #########
    #!substdef "!MY_IP_ADDR!这里填公网IP地址!g"
    #!substdef "!MY_DOMAIN!这里填公网IP地址!g"
    #!substdef "!MY_WS_PORT!8880!g"
    #!substdef "!MY_WSS_PORT!8443!g"
    #!substdef "!MY_MSRP_PORT!8881!g"
    #!substdef "!MY_WS_ADDR!tcp:MY_IP_ADDR:MY_WS_PORT!g"
    #!substdef "!MY_WSS_ADDR!tls:MY_IP_ADDR:MY_WSS_PORT!g"
    #!substdef "!MY_MSRP_ADDR!tcp:MY_IP_ADDR:MY_MSRP_PORT!g"
    #!substdef "!MSRP_MIN_EXPIRES!1800!g"
    #!substdef "!MSRP_MAX_EXPIRES!3600!g"
    
    #!define WITH_WEBSOCKETS
    # - !define WITH_TLS
    # - !define WITH_MSRP
    
    #!define WITH_MYSQL
    #!define WITH_AUTH
    #!define WITH_USRLOCDB
    
    #!define WITH_NAT
    #!define WITH_NATSIPPING
    #!define WITH_ANTIFLOOD
    #!define WITH_RTPENGINE
    
    #!define WITH_DEBUG
    #!define WITH_JSONRPC
    #!define WITH_IMC
    
    #!define DBURL "mysql://root:xxx@127.0.0.1/kamailio"
    
    # *** Value defines - IDs used later in config
    #!ifdef WITH_DEBUG
    #!define DBGLEVEL 3
    #!else
    #!define DBGLEVEL 2
    #!endif
    
    #!ifdef WITH_MYSQL
    # - database URL - used to connect to database server by modules such
    #       as: auth_db, acc, usrloc, a.s.o.
    #!trydef DBURL "mysql://kamailio:kamailiorw@localhost/kamailio"
    #!endif
    
    #!ifdef WITH_MULTIDOMAIN
    # - the value for 'use_domain' parameters
    #!define MULTIDOMAIN 1
    #!else
    #!define MULTIDOMAIN 0
    #!endif
    
    # - flags
    #   FLT_ - per transaction (message) flags
    #!define FLT_ACC 1
    #!define FLT_ACCMISSED 2
    #!define FLT_ACCFAILED 3
    #!define FLT_NATS 5
    
    #       FLB_ - per branch flags
    #!define FLB_NATB 6
    #!define FLB_NATSIPPING 7
    #!define FLB_RTPWS 8
    #!define FLB_BRIDGE 11
    
    ####### Global Parameters #########
    
    /* LOG Levels: 3=DBG, 2=INFO, 1=NOTICE, 0=WARN, -1=ERR, ... */
    debug=DBGLEVEL
    
    /* set to 'yes' to print log messages to terminal or use '-E' cli option */
    log_stderror=no
    
    memdbg=5
    memlog=5
    
    log_facility=LOG_LOCAL0
    log_prefix="{$mt $hdr(CSeq) $ci} "
    
    /* number of SIP routing processes for each UDP socket
     * - value inherited by tcp_children and sctp_children when not set explicitely */
    children=8
    
    /* uncomment the next line to disable TCP (default on) */
    # disable_tcp=yes
    
    /* number of SIP routing processes for all TCP/TLS sockets */
    # tcp_children=8
    
    /* uncomment the next line to disable the auto discovery of local aliases
     * based on reverse DNS on IPs (default on) */
    auto_aliases=no
    
    /* add local domain aliases - it can be set many times */
    # alias="sip.mydomain.com"
    alias=MY_DOMAIN
    
    /* listen sockets - if none set, Kamailio binds to all local IP addresses
     * - basic prototype (full prototype can be found in Wiki - Core Cookbook):
     *      listen=[proto]:[localip]:[lport] advertise [publicip]:[pport]
     * - it can be set many times to add more sockets to listen to */
    # listen=udp:10.0.0.10:5060
    listen=udp:MY_IP_ADDR:5060
    listen=tcp:MY_IP_ADDR:5060
    
    #!ifdef WITH_WEBSOCKETS
    listen=MY_WS_ADDR
    #!ifdef WITH_TLS
    listen=MY_WSS_ADDR
    #!endif
    #!endif
    #!ifdef WITH_MSRP
    listen=MY_MSRP_ADDR
    #!endif
    
    /* life time of TCP connection when there is no traffic
     * - a bit higher than registration expires to cope with UA behind NAT */
    tcp_connection_lifetime=3605
    
    /* upper limit for TCP connections (it includes the TLS connections) */
    tcp_max_connections=2048
    
    #!ifdef WITH_JSONRPC
    tcp_accept_no_cl=yes
    #!endif
    
    #!ifdef WITH_TLS
    enable_tls=yes
    
    /* upper limit for TLS connections */
    tls_max_connections=2048
    #!endif
    
    /* set it to yes to enable sctp and load sctp.so module */
    enable_sctp=no
    
    ####### Custom Parameters #########
    
    /* These parameters can be modified runtime via RPC interface
     * - see the documentation of 'cfg_rpc' module.
     *
     * Format: group.id = value 'desc' description
     * Access: $sel(cfg_get.group.id) or @cfg_get.group.id */
    
    #!ifdef WITH_PSTN
    /* PSTN GW Routing
     *
     * - pstn.gw_ip: valid IP or hostname as string value, example:
     * pstn.gw_ip = "10.0.0.101" desc "My PSTN GW Address"
     *
     * - by default is empty to avoid misrouting */
    pstn.gw_ip = "" desc "PSTN GW Address"
    pstn.gw_port = "" desc "PSTN GW Port"
    #!endif
    
    #!ifdef WITH_VOICEMAIL
    /* VoiceMail Routing on offline, busy or no answer
     *
     * - by default Voicemail server IP is empty to avoid misrouting */
    voicemail.srv_ip = "" desc "VoiceMail IP Address"
    voicemail.srv_port = "5060" desc "VoiceMail Port"
    #!endif
    
    ####### Modules Section ########
    
    /* set paths to location of modules */
    # mpath="/usr/lib/x86_64-linux-gnu/kamailio/modules/"
    
    #!ifdef WITH_MYSQL
    loadmodule "db_mysql.so"
    #!endif
    
    #!ifdef WITH_JSONRPC
    loadmodule "xhttp.so"
    #!endif
    loadmodule "jsonrpcs.so"
    loadmodule "kex.so"
    loadmodule "corex.so"
    loadmodule "tm.so"
    loadmodule "tmx.so"
    loadmodule "sl.so"
    loadmodule "rr.so"
    loadmodule "pv.so"
    loadmodule "maxfwd.so"
    loadmodule "usrloc.so"
    loadmodule "registrar.so"
    loadmodule "textops.so"
    loadmodule "textopsx.so"
    loadmodule "siputils.so"
    loadmodule "xlog.so"
    loadmodule "sanity.so"
    loadmodule "ctl.so"
    loadmodule "cfg_rpc.so"
    loadmodule "acc.so"
    loadmodule "counters.so"
    #!ifdef WITH_IMC
    loadmodule "imc.so"
    #!endif
    
    #!ifdef WITH_AUTH
    loadmodule "auth.so"
    loadmodule "auth_db.so"
    #!ifdef WITH_IPAUTH
    loadmodule "permissions.so"
    #!endif
    #!endif
    
    #!ifdef WITH_ALIASDB
    loadmodule "alias_db.so"
    #!endif
    
    #!ifdef WITH_SPEEDDIAL
    loadmodule "speeddial.so"
    #!endif
    
    #!ifdef WITH_MULTIDOMAIN
    loadmodule "domain.so"
    #!endif
    
    #!ifdef WITH_PRESENCE
    loadmodule "presence.so"
    loadmodule "presence_xml.so"
    #!endif
    
    #!ifdef WITH_NAT
    loadmodule "nathelper.so"
    #!ifdef WITH_RTPENGINE
    loadmodule "rtpengine.so"
    #!else
    loadmodule "rtpproxy.so"
    #!endif
    #!endif
    
    #!ifdef WITH_TLS
    loadmodule "tls.so"
    #!endif
    
    #!ifdef WITH_ANTIFLOOD
    loadmodule "htable.so"
    loadmodule "pike.so"
    #!endif
    
    #!ifdef WITH_DEBUG
    loadmodule "debugger.so"
    #!endif
    
    #!ifdef WITH_MSRP
    loadmodule "msrp.so"
    loadmodule "cfgutils.so"
    #!endif
    
    # ----- websocket -----
    #!ifdef WITH_WEBSOCKETS
    loadmodule "websocket.so"
    #!endif
    
    
    # ----------------- setting module-specific parameters ---------------
    
    #!ifdef WITH_IMC
    modparam("imc", "db_url", DBURL)
    #!endif
    
    # ----- jsonrpcs params -----
    modparam("jsonrpcs", "pretty_format", 1)
    /* set the path to RPC fifo control file */
    # modparam("jsonrpcs", "fifo_name", "/run/kamailio/kamailio_rpc.fifo")
    /* set the path to RPC unix socket control file */
    # modparam("jsonrpcs", "dgram_socket", "/run/kamailio/kamailio_rpc.sock")
    #!ifdef WITH_JSONRPC
    modparam("jsonrpcs", "transport", 7)
    #!endif
    
    # ----- ctl params -----
    /* set the path to RPC unix socket control file */
    # modparam("ctl", "binrpc", "unix:/run/kamailio/kamailio_ctl")
    
    # ----- sanity params -----
    modparam("sanity", "autodrop", 0)
    
    # ----- tm params -----
    # auto-discard branches from previous serial forking leg
    modparam("tm", "failure_reply_mode", 3)
    # default retransmission timeout: 30sec
    modparam("tm", "fr_timer", 30000)
    # default invite retransmission timeout after 1xx: 120sec
    modparam("tm", "fr_inv_timer", 120000)
    
    # ----- rr params -----
    # set next param to 1 to add value to ;lr param (helps with some UAs)
    modparam("rr", "enable_full_lr", 0)
    # do not append from tag to the RR (no need for this script)
    modparam("rr", "append_fromtag", 0)
    
    # ----- registrar params -----
    modparam("registrar", "method_filtering", 1)
    /* uncomment the next line to disable parallel forking via location */
    # modparam("registrar", "append_branches", 0)
    /* uncomment the next line not to allow more than 10 contacts per AOR */
    # modparam("registrar", "max_contacts", 10)
    /* max value for expires of registrations */
    modparam("registrar", "max_expires", 3600)
    /* set it to 1 to enable GRUU */
    modparam("registrar", "gruu_enabled", 0)
    /* set it to 0 to disable Path handling */
    modparam("registrar", "use_path", 1)
    /* save Path even if not listed in Supported header */
    modparam("registrar", "path_mode", 0)
    
    # ----- acc params -----
    /* what special events should be accounted ? */
    modparam("acc", "early_media", 0)
    modparam("acc", "report_ack", 0)
    modparam("acc", "report_cancels", 0)
    /* by default ww do not adjust the direct of the sequential requests.
     * if you enable this parameter, be sure the enable "append_fromtag"
     * in "rr" module */
    modparam("acc", "detect_direction", 0)
    /* account triggers (flags) */
    modparam("acc", "log_flag", FLT_ACC)
    modparam("acc", "log_missed_flag", FLT_ACCMISSED)
    modparam("acc", "log_extra",
            "src_user=$fU;src_domain=$fd;src_ip=$si;"
            "dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
    modparam("acc", "failed_transaction_flag", FLT_ACCFAILED)
    /* enhanced DB accounting */
    #!ifdef WITH_ACCDB
    modparam("acc", "db_flag", FLT_ACC)
    modparam("acc", "db_missed_flag", FLT_ACCMISSED)
    modparam("acc", "db_url", DBURL)
    modparam("acc", "db_extra",
            "src_user=$fU;src_domain=$fd;src_ip=$si;"
            "dst_ouser=$tU;dst_user=$rU;dst_domain=$rd")
    #!endif
    
    # ----- usrloc params -----
    modparam("usrloc", "timer_interval", 60)
    modparam("usrloc", "timer_procs", 1)
    modparam("usrloc", "use_domain", MULTIDOMAIN)
    /* enable DB persistency for location entries */
    #!ifdef WITH_USRLOCDB
    modparam("usrloc", "db_url", DBURL)
    modparam("usrloc", "db_mode", 2)
    #!endif
    
    # ----- auth_db params -----
    #!ifdef WITH_AUTH
    modparam("auth_db", "db_url", DBURL)
    modparam("auth_db", "calculate_ha1", yes)
    modparam("auth_db", "password_column", "password")
    modparam("auth_db", "load_credentials", "")
    modparam("auth_db", "use_domain", MULTIDOMAIN)
    
    # ----- permissions params -----
    #!ifdef WITH_IPAUTH
    modparam("permissions", "db_url", DBURL)
    modparam("permissions", "load_backends", 1)
    #!endif
    
    #!endif
    
    # ----- alias_db params -----
    #!ifdef WITH_ALIASDB
    modparam("alias_db", "db_url", DBURL)
    modparam("alias_db", "use_domain", MULTIDOMAIN)
    #!endif
    
    # ----- speeddial params -----
    #!ifdef WITH_SPEEDDIAL
    modparam("speeddial", "db_url", DBURL)
    modparam("speeddial", "use_domain", MULTIDOMAIN)
    #!endif
    
    # ----- domain params -----
    #!ifdef WITH_MULTIDOMAIN
    modparam("domain", "db_url", DBURL)
    /* register callback to match myself condition with domains list */
    modparam("domain", "register_myself", 1)
    #!endif
    
    #!ifdef WITH_PRESENCE
    # ----- presence params -----
    modparam("presence", "db_url", DBURL)
    
    # ----- presence_xml params -----
    modparam("presence_xml", "db_url", DBURL)
    modparam("presence_xml", "force_active", 1)
    #!endif
    
    #!ifdef WITH_NAT
    #!ifdef WITH_RTPENGINE
    # ----- rtpengine params -----
    modparam("rtpengine", "rtpengine_sock", "udp:127.0.0.1:2223")
    modparam("rtpengine", "extra_id_pv", "$avp(extra_id)")
    #!else
    # ----- rtpproxy params -----
    modparam("rtpproxy", "rtpproxy_sock", "udp:127.0.0.1:7722")
    #!endif
    # ----- nathelper params -----
    modparam("nathelper", "natping_interval", 30)
    modparam("nathelper", "ping_nated_only", 1)
    modparam("nathelper", "sipping_bflag", FLB_NATSIPPING)
    modparam("nathelper", "sipping_from", "sip:pinger@kamailio.org")
    
    # params needed for NAT traversal in other modules
    modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
    modparam("usrloc", "nat_bflag", FLB_NATB)
    #!endif
    
    #!ifdef WITH_TLS
    # ----- tls params -----
    modparam("tls", "config", "/etc/kamailio/tls.cfg")
    #!endif
    
    #!ifdef WITH_ANTIFLOOD
    # ----- pike params -----
    modparam("pike", "sampling_time_unit", 2)
    modparam("pike", "reqs_density_per_unit", 16)
    modparam("pike", "remove_latency", 4)
    
    # ----- htable params -----
    /* ip ban htable with autoexpire after 5 minutes */
    modparam("htable", "htable", "ipban=>size=8;autoexpire=300;")
    #!endif
    
    #!ifdef WITH_DEBUG
    # ----- debugger params -----
    modparam("debugger", "cfgtrace", 1)
    modparam("debugger", "log_level_name", "exec")
    #!endif
    
    # ----- websocket -----
    #!ifdef WITH_WEBSOCKETS
    # ----- nathelper params -----
    modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)")
    # Note: leaving NAT pings turned off here as nathelper is _only_ being used for
    #       WebSocket connections.  NAT pings are not needed as WebSockets have
    #       their own keep-alives.
    #!endif
    
    #!ifdef WITH_MSRP
    # ----- htable params -----
    modparam("htable", "htable", "msrp=>size=8;autoexpire=MSRP_MAX_EXPIRES;")
    #!endif
    
    ####### Routing Logic ########
    
    
    /* Main SIP request routing logic
     * - processing of any incoming SIP request starts with this route
     * - note: this is the same as route { ... } */
    request_route {
    
            #!ifdef WITH_IMC
            if(is_method("MESSAGE") && (uri=~"sip:chat-[0-9]+@" || uri=~"sip:chat-manager@")){
            if(imc_manager()){
                            sl_send_reply("200", "ok");
            } else {
                            sl_send_reply("500", "command error");
            }
            exit;
            }
            #!endif
    
            # per request initial checks
            route(REQINIT);
    
            xlog("L_INFO", "START: $rm from $fu (IP:$si:$sp)\n");
            # ----- websocket -----
            #!ifdef WITH_WEBSOCKETS
            if (nat_uac_test(64)) {
                    # Do NAT traversal stuff for requests from a WebSocket
                    # connection - even if it is not behind a NAT!
                    # This won't be needed in the future if Kamailio and the
                    # WebSocket client support Outbound and Path.
                    force_rport();
                    if (is_method("REGISTER")) {
                            fix_nated_register();
                    } else {
                            if (!add_contact_alias()) {
                                    xlog("L_ERR", "Error aliasing contact <$ct>\n");
                                    sl_send_reply("400", "Bad Request");
                                    exit;
                            }
                    }
            }
            #!endif
    
            # NAT detection
            route(NATDETECT);
    
            # CANCEL processing
            if (is_method("CANCEL")) {
                    if (t_check_trans()) {
                            route(RELAY);
                    }
                    exit;
            }
    
            # handle retransmissions
            if (!is_method("ACK")) {
                    if(t_precheck_trans()) {
                            t_check_trans();
                            exit;
                    }
                    t_check_trans();
            }
    
            # handle requests within SIP dialogs
            route(WITHINDLG);
    
            ### only initial requests (no To tag)
    
            # authentication
            route(AUTH);
    
            # record routing for dialog forming requests (in case they are routed)
            # - remove preloaded route headers
            remove_hf("Route");
            if (is_method("INVITE|SUBSCRIBE")) {
                    record_route();
            }
    
            # account only INVITEs
            if (is_method("INVITE")) {
                    setflag(FLT_ACC); # do accounting
            }
    
            # dispatch requests to foreign domains
            route(SIPOUT);
    
            ### requests for my local domains
    
            # handle presence related requests
            route(PRESENCE);
    
            # handle registrations
            route(REGISTRAR);
    
            if ($rU==$null) {
                    # request with no Username in RURI
                    sl_send_reply("484","Address Incomplete");
                    exit;
            }
    
            # dispatch destinations to PSTN
            route(PSTN);
    
            # user location service
            route(LOCATION);
    }
    
    # Wrapper for relaying requests
    route[RELAY] {
    
            # enable additional event routes for forwarded requests
            # - serial forking, RTP relaying handling, a.s.o.
            if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) {
                    if(!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH");
            }
            if (is_method("INVITE|SUBSCRIBE|UPDATE")) {
                    if(!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY");
            }
            if (is_method("INVITE")) {
                    if(!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE");
            }
    
            if (!t_relay()) {
                    sl_reply_error();
            }
            exit;
    }
    
    # Per SIP request initial checks
    route[REQINIT] {
            # no connect for sending replies
            set_reply_no_connect();
            # enforce symmetric signaling
            # - send back replies to the source address of request
            force_rport();
    
            #!ifdef WITH_ANTIFLOOD
            # flood detection from same IP and traffic ban for a while
            # be sure you exclude checking trusted peers, such as pstn gateways
            # - local host excluded (e.g., loop to self)
            if(src_ip!=myself) {
                    if($sht(ipban=>$si)!=$null) {
                            # ip is already blocked
                            xdbg("request from blocked IP - $rm from $fu (IP:$si:$sp)\n");
                            exit;
                    }
                    if (!pike_check_req()) {
                            xlog("L_ALERT","ALERT: pike blocking $rm from $fu (IP:$si:$sp)\n");
                            $sht(ipban=>$si) = 1;
                            exit;
                    }
            }
            #!endif
    
            if($ua =~ "friendly|scanner|sipcli|sipvicious|VaxSIPUserAgent") {
                    # silent drop for scanners - uncomment next line if want to reply
                    # sl_send_reply("200", "OK");
                    exit;
            }
    
            if (!mf_process_maxfwd_header("10")) {
                    sl_send_reply("483","Too Many Hops");
                    exit;
            }
    
            if(is_method("OPTIONS") && uri==myself && $rU==$null) {
                    sl_send_reply("200","Keepalive");
                    exit;
            }
    
            if(!sanity_check("17895", "7")) {
                    xlog("Malformed SIP request from $si:$sp\n");
                    exit;
            }
    }
    
    # Handle requests within SIP dialogs
    route[WITHINDLG] {
            if (!has_totag()) return;
    
            # sequential request withing a dialog should
            # take the path determined by record-routing
            if (loose_route()) {
    
                    #!ifdef WITH_WEBSOCKETS
                    if ($du == "") {
                            if (!handle_ruri_alias()) {
                                    xlog("L_ERR", "Bad alias <$ru>\n");
                                    sl_send_reply("400", "Bad Request");
                                    exit;
                            }
                    }
                    #!endif
    
                    route(DLGURI);
                    if (is_method("BYE")) {
                            setflag(FLT_ACC); # do accounting ...
                            setflag(FLT_ACCFAILED); # ... even if the transaction fails
                    } else if ( is_method("ACK") ) {
                            # ACK is forwarded statelessly
                            route(NATMANAGE);
                    } else if ( is_method("NOTIFY") ) {
                            # Add Record-Route for in-dialog NOTIFY as per RFC 6665.
                            record_route();
                    }
                    route(RELAY);
                    exit;
            }
    
            if (is_method("SUBSCRIBE") && uri == myself) {
                    # in-dialog subscribe requests
                    route(PRESENCE);
                    exit;
            }
            if ( is_method("ACK") ) {
                    if ( t_check_trans() ) {
                            # no loose-route, but stateful ACK;
                            # must be an ACK after a 487
                            # or e.g. 404 from upstream server
                            route(RELAY);
                            exit;
                    } else {
                            # ACK without matching transaction ... ignore and discard
                            exit;
                    }
            }
            sl_send_reply("404","Not here");
            exit;
    }
    
    # Handle SIP registrations
    route[REGISTRAR] {
            if (!is_method("REGISTER")) return;
    
            if(isflagset(FLT_NATS)) {
                    setbflag(FLB_NATB);
                    #!ifdef WITH_NATSIPPING
                    # do SIP NAT pinging
                    setbflag(FLB_NATSIPPING);
                    #!endif
            }
            if (!save("location")) {
                    sl_reply_error();
            }
            exit;
    }
    
    # User location service
    route[LOCATION] {
    
            #!ifdef WITH_SPEEDDIAL
            # search for short dialing - 2-digit extension
            if($rU=~"^[0-9][0-9]$") {
                    if(sd_lookup("speed_dial")) {
                            route(SIPOUT);
                    }
            }
            #!endif
    
            #!ifdef WITH_ALIASDB
            # search in DB-based aliases
            if(alias_db_lookup("dbaliases")) {
                    route(SIPOUT);
            }
            #!endif
    
            $avp(oexten) = $rU;
            if (!lookup("location")) {
                    $var(rc) = $rc;
                    route(TOVOICEMAIL);
                    t_newtran();
                    switch ($var(rc)) {
                            case -1:
                            case -3:
                                    send_reply("404", "Not Found");
                                    exit;
                            case -2:
                                    send_reply("405", "Method Not Allowed");
                                    exit;
                    }
            }
    
            # when routing via usrloc, log the missed calls also
            if (is_method("INVITE")) {
                    setflag(FLT_ACCMISSED);
            }
    
            route(RELAY);
            exit;
    }
    
    # Presence server processing
    route[PRESENCE] {
            if(!is_method("PUBLISH|SUBSCRIBE")) return;
    
            if(is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") {
                    route(TOVOICEMAIL);
                    # returns here if no voicemail server is configured
                    sl_send_reply("404", "No voicemail service");
                    exit;
            }
    
            #!ifdef WITH_PRESENCE
            #!ifdef WITH_MSGREBUILD
            # apply changes in case the request headers or body were modified
            msg_apply_changes();
            #!endif
            if (!t_newtran()) {
                    sl_reply_error();
                    exit;
            }
    
            if(is_method("PUBLISH")) {
                    handle_publish();
                    t_release();
            } else if(is_method("SUBSCRIBE")) {
                    handle_subscribe();
                    t_release();
            }
            exit;
            #!endif
    
            # if presence enabled, this part will not be executed
            if (is_method("PUBLISH") || $rU==$null) {
                    sl_send_reply("404", "Not here");
                    exit;
            }
            return;
    }
    
    # IP authorization and user authentication
    route[AUTH] {
            #!ifdef WITH_AUTH
    
            #!ifdef WITH_IPAUTH
            if((!is_method("REGISTER")) && allow_source_address()) {
                    # source IP allowed
                    return;
            }
            #!endif
    
            if (is_method("REGISTER") || from_uri==myself) {
                    # authenticate requests
                    if (!auth_check("$fd", "subscriber", "1")) {
                            auth_challenge("$fd", "0");
                            exit;
                    }
                    # user authenticated - remove auth header
                    if(!is_method("REGISTER|PUBLISH"))
                            consume_credentials();
            }
            # if caller is not local subscriber, then check if it calls
            # a local destination, otherwise deny, not an open relay here
            if (from_uri!=myself && uri!=myself) {
                    sl_send_reply("403","Not relaying");
                    exit;
            }
    
            #!else
    
            # authentication not enabled - do not relay at all to foreign networks
            if(uri!=myself) {
                    sl_send_reply("403","Not relaying");
                    exit;
            }
    
            #!endif
            return;
    }
    
    # Caller NAT detection
    route[NATDETECT] {
            force_rport();
            #!ifdef WITH_NAT
            if (nat_uac_test("19")) {
                    if (is_method("REGISTER")) {
                            fix_nated_register();
                    } else {
                            if(is_first_hop()) {
                                    set_contact_alias();
                            }
                    }
                    setflag(FLT_NATS);
            }
            #!endif
            return;
    }
    
    # RTPProxy control and signaling updates for NAT traversal
    route[NATMANAGE] {
            #!ifdef WITH_NAT
            if (is_request()) {
                    if(has_totag()) {
                            if(check_route_param("nat=yes")) {
                                    setbflag(FLB_NATB);
                            }
    
                            #!ifdef WITH_RTPENGINE
                            if (check_route_param("rtp=bridge")) {
                                    xlog("L_INFO", "route-param rtp=bridge \n");
                                    setbflag(FLB_BRIDGE);
                            }
    
                            if (check_route_param("rtp=ws")) {
                                    xlog("L_INFO", "route-param rtp=ws \n");
                                    setbflag(FLB_RTPWS);
                            }
                            #!endif
                    }
            }
            #!ifdef WITH_RTPENGINE
    
            #if (!isbflagset(FLB_BRIDGE)) {
            #       xlog("L_INFO", " bridge flag not set , return \n");
            #       return;
            #}
    
            if ( !( isflagset(FLT_NATS) || isbflagset(FLB_NATB) || isbflagset(FLB_RTPWS)) ) {
                    xlog("L_INFO", " neither flag set , NATS , NATB , FLB_RTPWS , return \n");
                    return;
            }
    
            $xavp(r=>$T_branch_idx) = "replace-origin replace-session-connection";
    
            if (!nat_uac_test("8")) {
                    xlog("L_INFO", " trust-address \n ");
                    $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " trust-address";
            }
    
            if (is_request()) {
                    if (!has_totag()) {
                            if (!t_is_failure_route()) {
                                    $avp(extra_id) = @via[1].branch + $T_branch_idx;
                                    $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " via-branch=extra";
                            }
                    }
            }
    
            if (is_reply()) {
                    $avp(extra_id) = @via[2].branch + $T_branch_idx;
                    $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " via-branch=extra";
            }
    
            if (isbflagset(FLB_RTPWS)) {
                    if ($proto =~ "ws") {
                            # webrtc to sip
                            xlog("L_INFO", " webrtc to sip \n ");
                            $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " rtcp-mux-demux DTLS=off SDES-off ICE=remove RTP/AVP";
                    } else {
                            # sip to webrtc
                            $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " rtcp-mux-offer generate-mid DTLS=passive SDES-off ICE=force RTP/SAVPF";
                    }
            } else {
                    if ($proto =~ "ws") {
                            # doesnt need bridging, and protcol is ws , thus webrtc to webrtc call
                            $xavp(r=>$T_branch_idx) = $xavp(r=>$T_branch_idx) + " generate-mid DTLS=passive SDES-off ICE=force";
                    }
            }
    
            xlog("L_INFO", "NATMANAGE branch_id:$T_branch_idx ruri: $ru, method:$rm, status:$rs, extra_id: $avp(extra_id), rtpengine_manage: $xavp(r=>$T_branch_idx)\n");
    
            rtpengine_manage($xavp(r=>$T_branch_idx));
    
            #!else
    
            if (!(isflagset(FLT_NATS) || isbflagset(FLB_NATB))) return;
    
            if(nat_uac_test("8")) {
                    rtpproxy_manage("co");
            } else {
                    rtpproxy_manage("cor");
            }
    
            #!endif
    
            if (is_request()) {
                    if (!has_totag()) {
                            if(t_is_branch_route()) {
                                    add_rr_param(";nat=yes");
                            }
    
                            #!ifdef WITH_RTPENGINE
                            if (isbflagset(FLB_BRIDGE)) {
                                    add_rr_param(";rtp=bridge");
                            }
    
                            if (isbflagset(FLB_RTPWS)) {
                                    add_rr_param(";rtp=ws");
                            }
                            #!endif
                    }
            }
            if (is_reply()) {
                    if(isbflagset(FLB_NATB)) {
                            if(is_first_hop())
                                    set_contact_alias();
                    }
            }
    
            if(isbflagset(FLB_NATB)) {
                    # no connect message in a dialog involving NAT traversal
                    if (is_request()) {
                            if(has_totag()) {
                                    set_forward_no_connect();
                            }
                    }
            }
            #!endif
            return;
    }
    
    #!ifdef WITH_RTPENGINE
    route[BRIDGING] {
            # if traffic is from ws and going to not ws or vice versa , do bridging by setting flag
            if (!has_totag()) {
                    if ($proto =~ "ws" && !($ru =~ "transport=ws")) {
                            # incoming protoocl ws , outgoing protocl is not ws , need bridging
                            setbflag(FLB_RTPWS);
                    } else if (!($proto =~ "ws") && $ru =~ "transport=ws") {
                            # incoming protocl not ws , outgoing protocl ws , need bridging
                            setbflag(FLB_RTPWS);
                    }
            }
    }
    #!endif
    
    # URI update for dialog requests
    route[DLGURI] {
            #!ifdef WITH_NAT
            if(!isdsturiset()) {
                    handle_ruri_alias();
            }
            #!endif
            return;
    }
    
    # Routing to foreign domains
    route[SIPOUT] {
            if (uri==myself) return;
    
            append_hf("P-Hint: outbound\r\n");
            route(RELAY);
            exit;
    }
    
    # PSTN GW routing
    route[PSTN] {
            #!ifdef WITH_PSTN
            # check if PSTN GW IP is defined
            if (strempty($sel(cfg_get.pstn.gw_ip))) {
                    xlog("SCRIPT: PSTN routing enabled but pstn.gw_ip not defined\n");
                    return;
            }
    
            # route to PSTN dialed numbers starting with '+' or '00'
            #     (international format)
            # - update the condition to match your dialing rules for PSTN routing
            if(!($rU=~"^(\+|00)[1-9][0-9]{3,20}$")) return;
    
            # only local users allowed to call
            if(from_uri!=myself) {
                    sl_send_reply("403", "Not Allowed");
                    exit;
            }
    
            # normalize target number for pstn gateway
            # - convert leading 00 to +
            if (starts_with("$rU", "00")) {
                    strip(2);
                    prefix("+");
            }
    
            if (strempty($sel(cfg_get.pstn.gw_port))) {
                    $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip);
            } else {
                    $ru = "sip:" + $rU + "@" + $sel(cfg_get.pstn.gw_ip) + ":"
                                            + $sel(cfg_get.pstn.gw_port);
            }
    
            route(RELAY);
            exit;
            #!endif
    
            return;
    }
    
    # Routing to voicemail server
    route[TOVOICEMAIL] {
    #!ifdef WITH_VOICEMAIL
            if(!is_method("INVITE|SUBSCRIBE")) return;
    
            # check if VoiceMail server IP is defined
            if (strempty($sel(cfg_get.voicemail.srv_ip))) {
                    xlog("SCRIPT: VoiceMail routing enabled but IP not defined\n");
                    return;
            }
            if(is_method("INVITE")) {
                    if($avp(oexten)==$null) return;
    
                    $ru = "sip:" + $avp(oexten) + "@" + $sel(cfg_get.voicemail.srv_ip)
                                    + ":" + $sel(cfg_get.voicemail.srv_port);
            } else {
                    if($rU==$null) return;
    
                    $ru = "sip:" + $rU + "@" + $sel(cfg_get.voicemail.srv_ip)
                                    + ":" + $sel(cfg_get.voicemail.srv_port);
            }
            route(RELAY);
            exit;
    #!endif
    
            return;
    }
    
    # Manage outgoing branches
    branch_route[MANAGE_BRANCH] {
    
            xlog("L_INFO", "MANAGE_BRANCH: New branch [$T_branch_idx] to $ru\n");
            #!ifdef WITH_RTPENGINE
            t_on_branch_failure("rtpengine");
            route(BRIDGING);
            #!endif
    
            route(NATMANAGE);
    }
    
    # Manage incoming replies
    reply_route {
            if(!sanity_check("17604", "6")) {
                    xlog("Malformed SIP response from $si:$sp\n");
                    drop;
            }
    }
    
    # Manage incoming replies in transaction context
    onreply_route[MANAGE_REPLY] {
            xdbg("incoming reply\n");
            if(status=~"[12][0-9][0-9]") {
                    route(NATMANAGE);
            }
    }
    
    # Manage failure routing cases
    failure_route[MANAGE_FAILURE] {
            route(NATMANAGE);
    
            if (t_is_canceled()) exit;
    
    
            #!ifdef WITH_BLOCK3XX
            # block call redirect based on 3xx replies.
            if (t_check_status("3[0-9][0-9]")) {
                    t_reply("404","Not found");
                    exit;
            }
            #!endif
    
            #!ifdef WITH_BLOCK401407
            # block call redirect based on 401, 407 replies.
            if (t_check_status("401|407")) {
                    t_reply("404","Not found");
                    exit;
            }
            #!endif
    
            #!ifdef WITH_VOICEMAIL
            # serial forking
            # - route to voicemail on busy or no answer (timeout)
            if (t_check_status("486|408")) {
                    $du = $null;
                    route(TOVOICEMAIL);
                    exit;
            }
            #!endif
    }
    
    # ----- websocket -----
    
    # JSONRPC over HTTP(S) routing
    #!ifdef WITH_JSONRPC
    event_route[xhttp:request] {
            set_reply_close();
            set_reply_no_connect();
    
            #!ifdef WITH_WEBSOCKETS
            if ($Rp != MY_WS_PORT
            #!ifdef WITH_TLS
                            && $Rp != MY_WSS_PORT
            #!endif
            ){
    
                    xlog("L_WARN", "HTTP request received on $Rp\n");
                    xhttp_reply("403", "Forbidden", "", "");
                    exit;
    
            }
    
            xlog("L_DBG", "HTTP Request Received\n");
    
            if ($hdr(Upgrade)=~"websocket"
                    && $hdr(Connection)=~"Upgrade"
                    && $rm=~"GET") {
    
                    # Validate Host - make sure the client is using the correct
                    # alias for WebSockets
                    if ($hdr(Host) == $null || !is_myself("sip:" + $hdr(Host))) {
                            xlog("L_WARN", "Bad host $hdr(Host)\n");
                            xhttp_reply("403", "Forbidden", "", "");
                            exit;
                    }
    
                    # Optional... validate Origin - make sure the client is from an
                    # authorised website.  For example,
                    #
                    # if ($hdr(Origin) != "http://communicator.MY_DOMAIN"
                    #     && $hdr(Origin) != "https://communicator.MY_DOMAIN") {
                    #       xlog("L_WARN", "Unauthorised client $hdr(Origin)\n");
                    #       xhttp_reply("403", "Forbidden", "", "");
                    #       exit;
                    # }
    
                    # Optional... perform HTTP authentication
    
                    # ws_handle_handshake() exits (no further configuration file
                    # processing of the request) when complete.
                    if (ws_handle_handshake())
                    {
                            # Optional... cache some information about the
                            # successful connection
                            exit;
                    }
            }
    
            xhttp_reply("404", "Not Found", "", "");
            exit;
    
            #!endif
    
            if(src_ip!=127.0.0.1) {
                    xhttp_reply("403", "Forbidden", "text/html",
                                    "<html><body>Not allowed from $si</body></html>");
                    exit;
            }
            if ($hu =~ "^/RPC") {
                    jsonrpc_dispatch();
                    exit;
            }
    
            xhttp_reply("200", "OK", "text/html",
                                    "<html><body>Wrong URL $hu</body></html>");
        exit;
    }
    #!endif
    
    #!ifdef WITH_WEBSOCKETS
    onreply_route {
            if (($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)&& !(proto == WS || proto == WSS)) {
                    xlog("L_WARN", "SIP response received on $Rp\n");
                    drop;
            }
    
            if (nat_uac_test(64)) {
                    # Do NAT traversal stuff for replies to a WebSocket connection
                    # - even if it is not behind a NAT!
                    # This won't be needed in the future if Kamailio and the
                    # WebSocket client support Outbound and Path.
                    add_contact_alias();
            }
    }
    
    event_route[websocket:closed] {
            xlog("L_INFO", "WebSocket connection from $si:$sp has closed\n");
    }
    #!endif
    
    #!ifdef WITH_RTPENGINE
    event_route[tm:branch-failure:rtpengine] {
            xlog("L_INFO", "BRANCH FAILED: $sel(via[1].branch) + $T_branch_idx");
            $avp(extra_id) = @via[1].branch + $T_branch_idx;
            rtpengine_delete("via-branch=extra");
    }
    #!endif
    
    #!ifdef WITH_MSRP
    event_route[msrp:frame-in] {
            msrp_reply_flags("1");
    
            if ((($Rp == MY_WS_PORT || $Rp == MY_WSS_PORT)
                    && !(proto == WS || proto == WSS)) && $Rp != MY_MSRP_PORT) {
                    xlog("L_WARN", "MSRP request received on $Rp\n");
                    msrp_reply("403", "Action-not-allowed");
                    exit;
            }
    
            if (msrp_is_reply()) {
                    msrp_relay();
            } else if($msrp(method)=="AUTH") {
                    if($msrp(nexthops)>0) {
                            msrp_relay();
                            exit;
                    }
    
                    if (!www_authenticate("MY_DOMAIN", "subscriber",
                                            "$msrp(method)")) {
                            if (auth_get_www_authenticate("MY_DOMAIN", "1",
                                                            "$var(wauth)")) {
                                    msrp_reply("401", "Unauthorized",
                                                            "$var(wauth)");
                            } else {
                                    msrp_reply("500", "Server Error");
                            }
                            exit;
                    }
    
                    if ($hdr(Expires) != $null) {
                            $var(expires) = (int) $hdr(Expires);
                            if ($var(expires) < MSRP_MIN_EXPIRES) {
                                    msrp_reply("423", "Interval Out-of-Bounds",
                                            "Min-Expires: MSRP_MIN_EXPIRES\r\n");
                                    exit;
                            } else if ($var(expires) > MSRP_MAX_EXPIRES) {
                                    msrp_reply("423", "Interval Out-of-Bounds",
                                            "Max-Expires: MSRP_MAX_EXPIRES\r\n");
                                    exit;
                            }
                    } else {
                            $var(expires) = MSRP_MAX_EXPIRES;
                    }
    
                    $var(cnt) = $var(cnt) + 1;
                    pv_printf("$var(sessid)", "s.$(pp).$(var(cnt)).$(RANDOM)");
                    $sht(msrp=>$var(sessid)::srcaddr) = $msrp(srcaddr);
                    $sht(msrp=>$var(sessid)::srcsock) = $msrp(srcsock);
                    $shtex(msrp=>$var(sessid)) = $var(expires) + 5;
                    # - Use-Path: the MSRP address for server + session id
                    $var(hdrs) = "Use-Path: msrps://MY_IP_ADDR:MY_MSRP_PORT/"
                                            + $var(sessid) + ";tcp\r\n"
                                            + "Expires: " + $var(expires) + "\r\n";
                    msrp_reply("200", "OK", "$var(hdrs)");
            } else if ($msrp(method)=="SEND" || $msrp(method)=="REPORT") {
                    if ($msrp(nexthops)>1) {
                            if ($msrp(method)!="REPORT") {
                                    msrp_reply("200", "OK");
                            }
                            msrp_relay();
                            exit;
                    }
                    $var(sessid) = $msrp(sessid);
                    if ($sht(msrp=>$var(sessid)::srcaddr) == $null) {
                            # one more hop, but we don't have address in htable
                            msrp_reply("481", "Session-does-not-exist");
                            exit;
                    } else if ($msrp(method)!="REPORT") {
                            msrp_reply("200", "OK");
                    }
                    msrp_relay_flags("1");
                    msrp_set_dst("$sht(msrp=>$var(sessid)::srcaddr)",
                                    "$sht(msrp=>$var(sessid)::srcsock)");
                    msrp_relay();
            } else {
                    msrp_reply("501", "Request-method-not-understood");
            }
    }
    #!endif
    

    保存好配置文件,接下来就是启动服务了。

    #创建数据库
    kamdbctl create
    # 创建用户 1001 为登录用户名 123456 为sip登录密码
    kamctl add 1001 123456
    # 也可以使用下面的 sh脚本批量创建用户
    vim create_user.sh
    # 复制以下代码保存到文件
    #!/bin/bash
    for((i=1;i<=2000;i++));
    do
    kamctl add $((1000+$i)) 'admin888..'
    done
    chmod +x create_user.sh
    sh ./create_user.sh
    
    systemctl start kamailio.service # 没有报错说明你成功了
    systemctl status kamailio.service  #查看服务状态
    # 开启 kamailio debug 日志
    vim /etc/rsyslog.conf
    # 在该文件的结尾添加如下代码
    local0.* -/var/log/kamailio.log
    systemctl restart rsyslog #重启日志服务
    tail -f /var/log/kamailio.log #实时输出日志
    

    客户端的使用我在文末面贴上代码

    完成上面这些步骤,基本上就可以使用 jssip、dart_sip_ua、flutter sip_ua 等工具连接服务器了
    但是这并没有完成,因为音视频通话你会发现 本地同一个WIFI局域网通话没有任何问题,一但不在一个局域网就会没有声音和视频,这就涉及到公网NAT转发的问题,大致意思就是 A设备注册的地址是192.168.1.2、B设备注册的地址是192.168.1.3,这两个地址都是手机的内网地址,他们不在一个内网是无法通讯的,就需要Rtpengine 或 RTPProxy作为中间件把这两个地址的网络打通,由于 RTPProxy 对websocket支持貌似不友好,所以这里我就把它抛弃了。

    安装 Rtpengine

    vim /etc/apt/sources.list
    #在结尾处添加如下一行
    # sipwise
    deb https://dfx.at/rtpengine/10.2/ stable main
    sudo apt update
    :wq #保存
    
    wget https://dfx.at/rtpengine/latest/pool/main/r/rtpengine-dfx-repo-keyring/rtpengine-dfx-repo-keyring_1.0_all.deb
    sudo dpkg -i rtpengine-dfx-repo-keyring_1.0_all.deb
    sudo apt-key add /usr//share/keyrings/dfx.at-rtpengine-archive-keyring.gpg
    sudo apt update
    sudo apt install rtpengine
    

    至此 rtpengine 安装完成
    启动服务

    vim /etc/rtpengine/rtpengine.sample.conf
    [rtpengine]
    table = -1
    # no-fallback = false
    ### for userspace forwarding only:
    # table = -1
    
    ### a single interface:
    # interface = 123.234.345.456
    ### separate multiple interfaces with semicolons:
    # interface = internal/12.23.34.45;external/23.34.45.54
    ### for different advertised address:
    interface = 这里换成你的公网IP
    
    listen-ng = 127.0.0.1:2223
    #listen-tcp = 8880
    #listen-udp = 5060
    
    ### interface for HTTP, WS and Prometheus
    # listen-http = 9101
    
    timeout = 60
    silent-timeout = 3600
    tos = 184
    #control-tos = 184
    # delete-delay = 30
    # final-timeout = 10800
    
    # foreground = false
    # pidfile = /run/ngcp-rtpengine-daemon.pid
    # num-threads = 16
    
    port-min = 30000
    port-max = 60000
    # max-sessions = 5000
    
    # recording-dir = /var/spool/rtpengine
    # recording-method = proc
    # recording-format = raw
    
    # redis = 127.0.0.1:6379/5
    # redis-write = password@12.23.34.45:6379/42
    # redis-num-threads = 8
    # no-redis-required = false
    # redis-expires = 86400
    # redis-allowed-errors = -1
    # redis-disable-time = 10
    # redis-cmd-timeout = 0
    # redis-connect-timeout = 1000
    
    b2b-url = http://127.0.0.1:8090/
    # xmlrpc-format = 0
    
    # 6 为 debug 具体请参照官网
    log-level = 6
    # log-stderr = false
    # 系统日志匹配前缀
    log-facility = local5
    # log-facility-cdr = local0
    # log-facility-rtcp = local1
    
    # graphite = 127.0.0.1:9006
    # graphite-interval = 60
    # graphite-prefix = foobar.
    
    # homer = 123.234.345.456:65432
    # homer-protocol = udp
    # homer-id = 2001
    
    # sip-source = false
    # dtls-passive = false
    
    [rtpengine-testing]
    table = -1
    interface = 这里换成你的公网IP
    listen-ng = 2223
    foreground = true
    log-stderr = true
    log-level = 7
    
    # 配置日志
    vim /etc/rsyslog.conf
    # 在该文件的结尾添加如下代码
    local5.* -/var/log/rtpengine.log
    systemctl restart rsyslog #重启日志服务
    tail -f /var/log/rtpengine.log #实时输出日志
    
    rtpengine --config-file=/etc/rtpengine/rtpengine.sample.conf
    # 配置好后,需要重启一下  kamailio 
    systemctl restart kamailio.service
    

    至此 rtpengine 配置完成,可以进行公网视频通话了,有声音也有视频。

    jssip客户端的使用

    这个使用的方法就比较多了,可以使用npm安装和单独使用,这里就不一一演示了,可以直接看官方文档

    npm install jssip --save
    #使用文档请看 https://www.npmjs.com/package/jssip
    

    flutter ua_sip 的使用

    # 运行命令
    flutter pub add sip_ua
    # 创建文件
    sip_util.dart
    
    # 以下是 sip_util.dart 代码
    import 'package:flutter/material.dart';
    import 'package:sip_ua/sip_ua.dart';
    import 'package:flutter_webrtc/flutter_webrtc.dart';
    import 'package:audioplayers/audioplayers.dart' as audio;
    
    class SipUtil implements SipUaHelperListener{
    
      final SIPUAHelper sipHelper = SIPUAHelper();
      bool isRegister = false;
      //是否正在通话
      bool isInCall = false;
      String serverIp = "xxx";
      String wsPort = "8880";
    
      Map saveUserData = {};
      Map suUserData = {};
      Map ruUserData = {};
    
      bool isVideo = false;
      int callMsgId = 0;
    
      MediaStream? mediaStream;
    
      audio.AudioPlayer? audioPlayer;
    
      void init(var userData){
        saveUserData = userData;
        if(isRegister) return;
        sipHelper.addSipUaHelperListener(this);
        UaSettings settings = UaSettings();
    
        settings.webSocketUrl = 'ws://$serverIp:$wsPort';
        settings.webSocketSettings.extraHeaders = {};
        settings.webSocketSettings.allowBadCertificate = true;
    
        settings.uri = "${userData['sip_user']}@$serverIp";
        settings.authorizationUser = "${userData['sip_user']}";
        settings.password = "${userData['sip_passwd']}";
        settings.displayName = "${userData['nickname']}";
        settings.userAgent = 'Dart SIP Client v1.0.0';
        settings.dtmfMode = DtmfMode.RFC2833;
    
        sipHelper.start(settings);
      }
    
      dynamic callSip(suUser,ruUser,{BuildContext? context,bool toIsVideo = false}) async{
    
        //通话中禁止新开
        if(sipUtil.isInCall){
          print("通话中禁止新开");
          return;
        }
    
        if(sipUtil.mediaStream!=null){
          sipUtil.mediaStream!.dispose();
          sipUtil.mediaStream = null;
        }
    
        if(!isRegister){
          sipUtil.init(saveUserData);
          while(!sipUtil.isRegister){
            await Future.delayed(const Duration(milliseconds: 1500));
          }
          util.hideLoading();
        }
    
        sipUtil.isVideo = toIsVideo;
    
        try{
          final mediaConstraints = <String, dynamic>{'audio': true, 'video': sipUtil.isVideo};
          MediaStream mediaStream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
          bool result = await sipHelper.call(
              ruUser['sip_user'], voiceonly: !sipUtil.isVideo,mediaStream: mediaStream
          );
          //拨打失败
          if(!result){
            return print("拨打失败,未知原因");
          }
        }catch(e){
          print(e.toString());
          return;
        }
    
        sipUtil.suUserData = suUser;
        sipUtil.ruUserData = ruUser;
      }
    
      @override
      void callStateChanged(Call call, CallState state) async{
       print("触发事件:${EnumHelper.getName(state.state)}");
    
        //这里第一次通知事件传不到通话视图,所以需要储存一下
        if(state.state==CallStateEnum.STREAM&&state.originator=="local"){
          mediaStream = state.stream;
          Logger.w("流事件类型:${state.originator}");
        }
    
        //处理音乐,这里的音乐自己添加
        if(call.direction=="INCOMING"&&state.state==CallStateEnum.PROGRESS){
          audioPlayer = await util.audioPlayAssetsToLocal("assets/mp3/call_inc.mp3");
        }
        if(call.direction!="INCOMING"&&state.state==CallStateEnum.PROGRESS){
          audioPlayer = await util.audioPlayAssetsToLocal("assets/mp3/call_wait.mp3");
        }
    
        if(audioPlayer!=null&&state.state==CallStateEnum.ACCEPTED){
          audioPlayer!.stop();
        }
    
        if(state.state==CallStateEnum.FAILED||state.state==CallStateEnum.ENDED){
    
          sipUtil.isInCall = false;
          if(sipUtil.mediaStream!=null){
            sipUtil.mediaStream!.dispose();
            sipUtil.mediaStream = null;
          }
    
          if(audioPlayer!=null){
            audioPlayer!.stop();
          }
          //播放音乐,这里的音乐自己添加到 assets
          util.audioPlayAssetsToLocal("assets/mp3/call_end.mp3");
        }
    
        //唤起视频界面 打入 打出 都是 CALL_INITIATION
        if (state.state == CallStateEnum.CALL_INITIATION) {
    
          print("收到电话呼叫:is back ${publicValUtil.isBackstage}");
          if(publicValUtil.isBackstage){}
          BuildContext context = routeUtil.navigatorKey.currentContext!;
    
          //INCOMING 为对方打进来
          if(call.direction=="INCOMING"){
    
            //通话中禁止呼叫
            if(sipUtil.isInCall){
              call.hangup();return;
            }
    
            sipUtil.isInCall = true;
            //对方打过来
            var result = await apiUtil.apiChatContactsDialogGetSipUser(
                data: {"sip_user":"${call.remote_identity}"});
    
            Map userData;
            if(result['code'] != 200){
              userData = {};
            }else{
              userData = result['data']['user'];
            }
    
            bool isVideo = call.remote_has_video;
            //这里可以放一些唤起通话界面的逻辑
            //sheetsUtil.showCallScreenModalSheet(context, userData,call,isIncoming: true,isVideo: isVideo);
          }else{
            sipUtil.isInCall = true;
            //这里可以放一些唤起通话界面的逻辑
            //sheetsUtil.showCallScreenModalSheet(context, sipUtil.ruUserData, call,isVideo:sipUtil.isVideo);
          }
        }
      }
    
      @override
      void onNewMessage(SIPMessageRequest msg) {
        // TODO: implement onNewMessage
      }
    
      @override
      void registrationStateChanged(RegistrationState state) {
        print("收到注册服务:${EnumHelper.getName(state.state)}");
        if(state.state!=RegistrationStateEnum.REGISTERED){
          isRegister = false;
        }else{
          isRegister = true;
        }
      }
    
      @override
      void transportStateChanged(TransportState state) {
        // TODO: implement transportStateChanged
      }
    }
    
    //初始化视频
    sipUtil.init({"sip_user":"xxx","sip_passwd":"xxx","nickname":"xxx"});
    

    相关文章

      网友评论

        本文标题:WebRTC+Kamamilio+rtpengine+Webso

        本文链接:https://www.haomeiwen.com/subject/zcjoyrtx.html