SIP registrace

Sip registrace je podobná http digest autentizaci. Aby se nepřenášelo nechráněné heslo, provádí se ve dvou krocích. Na první REGISTER odpoví SIP proxy server Not authorized. Z této odpovědi je použito políčko nonce - náhodný řetězec generovaný SIP proxy, se kterým se zašifruje heslo hashovací funkcí MD5 a provede se druhý REGISTER. V druhém REGISTER zašifrované heslo je obsaženo v políčku response, což vede k úspěšné registraci.

Hlavní program

Program má demonstrovat co nejednodušším způsobem registraci a reakci na příchozí hovory (INVITE). Program neošetřuje možné problémy s internetem a navazováním/vypadávání TCP spojení. Číslo linky, heslo a doba platnosti registrace v sekundách se nastavuje na začátku skriptu. Po vypršení registrace se automaticky provede přeregistrace.

sipclient.rb
require 'socket'  
require "digest/md5"
 
$cislo_linky="300100"
$sip_heslo="222eg4bf"
$expires=120
 
 
$s = TCPSocket.open("sip.odorik.cz", 5060)
 
$lokalni_ip_a_port="#{$s.addr[2]}:#{$s.addr[1]}"
 
def nacti_sip_hlavicky
   radek_cislo=0
   while (line = $s.gets)!="\r\n"   # čteme jednotlivé řádky hlavičky, prázdná řádka znamená konec hlavičky
      radek_cislo=radek_cislo+1
      if radek_cislo==1   # Podle prvního řádku se pozná, jaký tip SIP zprávy to je - jmenuje se sip request line
           puts  $sip_request_line=line.dup
           if line.start_with?("SIP/2.0 ")  # je to response
               $sip_request_line="response"
               $response_code=/SIP\/2.0 ([0-9]*) /.match(line)[1]
            next
          end
          if line.start_with?("INVITE ")  # Volají nám. Na jaké číslo?
               $called_phone_number=/([0-9]*)@/.match(line)[1]
           next 
          end 
      else
          if line.start_with?("WWW-Authenticate:")
               $nonce=/nonce="([^"]*)"/.match(line)[1]
               $digest_realm=/Digest realm="([^"]*)"/.match(line)[1]
            next
          end   
         if line.start_with?("Call-ID: ")
               $callid=/Call-ID: ([^\s]*)/.match(line)[1]
             next
         end 
 
         if line.start_with?("CSeq: ")
               $cseq=/CSeq: ([0-9]*)/.match(line)[1].to_i
             next
         end  
         if line.start_with?("Content-Length: ")
             $content_length=line.dup.gsub("Content-Length: ","").to_i
             next
          end   
 
          if line.start_with?("From:")
            $from_header=/sip:([^@]*)@/.match(line)[1]
            next
          end  
      end 
   end
 
   if $content_length>0
     $header_body=$s.read($content_length)
   end  
end
 
 
def registrace_faze1(expires=$expires)
    sip= <<-END_HEADERS
    REGISTER sip:sip.odorik.cz SIP/2.0
    Via: SIP/2.0/TCP #{$lokalni_ip_a_port};branch=z9hG4bK-#{('a'..'z').to_a.shuffle[0,8].join}
    From: <sip:#{$cislo_linky}@sip.odorik.cz>;tag=3778c6ce2e0777ceo2
    To: <sip:#{$cislo_linky}@sip.odorik.cz>
    Call-ID: #{$last_reg_callid=(('a'..'z').to_a.shuffle[0,15].join).to_s}
    CSeq: 1 REGISTER
    Max-Forwards: 69
    Contact: <sip:<#{$cislo_linky}@#{$lokalni_ip_a_port};transport=tcp>;expires=#{expires}
    User-Agent: Ruby script
    Content-Length: 0
    Allow: ACK, BYE, CANCEL, INFO, INVITE, NOTIFY, OPTIONS, PRACK, REFER, UPDATE, MESSAGE
    Supported: 100rel, replaces
    END_HEADERS
    sip.gsub!(/^[\s\t]*/,"") # odstraním mezery a tabulátory zleva, které zde jsou kvůli přehlednosti
    sip.gsub!(/\n/,"\r\n") # HTTP i SIP vymýšleli lidé od Windows, tedy namysleli si dva znaky pro nový řádek místo jednoho
    sip<< "\r\n" # konec hlavičky je zakončen prázdným řádkem
    # https://www.sitepoint.com/understanding-scope-in-ruby/
    $s.write(sip)
    $second_reg_stage_expires=expires  # druhá fáze registrace bude mít expiraci vždy stejnou jako ta první
