-
-
Save vodik/3795620 to your computer and use it in GitHub Desktop.
script to record audio from sangoma cards
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# Tyler Goodlet [email protected] -- 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