diff options
author | Gerald Begumisa <ben_g@users.sourceforge.net> | 2007-02-06 06:56:19 +0000 |
---|---|---|
committer | Gerald Begumisa <ben_g@users.sourceforge.net> | 2007-02-06 06:56:19 +0000 |
commit | 6ea47988e23c732814f792cb9dde7a35f9b26885 (patch) | |
tree | 886a952c4570e6d62d44bac3bdd9999daaf1ecfc /orkaudio/audiocaptureplugins/voip | |
parent | 94e3eb2d0cda7df2d876d1698db2e8e75cd0a0b1 (diff) |
IAX2 support added by Gerald Begumisa
git-svn-id: https://oreka.svn.sourceforge.net/svnroot/oreka/trunk@399 09dcff7a-b715-0410-9601-b79a96267cd0
Diffstat (limited to 'orkaudio/audiocaptureplugins/voip')
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/Iax2Session.cpp | 990 | ||||
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/Iax2Session.h | 275 | ||||
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/Makefile.am | 2 | ||||
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/PacketHeaderDefs.h | 49 | ||||
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/VoIp.cpp | 835 | ||||
-rw-r--r-- | orkaudio/audiocaptureplugins/voip/VoIp.dsp | 8 |
6 files changed, 2148 insertions, 11 deletions
diff --git a/orkaudio/audiocaptureplugins/voip/Iax2Session.cpp b/orkaudio/audiocaptureplugins/voip/Iax2Session.cpp new file mode 100644 index 0000000..6b072b5 --- /dev/null +++ b/orkaudio/audiocaptureplugins/voip/Iax2Session.cpp @@ -0,0 +1,990 @@ +/* + * Oreka -- A media capture and retrieval platform + * + * Copyright (C) 2005, orecx LLC + * + * http://www.orecx.com + * + * This program is free software, distributed under the terms of + * the GNU General Public License. + * Please refer to http://www.gnu.org/copyleft/gpl.html + * + */ +#pragma warning( disable: 4786 ) // disables truncated symbols in browse-info warning +#define _WINSOCKAPI_ // prevents the inclusion of winsock.h + +#include "Utils.h" +#include "AudioCapture.h" +#include "Iax2Session.h" +#include "AudioCapturePlugin.h" +#include "AudioCapturePluginCommon.h" +#include <list> +#include "ConfigManager.h" +#include "VoIpConfig.h" +#include "ace/OS_NS_arpa_inet.h" + +extern AudioChunkCallBackFunction g_audioChunkCallBack; +extern CaptureEventCallBackFunction g_captureEventCallBack; + +extern VoIpConfigTopObjectRef g_VoIpConfigTopObjectRef; +#define DLLCONFIG g_VoIpConfigTopObjectRef.get()->m_config + +Iax2Session::Iax2Session(CStdString& trackingId) +{ + m_trackingId = trackingId; + m_lastUpdated = time(NULL); + m_log = Logger::getLogger("Iax2session"); + m_invitorIp.s_addr = 0; + m_inviteeIp.s_addr = 0; + m_localIp.s_addr = 0; + m_remoteIp.s_addr = 0; + m_direction = CaptureEvent::DirUnkn; + m_numIax2Packets = 0; + m_started = false; + m_stopped = false; + m_beginDate = 0; + m_codec = 0; + m_hasDuplicateIax2 = false; + m_iax2_state = IAX2_STATE_WAITING; + m_invitor_scallno = 0; + m_invitee_scallno = 0; + + /* Not sure if IAX2 needs these... */ + m_highestIax2SeqNumDelta = 0; + m_minIax2SeqDelta = (double)DLLCONFIG.m_rtpDiscontinuityMinSeqDelta; + m_minIax2TimestampDelta = (double)DLLCONFIG.m_rtpDiscontinuityMinSeqDelta * 160; +} + +void Iax2Session::Stop() +{ + CStdString logMsg; + + logMsg.Format("[%s] %s Session stop, numIax2Pkts:%d dupl:%d seqDelta:%d " + "lastUpdated:%u", m_trackingId, m_capturePort, m_numIax2Packets, + m_hasDuplicateIax2, m_highestIax2SeqNumDelta, m_lastUpdated); + + LOG4CXX_INFO(m_log, logMsg); + + if(m_started && !m_stopped) { + CaptureEventRef stopEvent(new CaptureEvent); + stopEvent->m_type = CaptureEvent::EtStop; + stopEvent->m_timestamp = m_lastUpdated; + g_captureEventCallBack(stopEvent, m_capturePort); + m_stopped = true; + } +} + +void Iax2Session::Start() +{ + CaptureEventRef startEvent(new CaptureEvent); + + m_started = true; + time(&m_beginDate); + GenerateOrkUid(); + startEvent->m_type = CaptureEvent::EtStart; + startEvent->m_timestamp = m_beginDate; + startEvent->m_value = m_trackingId; + CStdString timestamp = IntToString(startEvent->m_timestamp); + + LOG4CXX_INFO(m_log, "[" + m_trackingId + "] " + m_capturePort + " " + + IAX2_PROTOCOL_STR + " Session start, timestamp:" + timestamp); + + g_captureEventCallBack(startEvent, m_capturePort); +} + +void Iax2Session::GenerateOrkUid() +{ + struct tm date = {0}; + int month, year; + + ACE_OS::localtime_r(&m_beginDate, &date); + + month = date.tm_mon + 1; + year = date.tm_year + 1900; + + m_orkUid.Format("%.4d%.2d%.2d_%.2d%.2d%.2d_%s", year, month, date.tm_mday, + date.tm_hour, date.tm_min, date.tm_sec, m_trackingId); +} + +/* We index with the invitor because the invitor is the first party + * that provides us with complete information i.e the IP address + * and the call number, which we need. Remember, though that + * call numbers are scarce by nature and may be reused. */ +void Iax2Session::ProcessMetadataIax2Incoming() +{ + char szNewSrcIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_invitorIp, szNewSrcIp, sizeof(szNewSrcIp)); + + m_remoteParty = m_new->m_caller; + m_localParty = m_new->m_callee; + m_direction = CaptureEvent::DirIn; + m_localIp = m_inviteeIp; + m_remoteIp = m_invitorIp; + m_capturePort.Format("%s,%d", szNewSrcIp, m_invitor_scallno); +} + +/* We index with the invitor because the invitor is the first party + * that provides us with complete information i.e the IP address + * and the call number, which we need. Remember, though that + * call numbers are scarce by nature and may be reused. */ +void Iax2Session::ProcessMetadataIax2Outgoing() +{ + char szNewSrcIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_invitorIp, szNewSrcIp, sizeof(szNewSrcIp)); + m_remoteParty = m_new->m_callee; + m_localParty = m_new->m_caller; + m_direction = CaptureEvent::DirOut; + m_capturePort.Format("%s,%d", szNewSrcIp, m_invitor_scallno); + m_localIp = m_invitorIp; + m_remoteIp = m_inviteeIp; +} + +/* Is this function ever necessary? */ +void Iax2Session::UpdateMetadataIax2(Iax2PacketInfoRef& iax2Packet, bool sourceIax2AddressIsNew) +{ + // Find out if the new IAX2 packet could match one of the IAX2 NEW messages associated with the session + Iax2NewInfoRef invite, tmpNew; + std::list<Iax2NewInfoRef>::iterator it; + + for(it = m_news.begin(); it != m_news.end(); it++) { + tmpNew = *it; + + if(tmpNew->m_validated) + break; + + if(sourceIax2AddressIsNew) { + if((unsigned int)(iax2Packet->m_sourceIp.s_addr) == + (unsigned int)(tmpNew->m_receiverIp.s_addr)) + invite = tmpNew; + } else { + if((unsigned int)(iax2Packet->m_destIp.s_addr) == + (unsigned int)(tmpNew->m_receiverIp.s_addr)) + invite = tmpNew; + } + } + + if(invite.get()) { + CStdString inviteString, iax2String, logMsg; + + // The NEW has generated an IAX2 voice stream + invite->m_validated = true; + + // Update session metadata with NEW info + m_remoteParty = invite->m_caller; + m_localParty = invite->m_callee; + m_localIp = invite->m_receiverIp; + + // Do some logging + invite->ToString(inviteString); + iax2Packet->ToString(iax2String); + + logMsg.Format("[%s] metadata update: local:%s remote:%s " + "IAX2 Pkt:%s NEW Info:%s", m_trackingId, m_localParty, + m_remoteParty, iax2String, inviteString); + LOG4CXX_INFO(m_log, logMsg); + + // Report Local party + CaptureEventRef event(new CaptureEvent()); + event->m_type = CaptureEvent::EtLocalParty; + event->m_value = m_localParty; + g_captureEventCallBack(event, m_capturePort); + + // Report remote party + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtRemoteParty; + event->m_value = m_remoteParty; + g_captureEventCallBack(event, m_capturePort); + + // Report Local IP + char szLocalIp[16]; + ACE_OS::inet_ntop(AF_INET, (void*)&m_localIp, szLocalIp, sizeof(szLocalIp)); + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtLocalIp; + event->m_value = szLocalIp; + g_captureEventCallBack(event, m_capturePort); + + // Trigger metadata update + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtUpdate; + g_captureEventCallBack(event, m_capturePort); + } +} + +void Iax2Session::ProcessMetadataIax2(Iax2PacketInfoRef& iax2Packet) +{ + /* This is the first full IAX2_FRAME_VOICE frame which will tell us + * the codec being used */ + if(((unsigned int)iax2Packet->m_sourceIp.s_addr != (unsigned int)m_invitorIp.s_addr) && + ((unsigned int)iax2Packet->m_destIp.s_addr != (unsigned int)m_invitorIp.s_addr)) + { + LOG4CXX_ERROR(m_log, "[" + m_trackingId + "] " + m_srcIpAndCallNo + " alien IAX2 packet"); + } + + /* Obtain the codec information */ + m_codec = iax2Packet->m_payloadType; + + // work out capture port and direction + if(DLLCONFIG.IsMediaGateway(m_invitorIp)) { + if(DLLCONFIG.IsMediaGateway(m_inviteeIp)) { + // Media gateway talking to media gateway, this is probably incoming + ProcessMetadataIax2Incoming(); + } else if(DLLCONFIG.IsPartOfLan(m_inviteeIp)) { + // Gateway to LAN, this is pobably incoming + ProcessMetadataIax2Incoming(); + } else { + // Gateway to outside address, probably outgoing but treat as incoming for now because + // It can be due to misconfigured LAN Mask, odds are it's still incoming. + ProcessMetadataIax2Incoming(); + } + } else if (DLLCONFIG.IsPartOfLan(m_invitorIp)) { + ProcessMetadataIax2Outgoing(); + } else { + // SIP invitor media IP address is an outside IP address + if(DLLCONFIG.IsMediaGateway(m_inviteeIp)) { + ProcessMetadataIax2Incoming(); + } else if(DLLCONFIG.IsPartOfLan(m_inviteeIp)) { + ProcessMetadataIax2Incoming(); + } else { + // SIP invitee media address is an outside IP address + ProcessMetadataIax2Outgoing(); + } + } +} + +void Iax2Session::ReportMetadata() +{ + char szLocalIp[16], szRemoteIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_localIp, szLocalIp, sizeof(szLocalIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_remoteIp, szRemoteIp, sizeof(szRemoteIp)); + + // Make sure Local Party is always reported + if(m_localParty.IsEmpty()) { + m_localParty = szLocalIp; + } + + // Report Local party + CaptureEventRef event(new CaptureEvent()); + event->m_type = CaptureEvent::EtLocalParty; + event->m_value = m_localParty; + g_captureEventCallBack(event, m_capturePort); + + // Report remote party + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtRemoteParty; + event->m_value = m_remoteParty; + g_captureEventCallBack(event, m_capturePort); + + // Report direction + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtDirection; + event->m_value = CaptureEvent::DirectionToString(m_direction); + g_captureEventCallBack(event, m_capturePort); + + // Report Local IP address + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtLocalIp; + event->m_value = szLocalIp; + g_captureEventCallBack(event, m_capturePort); + + // Report Remote IP address + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtRemoteIp; + event->m_value = szRemoteIp; + g_captureEventCallBack(event, m_capturePort); + + // Report OrkUid + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtOrkUid; + event->m_value = m_orkUid; + g_captureEventCallBack(event, m_capturePort); + + // Report native Call ID + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtCallId; + event->m_value = m_srcIpAndCallNo; + g_captureEventCallBack(event, m_capturePort); + + // Report end of metadata + event.reset(new CaptureEvent()); + event->m_type = CaptureEvent::EtEndMetadata; + g_captureEventCallBack(event, m_capturePort); +} + +static char *iax2_state_to_str(int iax2state) +{ + switch(iax2state) { + case IAX2_STATE_WAITING: + return "WAITING"; + case IAX2_STATE_LINKED: + return "LINKED"; + case IAX2_STATE_UP: + return "UP"; + default: + return "UNKNOWN"; + } + + /* NOT REACHED */ + return "UNKNOWN"; +} + +/* + * Returns false if packet is received out-of-sync (when we are in the wrong state) + */ +bool Iax2Session::AddIax2Packet(Iax2PacketInfoRef& iax2Packet) +{ + CStdString logMsg; + unsigned char channel = 0; + + /* What is our state? We need to be in the IAX2_STATE_LINKED state + * or the IAX2_STATE_UP state to receive any voice frames */ + if((m_iax2_state != IAX2_STATE_LINKED) && (m_iax2_state != IAX2_STATE_UP)) { + logMsg.Format("[%s] receiving voice packets while in %s state? " + "Destroying session...", m_trackingId, + iax2_state_to_str(m_iax2_state)); + return false; + } + + /* We do not have a voice packet yet */ + if(m_lastIax2Packet.get() == NULL) { + /* The first IAX2 voice frame is expected to be a full frame, + * complete with the payload type information. If this is not + * the case, we will continue dropping any mini frames */ + + if(iax2Packet->m_frame_type != IAX2_FRAME_FULL) { + logMsg.Format("[%s] 1st packet is not a full frame! Dropping...", m_trackingId); + LOG4CXX_ERROR(m_log, logMsg); + return true; + } + + ProcessMetadataIax2(iax2Packet); + m_iax2_state = IAX2_STATE_UP; + } + + /* If we get another full voice packet, we need to update our codec + * information */ + if(iax2Packet->m_frame_type == IAX2_FRAME_FULL) + m_codec = iax2Packet->m_payloadType; + + m_lastIax2Packet = iax2Packet; + if(m_lastIax2PacketSide1.get() == NULL) { + // First IAX2 packet for side 1 + m_lastIax2PacketSide1 = iax2Packet; + channel = 1; + if(m_log->isInfoEnabled()) + { + iax2Packet->ToString(logMsg); + logMsg = "[" + m_trackingId + "] 1st packet s1: " + logMsg; + LOG4CXX_INFO(m_log, logMsg); + } + } else { + // Comparing destination IP address to find out if side1, see (1) + if((unsigned int)iax2Packet->m_destIp.s_addr == (unsigned int)m_lastIax2PacketSide1->m_destIp.s_addr) + { + if(iax2Packet->m_timestamp == m_lastIax2PacketSide1->m_timestamp) + { + m_hasDuplicateIax2 = true; + return true; // dismiss duplicate IAX2 packet + } + /* XXX Detect discontinuity using timestamps? */ + /* + else + { + double seqNumDelta = (double)iax2Packet->m_seqNum - (double)m_lastIax2PacketSide1->m_seqNum; + if(DLLCONFIG.m_iax2DiscontinuityDetect) + { + double timestampDelta = (double)iax2Packet->m_timestamp - (double)m_lastIax2PacketSide1->m_timestamp; + if( abs(seqNumDelta) > m_minIax2SeqDelta && + abs(timestampDelta) > m_minIax2TimestampDelta) + { + logMsg.Format("[%s] IAX2 discontinuity s1: before: seq:%u ts:%u after: seq:%u ts:%u", + m_trackingId, m_lastIax2PacketSide1->m_seqNum, m_lastIax2PacketSide1->m_timestamp, + iax2Packet->m_seqNum, iax2Packet->m_timestamp); + LOG4CXX_INFO(m_log, logMsg); + return false; + } + } + if(seqNumDelta > (double)m_highestIax2SeqNumDelta) + { + m_highestIax2SeqNumDelta = (unsigned int)seqNumDelta; + } + } */ + m_lastIax2PacketSide1 = iax2Packet; + channel = 1; + } + else + { + if(m_lastIax2PacketSide2.get() == NULL) + { + // First IAX2 packet for side 2 + if(m_log->isInfoEnabled()) + { + iax2Packet->ToString(logMsg); + logMsg = "[" + m_trackingId + "] 1st packet s2: " + logMsg; + LOG4CXX_INFO(m_log, logMsg); + } + } + else + { + if(iax2Packet->m_timestamp == m_lastIax2PacketSide2->m_timestamp) + { + m_hasDuplicateIax2 = true; + return true; // dismiss duplicate IAX2 packet + } + /* XXX Detect discontinuity using timestamps? */ + /* + else + { + double seqNumDelta = (double)iax2Packet->m_seqNum - (double)m_lastIax2PacketSide2->m_seqNum; + if(DLLCONFIG.m_iax2DiscontinuityDetect) + { + double timestampDelta = (double)iax2Packet->m_timestamp - (double)m_lastIax2PacketSide2->m_timestamp; + if( abs(seqNumDelta) > m_minIax2SeqDelta && + abs(timestampDelta) > m_minIax2TimestampDelta) + { + logMsg.Format("[%s] IAX2 discontinuity s2: before: seq:%u ts:%u after: seq:%u ts:%u", + m_trackingId, m_lastIax2PacketSide2->m_seqNum, m_lastIax2PacketSide2->m_timestamp, + iax2Packet->m_seqNum, iax2Packet->m_timestamp); + LOG4CXX_INFO(m_log, logMsg); + return false; + } + } + if(seqNumDelta > (double)m_highestIax2SeqNumDelta) + { + m_highestIax2SeqNumDelta = (unsigned int)seqNumDelta; + } + }*/ + } + m_lastIax2PacketSide2 = iax2Packet; + channel = 2; + } + } + + m_numIax2Packets++; + + /* Can stream change happen with IAX2?? + bool hasSourceAddress = m_iax2AddressList.HasAddressOrAdd(iax2Packet->m_sourceIp, iax2Packet->m_sourcePort); + bool hasDestAddress = m_iax2AddressList.HasAddressOrAdd(iax2Packet->m_destIp, iax2Packet->m_destPort); + if( hasSourceAddress == false || hasDestAddress == false ) + { + iax2Packet->ToString(logMsg); + logMsg.Format("[%s] new IAX2 stream s%d: %s", + m_trackingId, channel, logMsg); + LOG4CXX_INFO(m_log, logMsg); + + if(m_protocol == ProtIax2 && m_started) // make sure this only happens if ReportMetadata() already been called for the session + { + UpdateMetadataIax2(iax2Packet, hasDestAddress); + } + } + */ + + if(m_numIax2Packets == 1) { + Start(); + ReportMetadata(); + } + + if(m_started) { + AudioChunkDetails details; + AudioChunkRef chunk(new AudioChunk()); + + details.m_arrivalTimestamp = iax2Packet->m_arrivalTimestamp; + details.m_numBytes = iax2Packet->m_payloadSize; + details.m_timestamp = iax2Packet->m_timestamp; + details.m_rtpPayloadType = m_codec; + details.m_channel = channel; + details.m_encoding = AlawAudio; + + chunk->SetBuffer(iax2Packet->m_payload, details); + g_audioChunkCallBack(chunk, m_capturePort); + + m_lastUpdated = iax2Packet->m_arrivalTimestamp; + } + + return true; +} + +/* Report AUTHREQ so as to get the invitee call number */ +void Iax2Session::ReportIax2Authreq(Iax2AuthreqInfoRef& authreq) +{ + m_invitee_scallno = StringToInt(authreq->m_sender_callno); +} + +/* Obtain the invitee call number from the ACCEPT, in case an AUTHREQ + * was not sent */ +void Iax2Session::ReportIax2Accept(Iax2AcceptInfoRef& acceptinfo) +{ + m_invitee_scallno = StringToInt(acceptinfo->m_sender_callno); +} + +/* Report NEW */ +void Iax2Session::ReportIax2New(Iax2NewInfoRef& invite) +{ + if(m_new.get() == NULL) { + char szFromIax2Ip[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&invite->m_senderIp, szFromIax2Ip, sizeof(szFromIax2Ip)); + + m_new = invite; + m_invitorIp = invite->m_senderIp; + m_inviteeIp = invite->m_receiverIp; + m_invitor_scallno = StringToInt(invite->m_callNo); + m_srcIpAndCallNo = CStdString(szFromIax2Ip) + "," + invite->m_callNo; + } else { + CStdString inviteString; + invite->ToString(inviteString); + CStdString logMsg; + logMsg.Format("[%s] associating NEW:%s", m_trackingId, inviteString); + LOG4CXX_INFO(m_log, logMsg); + } + + m_news.push_front(invite); +} + + +//===================================================================== +Iax2Sessions::Iax2Sessions() +{ + m_log = Logger::getLogger("iax2sessions"); + if(CONFIG.m_debug) { + m_alphaCounter.Reset(); + } +} + +/* In the cases where AUTHREQ was not sent, we need to still obtain the + * invitee call number and make a map for it */ +void Iax2Sessions::ReportIax2Accept(Iax2AcceptInfoRef& acceptinfo) +{ + CStdString srcIpAndCallNo, log_msg, destIpAndCallNo, numSessions, acceptString; + std::map<CStdString, Iax2SessionRef>::iterator pair; + char invitor_ip[16], invitee_ip[16]; + Iax2SessionRef session; + + /* Assumption is that the receiver is the invitor */ + ACE_OS::inet_ntop(AF_INET, (void*)&acceptinfo->m_receiverIp, invitor_ip, sizeof(invitor_ip)); + ACE_OS::inet_ntop(AF_INET, (void*)&acceptinfo->m_senderIp, invitee_ip, sizeof(invitee_ip)); + srcIpAndCallNo = CStdString(invitor_ip) + "," + acceptinfo->m_receiver_callno; + destIpAndCallNo = CStdString(invitee_ip) + "," + acceptinfo->m_sender_callno; + + pair = m_bySrcIpAndCallNo.find(srcIpAndCallNo); + if(pair != m_bySrcIpAndCallNo.end()) { + session = pair->second; + if(session.get() != NULL) { + /* Excellent! We have an existing session. Now check if we have + * already mapped the invitee call number and IP address */ + + pair = m_byDestIpAndCallNo.find(destIpAndCallNo); + if(pair != m_byDestIpAndCallNo.end()) { + /* We already have a mapping and all necessary information */ + + session = pair->second; + if(session.get() != NULL) { + session->m_iax2_state = IAX2_STATE_LINKED; + LOG4CXX_INFO(m_log, "[" + session->m_trackingId + "] " + "received ACCEPT. Session already " + "registered. Now LINKED."); + return; + } + } + + /* We need to enter the information */ + log_msg.Format("[%s] got ACCEPT for this session. Registering " + "sender call number", session->m_trackingId); + LOG4CXX_INFO(m_log, log_msg); + + session->m_destIpAndCallNo = destIpAndCallNo; + session->m_iax2_state = IAX2_STATE_LINKED; + + m_byDestIpAndCallNo.insert(std::make_pair(session->m_destIpAndCallNo, session)); + session->ReportIax2Accept(acceptinfo); + + acceptinfo->ToString(acceptString); + LOG4CXX_INFO(m_log, "[" + session->m_trackingId + "] supplemented " + "by IAX2 ACCEPT:" + acceptString + ". Status now LINKED"); + + return; + } + } + + /* We don't have an existing session? */ + acceptinfo->ToString(acceptString); + log_msg.Format("Got ACCEPT [%s] but couldn't find corresponding NEW", acceptString); + LOG4CXX_ERROR(m_log, log_msg); + + return; +} + +/* After a NEW has been sent, an AUTHREQ may be sent by the sender and this + * is the stage where we grab the callno of the sender */ +void Iax2Sessions::ReportIax2Authreq(Iax2AuthreqInfoRef& authreq) +{ + CStdString srcIpAndCallNo, log_msg, destIpAndCallNo, numSessions, authreqString; + std::map<CStdString, Iax2SessionRef>::iterator pair; + char invitor_ip[16], invitee_ip[16]; + Iax2SessionRef session; + + /* Assumption is that the receiver is the invitor */ + ACE_OS::inet_ntop(AF_INET, (void*)&authreq->m_receiverIp, invitor_ip, sizeof(invitor_ip)); + ACE_OS::inet_ntop(AF_INET, (void*)&authreq->m_senderIp, invitee_ip, sizeof(invitee_ip)); + srcIpAndCallNo = CStdString(invitor_ip) + "," + authreq->m_receiver_callno; + destIpAndCallNo = CStdString(invitee_ip) + "," + authreq->m_sender_callno; + + pair = m_bySrcIpAndCallNo.find(srcIpAndCallNo); + if(pair != m_bySrcIpAndCallNo.end()) { + /* Excellent! We have an existing session */ + session = pair->second; + + if(session.get() != NULL) { + session->m_destIpAndCallNo = destIpAndCallNo; + m_byDestIpAndCallNo.insert(std::make_pair(session->m_destIpAndCallNo, session)); + session->ReportIax2Authreq(authreq); + session->m_iax2_state = IAX2_STATE_WAITING; + + authreq->ToString(authreqString); + LOG4CXX_INFO(m_log, "[" + session->m_trackingId + "] " + "supplemented by IAX2 AUTHREQ:" + authreqString + + ". Status now WAITING"); + + return; + } + } + + /* We don't have an existing session? */ + authreq->ToString(authreqString); + log_msg.Format("Got AUTHREQ [%s] but couldn't find corresponding NEW", authreqString); + LOG4CXX_ERROR(m_log, log_msg); + + return; +} + +void Iax2Sessions::ReportIax2New(Iax2NewInfoRef& invite) +{ + char szFromIax2Ip[16]; + std::map<CStdString, Iax2SessionRef>::iterator pair; + + ACE_OS::inet_ntop(AF_INET, (void*)&invite->m_senderIp, szFromIax2Ip, sizeof(szFromIax2Ip)); + CStdString IpAndCallNo = CStdString(szFromIax2Ip) + "," + invite->m_callNo; + + pair = m_bySrcIpAndCallNo.find(IpAndCallNo); + if (pair != m_bySrcIpAndCallNo.end()) { + // The session already exists, check the state + CStdString logmsg; + + Iax2SessionRef session = pair->second; + + if(session.get() != NULL) { + if(session->m_iax2_state == IAX2_STATE_UP) { + CStdString log_msg; + + log_msg.Format("[%s] is in the UP state but we've " + "got another NEW from this same IP %s and same " + "source call number %s", session->m_trackingId, + szFromIax2Ip, invite->m_callNo); + + LOG4CXX_ERROR(m_log, log_msg); + return; + } + } + + /* Stop this session and proceed */ + Stop(session); + } + + /* Create a new session */ + CStdString trackingId = m_alphaCounter.GetNext(); + Iax2SessionRef session(new Iax2Session(trackingId)); + session->m_srcIpAndCallNo = IpAndCallNo; + session->ReportIax2New(invite); + m_bySrcIpAndCallNo.insert(std::make_pair(session->m_srcIpAndCallNo, session)); + + CStdString numSessions = IntToString(m_bySrcIpAndCallNo.size()); + LOG4CXX_DEBUG(m_log, CStdString("BySrcIpAndCallNo: ") + numSessions); + + CStdString inviteString; + invite->ToString(inviteString); + LOG4CXX_INFO(m_log, "[" + trackingId + "] created by IAX2 NEW:" + inviteString + " for " + session->m_srcIpAndCallNo); +} + +/* This function is called as a result of a) an IAX2 HANGUP; b) a + * CONTROL HANGUP; or c) an IAX2 REJECT */ +void Iax2Sessions::ReportIax2Hangup(Iax2HangupInfoRef& bye) +{ + CStdString senderIpAndCallNo, log_msg, receiverIpAndCallNo, numSessions, hangupString; + std::map<CStdString, Iax2SessionRef>::iterator pair; + char sender_ip[16], receiver_ip[16]; + Iax2SessionRef session; + + ACE_OS::inet_ntop(AF_INET, (void*)&bye->m_receiverIp, receiver_ip, sizeof(receiver_ip)); + ACE_OS::inet_ntop(AF_INET, (void*)&bye->m_senderIp, sender_ip, sizeof(sender_ip)); + receiverIpAndCallNo = CStdString(receiver_ip) + "," + bye->m_receiver_callno; + senderIpAndCallNo = CStdString(sender_ip) + "," + bye->m_sender_callno; + + /* The recipient of the HANGUP is the Invitor */ + pair = m_bySrcIpAndCallNo.find(receiverIpAndCallNo); + if (pair != m_bySrcIpAndCallNo.end()) { + Iax2SessionRef session = pair->second; + if(session.get() != NULL) { + log_msg.Format("[%s] %s: Hanging up session", session->m_trackingId, session->m_srcIpAndCallNo); + LOG4CXX_INFO(m_log, log_msg); + Stop(session); + } + } + + /* The sender of the HANGUP is the Invitor */ + pair = m_bySrcIpAndCallNo.find(senderIpAndCallNo); + if (pair != m_bySrcIpAndCallNo.end()) { + Iax2SessionRef session = pair->second; + if(session.get() != NULL) { + log_msg.Format("[%s] %s: Hanging up session", session->m_trackingId, session->m_srcIpAndCallNo); + LOG4CXX_INFO(m_log, log_msg); + Stop(session); + } + } + + /* The recipient of the HANGUP is the Invitee */ + pair = m_byDestIpAndCallNo.find(receiverIpAndCallNo); + if (pair != m_byDestIpAndCallNo.end()) { + Iax2SessionRef session = pair->second; + if(session.get() != NULL) { + log_msg.Format("[%s] %s: Hanging up session", session->m_trackingId, session->m_srcIpAndCallNo); + LOG4CXX_INFO(m_log, log_msg); + Stop(session); + } + } + + /* The sender of the HANGUP is the Invitee */ + pair = m_byDestIpAndCallNo.find(senderIpAndCallNo); + if (pair != m_byDestIpAndCallNo.end()) { + Iax2SessionRef session = pair->second; + if(session.get() != NULL) { + log_msg.Format("[%s] %s: Hanging up session", session->m_trackingId, session->m_srcIpAndCallNo); + LOG4CXX_INFO(m_log, log_msg); + Stop(session); + } + } +} + +void Iax2Sessions::Stop(Iax2SessionRef& session) +{ + CStdString numSessions; + + session->Stop(); + + if(session->m_srcIpAndCallNo.size() > 0) { + m_bySrcIpAndCallNo.erase(session->m_srcIpAndCallNo); + + numSessions = IntToString(m_bySrcIpAndCallNo.size()); + LOG4CXX_DEBUG(m_log, CStdString("BySrcIpAndPort: ") + numSessions); + } + + if(session->m_destIpAndCallNo.size() > 0) { + m_byDestIpAndCallNo.erase(session->m_destIpAndCallNo); + + numSessions = IntToString(m_byDestIpAndCallNo.size()); + LOG4CXX_DEBUG(m_log, CStdString("ByDestIpAndPort: ") + numSessions); + } +} + + +void Iax2Sessions::ReportIax2Packet(Iax2PacketInfoRef& iax2Packet) +{ + Iax2SessionRef session; + CStdString logMsg, sourcecallno, IpAndCallNo; + std::map<CStdString, Iax2SessionRef>::iterator pair; + char szSourceIp[16]; + + /* Add this IAX2 voice frame to a session which matches either the source + * IP address and call number or the destination IP address and call number. + * In this context "source IP" refers to the IP address of the machine which + * initiated the session i.e sent the "NEW". Likewise "destination IP" + * refers to the machine to whom the "NEW" was sent. There is no chance + * that there could be a duplicate */ + + ACE_OS::inet_ntop(AF_INET, (void*)&iax2Packet->m_sourceIp, szSourceIp, + sizeof(szSourceIp)); + sourcecallno = IntToString(iax2Packet->m_sourcecallno); + + IpAndCallNo = CStdString(szSourceIp) + "," + sourcecallno; + + pair = m_bySrcIpAndCallNo.find(IpAndCallNo); + if(pair != m_bySrcIpAndCallNo.end()) { + session = pair->second; + + if(session.get() != NULL) { + if(!session->AddIax2Packet(iax2Packet)) { + /* Discontinuity detected? */ + Stop(session); + } + } + + return; + } + + /* Search in the destination IP map */ + pair = m_byDestIpAndCallNo.find(IpAndCallNo); + if(pair != m_byDestIpAndCallNo.end()) { + session = pair->second; + + if(session.get() != NULL) { + if(!session->AddIax2Packet(iax2Packet)) { + /* Discontinuity detected? */ + Stop(session); + } + } + + return; + } + + /* XXX Tracking?? */ + CStdString pktinfo; + iax2Packet->ToString(pktinfo); + //LOG4CXX_INFO(m_log, "Could not figure out where to place packet from "+IpAndCallNo+": [" + pktinfo +"]"); +} + +void Iax2Sessions::StopAll() +{ + time_t forceExpiryTime = time(NULL) + 2*DLLCONFIG.m_rtpSessionWithSignallingTimeoutSec; + Hoover(forceExpiryTime); +} + +void Iax2Sessions::Hoover(time_t now) +{ + CStdString numSessions; + int timeoutSeconds = 0; + Iax2SessionRef session; + std::map<CStdString, Iax2SessionRef>::iterator pair; + std::list<Iax2SessionRef> toDismiss; + std::list<Iax2SessionRef>::iterator it; + + numSessions = IntToString(m_bySrcIpAndCallNo.size()); + LOG4CXX_DEBUG(m_log, "Hoover - check " + numSessions + " sessions time:" + IntToString(now)); + timeoutSeconds = DLLCONFIG.m_rtpSessionWithSignallingTimeoutSec; + + for(pair=m_bySrcIpAndCallNo.begin(); pair!=m_bySrcIpAndCallNo.end(); pair++) { + session = pair->second; + + if((now - session->m_lastUpdated) > timeoutSeconds) + toDismiss.push_back(session); + } + + for (it=toDismiss.begin(); it!=toDismiss.end(); it++) { + session = *it; + LOG4CXX_INFO(m_log, "[" + session->m_trackingId + "] " + session->m_srcIpAndCallNo + " Expired"); + Stop(session); + } + + /* Just in case? */ + toDismiss.clear(); + for(pair=m_byDestIpAndCallNo.begin(); pair!=m_byDestIpAndCallNo.end(); pair++) { + session = pair->second; + + if((now - session->m_lastUpdated) > timeoutSeconds) + toDismiss.push_back(session); + } + + for (it=toDismiss.begin(); it!=toDismiss.end(); it++) { + session = *it; + LOG4CXX_INFO(m_log, "[" + session->m_trackingId + "] " + session->m_destIpAndCallNo + " Expired"); + Stop(session); + } +} + +//========================================================== +Iax2NewInfo::Iax2NewInfo() +{ + m_senderIp.s_addr = 0; + m_receiverIp.s_addr = 0; + m_caller = "HIDDEN"; /* Unless obtained */ + m_validated= false; +} + +void Iax2NewInfo::ToString(CStdString& string) +{ + char senderIp[16], receiverIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_senderIp, senderIp, sizeof(senderIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_receiverIp, receiverIp, + sizeof(receiverIp)); + + string.Format("sender:%s receiver: %s caller:%s callee:%s srccallno: %s", + senderIp, receiverIp, m_caller, m_callee, m_callNo); +} + +//========================================================== +Iax2AuthreqInfo::Iax2AuthreqInfo() +{ + m_senderIp.s_addr = 0; + m_receiverIp.s_addr = 0; +} + +void Iax2AuthreqInfo::ToString(CStdString& string) +{ + char senderIp[16], receiverIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_senderIp, senderIp, sizeof(senderIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_receiverIp, receiverIp, + sizeof(receiverIp)); + + string.Format("sender:%s receiver:%s sender_callno:%s receiver_callno:%s", + senderIp, receiverIp, m_sender_callno, m_receiver_callno); +} + +//========================================================== +Iax2AcceptInfo::Iax2AcceptInfo() +{ + m_senderIp.s_addr = 0; + m_receiverIp.s_addr = 0; +} + +void Iax2AcceptInfo::ToString(CStdString& string) +{ + char senderIp[16], receiverIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_senderIp, senderIp, sizeof(senderIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_receiverIp, receiverIp, + sizeof(receiverIp)); + + string.Format("sender:%s receiver:%s sender_callno:%s receiver_callno:%s", + senderIp, receiverIp, m_sender_callno, m_receiver_callno); +} + +//========================================================== +Iax2HangupInfo::Iax2HangupInfo() +{ + m_senderIp.s_addr = 0; + m_receiverIp.s_addr = 0; +} + +void Iax2HangupInfo::ToString(CStdString& string) +{ + char senderIp[16], receiverIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_senderIp, senderIp, sizeof(senderIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_receiverIp, receiverIp, + sizeof(receiverIp)); + + string.Format("sender:%s receiver:%s sender_callno:%s receiver_callno:%s", + senderIp, receiverIp, m_sender_callno, m_receiver_callno); +} + +//========================================================== +Iax2PacketInfo::Iax2PacketInfo() +{ +} + +void Iax2PacketInfo::ToString(CStdString& string) +{ + char senderIp[16], receiverIp[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&m_sourceIp, senderIp, sizeof(senderIp)); + ACE_OS::inet_ntop(AF_INET, (void*)&m_destIp, receiverIp, + sizeof(receiverIp)); + + string.Format("sender:%s receiver:%s sender_callno:%d receiver_callno:%d " + "type:%d size:%d timestamp: %d", senderIp, receiverIp, + m_sourcecallno, m_destcallno, m_payloadType, + m_payloadSize, m_timestamp); +} + diff --git a/orkaudio/audiocaptureplugins/voip/Iax2Session.h b/orkaudio/audiocaptureplugins/voip/Iax2Session.h new file mode 100644 index 0000000..ef1944f --- /dev/null +++ b/orkaudio/audiocaptureplugins/voip/Iax2Session.h @@ -0,0 +1,275 @@ +/* + * Oreka -- A media capture and retrieval platform + * + * Copyright (C) 2005, orecx LLC + * + * http://www.orecx.com + * + * This program is free software, distributed under the terms of + * the GNU General Public License. + * Please refer to http://www.gnu.org/copyleft/gpl.html + * + */ + +#ifndef __IAX2SESSION_H__ +#define __IAX2SESSION_H__ + +#include <log4cxx/logger.h> +#include "Iax2Session.h" +#include <map> +#include "ace/Singleton.h" +#include "PacketHeaderDefs.h" + +using namespace log4cxx; + +/* The frame types we're interested in */ +#define IAX2_FRAME_VOICE 2 +#define IAX2_FRAME_CONTROL 4 +#define IAX2_FRAME_IAX 6 + +/* The full frame subclass values for IAX2_FRAME_IAX frames + * which we're interested in */ +#define IAX2_COMMAND_NEW 1 +#define IAX2_COMMAND_HANGUP 5 +#define IAX2_COMMAND_REJECT 6 +#define IAX2_COMMAND_ACCEPT 7 +#define IAX2_COMMAND_AUTHREQ 8 + +/* The control frame subclass values for IAX2_FRAME_CONTROL + * which we're interested in */ +#define IAX2_CONTROL_HANGUP 1 + +/* The information elements we're interested in */ +#define IAX2_IE_CALLED_NUMBER 1 +#define IAX2_IE_CALLING_NUMBER 2 +#define IAX2_IE_USERNAME 6 +#define IAX2_IE_FORMAT 9 +#define IAX2_IE_AUTHMETHODS 14 +#define IAX2_IE_CHALLENGE 15 + +struct iax2_ies { + char *caller; + char *callee; /* Mandatory for NEW */ + unsigned int format; /* Format Mandatory for ACCEPT */ + char *username; /* Mandatory for AUTHREQ */ + unsigned int authmethods; /* Mandatory for AUTHREQ */ + char *challenge; /* Mandatory for AUTHREQ */ +}; + +/* Supported Voice Codecs */ +/* G.723.1 compression */ +#define IAX2_CODEC_G723_1 (1 << 0) +/* GSM compression */ +#define IAX2_CODEC_GSM (1 << 1) +/* Raw mu-law data (G.711) */ +#define IAX2_CODEC_ULAW (1 << 2) +/* Raw A-law data (G.711) */ +#define IAX2_CODEC_ALAW (1 << 3) +/* ADPCM (G.726, 32kbps) */ +#define IAX2_CODEC_G726 (1 << 4) +/* ADPCM (IMA) */ +#define IAX2_CODEC_ADPCM (1 << 5) +/* Raw 16-bit Signed Linear (8000 Hz) PCM */ +#define IAX2_CODEC_SLINEAR (1 << 6) +/* LPC10, 180 samples/frame */ +#define IAX2_CODEC_LPC10 (1 << 7) +/* G.729A audio */ +#define IAX2_CODEC_G729A (1 << 8) +/* SpeeX Free Compression */ +#define IAX2_CODEC_SPEEX (1 << 9) +/* iLBC Free Compression */ +#define IAX2_CODEC_ILBC (1 << 10) + +class Iax2AcceptInfo +{ +public: + Iax2AcceptInfo(); + void ToString(CStdString& string); + + struct in_addr m_senderIp; + struct in_addr m_receiverIp; + + CStdString m_sender_callno; + CStdString m_receiver_callno; +}; +typedef boost::shared_ptr<Iax2AcceptInfo> Iax2AcceptInfoRef; + + +class Iax2AuthreqInfo +{ +public: + Iax2AuthreqInfo(); + void ToString(CStdString& string); + + struct in_addr m_senderIp; + struct in_addr m_receiverIp; + + CStdString m_sender_callno; + CStdString m_receiver_callno; +}; +typedef boost::shared_ptr<Iax2AuthreqInfo> Iax2AuthreqInfoRef; + +class Iax2NewInfo +{ +public: + Iax2NewInfo(); + void ToString(CStdString& string); + + struct in_addr m_senderIp; + struct in_addr m_receiverIp; + + CStdString m_caller; /* CALLING NUMBER */ + CStdString m_callee; /* CALLED NUMBER */ + CStdString m_callNo; /* This is the source call number */ + + bool m_validated; // true when an IAX2 voice stream has been seen for the NEW +}; +typedef boost::shared_ptr<Iax2NewInfo> Iax2NewInfoRef; + +class Iax2HangupInfo +{ +public: + Iax2HangupInfo(); + void ToString(CStdString& string); + /* Even if we did not have the destination call number, + * we will be able to look it up using m_byDestIpAndCallNo */ + struct in_addr m_senderIp; + struct in_addr m_receiverIp; + + CStdString m_sender_callno; + CStdString m_receiver_callno; +}; +typedef boost::shared_ptr<Iax2HangupInfo> Iax2HangupInfoRef; + +#define IAX2_FRAME_FULL 1 +#define IAX2_FRAME_MINI 2 +#define IAX2_FRAME_TRUNK 3 + +/* IAX2_FRAME_VOICE Packet Information. May be full or mini.*/ +class Iax2PacketInfo +{ +public: + Iax2PacketInfo(); + void ToString(CStdString& string); + + struct in_addr m_sourceIp; + struct in_addr m_destIp; + unsigned short m_sourcecallno; /* Always there */ + unsigned short m_destcallno; /* May be zero for mini frames */ + + unsigned int m_frame_type; + unsigned int m_payloadSize; + unsigned short m_payloadType; + unsigned char* m_payload; + unsigned short m_seqNum; + unsigned int m_timestamp; + time_t m_arrivalTimestamp; +}; +typedef boost::shared_ptr<Iax2PacketInfo> Iax2PacketInfoRef; + +// ============================================================ + +#define IAX2_STATE_WAITING 1 +#define IAX2_STATE_LINKED 2 +#define IAX2_STATE_UP 3 + +class Iax2Session +{ +public: + Iax2Session(CStdString& trackingId); +#define IAX2_PROTOCOL_NUM 1 +#define IAX2_PROTOCOL_STR "IAX2" + void Stop(); + void Start(); + bool AddIax2Packet(Iax2PacketInfoRef& iax2Packet); + void ReportIax2New(Iax2NewInfoRef& newinfo); + void ReportIax2Accept(Iax2AcceptInfoRef& acceptinfo); + void ReportIax2Authreq(Iax2AuthreqInfoRef& authreq); + + CStdString m_trackingId; + CStdString m_srcIpAndCallNo; + CStdString m_destIpAndCallNo; + Iax2NewInfoRef m_new; + time_t m_lastUpdated; + CStdString m_localParty; + CStdString m_remoteParty; + CaptureEvent::DirectionEnum m_direction; + int m_numIax2Packets; + int m_codec; + int m_iax2_state; + +private: + /* XXX Way of "freeing" sessions which've been idle for a + * while? Just in case they never hang up. Answer is Hoover() */ + void ProcessMetadataIax2(Iax2PacketInfoRef&); + void ProcessMetadataIax2Incoming(); + void ProcessMetadataIax2Outgoing(); + void UpdateMetadataIax2(Iax2PacketInfoRef& iax2Packet, bool); + void ProcessMetadataRawIax2(Iax2PacketInfoRef&); + void ProcessMetadataSkinny(Iax2PacketInfoRef& iax2Packet); + void ReportMetadata(); + void GenerateOrkUid(); + + Iax2PacketInfoRef m_lastIax2Packet; + Iax2PacketInfoRef m_lastIax2PacketSide1; + Iax2PacketInfoRef m_lastIax2PacketSide2; + + struct in_addr m_invitorIp; + struct in_addr m_inviteeIp; + struct in_addr m_localIp; + struct in_addr m_remoteIp; + + unsigned short m_invitor_scallno; + unsigned short m_invitee_scallno; + + //struct in_addr m_localMac; + //struct in_addr m_remoteMac; + LoggerPtr m_log; + CStdString m_capturePort; + bool m_started; + bool m_stopped; + time_t m_beginDate; + CStdString m_orkUid; + bool m_hasDuplicateIax2; + unsigned int m_highestIax2SeqNumDelta; + double m_minIax2SeqDelta; + double m_minIax2TimestampDelta; + //TcpAddressList m_iax2AddressList; + std::list<Iax2NewInfoRef> m_news; +}; +typedef boost::shared_ptr<Iax2Session> Iax2SessionRef; + +//=================================================================== +class Iax2Sessions +{ +public: + Iax2Sessions(); + void Stop(Iax2SessionRef& session); + void StopAll(); + void ReportIax2New(Iax2NewInfoRef& invite); + void ReportIax2Hangup(Iax2HangupInfoRef& hangup); + void ReportIax2Accept(Iax2AcceptInfoRef& acceptinfo); + void ReportIax2Authreq(Iax2AuthreqInfoRef& authreq); + void ReportIax2Packet(Iax2PacketInfoRef& iax2Packet); + void Hoover(time_t now); +private: + std::map<CStdString, Iax2SessionRef> m_bySrcIpAndCallNo; /* With IAX2 the callnos can easily + * be duplicated across machines + * so we need to use the ip and callno */ + std::map<CStdString, Iax2SessionRef> m_byDestIpAndCallNo; /* Invitee Ip and Call Id */ + LoggerPtr m_log; + AlphaCounter m_alphaCounter; +}; +typedef ACE_Singleton<Iax2Sessions, ACE_Thread_Mutex> Iax2SessionsSingleton; + +/* Miscellaneous */ +static inline unsigned int get_unaligned_uint32(void *p) +{ + struct pckd { unsigned int d; } __attribute__((packed)); + struct pckd *pp = (struct pckd *)p; + + return pp->d; +} + +#endif + diff --git a/orkaudio/audiocaptureplugins/voip/Makefile.am b/orkaudio/audiocaptureplugins/voip/Makefile.am index 6fe21b1..5ea43a8 100644 --- a/orkaudio/audiocaptureplugins/voip/Makefile.am +++ b/orkaudio/audiocaptureplugins/voip/Makefile.am @@ -1,6 +1,6 @@ METASOURCES = AUTO lib_LTLIBRARIES = libvoip.la -libvoip_la_SOURCES = VoIpConfig.cpp VoIp.cpp Rtp.cpp RtpSession.cpp \ +libvoip_la_SOURCES = VoIpConfig.cpp VoIp.cpp Rtp.cpp Iax2Session.cpp RtpSession.cpp \ AudioCapturePluginCommon.cpp PacketHeaderDefs.cpp libvoip_la_LDFLAGS = -module AM_CPPFLAGS = -D_REENTRANT diff --git a/orkaudio/audiocaptureplugins/voip/PacketHeaderDefs.h b/orkaudio/audiocaptureplugins/voip/PacketHeaderDefs.h index efa959a..654ba95 100644 --- a/orkaudio/audiocaptureplugins/voip/PacketHeaderDefs.h +++ b/orkaudio/audiocaptureplugins/voip/PacketHeaderDefs.h @@ -215,5 +215,54 @@ typedef enum int SkinnyMessageToEnum(CStdString& msg); CStdString SkinnyMessageToString(int msgEnum); +/* + * IAX2 Packet Headers + */ + +/* XXX No support for encryption */ + +/* Full frame */ +struct Iax2FullHeader { + unsigned short scallno; + unsigned short dcallno; + unsigned int ts; + unsigned char oseqno; + unsigned char iseqno; + unsigned char type; + unsigned char c_sub; + unsigned char ie_data[0]; +} __attribute__ ((__packed__)); + +/* Mini frame for voice */ +struct Iax2MiniHeader { + unsigned short scallno; + unsigned short ts; /* Low 16 bits from transmitting peer's full 32-bit ts */ + unsigned char data[0]; +} __attribute__ ((__packed__)); + +/* Meta trunk frame */ +struct Iax2MetaTrunkHeader { + unsigned short meta; /* zero for meta frames */ + unsigned char metacmd; + unsigned char cmddata; + unsigned int ts; + unsigned char data[0]; +} __attribute__ ((__packed__)); + +/* Mini trunked frame with timestamps (trunk timestamps + * flag is set to 0 */ +struct Iax2MetaTrunkEntry { + unsigned short scallno; + unsigned short len; + unsigned char data[0]; +} __attribute__ ((__packed__)); + +/* Mini trunked frame with timestamps (trunk timestamps + * flag is set to 1 */ +struct Iax2MetaTrunkEntryTs { + unsigned short len; + struct Iax2MiniHeader mini; +} __attribute__ ((__packed__)); + #endif diff --git a/orkaudio/audiocaptureplugins/voip/VoIp.cpp b/orkaudio/audiocaptureplugins/voip/VoIp.cpp index ab3de80..ef6807f 100644 --- a/orkaudio/audiocaptureplugins/voip/VoIp.cpp +++ b/orkaudio/audiocaptureplugins/voip/VoIp.cpp @@ -37,6 +37,7 @@ #include "PacketHeaderDefs.h" #include "Rtp.h" #include "RtpSession.h" +#include "Iax2Session.h" extern AudioChunkCallBackFunction g_audioChunkCallBack; extern CaptureEventCallBackFunction g_captureEventCallBack; @@ -204,11 +205,765 @@ char* GrabLine(char* start, char* limit, CStdString& out) return c; } +static int iax2_codec_to_rtp_payloadtype(int codec) +{ + switch(codec) { + case IAX2_CODEC_ULAW: + return 0; + case IAX2_CODEC_G726: + return 2; + case IAX2_CODEC_GSM: + return 3; + case IAX2_CODEC_G723_1: + return 4; + case IAX2_CODEC_ADPCM: + return 5; + case IAX2_CODEC_LPC10: + return 7; + case IAX2_CODEC_ALAW: + return 8; + case IAX2_CODEC_SLINEAR: + return 9; + case IAX2_CODEC_G729A: + return 18; + case IAX2_CODEC_ILBC: + return 97; + default: + return -1; + } + + /* NOT REACHED */ + return -1; +} + +static int get_uncompressed_subclass(unsigned char c_sub) +{ + if (c_sub & 0x80) { + /* 'C' bit is set (refer to standard) */ + if (c_sub == 0xFF) + return -1; + else + return 1 << (c_sub & ~0x80 & 0x1F); + } else { + /* 'C' bit in SubClass component not set */ + return c_sub; + } +} + +static int parse_iax2_ies(struct iax2_ies *ies, unsigned char *data, int datalen) +{ + int len = 0, ie = 0, odlen = datalen, pass=1; + CStdString logmsg; + + memset(ies, 0, (int)sizeof(struct iax2_ies)); + while(datalen >= 2) { + ie = data[0]; + len = data[1]; + + //logmsg.Format("Looking up IE %d (len=%d)", ie, len); + //LOG4CXX_INFO(s_packetLog, logmsg); + + if (len > datalen - 2) { + /* Strange. The quoted length of the IE is past the actual + * bounds of the IEs size */ + logmsg.Format("Error parsing IEs Pass=%d Length of IE=%d, " + "datalen-2=%d, IE=%d, OrigDlen=%d", pass, len, datalen-2, ie, odlen); + LOG4CXX_INFO(s_packetLog, logmsg); + return -1; + } + + switch(ie) { + case IAX2_IE_CALLED_NUMBER: + ies->callee = (char *)data + 2; + break; + case IAX2_IE_CALLING_NUMBER: + ies->caller = (char *)data + 2; + break; + case IAX2_IE_FORMAT: + if(len == (int)sizeof(unsigned int)) + ies->format = ntohl(get_unaligned_uint32(data+2)); + else + ies->format = 0; /* Invalid */ + break; + case IAX2_IE_USERNAME: + ies->username = (char *)data + 2; + break; + case IAX2_IE_AUTHMETHODS: + if(len == (int)sizeof(unsigned int)) + ies->authmethods = ntohl(get_unaligned_uint32(data+2)); + else + ies->authmethods = 0; /* Invalid */ + break; + case IAX2_IE_CHALLENGE: + ies->challenge = (char *)data + 2; + break; + default: + /* Ignore the rest */ + break; + } + +#if 0 /* Debug headaches caused by udpHeader->len */ + char tmpt[256]; + memset(tmpt, 0, sizeof(tmpt)); + memcpy(tmpt, data+2, len); + logmsg.Format("Got %s", tmpt); + LOG4CXX_INFO(s_packetLog, logmsg); +#endif + + data[0] = 0; + datalen -= (len + 2); + data += (len + 2); + pass++; + } + + *data = '\0'; + if(datalen) { + /* IE contents likely to be invalid because we should have totally + * consumed the entire amount of data */ + CStdString logmsg; + + logmsg.Format("Error parsing IEs. datalen left=%d", len, datalen, ie); + LOG4CXX_INFO(s_packetLog, logmsg); + + return -1; + } + + return 0; +} + +void iax2_dump_frame(struct Iax2FullHeader *fh, char *source, char *dest) +{ + const char *frames[] = { + "(0?)", + "DTMF ", + "VOICE ", + "VIDEO ", + "CONTROL", + "NULL ", + "IAX ", + "TEXT ", + "IMAGE ", + "HTML ", + "CNG " }; + const char *iaxs[] = { + "(0?)", + "NEW ", + "PING ", + "PONG ", + "ACK ", + "HANGUP ", + "REJECT ", + "ACCEPT ", + "AUTHREQ", + "AUTHREP", + "INVAL ", + "LAGRQ ", + "LAGRP ", + "REGREQ ", + "REGAUTH", + "REGACK ", + "REGREJ ", + "REGREL ", + "VNAK ", + "DPREQ ", + "DPREP ", + "DIAL ", + "TXREQ ", + "TXCNT ", + "TXACC ", + "TXREADY", + "TXREL ", + "TXREJ ", + "QUELCH ", + "UNQULCH", + "POKE ", + "PAGE ", + "MWI ", + "UNSPRTD", + "TRANSFR", + "PROVISN", + "FWDWNLD", + "FWDATA " + }; + const char *cmds[] = { + "(0?)", + "HANGUP ", + "RING ", + "RINGING", + "ANSWER ", + "BUSY ", + "TKOFFHK", + "OFFHOOK" }; + char class2[20]; + char subclass2[20]; + CStdString tmp; + const char *cclass; + const char *subclass; + char *dir; + + if (fh->type >= (int)sizeof(frames)/(int)sizeof(frames[0])) { + snprintf(class2, sizeof(class2), "(%d?)", fh->type); + cclass = class2; + } else { + cclass = frames[(int)fh->type]; + } + + if (fh->type == IAX2_FRAME_IAX) { + if (fh->c_sub >= (int)sizeof(iaxs)/(int)sizeof(iaxs[0])) { + snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->c_sub); + subclass = subclass2; + } else { + subclass = iaxs[(int)fh->c_sub]; + } + } else if (fh->type == IAX2_FRAME_CONTROL) { + if (fh->c_sub >= (int)sizeof(cmds)/(int)sizeof(cmds[0])) { + snprintf(subclass2, sizeof(subclass2), "(%d?)", fh->c_sub); + subclass = subclass2; + } else { + subclass = cmds[(int)fh->c_sub]; + } + } else { + snprintf(subclass2, sizeof(subclass2), "%d", fh->c_sub); + subclass = subclass2; + } + + tmp.Format("IAX2-Frame -- OSeqno: %3.3d ISeqno: %3.3d Type: %s Subclass: %s", + fh->oseqno, fh->iseqno, cclass, subclass); + LOG4CXX_INFO(s_packetLog, tmp); + tmp.Format(" Timestamp: %05lums SCall: %5.5d DCall: %5.5d [Source: %s Dest: %s]", + (unsigned long)ntohl(fh->ts), + ntohs(fh->scallno) & ~0x8000, ntohs(fh->dcallno) & ~0x8000, source, dest); + + LOG4CXX_INFO(s_packetLog, tmp); +} + +/* See if this is an IAX2 NEW. If so, process */ +bool TryIax2New(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + struct iax2_ies ies; + int ies_len = 0, udp_act_payload_len = 0; + Iax2NewInfoRef info(new Iax2NewInfo()); + char source_ip[16], dest_ip[16]; + CStdString logmsg; + + memset(&ies, 0, sizeof(ies)); + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + ies_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + +#if 0 /* Debug headaches caused by udpHeader->len */ + /* Beware that udpHeader->len is not the length of the udpPayload + * but rather this includes the length of the UDP header as well. + * I.e watch out for the figure "8" as you debug ;-) */ + { + char source_ip[16], dest_ip[16]; + + ACE_OS::inet_ntop(AF_INET, (void*)&ipHeader->ip_src, source_ip, sizeof(source_ip)); + ACE_OS::inet_ntop(AF_INET, (void*)&ipHeader->ip_dest, dest_ip, sizeof(dest_ip)); + iax2_dump_frame(fh, source_ip, dest_ip); + } + + logmsg.Format("UDP_Payload=%p UDP+FH_Payload=%p FH->IEDATA=%p ies_len=%d " + "udpHeader->len-sizeof(fullhdr)=%d (ntohs(udpHeader->len)" + "-sizeof(UdpHeaderStruct))=%d", udpPayload, udpPayload+ + sizeof(*fh), fh->ie_data, ies_len, (ntohs(udpHeader->len)- + sizeof(*fh)), (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct))); + LOG4CXX_INFO(s_packetLog, logmsg); +#endif + + if(fh->type != IAX2_FRAME_IAX) + return false; /* Frame type must be IAX */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_COMMAND_NEW) + return false; /* Subclass must be NEW */ + + if(parse_iax2_ies(&ies, fh->ie_data, ies_len)) + return false; /* Invalid "full" frame received */ + + if(!ies.callee) + return false; /* According to the SPEC, a NEW MUST have a + * callee (Called Number) */ + + if(!strlen(ies.callee)) + return false; /* According to the SPEC, a NEW MUST have a + * callee (Called Number) */ + + if(!ies.caller) { + ies.caller = "WITHELD"; + } else { + if(!strlen(ies.caller)) { + ies.caller = "WITHELD"; + } + } + + /* Statistically this is most likely a NEW IAX2 frame. */ + + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_caller = CStdString(ies.caller); + info->m_callee = CStdString(ies.callee); + info->m_callNo = IntToString(ntohs(fh->scallno) & ~0x8000); + + /* Report the packet */ + Iax2SessionsSingleton::instance()->ReportIax2New(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 NEW frame"); + + return true; +} + +bool TryIax2Accept(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + struct iax2_ies ies; + int ies_len = 0, udp_act_payload_len = 0; + Iax2AcceptInfoRef info(new Iax2AcceptInfo()); + + memset(&ies, 0, sizeof(ies)); + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_IAX) + return false; /* Frame type must be IAX */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_COMMAND_ACCEPT) + return false; /* Subclass must be ACCEPT */ + + ies_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + + /* In this case, this just serves to test the integrity of the + * Information Elements */ + if(parse_iax2_ies(&ies, fh->ie_data, ies_len)) + return false; /* Invalid "full" frame received */ + + if(!ies.format) + return false; /* According to the SPEC, ACCEPT must have + * a format specified */ + + /* We have an ACCEPT */ + + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_sender_callno = IntToString(ntohs(fh->scallno) & ~0x8000); + info->m_receiver_callno = IntToString(ntohs(fh->dcallno) & ~0x8000); + + Iax2SessionsSingleton::instance()->ReportIax2Accept(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 ACCEPT frame"); + + return true; +} + +bool TryIax2Authreq(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + struct iax2_ies ies; + int ies_len = 0, udp_act_payload_len = 0; + Iax2AuthreqInfoRef info(new Iax2AuthreqInfo()); + + memset(&ies, 0, sizeof(ies)); + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_IAX) + return false; /* Frame type must be IAX */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_COMMAND_AUTHREQ) + return false; /* Subclass must be AUTHREQ */ + + ies_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + + /* In this case, this just serves to test the integrity of the + * Information Elements */ + if(parse_iax2_ies(&ies, fh->ie_data, ies_len)) + return false; /* Invalid "full" frame received */ + + + if(!ies.username) + return false; /* According to the spec AUTHREQ must have + * a user name. Can it be empty? */ + + /* + if(!strlen(ies.username)) + return false; * According to the spec AUTHREQ must have + * a user name. * + */ + + if(!ies.authmethods) + return false; /* According to the spec AUTHREQ must have + * AUTHMETHODS */ + + if(!ies.challenge) + return false; /* According to the spec, AUTHREQ must have + * a CHALLENGE string. Can it be empty? */ + + + /* We have an AUTHREQ */ + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_sender_callno = IntToString(ntohs(fh->scallno) & ~0x8000); + info->m_receiver_callno = IntToString(ntohs(fh->dcallno) & ~0x8000); + + /* Report the packet */ + Iax2SessionsSingleton::instance()->ReportIax2Authreq(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 AUTHREQ frame"); + + return true; +} + +/* HANGUP via IAX frame */ +bool TryIax2Hangup(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + struct iax2_ies ies; + int ies_len = 0, udp_act_payload_len = 0; + Iax2HangupInfoRef info(new Iax2HangupInfo()); + + memset(&ies, 0, sizeof(ies)); + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_IAX) + return false; /* Frame type must be IAX */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_COMMAND_HANGUP) + return false; /* Subclass must be HANGUP */ + + ies_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + + /* In this case, this just serves to test the integrity of the + * Information Elements */ + if(parse_iax2_ies(&ies, fh->ie_data, ies_len)) + return false; /* Invalid "full" frame received */ + + /* We have a HANGUP */ + + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_sender_callno = IntToString(ntohs(fh->scallno) & ~0x8000); + info->m_receiver_callno = IntToString(ntohs(fh->dcallno) & ~0x8000); + + /* Report the packet */ + Iax2SessionsSingleton::instance()->ReportIax2Hangup(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 HANGUP frame"); + + return true; +} + +/* HANGUP via CONTROL frame */ +bool TryIax2ControlHangup(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + Iax2HangupInfoRef info(new Iax2HangupInfo()); + int udp_act_payload_len = 0; + + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_CONTROL) + return false; /* Frame type must be CONTROL */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_CONTROL_HANGUP) + return false; /* Subclass must be HANGUP */ + + /* We have a HANGUP */ + + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_sender_callno = IntToString(ntohs(fh->scallno) & ~0x8000); + info->m_receiver_callno = IntToString(ntohs(fh->dcallno) & ~0x8000); + + /* Report the packet */ + Iax2SessionsSingleton::instance()->ReportIax2Hangup(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 CONTROL HANGUP frame"); + + return true; +} + +bool TryIax2Reject(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + struct iax2_ies ies; + int ies_len = 0, udp_act_payload_len = 0; + Iax2HangupInfoRef info(new Iax2HangupInfo()); + + memset(&ies, 0, sizeof(ies)); + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_IAX) + return false; /* Frame type must be IAX */ + + if(get_uncompressed_subclass(fh->c_sub) != IAX2_COMMAND_REJECT) + return false; /* Subclass must be REJECT */ + + ies_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + + /* In this case, this just serves to test the integrity of the + * Information Elements */ + if(parse_iax2_ies(&ies, fh->ie_data, ies_len)) + return false; /* Invalid "full" frame received */ + + /* We have a REJECT */ + + info->m_senderIp = ipHeader->ip_src; + info->m_receiverIp = ipHeader->ip_dest; + info->m_sender_callno = IntToString(ntohs(fh->scallno) & ~0x8000); + info->m_receiver_callno = IntToString(ntohs(fh->dcallno) & ~0x8000); + + /* Report the packet */ + Iax2SessionsSingleton::instance()->ReportIax2Hangup(info); + + LOG4CXX_INFO(s_packetLog, "Processed IAX2 REJECT frame"); + + return true; +} + +bool TryIax2FullVoiceFrame(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2FullHeader *fh = (struct Iax2FullHeader *)udpPayload; + int data_len = 0, codec = 0, pt = 0, udp_act_payload_len = 0; + Iax2PacketInfoRef info(new Iax2PacketInfo()); + + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*fh)) + return false; /* Frame too small */ + + if(!(ntohs(fh->scallno) & 0x8000)) + return false; /* Not a full frame */ + + if(fh->type != IAX2_FRAME_VOICE) + return false; /* Frame type must be VOICE */ + + codec = get_uncompressed_subclass(fh->c_sub); + if((pt = iax2_codec_to_rtp_payloadtype(codec)) < 0) { + CStdString logmsg; + + logmsg.Format("Invalid payload type %d received for " + "IAX_FRAME_VOICE, IAX2 codec %d", pt, codec); + LOG4CXX_INFO(s_packetLog, logmsg); + return false; /* Invalid codec type received */ + } + + data_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*fh)); + if(data_len == 0) + return false; /* Empty packet? */ + + /* We have a full VOICE frame */ + + info->m_sourceIp = ipHeader->ip_src; + info->m_destIp = ipHeader->ip_dest; + info->m_sourcecallno = (ntohs(fh->scallno) & ~0x8000); + info->m_destcallno = (ntohs(fh->dcallno) & ~0x8000); + info->m_payloadSize = data_len; + info->m_payload = udpPayload+sizeof(*fh); + info->m_payloadType = pt; + info->m_timestamp = ntohl(fh->ts); + info->m_arrivalTimestamp = time(NULL); + info->m_frame_type = IAX2_FRAME_FULL; + + Iax2SessionsSingleton::instance()->ReportIax2Packet(info); + + CStdString logmsg; + logmsg.Format("Processed IAX2 FULL VOICE fram, pt %d", pt); + LOG4CXX_INFO(s_packetLog, logmsg); + + return true; +} + +bool TryIax2MetaTrunkFrame(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2MetaTrunkHeader *mh = (struct Iax2MetaTrunkHeader *)udpPayload; + struct Iax2MetaTrunkEntry *supermini = NULL; + struct Iax2MetaTrunkEntryTs *mini = NULL; + int content_type = 0; /* 0 means mini frames, 1 means super mini (no timestampes) */ + int frame_ts = 0; /* Timestamp of frame */ + int data_len = 0; + int entries = 0, udp_act_payload_len = 0; + Iax2PacketInfoRef info(new Iax2PacketInfo()); + + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*mh)) + return false; /* Frame too small */ + + if(mh->meta != 0) + return false; /* Must be zero */ + + if(mh->metacmd & 0x80) + return false; /* 'V' bit must be set to zero */ + + if(mh->metacmd != 1) + return false; /* metacmd must be 1 */ + + /* Get the length of the information apart from the + * meta trunk header */ + data_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*mh)); + if(data_len == 0) + return false; /* Empty packet? */ + + /* Step over the meta trunk header */ + udpPayload += sizeof(*mh); + + /* Determine whether the trunk contents have their own + * timestamps or not */ + if(mh->cmddata & 0x01) + content_type = 1; + else + content_type = 0; + + if(content_type) { + /* Have timestamps */ + + while(data_len) { + if(data_len < sizeof(*mini)) + break; + + mini = (struct Iax2MetaTrunkEntryTs *)udpPayload; + + if(data_len < sizeof(*mini)+ntohs(mini->len)) + break; + + info->m_sourceIp = ipHeader->ip_src; + info->m_destIp = ipHeader->ip_dest; + info->m_sourcecallno = (ntohs(mini->mini.scallno) & ~0x8000); + info->m_destcallno = 0; + info->m_payloadSize = ntohs(mini->len); + info->m_payload = udpPayload+sizeof(*mini); + info->m_payloadType = 0; + info->m_timestamp = ntohs(mini->mini.ts); + info->m_arrivalTimestamp = time(NULL); + info->m_frame_type = IAX2_FRAME_MINI; + + Iax2SessionsSingleton::instance()->ReportIax2Packet(info); + entries += 1; + + udpPayload += sizeof(*mini)+ntohs(mini->len); + data_len -= sizeof(*mini)+ntohs(mini->len); + } + } else { + /* Have no timestamps */ + while(data_len) { + if(data_len < sizeof(*supermini)) + break; + + supermini = (struct Iax2MetaTrunkEntry *)udpPayload; + + if(data_len < sizeof(*supermini)+ntohs(supermini->len)) + break; + + info->m_sourceIp = ipHeader->ip_src; + info->m_destIp = ipHeader->ip_dest; + info->m_sourcecallno = (ntohs(supermini->scallno) & ~0x8000); + info->m_destcallno = 0; + info->m_payloadSize = ntohs(supermini->len); + info->m_payload = udpPayload+sizeof(*supermini); + info->m_payloadType = 0; + info->m_timestamp = 0; + info->m_arrivalTimestamp = time(NULL); + info->m_frame_type = IAX2_FRAME_MINI; + + Iax2SessionsSingleton::instance()->ReportIax2Packet(info); + entries += 1; + + udpPayload += sizeof(*supermini)+ntohs(supermini->len); + data_len -= sizeof(*supermini)+ntohs(supermini->len); + } + } + + + if(entries > 0) { + CStdString logmsg; + + logmsg.Format("Processed IAX2 Meta Trunk packet with %d entries", entries); + LOG4CXX_DEBUG(s_packetLog, logmsg); + return true; + } + + return false; /* No valid entries in this so-called meta trunk frame */ +} + +bool TryIax2MiniVoiceFrame(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, + UdpHeaderStruct* udpHeader, u_char* udpPayload) +{ + struct Iax2MiniHeader *mini = (struct Iax2MiniHeader *)udpPayload; + int data_len = 0, udp_act_payload_len = 0; + Iax2PacketInfoRef info(new Iax2PacketInfo()); + + udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + if(udp_act_payload_len < sizeof(*mini)) + return false; /* Frame too small */ + + if((ntohs(mini->scallno) & 0x8000)) + return false; /* Not a Mini frame */ + + data_len = ((u_char*)ipHeader+ntohs(ipHeader->ip_len))-(udpPayload+sizeof(*mini)); + if(data_len == 0) + return false; /* Empty packet? */ + + info->m_sourceIp = ipHeader->ip_src; + info->m_destIp = ipHeader->ip_dest; + info->m_sourcecallno = (ntohs(mini->scallno) & ~0x8000); + info->m_destcallno = 0; + info->m_payloadSize = data_len; + info->m_payload = udpPayload+sizeof(*mini); + info->m_payloadType = 0; + info->m_timestamp = ntohl(mini->ts); + info->m_arrivalTimestamp = time(NULL); + info->m_frame_type = IAX2_FRAME_MINI; + + Iax2SessionsSingleton::instance()->ReportIax2Packet(info); + + //LOG4CXX_INFO(s_packetLog, "Processed IAX2 Mini Voice packet"); + + return true; +} + bool TryRtp(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, UdpHeaderStruct* udpHeader, u_char* udpPayload) { bool result = false; RtpHeaderStruct* rtpHeader = (RtpHeaderStruct*)udpPayload; + /* Ensure that the UDP payload is at least sizeof(RtpHeaderStruct) */ + if(ntohs(udpHeader->len) < sizeof(RtpHeaderStruct)) + return false; + if (rtpHeader->version == 2) { u_short source = ntohs(udpHeader->source); @@ -268,6 +1023,13 @@ bool TryRtp(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, UdpH bool TrySipBye(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader, UdpHeaderStruct* udpHeader, u_char* udpPayload) { bool result = false; + int udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + + /* Judgine from the memcmp() below, we need the UDP payload + * length to be at least 3 bytes. -- Gerald */ + if(udp_act_payload_len < 3) + return false; /* Frame too small */ + if (memcmp("BYE", (void*)udpPayload, 3) == 0) { result = true; @@ -292,6 +1054,13 @@ bool TrySipInvite(EthernetHeaderStruct* ethernetHeader, IpHeaderStruct* ipHeader { bool result = false; bool drop = false; + int udp_act_payload_len = (ntohs(udpHeader->len)-sizeof(UdpHeaderStruct)); + + /* Judgine from the memcmp() below, we need the UDP payload + * length to be at least 3 bytes. -- Gerald */ + if(udp_act_payload_len < 6) + return false; /* Frame too small */ + if (memcmp("INVITE", (void*)udpPayload, 6) == 0) { result = true; @@ -655,21 +1424,66 @@ void HandlePacket(u_char *param, const struct pcap_pkthdr *header, const u_char { UdpHeaderStruct* udpHeader = (UdpHeaderStruct*)((char *)ipHeader + ipHeaderLength); - if( ntohs(udpHeader->source) > 1024 && ntohs(udpHeader->dest) > 1024 ) - { + if(ntohs(udpHeader->source) > 1024 && ntohs(udpHeader->dest) > 1024) { + bool detectedUsefulPacket = false; u_char* udpPayload = (u_char *)udpHeader + sizeof(UdpHeaderStruct); - MutexSentinel mutexSentinel(s_mutex); // serialize access for competing pcap threads + MutexSentinel mutexSentinel(s_mutex); // serialize access for competing pcap threads + + detectedUsefulPacket = TryIax2New(ethernetHeader, ipHeader, udpHeader, udpPayload); + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2Accept(ethernetHeader, ipHeader, udpHeader, + udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2Authreq(ethernetHeader, ipHeader, udpHeader, + udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2Hangup(ethernetHeader, ipHeader, udpHeader, + udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2ControlHangup(ethernetHeader, ipHeader, udpHeader, + udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2Reject(ethernetHeader, ipHeader, udpHeader, + udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2FullVoiceFrame(ethernetHeader, ipHeader, + udpHeader, udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2MetaTrunkFrame(ethernetHeader, ipHeader, + udpHeader, udpPayload); + } + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryIax2MiniVoiceFrame(ethernetHeader, ipHeader, + udpHeader, udpPayload); + } - bool detectedUsefulPacket = TryRtp(ethernetHeader, ipHeader, udpHeader, udpPayload); + if(!detectedUsefulPacket) { + detectedUsefulPacket = TryRtp(ethernetHeader, ipHeader, udpHeader, udpPayload); + } - if(!detectedUsefulPacket) - { - detectedUsefulPacket= TrySipInvite(ethernetHeader, ipHeader, udpHeader, udpPayload); + if(!detectedUsefulPacket) { + detectedUsefulPacket= TrySipInvite(ethernetHeader, ipHeader, udpHeader, + udpPayload); } - if(!detectedUsefulPacket) - { - detectedUsefulPacket = TrySipBye(ethernetHeader, ipHeader, udpHeader, udpPayload); + + if(!detectedUsefulPacket) { + detectedUsefulPacket = TrySipBye(ethernetHeader, ipHeader, udpHeader, + udpPayload); } } } @@ -711,6 +1525,7 @@ void HandlePacket(u_char *param, const struct pcap_pkthdr *header, const u_char MutexSentinel mutexSentinel(s_mutex); // serialize access for competing pcap threads s_lastHooveringTime = now; RtpSessionsSingleton::instance()->Hoover(now); + Iax2SessionsSingleton::instance()->Hoover(now); } } diff --git a/orkaudio/audiocaptureplugins/voip/VoIp.dsp b/orkaudio/audiocaptureplugins/voip/VoIp.dsp index cc39490..655dde1 100644 --- a/orkaudio/audiocaptureplugins/voip/VoIp.dsp +++ b/orkaudio/audiocaptureplugins/voip/VoIp.dsp @@ -124,6 +124,14 @@ SOURCE=.\Rtp.h # End Source File # Begin Source File +SOURCE=.\Iax2Session.cpp +# End Source File +# Begin Source File + +SOURCE=.\Iax2Session.h +# End Source File +# Begin Source File + SOURCE=.\RtpSession.cpp # End Source File # Begin Source File |