From 1a221a32b2ed919ae59584feb0c0a63b3182e7fe Mon Sep 17 00:00:00 2001 From: gardners Date: Thu, 19 Apr 2012 07:14:51 +0930 Subject: [PATCH] Begun work on VoMP implementation. --- Android.mk | 3 +- Makefile.in | 4 +- vomp.c | 438 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 442 insertions(+), 3 deletions(-) create mode 100644 vomp.c diff --git a/Android.mk b/Android.mk index f79981cb..bafd249f 100644 --- a/Android.mk +++ b/Android.mk @@ -63,7 +63,8 @@ LOCAL_SRC_FILES:= \ serval-dna/sha2.c \ serval-dna/simulate.c \ serval-dna/srandomdev.c \ - serval-dna/keyring.c + serval-dna/keyring.c \ + serval-dna/vomp.c LOCAL_MODULE:= dna diff --git a/Makefile.in b/Makefile.in index 387cc199..a14a6d23 100644 --- a/Makefile.in +++ b/Makefile.in @@ -5,7 +5,7 @@ SRCS= dna.c server.c client.c peers.c ciphers.c responses.c packetformats.c data rhizome.c rhizome_http.c rhizome_bundle.c rhizome_database.c rhizome_crypto.c \ rhizome_packetformats.c rhizome_fetch.c sqlite3.c encode.c sha2.c randombytes.c \ overlay_broadcast.c dna_identity.c commandline.c serval_packetvisualise.c \ - trans_cache.c keyring.c + trans_cache.c keyring.c vomp.c OBJS= dna.o server.o client.o peers.o ciphers.o responses.o packetformats.o dataformats.o \ srandomdev.o simulate.o batman.o gateway.o \ @@ -14,7 +14,7 @@ OBJS= dna.o server.o client.o peers.o ciphers.o responses.o packetformats.o data rhizome.o rhizome_http.o rhizome_bundle.o rhizome_database.o rhizome_crypto.o \ rhizome_packetformats.o rhizome_fetch.o sqlite3.o encode.o sha2.o randombytes.o \ overlay_broadcast.o dna_identity.o commandline.o serval_packetvisualise.o \ - trans_cache.o keyring.o + trans_cache.o keyring.o vomp.o HDRS= Makefile serval.h sqlite-amalgamation-3070900/sqlite3.h sha2.h rhizome.h diff --git a/vomp.c b/vomp.c new file mode 100644 index 00000000..f8014e90 --- /dev/null +++ b/vomp.c @@ -0,0 +1,438 @@ +/* +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" + +typedef struct vomp_call_half { + unsigned char sid[SID_SIZE]; + unsigned char state; + unsigned char codec; + unsigned int session; +#define VOMP_SESSION_MASK 0xffffff + unsigned int sequence; + /* the following is from call creation, not start of audio flow */ + unsigned long long milliseconds_since_call_start; +} vomp_call_half; + +typedef struct vomp_call_state { + vomp_call_half local; + vomp_call_half remote; + int ringing; + unsigned long long last_activity; + int audio_started; +} vomp_call_state; + +#define VOMP_CODEC_CODEC2_2400 0x01 +#define VOMP_CODEC_CODEC2_1400 0x02 +#define VOMP_CODEC_GSM 0x03 +#define VOMP_CODEC_DTMF 0x04 +#define VOMP_CODEC_ENGAGED 0x05 +#define VOMP_CODEC_CODECSISUPPORT 0xfe +#define VOMP_CODEC_CHANGEYOURCODECTO 0xff + +#define VOMP_STATE_NOCALL 1 +#define VOMP_STATE_CALLPREP 2 +#define VOMP_STATE_RINGINGOUT 3 +#define VOMP_STATE_RINGINGIN 4 +#define VOMP_STATE_INCALL 5 +#define VOMP_STATE_CALLENDED 6 + +/* 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-1) i=expired_slot; + else if ((iin.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"); +} + +/* 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. */ +void vomp_mdp_received(overlay_mdp_frame *mdp) +{ + if (mdp->packetTypeAndFlags&(MDP_NOCRYPT|MDP_NOSIGN)) + { + /* stream-crypted audio frame */ + WHY("not implemented"); + return; + } + + /* 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 */ + WHY("No free call slots"); + return; + } + + /* We have a session number. Send a status update back to sender */ + call->last_activity=overlay_gettime_ms(); + vomp_send_status(call); + return; + } 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) { + WHY("VoMP frame does not correspond to an active call - stale traffic or replay attack?"); + return; + } + /* 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; + 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; + case (VOMP_STATE_CALLENDED<<3)|VOMP_STATE_CALLENDED: + /* We both agree the call is done. Destroy call. */ + vomp_call_destroy(call); + return; + } + + /* 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; + } + +}