#!/usr/bin/perl
# RTP-Proxy 0.1
# Receive announcments of redirected RTP-sessions from sip-redirectrtp, receive
# the packets, wiretap or do anything with them and forward them to the real
# targets.
#
# Copyright (C) 2005 Thomas Skora
#
# 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.
#
# Changelog:
# 0.1: first version, proof of concept, just forwards packets.
#
# TODO:
# - Add daemon mode.
# - Add wiretapping etc.
# - forward RTCP.

use Getopt::Std;
use NetPacket::IP;
use NetPacket::UDP;
#use Net::RawIP;
use Socket;
use IO::Socket;
use IO::Select;


$sigport = 64120;
$rtpport = 64121;
$udp_maxlength = 1400;
$udp_inittimeout = 0.5;
$udp_maxretries = 5;


sub HELP_MESSAGE {
  print <<EOH
  Usage: rtpproxy [options]

 -v       Be verbose.
 -s<port> Set signalisation port different to 64120.
 -r<port> Set RTP port different to 64121.
EOH
}


sub verbose {
  if ($opt_v) {
    print $_[0];
  }
}


sub incoming_sig {		# process incoming signalisation datagram
  my $addr = $sig->recv(my $msg, $udp_maxlength);
  my ($port, $ip) = sockaddr_in($addr);
  $ip = inet_ntoa($ip);

#  print "$msg\n";
  if ($msg =~ /^REGISTER/) {	# registration request (no really registration is done, just the RTP port is sent)
    verbose "Registration request from $ip:$port.\n";
    my $reply = <<EOR;
OK
rtpport=$rtpport
EOR
    $sig->send($reply,0,$addr);
  } elsif ($msg =~ /^ANNOUNCE/) { # RTP session announcment
    my ($callid) = ($msg =~ /^callid=(.*)$/m); # parse session description
    my ($fromip, $fromport) = ($msg =~ /from=([0-9\.]+):([0-9]+)/m);
    my ($toip, $toport) = ($msg =~ /to=([0-9\.]+):([0-9]+)/m);
    verbose "Session announcement for $callid from $ip:$port. $fromip:$fromport <-> $toip:$toport\n";

    # send reply
    my $reply = <<EOR;
OK
request=announce
callid=$callid
EOR
    $sig->send($reply,0,$addr);

    # add session to session description hash
    $session{$callid} = {
			 fromip   => $fromip,
			 fromport => $fromport,
			 toip   => $toip,
			 toport => $toport
			};

    # additional build from-to-hashes to making forwarding of RTP packets fast.
    $fromto{"$fromip:$fromport"} = {
				    ip   => $toip,
				    port => $toport
				   };
    $fromto{"$toip:$toport"} = { # same for the opposite direction
				ip   => $fromip,
				port => $fromport
			       };
  } elsif ($msg =~ /^DISCARD/) { # signalisation of ending RTP session
    my ($callid) = ($msg =~ /^callid=(.*)$/m);
    verbose "Discarding session $callid from $ip:$port.\n";
    undef $fromto{"$session{$callid}{fromip}:$session{$callid}{fromport}"};
    undef $fromto{"$session{$callid}{toip}:$session{$callid}{toport}"};
    undef $session{$callid};

    my $reply = <<EOR;
OK
request=discard
callid=$callid
EOR
    $sig->send($reply,0,$addr);
  }
}


sub incoming_rtp {		# receive RTP packet, process and forward it to real destination
  my $addr = $rtp->recv(my $msg, $udp_maxlength);
  my ($port, $ip) = sockaddr_in($addr);
  $ip = inet_ntoa($ip);

  # first forward incoming packet to real destination to keep latency low
  defined $fromto{"$ip:$port"} or return; # ignore packets from unknown sources
  my ($toip, $toport) = (inet_aton($fromto{"$ip:$port"}{ip}), $fromto{"$ip:$port"}{port});
  my $toaddr = pack_sockaddr_in($toport, $toip);
  $rtp->send($msg, 0, $toaddr);
}


sub waitevent {	# wait with select for incoming packets and process them.
  @ready = $sel->can_read();    # wait for incoming packet
#  print "Event!\n";
  foreach my $sock (@ready) {
    if ($sock == $sig) {	# something on the signalisation port
      incoming_sig;
    } elsif ($sock == $rtp) {	# something on rtp port
      incoming_rtp;
    }
  }
}


$Getopt::Std::STANDARD_HELP_VERSION = 1;
getopts('vs:r:');

!defined $opt_s or $sigport = $opt_s;
!defined $opt_r or $rtpport = $opt_r;

# create signalisation and RTP sockets
$sig = IO::Socket::INET->new(
			     Proto    => "udp",
			     LocalPort => "$sigport"
			    ) or die "Cannot create UDP socket: $@";
$rtp = IO::Socket::INET->new(
			     Proto    => "udp",
			     LocalPort => "$rtpport"
			    ) or die "Cannot create UDP socket: $@";
$sel = IO::Select->new($sig, $rtp);

verbose "Signalisation port: $sigport, RTP port: $rtpport\n";

while (1) {
  waitevent();
}
