新年早々暇なのでPerlでSIP Proxyを作った
いやホント暇ですね。
暇すぎるから超久々にPerlのコード書いちゃいましたよ。
年末の大掃除でたまたまラクダ本が出てきてなんだか懐かしかったもんで、つい。
ところで、はてなダイアリーってソースコードに色づけする機能があるんですね。こりゃ便利だ。
というわけで以下ソースコード。あ、ろくに試験してないんでろくに動かない可能性大です。パッチください。
#!/usr/bin/perl -w use strict; use Socket; our %LOCDAT = (); our ($destip, $destport); our ($myhost, $myport); if($#ARGV < 1) { print "usage:\n\tproxy.pl host port\n"; exit(1); } $myhost = $ARGV[0]; $myport = $ARGV[1]; socket(SOCKET, PF_INET, SOCK_DGRAM, 0) or die "socket: $!"; bind(SOCKET, pack_sockaddr_in($myport, inet_aton($myhost))) or die "bind: $!"; print "ok\n"; while(1){ my($buf); my($addr) = recv(SOCKET, $buf, 0x10000, 0) or next; my($srcport, $host) = unpack_sockaddr_in($addr); my($srcip) = inet_ntoa($host); print "<<<<< recvfrom ".$srcip.":".$srcport." <<<<<\n$buf\n"; my($body, $method, $requri, $stcode, $reason, @hdrs) = &parse($buf); if($method ne "") { @hdrs = &insert_received($srcip, @hdrs); &proc_request($body, $method, $requri, @hdrs); } else { &proc_response($body, $stcode, $reason, @hdrs); } } sub parse() { my($buf) = @_; my($body, $method, $requri, $stcode, $reason, @hdrs); $buf =~ m/(\r\n\r\n)|(\r\r)|(\n\n)/mo; $body = $'; $buf = $`; $buf =~ s/\r\n?/\n/mgo; $buf =~ s/\n[ \t]+/ /mgo; $buf =~ /([^ ]+) ([^ ]+) ([^\n]+)\n/mo; if($3 eq "SIP/2.0") { $method = $1; $requri = $2; $stcode = $reason = ""; } else { $method = $requri = ""; $stcode = $2; $reason = $3; } my(@tmp) = split(/\n/, $'); my($i); @hdrs = (); for($i=0; $i<@tmp; $i++) { $tmp[$i] =~ m/\s*:\s*/o; push(@hdrs, $`); push(@hdrs, $'); } return $body, $method, $requri, $stcode, $reason, @hdrs; } sub parse_addr_spec() { my($buf) = @_; my($user, $host, $port); $buf =~ m/sip:(([^@]+)@)?([^;:]+)(:(\d+))?/; if($2) { $user = $2; } else { $user = ""; } $host = $3; if($5) { $port = $5; } else { $port = 5060; } return $user, $host, $port; } sub parse_name_addr() { my($buf) = @_; if($buf =~ m/<([^>]+)>/o) { return $`, $1, $'; } return "", $buf, ""; } sub insert_received() { my($srcip, @hdrs) = @_; my($pos) = &search("via", "v", @hdrs); my(@ary) = split(/\s*,\s*/, $hdrs[$pos + 1]); $ary[0] .= ";received=".$srcip; $hdrs[$pos + 1] = join(", ", @ary); return @hdrs; } sub proc_register() { my($requri, @hdrs) = @_; my($to_user, $to_host, $to_port) = &search_addr_spec("to", "t", @hdrs); my($dsp, $contact, $prms) = &search_name_addr("contact", "m", @hdrs); $LOCDAT{$to_user} = $contact; @hdrs = &conv2resp(@hdrs); push(@hdrs, "Contact"); push(@hdrs, $contact); &send_resp("", "200", "OK", @hdrs); } sub proc_request() { my($body, $method, $requri, @hdrs) = @_; my($user, $host, $port) = &parse_addr_spec($requri); if($host eq $myhost && $port eq $myport) { if($method eq "REGISTER") { return &proc_register($requri, @hdrs); } elsif($user ne "") { $requri = $LOCDAT{$user}; if(!$requri) { if($method ne "ACK") { @hdrs = &conv2resp(@hdrs); &send_resp($body, "404", "Not Found", @hdrs); } return; } } } my($pos) = &search("via", "v", @hdrs); $hdrs[$pos + 1] = "SIP/2.0/UDP $myhost:$myport, ".$hdrs[$pos + 1]; $pos = &search("max-forwards", "", @hdrs); if($pos == -1) { push(@hdrs, "Max-Forwards"); push(@hdrs, "70"); } else { if($hdrs[$pos + 1] eq "0") { if($method ne "ACK") { @hdrs = &conv2resp(@hdrs); &send_resp($body, "483", "Too Many Hops", @hdrs); } return; } $hdrs[$pos + 1] = $hdrs[$pos + 1] - 1; } ($user, $destip, $destport) = &parse_addr_spec($requri); &send_msg($body, $method, $requri, "", "", @hdrs); } sub proc_response() { my($body, $stcode, $reason, @hdrs) = @_; my($pos) = &search("via", "v", @hdrs); my(@ary) = split(/\s*,\s*/, $hdrs[$pos+1]); shift(@ary); if($#ary == -1) { splice(@hdrs, $pos, 2); } else { $hdrs[$pos + 1] = join(", ", @ary); } &send_resp($body, $stcode, $reason, @hdrs); } sub search() { my($name1, $name2, @hdrs) = @_; my($i); for($i=0; $i<@hdrs; $i+=2) { if(lc($hdrs[$i]) eq $name1 || lc($hdrs[$i]) eq $name2) { return $i; } } return -1; } sub search_name_addr() { my($name1, $name2, @hdrs) = @_; my($pos) = &search($name1, $name2, @hdrs); if($pos == -1) { return "", "", ""; } my(@ary) = split(/\s*,\s*/, $hdrs[$pos + 1]); return &parse_name_addr($ary[0]); } sub search_addr_spec() { my($name1, $name2, @hdrs) = @_; my($dsp, $addr_spec, $prms) = &search_name_addr($name1, $name2, @hdrs); return &parse_addr_spec($addr_spec); } sub conv2resp() { my(@hdrs) = @_; my($i, @new_hdrs); for($i=0; $i<@hdrs; $i+=2) { my($name) = $hdrs[$i]; my($value) = $hdrs[$i + 1]; if(lc($name) eq "via" or lc($name) eq "v" or lc($name) eq "from" or lc($name) eq "f" or lc($name) eq "to" or lc($name) eq "t" or lc($name) eq "call-id" or lc($name) eq "i" or lc($name) eq "cseq") { push(@new_hdrs, $name); push(@new_hdrs, $value); } } return @new_hdrs; } sub send_resp() { my($body, $stcode, $reason, @hdrs) = @_; my($top_via) = &search("via", "v", @hdrs); my(@tmp) = split(/\s*,\s*/, $hdrs[$top_via + 1]); $tmp[0] =~ m/SIP\s*\/\s*2\.0\s*\/\s*UDP\s+/o; my($buf) = $'; $buf =~ m/([^:;]+)(\s*:\s*(\d+))?/o; $destip = $1; $destport = $3; $buf = $'; if($buf =~ m/;received=([^;]+)/o) { $destip = $1; } if($buf =~ m/;rport=([^;]+)/o) { $destport = $1; } &send_msg($body, "", "", $stcode, $reason, @hdrs); } sub send_msg() { my($body, $method, $requri, $stcode, $reason, @hdrs) = @_; my($buf, $i); if($destip eq $myhost and $destport eq $myport) { return; } if($method ne "") { $buf = $method." ".$requri." SIP/2.0\r\n"; } else { $buf = "SIP/2.0 ".$stcode." ".$reason."\r\n"; } for($i=0; $i<@hdrs; $i+=2) { $buf .= $hdrs[$i].": ".$hdrs[$i + 1]."\r\n"; } $buf .= "\r\n".$body; my($addr) = pack_sockaddr_in($destport, inet_aton($destip)); send(SOCKET, $buf, 0, $addr); print ">>>>> sendto $destip:$destport >>>>>\n$buf\n"; }