end
 
def registrace_faze2
   # viz https://en.wikipedia.org/wiki/Digest_access_authentication#Overview
   ha1=Digest::MD5.hexdigest("#{$cislo_linky}:#{$digest_realm}:#{$sip_heslo}")
   ha2=Digest::MD5.hexdigest("REGISTER:sip:sip.odorik.cz")
   response=Digest::MD5.hexdigest("#{ha1}:#{$nonce}:#{ha2}")
   sip= <<-END_HEADERS
   REGISTER sip:sip.odorik.cz SIP/2.0
   Via: SIP/2.0/TCP #{$lokalni_ip_a_port};branch=z9hG4bK-#{('a'..'z').to_a.shuffle[0,8].join}
   From: <sip:#{$cislo_linky}@sip.odorik.cz>;tag=3778c6ce2e0777ceo2
   To: <sip:#{$cislo_linky}@sip.odorik.cz>
   Call-ID: #{$callid}
   CSeq:  #{$cseq+1} REGISTER
   Max-Forwards: 69
   Authorization: Digest username="#{$cislo_linky}",realm="sip.odorik.cz",nonce="#{$nonce}",uri="sip:sip.odorik.cz",algorithm=MD5,response="#{response}"
   Contact: <sip:#{$cislo_linky}@#{$lokalni_ip_a_port};transport=tcp>;expires=#{$second_reg_stage_expires}
   User-Agent: Ruby script
   Content-Length: 0
   Allow: ACK, BYE, CANCEL, INFO, INVITE, NOTIFY, OPTIONS, PRACK, REFER, UPDATE, MESSAGE
   Supported: 100rel, replaces
   END_HEADERS
   sip.gsub!(/^[\s\t]*/,"")
   sip.gsub!(/\n/,"\r\n")
   sip<< "\r\n" # konec hlaviček je zakončen prázdným řádkem
   $s.write(sip)
end
 
 
puts " Pošleme registraci fáze 1"
registrace_faze1
 
 
Thread.new { # tohle vlákno čeká na a zpracovává příchozí SIP pakety
while true
  nacti_sip_hlavicky
 
  if $sip_request_line=="response" and $response_code=="401" and $last_reg_callid == $callid
      puts " Dostali jsme odpověď na fázi registrace 1 - pošleme registrace fáze 2 "
      registrace_faze2
  end  
 
  if $sip_request_line=="response" and $response_code=="200" and $last_reg_callid == $callid 
         puts " Jsme úspěšně (od)registrováni !!!!"
  end  
 
  if $sip_request_line.start_with?("INVITE")
      puts "volá nám číslo #{$from_header} na číslo #{$called_phone_number} "
  end
 
end
}
 
 
Thread.new {  # tohle vlákno zajistí novou registraci po expiraci
while true
  sleep($expires)
  registrace_faze1
end  
}
 
# hlavní vlákno bude obsluhovat menu
 
def precti_moznosti_menu
 sleep 1 # počkáme, až se zpracují předchozí požadavky v jiných vláknech
 puts ""
 puts "Zadej příkaz a zmáčkněte Enter:"
 puts "ukončit program    - k"
 puts "registrovat znovu  - r"
 puts "odregistrace       - o"
end 
 
precti_moznosti_menu
 
 
while (prikaz=gets.chomp)!="k"
  if prikaz=="r"
    puts "znovu zaregistrujeme"
    registrace_faze1    
  end  
  if prikaz=="o"
    puts "odregistrujeme = zaregistrujeme s nulovou expirací"
    registrace_faze1(0)
  end  
  precti_moznosti_menu  
end
 
 
puts "odregistrujeme = zaregistrujeme s nulovou expirací"
registrace_faze1(0)
 
sleep 1 # čekáme než proběhne 2. fáze odregistrace v jiných vláknech
$s.close

Řešení potíží

Sledujte, co se opravdu posílá a co opravdu odpovídá server:

ngrep -W byline host 81.31.45.51 and port 5060

Podrobnosti: http://forum.odorik.cz/viewtopic.php?f=29&t=3513

Inspirace

 
ruby_sip_client.txt · Poslední úprava: 2016/06/20 16:52 autor: misa