serval-dna/vomp.c
gardners a29a1283f7 More work towards integrating VoMP (creating unix domain socket
interface for call control/audio handling).
2012-04-19 21:54:36 +09:30

415 lines
16 KiB
C

/*
Serval Voice Over Mesh Protocol (VoMP)
Copyright (C) 2012 Paul Gardner-Stephen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
VoMP works using a 5-state model of a phone call, and relies on MDP for
auth-cryption of frames. VoMP provides it's own replay protection.
*/
#include "serval.h"
/* Although we only support one call at a time, we allow for multiple call states.
This is partly to deal with denial of service attacks that might occur by causing
the ejection of newly allocated session numbers before the caller has had a chance
to progress the call to a further state. */
int vomp_call_count=0;
#define VOMP_MAX_CALLS 16
vomp_call_state vomp_call_states[VOMP_MAX_CALLS];
vomp_call_state *vomp_find_or_create_call(unsigned char *remote_sid,
unsigned char *local_sid,
unsigned int sender_session,
unsigned int recvr_session)
{
int expired_slot=-1;
int i;
for(i=0;i<vomp_call_count;i++)
{
/* do the fast comparison first, and only if that matches proceed to
the slower SID comparisons */
if (sender_session&(sender_session!=vomp_call_states[i].remote.session))
continue;
if (recvr_session&(recvr_session!=vomp_call_states[i].local.session))
continue;
if (memcmp(remote_sid,vomp_call_states[i].remote.sid,SID_SIZE)) continue;
if (memcmp(local_sid,vomp_call_states[i].local.sid,SID_SIZE)) continue;
/* it matches. but has it expired (no activity in 120 seconds)? */
if (vomp_call_states[i].last_activity<(overlay_gettime_ms()-120000))
{
expired_slot=i;
continue;
}
return &vomp_call_states[i];
}
/* not in the list. So allocate a slot. */
if (expired_slot>-1) i=expired_slot;
else if ((i<VOMP_MAX_CALLS)&&(i==vomp_call_count)) {
/* there is room to allocate another, so do that */
vomp_call_count++;
} else {
/* no room, either reallocate an existing slot, or fail.
We try to reuse slots that either mark ended calls, or
*/
int candidates[VOMP_MAX_CALLS];
int candidate_count=0;
for(i=0;i<VOMP_MAX_CALLS;i++)
if ((vomp_call_states[i].local.state==VOMP_STATE_NOCALL)||
(vomp_call_states[i].local.state==VOMP_STATE_CALLENDED))
candidates[candidate_count++]=i;
i=candidates[random()%candidate_count];
}
/* prepare slot */
bzero(&vomp_call_states[i],sizeof(vomp_call_state));
bcopy(local_sid,&vomp_call_states[i].local.sid,SID_SIZE);
bcopy(remote_sid,&vomp_call_states[i].remote.sid,SID_SIZE);
if (!recvr_session) {
urandombytes((unsigned char *)&recvr_session,sizeof(int));
recvr_session&=VOMP_SESSION_MASK;
}
if (!sender_session) {
urandombytes((unsigned char *)&sender_session,sizeof(int));
sender_session&=VOMP_SESSION_MASK;
}
vomp_call_states[i].local.session=recvr_session;
vomp_call_states[i].remote.session=sender_session;
vomp_call_states[i].local.state=VOMP_STATE_NOCALL;
vomp_call_states[i].remote.state=VOMP_STATE_NOCALL;
return &vomp_call_states[i];
}
int vomp_send_status(vomp_call_state *call)
{
return WHY("Not implemented");
}
int vomp_call_start_audio(vomp_call_state *call)
{
return WHY("Not implemented");
}
int vomp_process_audio(vomp_call_state *call,overlay_mdp_frame *mdp)
{
// int first_frame_codec=mdp->in.payload[3];
// int recvr_seq=(mdp->in.payload[4]<<8)+mdp->in.payload[5];
// int sender_seq=(mdp->in.payload[4]<<8)+mdp->in.payload[5];
// unsigned int sender_millis=
// (mdp->in.payload[12]<<16)+(mdp->in.payload[13]<<8)+mdp->in.payload[14];
return WHY("Not implemented");
}
int vomp_call_stop_audio(vomp_call_state *call)
{
return WHY("Not implemented");
}
int vomp_call_start_ringing(vomp_call_state *call)
{
return WHY("Not implemented");
}
int vomp_call_rejected(vomp_call_state *call)
{
return WHY("Not implemented");
}
int vomp_call_error(vomp_call_state *call)
{
if (call->audio_started) vomp_call_stop_audio(call);
return WHY("Not implemented");
}
int vomp_call_destroy(vomp_call_state *call)
{
if (call->audio_started) vomp_call_stop_audio(call);
return WHY("Not implemented");
}
/* An MDP message of type MDP_VOMPEVENT received from the unix domain socket.
This is how user tasks request telephone calls and receive updated status
and audio from the call. We need the receiver socket so that we can
route the events back to wherever they should be going.
XXX - We should have some means of authenticating/protecting this interface
so that any old process cannot request a mesh call. Although, in fairness,
the user will know about the call because the call display will come up.
*/
int vomp_mdp_event(overlay_mdp_frame *mdp,
struct sockaddr_un *recvaddr,int recvaddrlen)
{
return WHY("Not implemented");
}
/* At this point we know the MDP frame is addressed to the VoMP port, but
we have not inspected the contents. As these frames are wire-format, we
must pay attention to endianness. */
int vomp_mdp_received(overlay_mdp_frame *mdp)
{
if (mdp->packetTypeAndFlags&(MDP_NOCRYPT|MDP_NOSIGN))
{
/* stream-crypted audio frame */
return WHY("not implemented");
}
/* only auth-crypted frames make it this far */
vomp_call_state *call=NULL;
switch(mdp->in.payload[0]) {
case 0x01: /* Ordinary VoMP state+optional audio frame */
{
// int recvr_state=mdp->in.payload[2];
int sender_state=mdp->in.payload[2];
unsigned int recvr_session=
(mdp->in.payload[6]<<16)+(mdp->in.payload[7]<<8)+mdp->in.payload[8];
unsigned int sender_session=
(mdp->in.payload[9]<<16)+(mdp->in.payload[10]<<8)+mdp->in.payload[11];
if (!recvr_session) {
/* wants to create a call session.
Main aim here: replay protection. An adversary should not be able to
replay previous VoMP packets to cause any action. We do this by
allocating a new session number for each call. As an adversary may be
trying to use such replays to cause a denial of service attack we need
to be able to track multiple potential session numbers even from the
same SID. */
call=vomp_find_or_create_call(mdp->in.src.sid,mdp->in.dst.sid,
sender_session,recvr_session);
if (!call) {
/* could not allocate a call slot, so do nothing */
return WHY("No free call slots");
}
/* We have a session number. Send a status update back to sender */
call->last_activity=overlay_gettime_ms();
return vomp_send_status(call);
} else {
/* A VoMP packet for a call apparently already in progress */
call=vomp_find_or_create_call(mdp->in.src.sid,mdp->in.dst.sid,
sender_session,recvr_session);
if (!call) {
return WHY("VoMP frame does not correspond to an active call - stale traffic or replay attack?");
}
/* Consider states: our actual state, sender state, what the sender thinks
our state is, and what we think the sender's state is. But largely it
breaks down to what we think our state is, and what they think their
state is. That leaves us with just 6X6=36 cases. */
int combined_state=call->local.state<<3;
combined_state|=sender_state;
switch(combined_state) {
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_NOCALL:
/* We both think that we are not yet in a call, and we have session numbers
at each end. Presumably waiting for further state synchronisation */
break;
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_CALLPREP:
/* The remote party is in the call-prep state, while we think no call
is in progress. Yet we have supplied them with a session number.
This is probably a state that should not occur, since when they
received the session # they should have moved to RINGINGOUT.
No action is required, but we probably shouldn't count it towards
valid call activity, so don't touch the recent activity timer.
Just return. */
return 0;
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_RINGINGOUT:
/* We have have issued a session, but think that no call is in progress.
The remote party is now indicating that they are trying to ring us.
So change our state to RINGINGIN, but don't yet flag this to the user
until we both have acknowledged this (when I am RINGINGIN and they are
RINGINGOUT). */
call->local.state=VOMP_STATE_RINGINGIN;
break;
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_RINGINGIN:
/* We think there is no call, while the remote end thinks that we are
ringing them. This could be because we hung the call up. That's okay.
We just keep persisting, because once they acknowledge this, we will
both move to CALLENDED and hang up */
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_INCALL:
/* As above, a call has probably been hung up by us, but the far end has
not yet realised. Nothing to do here, except wait for states to
synchronise. */
break;
case (VOMP_STATE_NOCALL<<3)|VOMP_STATE_CALLENDED:
/* Far end has given up on the call, so also move to CALLENDED */
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_NOCALL:
/* We are getting ready to ring, and the other end has issued a session
number, but nothing else. This means that we can now proceed to
the RINGINGOUT state, and wait for acknowledgement of that from the
far end. But we don't start ringing until the far end acknowledges
the state change. */
call->local.state=VOMP_STATE_RINGINGOUT;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_CALLPREP:
/* we are both in callprep stage, so we are both trying to ring each
other! This would take some pretty special timing to happen, but
let's not prevent it. We move to RINGINGOUT (as they probably will
as well). */
call->local.state=VOMP_STATE_RINGINGOUT;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_RINGINGOUT:
/* We are trying to call them, and they are trying to call us, again
this seems a very unlikely situation. But the appropriate action is
clear: get ready to start ringing. */
call->local.state=VOMP_STATE_RINGINGIN;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_RINGINGIN:
/* We are trying to call them, and they think we are trying to call them.
They seem to have guessed our next move, which is fine. We move to
RINGINGOUT. */
call->local.state=VOMP_STATE_RINGINGOUT;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_INCALL:
/* We are trying to call them, and they think we are already in a call.
This shouldn't happen either, but appropriate action is that we move
to in-call, and start handling audio. If audio is not available, we
can switch to engaged tone by sending a single VOMP_CODEC_ENGAGED
audio frame. Call-waiting not currently supported. */
call->local.state=VOMP_STATE_INCALL;
if (vomp_call_start_audio(call)) call->local.codec=VOMP_CODEC_ENGAGED;
break;
case (VOMP_STATE_CALLPREP<<3)|VOMP_STATE_CALLENDED:
/* far end says no call */
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_NOCALL:
/* We are calling them, and they have not yet answered, wait for
synchronisation. */
call->local.state=VOMP_STATE_RINGINGOUT;
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_CALLPREP:
/* we are calling them, and they are getting ready to call us, so wait
for synchronisation */
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_RINGINGOUT:
/* we are each calling each other, so move to INCALL and start audio */
call->local.state=VOMP_STATE_INCALL;
if (vomp_call_start_audio(call)) call->local.codec=VOMP_CODEC_ENGAGED;
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_RINGINGIN:
/* we are calling them and they are calling us, so keep on ringing.
Or if we haven't started making noise, then do so. */
if (!call->ringing) vomp_call_start_ringing(call);
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_INCALL:
/* we are calling them, and they have entered the call, so we should enter
the call as well. */
call->local.state=VOMP_STATE_INCALL;
if (vomp_call_start_audio(call)) call->local.codec=VOMP_CODEC_ENGAGED;
break;
case (VOMP_STATE_RINGINGOUT<<3)|VOMP_STATE_CALLENDED:
/* Other end has rejected call */
vomp_call_rejected(call);
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_NOCALL:
/* we are ringing and they think there is no call, so move to CALLENDED */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_error(call);
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_CALLPREP:
/* er, causaility loop here. We are ringing before they have started
ringing us. I guess we should stop ringing. Should we also abort the
call? */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_error(call);
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_RINGINGOUT:
/* we are ringing and they are ringing us. Make sure we are ringing. */
if (!call->ringing) vomp_call_start_ringing(call);
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_RINGINGIN:
/* er, we both think that the other is calling us. */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_error(call);
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_INCALL:
/* we think they are ringing us, but they think the call has
started. I guess we just keep ringing. */
if (!call->ringing) vomp_call_start_ringing(call);
break;
case (VOMP_STATE_RINGINGIN<<3)|VOMP_STATE_CALLENDED:
/* the far end has rejected our attempt to call them */
vomp_call_rejected(call);
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_NOCALL:
/* this shouldn't happen */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_error(call);
break;
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_CALLPREP:
/* this shouldn't happen either */
call->local.state=VOMP_STATE_CALLENDED;
vomp_call_error(call);
break;
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_RINGINGOUT:
/* we think the call is in progress, but the far end hasn't realised
we have picked up yet. Nothing to do here. */
break;
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_RINGINGIN:
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_INCALL:
/* play any audio that they have sent us. */
if (!call->audio_started) {
if (vomp_call_start_audio(call)) call->local.codec=VOMP_CODEC_ENGAGED;
}
vomp_process_audio(call,mdp);
break;
case (VOMP_STATE_INCALL<<3)|VOMP_STATE_CALLENDED:
/* far end hung up */
vomp_call_stop_audio(call);
call->local.state=VOMP_STATE_CALLENDED;
break;
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_NOCALL:
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_CALLPREP:
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_RINGINGOUT:
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_RINGINGIN:
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_INCALL:
/* For all of these states wait for the far end to synchronise,
but don't touch the call timer */
return 0;
case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_CALLENDED:
/* We both agree the call is done. Destroy call. */
return vomp_call_destroy(call);
}
/* touch call timer if the current state has not vetoed by returning */
call->last_activity=overlay_gettime_ms();
/* and then send an update to the call status */
vomp_send_status(call);
}
}
break;
case 0x02: /* codec selection, lists set of acceptable codec formats,
and may thus cause change of codec, including during the call */
break;
default:
/* unsupported VoMP frame */
break;
}
return WHY("Malformed VoMP MDP packet?");
}