class Rex::Proto::IAX2::Call
Attributes
audio_buff[RW]
audio_hook[RW]
busy[RW]
caller_name[RW]
caller_number[RW]
client[RW]
codec[RW]
dcall[RW]
dtmf[RW]
iseq[RW]
itime[RW]
oseq[RW]
queue[RW]
ring_finish[RW]
ring_start[RW]
scall[RW]
state[RW]
time_limit[RW]
Public Class Methods
new(client, src_id)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 23 def initialize(client, src_id) self.client = client self.scall = src_id self.dcall = 0 self.iseq = 0 self.oseq = 0 self.state = nil self.itime = ::Time.now self.queue = ::Queue.new self.audio_buff = [] self.busy = false self.dtmf = '' end
Public Instance Methods
audio_packet_data(pkt)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 318 def audio_packet_data(pkt) (pkt[0,1].unpack("C")[0] & 0x80 == 0) ? pkt[4,pkt.length-4] : pkt[12,pkt.length-12] end
decode_audio_frame(buff)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 296 def decode_audio_frame(buff) case self.codec # Convert u-law into signed PCM when IAX_CODEC_G711_MULAW Rex::Proto::IAX2::Codecs::MuLaw.decode(buff) # Convert a-law into signed PCM when IAX_CODEC_G711_ALAW Rex::Proto::IAX2::Codecs::ALaw.decode(buff) # Linear little-endian signed PCM is our native format when IAX_CODEC_LINEAR_PCM buff # Unsupported codec, return empty else dprint("UNKNOWN CODEC: #{self.codec.inspect}") '' end end
dial(number)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 108 def dial(number) self.client.send_new(self, number) res = wait_for(IAX_SUBTYPE_AUTHREQ, IAX_SUBTYPE_ACCEPT) return if not res # Handle authentication if its requested if res[1] == IAX_SUBTYPE_AUTHREQ chall = nil if res[2][14] == "\x00\x03" and res[1][15] self.dcall = res[0][0] chall = res[2][15] end self.client.send_authrep_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_ACCEPT) return if not res end self.codec = res[2][IAX_IE_DESIRED_CODEC].unpack("N")[0] self.state = :ringing self.ring_start = ::Time.now.to_i self.client.send_ack(self) true end
dprint(msg)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 41 def dprint(msg) self.client.dprint(msg) end
each_audio_frame(&block)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 290 def each_audio_frame(&block) self.audio_buff.each do |frame| block.call(frame) end end
handle_audio(pkt)
click to toggle source
Encoded audio from the client
# File lib/rex/proto/iax2/call.rb, line 271 def handle_audio(pkt) # Ignore audio received before the call is answered (ring ring) return if self.state != :answered # Extract the data from the packet (full or mini) data = audio_packet_data(pkt) # Decode the data into linear PCM frames buff = decode_audio_frame(data) # Call the caller-provided hook if its exists if self.audio_hook self.audio_buff(buff) # Otherwise append the frame to the buffer else self.audio_buff << buff end end
handle_control(pkt)
click to toggle source
Handling incoming control packets TODO: Enforce sequence order to prevent duplicates from breaking our state
# File lib/rex/proto/iax2/call.rb, line 160 def handle_control(pkt) src_call, dst_call, tstamp, out_seq, inp_seq, itype = pkt.unpack('nnNCCC') # Scrub the high bits out of the call IDs src_call ^= 0x8000 if (src_call & 0x8000 != 0) dst_call ^= 0x8000 if (dst_call & 0x8000 != 0) phdr = [ src_call, dst_call, tstamp, out_seq, inp_seq, itype ] info = nil stype = pkt[11,1].unpack("C")[0] info = process_elements(pkt, 12) if [IAX_TYPE_IAX, IAX_TYPE_CONTROL].include?(itype) if dst_call != self.scall dprint("Incoming packet to inactive call: #{dst_call} vs #{self.scall}: #{phdr.inspect} #{stype.inspect} #{info.inspect}") return end # Increment the received sequence number self.iseq = (self.iseq + 1) & 0xff if self.state == :hangup dprint("Packet received after hangup, replying with invalid") self.client.send_invalid(self) return end # Technically these all require an ACK reply # NEW, HANGUP, REJECT, ACCEPT, PONG, AUTHREP, REGREL, REGACK, REGREJ, TXREL case itype when IAX_TYPE_DTMF_BEGIN self.dprint("DTMF BEG: #{pkt[11,1]}") self.dtmf << pkt[11,1] when IAX_TYPE_DTMF_END self.dprint("DTMF END: #{pkt[11,1]}") when IAX_TYPE_CONTROL case stype when IAX_CTRL_HANGUP dprint("HANGUP") self.client.send_ack(self) self.state = :hangup when IAX_CTRL_RINGING dprint("RINGING") self.client.send_ack(self) when IAX_CTRL_BUSY dprint("BUSY") self.busy = true self.state = :hangup self.client.send_ack(self) when IAX_CTRL_ANSWER dprint("ANSWER") if self.state == :ringing self.state = :answered self.ring_finish = ::Time.now.to_i end self.client.send_ack(self) when IAX_CTRL_PROGRESS dprint("PROGRESS") when IAX_CTRL_PROCEED dprint("PROCEED") when 255 dprint("STOP SOUNDS") end # Acknowledge all control packets # self.client.send_ack(self) when IAX_TYPE_IAX dprint( ["RECV", phdr, stype, info].inspect ) case stype when IAX_SUBTYPE_HANGUP self.state = :hangup self.client.send_ack(self) when IAX_SUBTYPE_LAGRQ # Lagrps echo the timestamp self.client.send_lagrp(self, tstamp) when IAX_SUBTYPE_ACK # Nothing to do here when IAX_SUBTYPE_PING # Pongs echo the timestamp self.client.send_pong(self, tstamp) when IAX_SUBTYPE_PONG self.client.send_ack(self) else dprint( ["RECV-QUEUE", phdr, stype, info].inspect ) self.queue.push( [phdr, stype, info ] ) end when IAX_TYPE_VOICE v_codec = stype if self.state == :answered handle_audio(pkt) end self.client.send_ack(self) when nil dprint("Invalid control packet: #{pkt.unpack("H*")[0]}") end end
hangup()
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 133 def hangup self.client.send_hangup(self) self.state = :hangup true end
process_elements(data,off=0)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 147 def process_elements(data,off=0) res = {} while( off < data.length ) ie_type = data[off ,1].unpack("C")[0] ie_len = data[off + 1,2].unpack("C")[0] res[ie_type] = data[off + 2, ie_len] off += ie_len + 2 end res end
register()
click to toggle source
Register with the IAX endpoint
# File lib/rex/proto/iax2/call.rb, line 60 def register self.client.send_regreq(self) res = wait_for( IAX_SUBTYPE_REGAUTH, IAX_SUBTYPE_REGREJ ) return if not res if res[1] == IAX_SUBTYPE_REGREJ reason = res[2][IAX_IE_REGREJ_CAUSE] || "Unknown Reason" dprint("REGREJ: #{reason}") # Acknowledge the REGREJ self.client.send_ack(self) return end chall = nil if res[2][14] == "\x00\x03" and res[2][IAX_IE_CHALLENGE_DATA] self.dcall = res[0][0] chall = res[2][IAX_IE_CHALLENGE_DATA] end if chall.nil? dprint("REGAUTH: No challenge data received") return end self.client.send_regreq_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_REGACK, IAX_SUBTYPE_REGREJ ) return if not res if res[1] == IAX_SUBTYPE_REGREJ reason = res[2][IAX_IE_REGREJ_CAUSE] || "Unknown Reason" dprint("REGREJ: #{reason}") return end if res[2][IAX_IE_APPARENT_ADDR] r_fam, r_port, r_addr = res[2][IAX_IE_APPARENT_ADDR].unpack('nnA4') r_addr = r_addr.unpack("C*").map{|x| x.to_s }.join(".") dprint("REGACK: Registered from address #{r_addr}:#{r_port}") end # Acknowledge the REGACK self.client.send_ack(self) self.state = :registered true end
ring_time()
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 139 def ring_time (self.ring_finish || Time.now).to_i - self.ring_start.to_i end
timestamp()
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 143 def timestamp (( ::Time.now - self.itime) * 1000.0 ).to_i & 0xffffffff end
wait_for(*stypes)
click to toggle source
# File lib/rex/proto/iax2/call.rb, line 45 def wait_for(*stypes) begin ::Timeout.timeout( IAX_DEFAULT_TIMEOUT ) do while (res = self.queue.pop ) if stypes.include?(res[1]) return res end end end rescue ::Timeout::Error return nil end end