mirror of https://github.com/sipwise/rtpengine.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
220 lines
4.9 KiB
220 lines
4.9 KiB
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Socket;
|
|
use Socket6;
|
|
use Digest::SHA qw(hmac_sha1);
|
|
use Digest::CRC qw(crc32);
|
|
|
|
my ($controlling, $port, $username, $pwd, @addresses) = @ARGV;
|
|
|
|
my %attrs = (
|
|
6 => \&attr_un,
|
|
8 => \&attr_mi,
|
|
0x8028 => \&attr_fp,
|
|
0x25 => \&attr_use,
|
|
0x8029 => \&attr_controlled,
|
|
0x802a => \&attr_controlling,
|
|
);
|
|
|
|
my (@sockets, $packet, $tract, $code);
|
|
|
|
for my $addr (@addresses) {
|
|
my ($fam, $pka, $sin, $meth) = addrparse($addr, $port);
|
|
socket(my $fd, $fam, SOCK_DGRAM, 0) or die $!;
|
|
bind($fd, $sin) or die $!;
|
|
push(@sockets, {fd => $fd, fam => $fam, addr => $pka, sin => $sin, xormethod => $meth});
|
|
}
|
|
|
|
while (1) {
|
|
my $rin = '';
|
|
for my $s (@sockets) {
|
|
vec($rin, fileno($$s{fd}), 1) = 1;
|
|
}
|
|
|
|
select($rin, undef, undef, 1);
|
|
|
|
for my $s (@sockets) {
|
|
vec($rin, fileno($$s{fd}), 1) or next;
|
|
|
|
my $src = recv($$s{fd}, my $buf, 200, 0) or die $!;
|
|
print("\npacket from " . addrdeparse($$s{fam}, $src) . " on ".
|
|
addrdeparse($$s{fam}, $$s{sin}) . "\n");
|
|
|
|
my ($cmd, $len, $cookie, $attrs);
|
|
($cmd, $len, $cookie, $tract, $attrs) = unpack('nnN a12 a*', $buf);
|
|
|
|
if ($cookie != 0x2112A442) {
|
|
if ($buf =~ /^[\x14-\x3f]/s) {
|
|
print("DTLS\n");
|
|
}
|
|
else {
|
|
print("not stun: " . unpack("H*", $buf)."\n");
|
|
}
|
|
next;
|
|
}
|
|
if ($cmd != 1) {
|
|
print("not stun request\n")
|
|
next;
|
|
}
|
|
if (length($attrs) != $len) {
|
|
print("length mismatch\n")
|
|
next;
|
|
}
|
|
|
|
my ($list, $hash) = unpack_attrs($attrs);
|
|
|
|
if ($$list[$#$list]{name} ne 'fingerprint') {
|
|
print("last attr not fingerprint\n");
|
|
next;
|
|
}
|
|
if ($$list[$#$list-1]{name} ne 'message-integrity') {
|
|
print("last but one attr not MI\n")
|
|
next;
|
|
}
|
|
unless ($$hash{username}) {
|
|
print("no username\n")
|
|
next;
|
|
}
|
|
|
|
$$hash{controlling} and print("is controlling\n");
|
|
$$hash{controlled} and print("is controlled\n");
|
|
$$hash{'use-candidate'} and print("nominated\n");
|
|
|
|
print("local username is $$hash{username}{split}[0], remote is $$hash{username}{split}[1]\n");
|
|
|
|
if ($$hash{controlling} && $controlling || $$hash{controlled} && !$controlling) {
|
|
print("role conflict, replying with 487");
|
|
$code = 0x0111; # binding error
|
|
$packet = attr(0x9, pack('CCCC a16', 0, 0, 4, 87, 'Role conflict'));
|
|
$packet .= integrity();
|
|
$packet .= fingerprint();
|
|
}
|
|
else {
|
|
$code = 0x101; # binding success
|
|
my $xorattr = $$s{xormethod}($src);
|
|
$packet = attr(0x20, $xorattr);
|
|
$packet .= integrity();
|
|
$packet .= fingerprint();
|
|
}
|
|
|
|
$packet = header() . $packet;
|
|
|
|
print("sending reply\n");
|
|
send($$s{fd}, $packet, 0, $src);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
exit;
|
|
|
|
sub xor4 {
|
|
my ($src) = @_;
|
|
my @a = unpack_sockaddr_in($src);
|
|
return pack('nna4', 1, $a[0] ^ 0x2112, $a[1] ^ "\x21\x12\xa4\x42");
|
|
}
|
|
sub xor6 {
|
|
my ($src) = @_;
|
|
my @a = unpack_sockaddr_in6($src);
|
|
return pack('nna16', 2, $a[0] ^ 0x2112,
|
|
$a[1] ^ ("\x21\x12\xa4\x42" . $tract));
|
|
}
|
|
sub addrparse {
|
|
my ($addr, $port) = @_;
|
|
my $r = inet_pton(AF_INET, $addr);
|
|
$r and return (AF_INET, $r, pack_sockaddr_in($port, $r), \&xor4);
|
|
$r = inet_pton(AF_INET6, $addr);
|
|
$r and return (AF_INET6, $r, pack_sockaddr_in6($port, $r), \&xor6);
|
|
die;
|
|
}
|
|
sub addrdeparse {
|
|
my ($fam, $sin) = @_;
|
|
if ($fam == AF_INET) {
|
|
my @up = unpack_sockaddr_in($sin);
|
|
return inet_ntop(AF_INET, $up[1]) . ":$up[0]";
|
|
}
|
|
if ($fam == AF_INET6) {
|
|
my @up = unpack_sockaddr_in6($sin);
|
|
return '['.inet_ntop(AF_INET6, $up[1]) . "]:$up[0]";
|
|
}
|
|
die;
|
|
}
|
|
|
|
sub attr_un {
|
|
my ($cont) = @_;
|
|
return {name => 'username', split => [$cont =~ /(.*):(.*)/]};
|
|
}
|
|
sub attr_mi {
|
|
return {name => 'message-integrity'};
|
|
}
|
|
sub attr_fp {
|
|
my ($cont) = @_;
|
|
return {name => 'fingerprint', value => unpack('N', $cont)};
|
|
}
|
|
sub attr_use {
|
|
return {name => 'use-candidate'};
|
|
}
|
|
sub attr_controlling {
|
|
my ($cont) = @_;
|
|
return {name => 'controlling', tiebreaker => unpack('Q', $cont)};
|
|
}
|
|
sub attr_controlled {
|
|
my ($cont) = @_;
|
|
return {name => 'controlled', tiebreaker => unpack('Q', $cont)};
|
|
}
|
|
|
|
|
|
sub unpack_attrs {
|
|
my ($s) = @_;
|
|
|
|
my (@out, %out);
|
|
while ($s ne '') {
|
|
my ($type, $len, $cont);
|
|
($type, $len, $s) = unpack('nn a*', $s);
|
|
my $pad = 0;
|
|
while ((($len + $pad) % 4) != 0) {
|
|
$pad++;
|
|
}
|
|
($cont, $pad, $s) = unpack("a$len a$pad a*", $s);
|
|
my $ins = {type => $type, len => $len, content => $cont,
|
|
raw => pack('nna*a*', $type, $len, $cont, $pad)};
|
|
push(@out, $ins);
|
|
$out{$type} = $ins;
|
|
|
|
my $pars = $attrs{$type};
|
|
$pars or next;
|
|
|
|
my $ai = $pars->($cont);
|
|
%$ins = (%$ins, %$ai);
|
|
$out{$$ins{name}} = $ins;
|
|
}
|
|
return (\@out, \%out);
|
|
}
|
|
|
|
|
|
sub attr {
|
|
my ($type, $data) = @_;
|
|
my $len = length($data);
|
|
while ((length($data) % 4) != 0) {
|
|
$data .= "\0";
|
|
}
|
|
return pack('nn a*', $type, $len, $data);
|
|
}
|
|
sub header {
|
|
my ($add_length) = @_;
|
|
$add_length ||= 0;
|
|
return pack('nnN a12', $code, length($packet) + $add_length, 0x2112A442, $tract);
|
|
}
|
|
sub integrity {
|
|
my $h = header(24);
|
|
my $hmac = hmac_sha1($h.$packet, $pwd);
|
|
return attr(8, $hmac);
|
|
}
|
|
sub fingerprint {
|
|
my $h = header(8);
|
|
my $crc = crc32($h.$packet);
|
|
return attr(0x8028, pack('N', ($crc ^ 0x5354554e)));
|
|
}
|