diff --git a/.gitignore b/.gitignore index b4b627791..e31c8e4d3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dean* data/vagrant/.vagrant data/vagrant/vagrant_ansible_inventory_default data/tutorials/fs_evsock/freeswitch/etc/freeswitch/ +data/tutorials/sip_redirect/freeswitch/etc/freeswitch/ data/tutorial_tests/fs_evsock/freeswitch/etc/freeswitch/ vendor *.test diff --git a/agents/sipagent.go b/agents/sipagent.go index df8b720be..68507ecea 100644 --- a/agents/sipagent.go +++ b/agents/sipagent.go @@ -119,6 +119,9 @@ func (sa *SIPAgent) serveUDP(stop chan struct{}) (err error) { wg.Done() return } + if "ACK" == sipMessage.MethodFrom("Request") { + return + } var sipAnswer sipingo.Message var err error if sipAnswer, err = sa.handleMessage(sipMessage, saddr.String()); err != nil { @@ -204,6 +207,9 @@ func (sa *SIPAgent) serveTCP(stop chan struct{}) (err error) { wg.Done() continue } + if "ACK" == sipMessage.MethodFrom("Request") { + continue + } var sipAnswer sipingo.Message if sipAnswer, err = sa.handleMessage(sipMessage, conn.LocalAddr().String()); err != nil { continue diff --git a/data/tariffplans/sipagent/AccountActions.csv b/data/tariffplans/sipagent/AccountActions.csv index 44fa1907f..68e8a5ea1 100644 --- a/data/tariffplans/sipagent/AccountActions.csv +++ b/data/tariffplans/sipagent/AccountActions.csv @@ -1,2 +1,3 @@ #Tenant,Account,ActionPlanId,ActionTriggersId,AllowNegative,Disabled -cgrates.org,1001,AP_PACKAGE_10,,, \ No newline at end of file +cgrates.org,1001,AP_PACKAGE_10,,, +cgrates.org,1002,AP_PACKAGE_10,,, \ No newline at end of file diff --git a/data/tariffplans/sipagent/Chargers.csv b/data/tariffplans/sipagent/Chargers.csv new file mode 100644 index 000000000..0d85563cc --- /dev/null +++ b/data/tariffplans/sipagent/Chargers.csv @@ -0,0 +1,5 @@ +#Tenant,ID,FilterIDs,ActivationInterval,RunID,AttributeIDs,Weight +cgrates.org,DEFAULT,,,*default,*none,0 +cgrates.org,route1,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1003,0 +cgrates.org,route2,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1004,0 +cgrates.org,route3,,,*raw,*constant:*req.RequestType:*none;*constant:*req.Destination:1005,0 \ No newline at end of file diff --git a/data/tariffplans/sipagent/Routes.csv b/data/tariffplans/sipagent/Routes.csv index d3d0d4197..f1c02a8a9 100644 --- a/data/tariffplans/sipagent/Routes.csv +++ b/data/tariffplans/sipagent/Routes.csv @@ -1,5 +1,7 @@ #Tenant,ID,FilterIDs,ActivationInterval,Sorting,SortingParameters,RouteID,RouteFilterIDs,RouteAccountIDs,RouteRatingPlanIDs,RouteResourceIDs,RouteStatIDs,RouteWeight,RouteBlocker,RouteParameters,Weight -cgrates.org,SPL_ACNT_1001,*string:~*req.Account:1001,2017-11-27T00:00:00Z,*weight,,supplier1,,1001,RP_10CNT,,,20,,cgrates.org,20 -cgrates.org,SPL_ACNT_1001,,,,,supplier2,,1001,RP_20CNT,,,10,,cgrates.net,10 -cgrates.org,SPL_ACNT_1001,,,,,supplier3,,1001,RP_1CNT,,,5,,cgrates.com,5 - +cgrates.org,SPL_ACNT_1001,*string:~*req.Account:1001,2017-11-27T00:00:00Z,*weight,,route1,,1001,RP_10CNT,,,20,,cgrates.org,20 +cgrates.org,SPL_ACNT_1001,,,,,route2,,1001,RP_20CNT,,,10,,cgrates.net,10 +cgrates.org,SPL_ACNT_1001,,,,,route3,,1001,RP_1CNT,,,5,,cgrates.com,5 +cgrates.org,SPL_ACNT_1002,*string:~*req.Account:1002,2017-11-27T00:00:00Z,*weight,,route1,,1002,RP_10CNT,,,20,,1003@192.168.56.203,20 +cgrates.org,SPL_ACNT_1002,,,,,route2,,1002,RP_20CNT,,,10,,1004@192.168.57.203,10 +cgrates.org,SPL_ACNT_1002,,,,,route3,,1002,RP_1CNT,,,5,,1005@192.168.58.203,5 diff --git a/data/tutorials/sip_redirect/README.md b/data/tutorials/sip_redirect/README.md new file mode 100644 index 000000000..e25ec1539 --- /dev/null +++ b/data/tutorials/sip_redirect/README.md @@ -0,0 +1,18 @@ +Tutorial SIP Redirect +================ + +Scenario: +--------- + +- FreeSWITCH with minimal custom configuration. + + - Added following users (with configs in *etc/freeswitch/directory/default*): 1001-prepaid, 1002-postpaid, 1003-pseudoprepaid, 1004-rated, 1006-prepaid, 1007-prepaid. + - Have added inside default dialplan a redirect for the destinatin 1001 to the CGRateS SIPAgent that will populate the Contact header and add X-Identity header + +- **CGRateS** with following components: + + - RALs to calculate the maxusage + - RouteS to get the rounting information + - ChargerS to change the destination for each route + - SIPAgent to comunicate with FreeSWITCH + - SessionS to comunicate with the agent diff --git a/data/tutorials/sip_redirect/cgrates/etc/cgrates/cgrates.json b/data/tutorials/sip_redirect/cgrates/etc/cgrates/cgrates.json new file mode 100644 index 000000000..0e44d990c --- /dev/null +++ b/data/tutorials/sip_redirect/cgrates/etc/cgrates/cgrates.json @@ -0,0 +1,105 @@ +{ + +// Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments +// Copyright (C) ITsysCOM GmbH + +"general": { + "log_level": 7, + "node_id":"CGRSIP", +}, + + +"listen": { + "rpc_json": ":2012", + "rpc_gob": ":2013", + "http": ":2080", +}, + + +"stor_db": { + "db_password": "CGRateS.org", +}, + + +"schedulers": { + "enabled": true, +}, + + +"rals": { + "enabled": true, + "thresholds_conns": ["*localhost"], + "stats_conns": ["*localhost"], +}, + + +"cdrs": { + "enabled": true, + "stats_conns": ["*localhost"], + "chargers_conns": ["*internal"], + "rals_conns": ["*localhost"], + "sessions_cost_retries": 5, +}, + + +"chargers": { + "enabled": true, + "attributes_conns": ["*internal"], +}, + + +"sessions": { + "enabled": true, + "rals_conns": ["*localhost"], + "cdrs_conns": ["*localhost"], + "resources_conns": ["*localhost"], + "routes_conns": ["*localhost"], + "attributes_conns": ["*localhost"], + "stats_conns": ["*localhost"], + "thresholds_conns": ["*localhost"], + "chargers_conns": ["*internal"], + "debit_interval": "5s", + "channel_sync_interval":"7s", +}, + + +"attributes": { + "enabled": true, + "string_indexed_fields": ["Account"], +}, + + +"resources": { + "enabled": true, + "string_indexed_fields": ["Account"], +}, + + +"stats": { + "enabled": true, + "string_indexed_fields": ["Account","RunID","Destination"], +}, + + +"thresholds": { + "enabled": true, + "string_indexed_fields": ["Account"], +}, + + +"routes": { + "enabled": true, + "resources_conns": ["*internal"], + "stats_conns": ["*internal"], + "rals_conns": ["*internal"], + "string_indexed_fields": ["Account"], +}, + + +"apiers": { + "enabled": true, + "scheduler_conns": ["*internal"], +}, + + +} diff --git a/data/tutorials/sip_redirect/cgrates/etc/cgrates/redirect.json b/data/tutorials/sip_redirect/cgrates/etc/cgrates/redirect.json new file mode 100644 index 000000000..0a6d1c075 --- /dev/null +++ b/data/tutorials/sip_redirect/cgrates/etc/cgrates/redirect.json @@ -0,0 +1,141 @@ +{ + +"sip_agent": { + "enabled": true, // enables the SIP agent: + "listen": "192.168.56.203:6060", // address where to listen for SIP requests + "listen_net": "udp", // network to listen on + "request_processors": [ + { + "id": "Register", + "filters": ["*notstring:~*vars.Method:INVITE"], + "flags": ["*none"], + "request_fields":[], + "reply_fields":[ + {"tag": "Request", "path": "*rep.Request", "type": "*constant", + "value": "SIP/2.0 405 Method Not Allowed"} + ] + }, + { + "id": "RoutesQuery", + "filters": ["*string:~*vars.Method:INVITE"], + "flags": ["*event", "*routes","*stir_initiate","*continue"], + "request_fields":[ + {"tag": "Account", "path": "*cgreq.Account", "type": "*variable", + "value": "~*req.From{*sipuri_user}", "mandatory": true}, + {"tag": "Destination", "path": "*cgreq.Destination", "type": "*variable", + "value": "~*req.To{*sipuri_user}", "mandatory": true}, + {"tag": "SetupTime", "path": "*cgreq.SetupTime", "type": "*variable", + "value": "*now", "mandatory": true}, + {"tag": "Category", "path": "*cgreq.Category", "type": "*variable", + "value": "call", "mandatory": true}, + {"tag": "STIRPublicKeyPath", "path": "*opts.STIRPublicKeyPath", "type": "*constant", + "value": "/usr/share/cgrates/stir/stir_pubkey.pem", "mandatory": true}, + {"tag": "STIRPrivateKeyPath", "path": "*opts.STIRPrivateKeyPath", "type": "*constant", + "value": "/usr/share/cgrates/stir/stir_privatekey.pem", "mandatory": true}, + ], + "reply_fields":[ + ] + }, + { + "id": "RoutesQueryOneRoute", + "filters": ["*string:~*vars.Method:INVITE", + "*gte:~*cgrep.Routes.Count:1", + ], + "flags": ["*none","*continue"], // do not send request to CGRateS + "reply_fields":[ + {"tag": "Request", "path": "*rep.Request", "type": "*constant", + "value": "SIP/2.0 302 Moved Temporarily"}, + {"tag": "Contact", "path": "*rep.Contact", "type": "*composed", + "value":"\"1003\" "}, + + {"tag": "X-Identity", "path": "*rep.X-Identity", "type": "*variable", + "value":"~*cgrep.STIRIdentity[~*cgrep.Routes.SortedRoutes[0].RouteID]"}, + ] + }, + + { + "id": "RoutesQueryTwoRoute", + "filters": ["*string:~*vars.Method:INVITE", + "*gte:~*cgrep.Routes.Count:2", + ], + "flags": ["*none","*continue"], // do not send request to CGRateS + "reply_fields":[ + {"tag": "Contact", "path": "*rep.Contact", "type": "*composed", + "value":",\"1004\" "}, + ] + }, + { + "id": "RoutesQueryThreeRoute", + "filters": ["*string:~*vars.Method:INVITE", + "*gte:~*cgrep.Routes.Count:2", + ], + "flags": ["*none","*continue"], // do not send request to CGRateS + "reply_fields":[ + {"tag": "Contact", "path": "*rep.Contact", "type": "*composed", + "value":",\"1005\" "}, + ] + }, + ] + } + +} diff --git a/data/tutorials/sip_redirect/cgrates/etc/init.d/cgrates b/data/tutorials/sip_redirect/cgrates/etc/init.d/cgrates new file mode 100755 index 000000000..38a63f5ad --- /dev/null +++ b/data/tutorials/sip_redirect/cgrates/etc/init.d/cgrates @@ -0,0 +1,184 @@ +#! /bin/sh +### BEGIN INIT INFO +# Provides: cgrates +# Required-Start: $remote_fs $syslog +# Required-Stop: $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: CGRateS real-time charging system +# Description: Control CGRateS - carrier grade real-time charging system +### END INIT INFO + +# Author: DanB +# +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="CGRateS real-time charging system" +NAME=cgrates +DAEMON=/usr/bin/cgr-engine +USER=cgrates +GROUP=cgrates +DAEMON_OPTS="" +TUTFOLDER=/usr/share/cgrates/tutorials/sip_redirect/cgrates +TMP_DIR=/tmp/cgr_fsevsock/cgrates +SCRIPTNAME=$TUTFOLDER/etc/init.d/$NAME +RUNDIR=$TMP_DIR/run +PIDFILE=$RUNDIR/cgr-engine.pid +STACKTRACE=$RUNDIR/$NAME.strace +ENABLE=true +DAEMON_OPTS="-config_path=$TUTFOLDER/etc/cgrates" +CDREDIR=$TMP_DIR/cdre +ERSINDIR=$TMP_DIR/ers/in +ERSOUTDIR=$TMP_DIR/ers/out + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +if [ "$ENABLE" != "true" ]; then + echo "$DESC not yet configured. Edit $DEFAULTS first." + exit 0 +fi + +# Install the run folder +if [ ! -d $RUNDIR ]; then + mkdir -p $RUNDIR + touch $STACKTRACE + chown -R $USER:$GROUP $RUNDIR +fi +# Install the ers folder +if [ ! -d $ERSINDIR ]; then + mkdir -p $ERSINDIR + chown $USER:$GROUP $ERSINDIR + mkdir -p $ERSOUTDIR + chown $USER:$GROUP $ERSOUTDIR +fi +# Install the cdre folder +if [ ! -d $CDREDIR ]; then + mkdir -p $CDREDIR + chown $USER:$GROUP $CDREDIR +fi + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + echo "\n### Started at:" `date`>>$STACKTRACE + start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test\ + || return 1 + start-stop-daemon --start --quiet --chuid $USER:$GROUP --make-pidfile --pidfile $PIDFILE --background\ + --startas /bin/bash -- -c "exec $DAEMON $DAEMON_OPTS >> $STACKTRACE 2>&1" \ + || return 2 +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/data/tutorials/sip_redirect/freeswitch/etc/freeswitch_conf.tar.gz b/data/tutorials/sip_redirect/freeswitch/etc/freeswitch_conf.tar.gz new file mode 100644 index 000000000..51d7bf15b Binary files /dev/null and b/data/tutorials/sip_redirect/freeswitch/etc/freeswitch_conf.tar.gz differ diff --git a/data/tutorials/sip_redirect/freeswitch/etc/init.d/freeswitch b/data/tutorials/sip_redirect/freeswitch/etc/init.d/freeswitch new file mode 100755 index 000000000..8f5011a10 --- /dev/null +++ b/data/tutorials/sip_redirect/freeswitch/etc/init.d/freeswitch @@ -0,0 +1,145 @@ +#!/bin/sh +### -*- mode:shell-script; indent-tabs-mode:nil; sh-basic-offset:2 -*- +### BEGIN INIT INFO +# Provides: freeswitch +# Required-Start: $network $remote_fs $local_fs +# Required-Stop: $network $remote_fs $local_fs +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: FreeSWITCH Softswitch +# Description: FreeSWITCH Softswitch +### END INIT INFO + +# Author: Travis Cross + +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC=freeswitch +NAME=freeswitch +DAEMON=/usr/bin/freeswitch +USER=freeswitch +GROUP=freeswitch +TUTDIR=/usr/share/cgrates/tutorials/sip_redirect/freeswitch +SCRIPTNAME=$TUTDIR/etc/init.d/$NAME +TMP_DIR=/tmp/cgr_fsevsock/freeswitch +CONFDIR=$TUTDIR/etc/$NAME +RUNDIR=$TMP_DIR/run +LOGDIR=$TMP_DIR/log +PIDFILE=$RUNDIR/$NAME.pid +WORKDIR=$TMP_DIR/lib +DBDIR=$WORKDIR/db/ +DAEMON_ARGS="-rp -conf $CONFDIR -db $DBDIR -log $LOGDIR -u $USER -g $GROUP -nonat -nc" + + +[ -x $DAEMON ] || exit 0 +. /lib/init/vars.sh +. /lib/lsb/init-functions + +if [ ! -d $RUNDIR ]; then + mkdir -p $RUNDIR + chown -R $USER:$GROUP $RUNDIR + chmod -R ug=rwX,o= $RUNDIR +fi + +if [ ! -d $LOGDIR ]; then + mkdir -p $LOGDIR + chown -R $USER:$GROUP $LOGDIR +fi + +if [ ! -d $DBDIR ]; then + mkdir -p $DBDIR + chown -R $USER:$GROUP $DBDIR +fi + +do_start() { + if ! [ -f $CONFDIR/freeswitch.xml ]; then + echo "$NAME is not configured so not starting.">&2 + echo "Please review /usr/share/doc/$NAME/README.Debian">&2 + return 3 + fi + echo $DAEMON_ARGS + start-stop-daemon --start --quiet \ + --pidfile $PIDFILE --exec $DAEMON --name $NAME --user $USER \ + --test > /dev/null \ + || return 1 + ulimit -s 240 + start-stop-daemon --start --quiet \ + --pidfile $PIDFILE --exec $DAEMON --name $NAME --user $USER \ + --chdir $WORKDIR -- $DAEMON_ARGS $DAEMON_OPTS \ + || return 2 + return 0 +} + +stop_fs() { + start-stop-daemon --stop --quiet \ + --pidfile $PIDFILE --name $NAME --user $USER \ + --retry=TERM/30/KILL/5 +} + +stop_fs_children() { + start-stop-daemon --stop --quiet \ + --exec $DAEMON \ + --oknodo --retry=0/30/KILL/5 +} + +do_stop() { + stop_fs + RETVAL="$?" + [ "$RETVAL" -eq 2 ] && return 2 + stop_fs_children + [ "$?" -eq 2 ] && return 2 + rm -f $PIDFILE + return "$RETVAL" +} + +do_reload() { + start-stop-daemon --stop --quiet \ + --pidfile $PIDFILE --name $NAME --user $USER \ + --signal HUP +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + reload|force-reload) + log_daemon_msg "Reloading $DESC" "$NAME" + do_reload + log_end_msg $? + ;; + restart) + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1|*) log_end_msg 1 ;; + esac + ;; + *) log_end_msg 1 ;; + esac + ;; + *) + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +exit 0