Skip to content

Instantly share code, notes, and snippets.

@vodik
Forked from goodboy/wanrec.sh
Created September 27, 2012 18:38
Show Gist options
  • Save vodik/3795620 to your computer and use it in GitHub Desktop.
Save vodik/3795620 to your computer and use it in GitHub Desktop.
script to record audio from sangoma cards
#!/bin/bash
# Tyler Goodlet tgoodlet@sangoma.com -- initial version
# Dependencies:
# -bash 4.0
# -gawk <ver?>
# TODO: add tcpdump tracing of SIP legs
# support bash 3.0 (i.e. remove coproc, associative arrays)
# script debug options - x for echo v for verbose: "set -<variable>" (to turn on), "set +<variable>" (to turn off)
# Hardcoded Settings
######################
setnumber=""
setchannel="" # use channel=<channel number> to originate a call
setspan=""
interface="DAHDI" #ZAP
# Asterisk settings
app="Playback" # application
clip="demo-congrats" # audio clip
# Hwec record length ("" || "120")
rectime=""
# Set up call dial string
dialstring="$interface/$setchannel/$setnumber"
# Configuration {{{1
#####################
# poll period
sleeptime=0.01
# Do we originate?
declare origin=0
# }}}
# Base Functions {{{1
have() { type -P "$1" > /dev/null; }
trim() { head -n ${1:-$height}; }
# Error function
err () { echo "${0##*/}: ERROR $1" 1>&2 && exit 1; }
# Warning function
warn () { echo "${0##*/}: WARNING $1" 1>&2; }
# Check pipe success
psuccess() {
case ${PIPESTATUS[0]} in
0|141) return 0;;
*) return 1;;
esac
}
# check for type int (FYI: bash regex matching operator (=~) only supported > 3.0)
is_int () {
if [[ $1 =~ [0-9][0-9]*$ ]]; then
return 0
else
return 1
fi
}
# function to hash key value pairs from a text buffer
# expects the buffer entries to be oriented "key1 value1 key2 value2 ... keyN valueN"
hash_wrap () {
if is_int "$1"; then
echo "hash_wrap: ERROR name of hash table must be a string!"
exit 1
else
name="$1"
list=($2)
# create a dynamically named associative array for wrapping
# paired text elements (requires bash > 4.0)
declare -gA "${name}"
if [[ -z "$list" ]]; then
err "hash_wrap -> no elements parsed sucessfully!"
else
# pack output into associative array
#TODO: rewrite this to be order indifferent
for ((i=0; i<${#list[*]}; i+=2)); do
eval "${name}[${list[$i]}]=${list[(($i+1))]}"
done
fi
fi
}
cleanup () {
# Clean up backgrounded processes
echo
echo signalling wanpipemon...
if $wan > /dev/null; then
echo -e "\n" > ${WPM[0]}
fi
echo signalling dahdi_monitor...
pkill -SIGINT dahdi_monitor
# Post recording conversion
if [[ -d "$dahdidir" ]]; then
cd "$dahdidir"
#Convert raw streams into wavs using the sox:
for file in *.raw; do
echo "converting $file to ${file%%raw}wav"
sox -t raw -r 8000 -s -2 -c 1 "$file" "${file%%raw}wav"
done
fi
}
usage () {
cat << HERE
Usage: wanrec.sh [-o [ -c <channel>] [ -s <span>]] [number]
wanrec is an script for auto-tracing Sangoma cards
Wanrec parses a pbx's call status to find information needed to
initiate recording tools which capture audio and data. It tries to use
the dialled number (DNIS) to track a particular call.
If it's possible to record the channel of interest then the maximum
number of recording tools available will be used to do so.
Trace files are saved in pwd.
Tools include thus far:
audio -> dahdi_monitor, wan_ec_client
pcap -> wanpipemon -c trd
Supported pbx's:
Asterisk
OPTIONS:
-o Originate a call with given number and chan/span or if none is provided
uses harcoded value. Application settings (ex. Playback demon-congrats)
can only be changed manually from within this script.
-c Specify a channel to originate call, if none hard coded is used. (mutex with -s)
-s Specify a span to originate call, if none hard coded is used. (mutex with -c)
-h --help print this
ARGUMENTS:
number Must be a numeric token longer then one character
NOTES:
This script will only capture information on wanpipe interfaces marked as
'Connected' by the output from wanrouter status.
This script assumes that if you originate the call then you want to record it and
will greedily attempt to track that outbound call.
Currently support for zaptel is risky at best. As testing continues adjustments
will be made.
HERE
}
# }}}
# {{{1 Options Parsing
parse_options () {
while :
do
case $1 in
-h | --help | -\?)
usage
exit 0
;;
-o | --originate)
echo "originate flag detected -> call will originate from us!"
origin=1
shift
;;
-c | --channel)
if (( $origin )); then
echo "channel specified = $2"
if [[ -n $setspan ]]; then
warn "you already flagged a span to originate the call! Ignoring channel request!"
else
setchannel="$2"
fi
else
echo "You did not flag us as the call origin! Ignoring channel request!"
exit 1
fi
shift 2
continue
;;
-s | --span)
if (( $origin )); then
echo "span specified = $2"
if [[ -n $setchannel ]]; then
warn "you already flagged a channel to originate the call! Ignoring span request!"
else
setspan="$2"
fi
else
echo "You did not flag us as the call origin! Ignoring span request!"
exit 1
fi
shift 2
continue
;;
--) # End of all options
shift
break
;;
-*)
warn "Unknown option '$1'" >&2
shift
;;
*) # no more options. Grab number and stop loop
if is_int $1 && [[ ${#1} > 1 ]]; then
# ensure number longer then 1 character to avoid
# false-positive number matching in some scanning functions
number="$1"
echo "number specified = $number"
else
err "No number argument passed or is too short! See ./wanrec -h for details"
fi
break
;;
esac
done
}
#}}}
# Asterisk Specific {{{1
show_channels () {
# Read pbx channels and spit out error if pbx query fails
if [[ $pbx =~ asterisk ]]; then
result="$(asterisk -rx 'core show channels')"
(( $? != 0 )) && exit 1
gawk '/active channel/,/'\''/ {print}' <<< "$result"
elif [[ $pbx =~ freeswitch ]]; then
echo "FS not currently supported!"
exit 1
fi
}
show_load () {
# Read pbx load and spit out error if pbx query fails
if [[ $pbx =~ asterisk ]]; then
result="$(asterisk -rx 'core show channels' 2>&1)"
(( $? != 0 )) && exit 1
gawk '/active channel/,/'\''/ {print}' <<< "$result"
elif [[ $pbx =~ freeswitch ]]; then
echo "FS not currently supported!"
exit 1
fi
}
dahdi_scan () {
# Check active calls
declare number="$1"
[[ -z $number ]] && err "find_call -> ERROR no 'number' argument passed!"
dahdichan=($(asterisk -rx 'dahdi show channels' | gawk '
$0 ~ /'"$number"'/ {
if($2 ~ /'"$number"'/)
print $1
}'))
if [[ -z "$dahdichan" ]]; then
#warn "dahdi_scan -> no DAHDI channels found dialing that number!"
return 1
else
echo -e "number tracked on dahdi channel(s) -> ${dahdichan[*]}"
fi
}
dahdi_chan2span () {
#TODO: input: dahdi channel, output: span which contains that channel
span="$(asterisk -rx "dahdi show channel $1" | gawk '
$1 ~ /Span:/ {
print $2
}')"
if [[ -z "$span" ]]; then
warn "dahdi_chan2span -> no DAHDI span found for that channel!"
else
echo -e "channel $1 contained in dahdi span -> ${span}"
fi
}
poll_ast_channels () {
# TODO: currently, this function greedily grabs the first qualified channel
# consider rewriting to use coprocs so we can launch multiple recording sessions?
local chanrecord
#pathname expansions
local letters='ig'
local prefix_pe="[${letters}]"
#regex patterns
local prefix_re="[${letters}]{1}" # 'g' is used in older asterisk versions
local dig_re="${prefix_re}[0-9]+"
local anal_re='[0-9]+.*'
while true; do
# Check the asterisk channel load
channels="$(show_channels | gawk '
$0 ~ /'"$interface"'/ {
# check for number in "dial string" (i.e. outbound call)
if($1 ~ /'"$number"'/ && $2 ~ /None/) {
#mark and print the channel
printf("out/%s\n", $1);
}
# check for number in "location" (i.e. inbound call)
else if ($2 ~ '"/$number/"') {
#mark and print the channel
printf("in/%s\n", $1);
}
else {
printf("unknown/%s\n",$1);
}
}')"
if [[ "$channels" != "$chanrecord" ]]; then
echo "channel state change detected!"
# grab the diff for processing
#TODO: probably a better way of doing this
chandiff="$(diff <(echo "${chanrecord}") <(echo "${channels}") | gawk '
/^>/ {
diff = gensub(/^> (.*)/,"\\1", 1, $0);
print diff
}')"
#echo -e "chandiff :\n$chandiff"
# update our record
chanrecord="$channels"
#parse channel list
while read line; do
IFS='/' read direction _ span chan <<< "${line}";
# check for span / channel
if [[ "$span" =~ $dig_re ]]; then
span="${span#${prefix_pe}*}"
channel="${chan%-*}"
echo "tracking activity on span '$span', channel '$channel'"
elif [[ "$span" =~ $anal_re ]]; then
channel="${span%-*}"
echo "tracking activity on analogue channel '$channel'"
fi
# check for our prefix and print
if [[ "$direction" == "out" ]]; then
printf "Tracking outbound call: channel %s\n" ${channel}
continue
elif [[ "$direction" == "in" ]]; then
printf "Tracking inbound call: channel %s\n" ${channel}
continue
elif [[ "$direction" == "unknown" ]]; then
printf "channel activity with unknown direction: channel %s\n" ${channel}
continue
else
echo "channel released!" # if the diff is ""
fi
done <<< "${chandiff}"
echo -e "ending parser...\n"
# Scan dahdi channels
dahdi_scan "$number" && dahdi_chan2span "${dahdichan[0]}"
if [[ -n "$dahdichan" && -n "$span" ]]; then
#channel="${dahdichan[0]}"
echo "Found enough info from $interface leaving find_call..."
return 0
fi
else
#warn "find_call -> no channels found dialing that number!"
sleep $sleeptime
fi
done
}
# Function to originate a call in Asterisk
originate_call() {
echo
# originate outbound call (requires an input dial string)
echo -e originating call with dial string = ${1}...
#TODO: write out like core show where error is returned if channel is busy
if [[ $pbx =~ asterisk ]]; then
asterisk -rx "originate ${1} application ${app} ${clip}" & 2>&1
fi
}
# }}}
# Functions {{{1
call_load () {
# Parse the current call load
[[ -n "$1" ]] && echo "call_analyze: why\'d you pass arg???"
# Parse channel listing and print out current stats
# TIP: consider this gawk notation $1~/[[:digit:]]/
list="$(show_load | gawk '
/active channel/,/'\''/ {
OFS=""
print $2 $3
print $1
}')"
if [[ -z "$list" ]]; then
show_load
exit 1
fi
# Create a hash of the call load info
hash_wrap "cload" "$list"
}
# Function to find the channel with orginated call
find_call () {
declare number="$1"
[[ -z "$number" ]] && err "find_call -> ERROR no number argument passed!"
echo
echo "Attempting to track channel with dialed number '$number'..."
if [[ "${pbx##*/}" == "asterisk" ]]; then
echo -e "entering channel polling state...\n"
poll_ast_channels
else
echo "${pbx##*/} is not supported!"
return 1
fi
return 1 # could not find call
}
parse_hw () {
list="$(sort </proc/net/wanrouter/status | sort | gawk '
/Connected/ {
# hard code the fields of interest
interface = 1; ctype = 3; status = 7
span = gensub(/wanpipe([[:digit:]]+)/,"\\1", 1, $interface);
if ($ctype~/ANALOG/)
type = "analogue"
else
type = "digital";
print span,type
}')"
#echo -e "gawk output =\n${list}"
if [[ -z $list ]]; then
err "No interfaces connected! Check your physical layer with 'wanrouter status'"
else
hash_wrap "span2hw" "$list"
fi
unset list
}
parse_wpconfs () {
for file in /etc/wanpipe/wanpipe?.conf; do
[[ -e $file ]] || err "failed to parse wanpipe configurations!"
list+=($(gawk '
/FE_MEDIA/ {
# hard code the fields of interest
filename = "'"$file"'"
linktype = NF #last horizontal field in config
span = gensub(/.*wanpipe([[:digit:]])+\.conf/,"\\1", 1, filename);
if ($linktype ~ /T1/)
width = "24"
else if($linktype ~ /E1/ || $linktype ~ /J1/)
width = "31"
else if ($linktype ~/FXO.*FXS/) {
while(line !~ /EMPTY/) {
getline line < "/proc/net/wanrouter/hwprobe_verbose"
if(line ~ /FX[OS]/)
width++
}
}
print span,width
}' "$file"))
done
#echo -e "gawk output = ${list[*]}"
hash_wrap "span2width" "${list[*]}"
return 0
}
wanpipe_index () {
# takes in the ablsolute (dahdi) channel and computes
# the relative (wan_ec_client) channel
# NOTE: relies on span2width array having been created
local absolutechannel="$1"
local accum=0
#if span2width dne! QUIT!
#calculate span channel number
for ((i = 1; i < $span; i++)); do
accum="$((accum + ${span2width[${i}]}))"
done
wanpipechan="$((absolutechannel - accum))"
}
#}}}
# START OF SCRIPT
echo
echo "starting wanrec script..."
echo "running script as $(whoami)"
if [[ $# > 0 ]]; then
echo
echo -e "parsing arguments..."
parse_options $*
else
echo
warn "${0}: WARNING no arguments passed!"
echo -e "using hardcoded settings..."
fi
echo
echo "checking pbx..."
pbx="$(type -P asterisk)" || { pbx="$(type -P freeswitch)" && err "FS not currently supported!" || err "No pbx detected!" ;}
echo "pbx = ${pbx##*/}"
echo
echo "scanning hardware interfaces..."
# creates "span2hw" hash
parse_hw
# creates "span2width" hash
parse_wpconfs
for key in ${!span2width[@]}; do
echo "span $key is ${span2hw[$key]} with ${span2width[$key]} channels"
done
# Check initial call stats
echo
echo "starting ${pbx##*/} pre-call load analysis..."
#call_load
show_load
echo
# find our call with dialed number
if (( !${origin} )); then # we aren't the origin
#start scanning for the call
find_call $number
if [[ -z "$dahdichan" ]]; then #also check for other assignments?
err "find_call -> no channel found!"
fi
wanpipe_index $dahdichan
echo "calculated wanpipe channel to be $wanpipechan"
echo
else
# we are the origin so assign span/channel before trace
if [[ -n "$setchannel" ]]; then
echo "setting channel to $setchannel"
dahdichan="$setchannel"
middle="$dahdichan"
# will find and create $span
dahdi_chan2span $dahdichan
elif [[ -n "$setspan" ]]; then
echo "setting span to $setspan"
span="$setspan"
# is 'i' good for older ast versions??
middle="i$span"
else
err "trying to originate without user choosing an interface!"
fi
dialstring="$interface/$middle/$number"
fi
today="$(date "+%Y-%B-%d-%H-%M-%S")"
recdir="$(pwd)/recs-$today/"
tracedir="$recdir/trace/"
wan="$(type -P "wanpipemon")"
dahdidir="$recdir/dahdimon/"
dahdimon="$(type -P "dahdi_monitor")"
#should not be created if hwec channel not enabled
hwecdir="$recdir/hwec/"
wanec="$(type -P "wan_ec_client")"
echo "checking available recordings tools..."
echo span = $span
if [[ ${span2hw[$span]} = "analogue" ]]; then
warn "wanpipemon not used for analogue card!"
elif $wan > /dev/null; then
echo -e "wanpipemon is on path -> starting trace"
mkdir -p $tracedir
cd $tracedir
#TODO: check for equipment type as well as card type PRI,BRI,CPE,NET
# Keep this stuff in case we get < bash 4.0
#$($wanpipemon -i w2g1 -pcap -pcap_file isdn.pcap -prot ISDN -full -systime -c trd 2>&1) & 2>&1
#($wanpipemon -i w2g1 -pcap -pcap_file isdn.pcap -prot ISDN -full -systime -c trd & )
# Check for net equipment
equip="$(asterisk -rx "pri show span $span" | gawk '$0 ~ /^Type:/ {print $2}')"
net_re='Network'
if [[ $equip =~ $net_re ]]; then
netflag='-pcap_isdn_network'
echo "span $span is Network equipment!"
fi
echo 'running command: '"${wan} -i w${span}g1 -pcap -pcap_file isdn.pcap -prot ISDN ${netflag} -full -systime -c trd"
# save previous fds of stdout before assigned to pipe
{ coproc WPM { ${wan} -i w${span}g1 -pcap -pcap_file isdn.pcap -prot ISDN ${netflag} -full -systime -c trd; } >&3 ;} 3>&1 2>&1
else
warn "wanpipemon is not found?"
fi
if (( $origin )); then
#originate the call
originate_call $dialstring
#start scanning for the call
find_call $number
if [[ -z "$dahdichan" ]]; then
err "find_call -> no channel found!"
else
wanpipe_index $setchannel
echo "calculated wanpipe channel to be $wanpipechan"
echo
fi
fi
# dahdi_monitor
if [[ -z "$dahdimon" ]]; then
echo
echo -e "dahdi_monitor is on path -> starting dahdi recordings..."
mkdir -p $dahdidir
cd $dahdidir
echo "running command: $dahdimon $dahdichan -r rx_output.raw -t tx_output.raw"
if ! (type $wanec > /dev/null); then
$dahdimon $dahdichan -r rx_output.raw -t tx_output.raw 2>&1
else
$dahdimon $dahdichan -r rx_output.raw -t tx_output.raw 2>&1 &
fi
else
echo "dahdi_monitor not found!"
fi
# wan_ec_client
if [[ -z $wanec ]]; then
echo
echo "wan_ec_client is on path -> starting hwec recordings..."
mkdir -p $hwecdir
cd $hwecdir
# print hwec config and start recording
$wanec wanpipe$span stats_full $wanpipechan 2>&1 > hwec_stats_full.txt
# don't fork as it seems to cause failure more often
echo 'running command: '"exec $wanec wanpipe$span monitor$rectime $wanpipechan"
$wanec wanpipe$span monitor$rectime $wanpipechan
else
echo "hwec recorder not found!"
fi
cleanup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment