SIP Register ตอนที่ 1

JAIN SIP #4 (SIP Register )

วัตถุประสงค์

    สามารถเขียน SIP Client ให้ไป Register กับ SIP Server (ในทีนี้ใช้ Asterisk) ได้

ความรู้เบื้องต้น

  • Souce code ที่ผมจะนำมาเล่าในตอนนี้อ้างอิงจาก http://jain-sip.dev.java.net/ ใช้ตัวอย่าง Shootist Authentication ซึ่งจะอยู่ใน /jain-sip/src/examples/authorization
  • ความเดิมตอนที่แล้ว =>  JAIN SIP #3   ที่ผมค้างไว้ถึงการ Register SIP Client ไปยัง SIP Server

ทบทวนความรู้ JAIN SIP อีกครั้งนะครับ ก่อนที่เราจะส่ง SIP Message ไปหา SIP Server ได้นั้น สิ่งที่จำเป็นจะต้องมีคือ

  • SipFactory เอาไว้สร้าง sipStack (โดย create ด้วย properties ที่กำหนด) และ sip header message factory ต่าง ๆ (เช่น To, From, Contact) ซึ่งเราจะเอา factory เหล่านี้ไปสร้าง เป็น SIP header message อีกที
  • SipStack ที่ได้มาจาก SipFactory เอาไว้สร้าง ListeningPoint กับ SipProvider
  • จากนั้นก็เอา SipProvider มา add SipListener จาก ListeningPoint ที่ได้สร้างไว้จาก sipStack เพื่อรอรับ message ที่ Server หรือ ผู้ที่ติดต่ออีกฝ่าย ส่งมาให้

ครับ อันนี้คือทบทวนเรื่องเก่า ซึ่งเป็นการทำงานใน method init() ของ JAIN SIP นั่นเอง

ก่อนจะกล่าวถึง register method ผมขออธิบายขั้นตอนการทำงานของ SIP Register ก่อนนะครับ

  1. SIP Client สร้าง SIP Register message จาก factory ต่าง ๆ แล้วส่งไปหา server โดย register request object (ตอนที่ 1)
  2. เมื่อ SIP Server ได้รับ register request SIP Server ก็จะตอบ 100 Trying และ 401 Unauthorized กลับมาหา SIP Client
  3. SIP Client สามารถตรวจสอบ message Trying และ Unauthorized จาก method processResponse (ที่ได้จากการ implements interface SIPListener)
  4. SIP Client ส่ง SIP Register message ไปหา SIP Server อีกครั้ง โดยครั้งนี้จะต้องแนบ Authentication header (ที่ได้จากการทำ message digest โดย md5) แนบไป
  5. SIP Server ตรวจสอบ Register header และ authentication header ถ้าหากถูกต้อง ก็จะส่ง 200 OK มาให้เป็นอันว่า Register กับ SIP Server เรียบร้อย
  6. เมื่อต้องการ DeRegister ให้ส่ง SIP Register ที่มี expire (อยู่ใน contact header) ที่มีค่า = 0
  7. SIP Server ก็จะตอบ 100 Trying และ 200 OK กลับมาให้ถือว่าเป็นการ DeRegister เรียบร้อย 

ผมขอแสดงตัวแปร global ทั้งหลายที่ใช้งานก่อนนะครับ จะได้รู้ว่าตัวแปรนี้ เป็นตัวแปรชนิดอะไร

  private static SipProvider sipProvider;
 private static AddressFactory addressFactory;
 private static MessageFactory messageFactory;
 private static HeaderFactory headerFactory;
 private static SipStack sipStack;
 private ContactHeader contactHeader;
 private ListeningPoint udpListeningPoint;
 private ClientTransaction inviteTid;
 protected ServerTransaction optionsTid;

 private AuthorizationHeader authHeader;
 private String callId;

 private Dialog dialog;
 long invco = 0;
 
 String myAddress = "147.127.240.90";
 String peerHostPort = "147.127.240.91";
 String transport = "udp";
 String USER_AUTH = "300";
 String PASS_AUTH = "300";
 String realm = "asterisk";

คราวนี้มาถึง method createRegister

 public Request createRegister(String callId, int expireTime)
    {
        SipURI fromAddress = null;
        Address fromNameAddress = null;
        FromHeader fromHeader = null;

        SipURI toAddress = null;
        Address toNameAddress = null;
        ToHeader toHeader = null;

        SipURI contactAddress = null;
        Address contactNameAddress = null;
        ContactHeader contactHeader = null;

        SipURI requestURI = null;

        ViaHeader viaHeader = null;
        ArrayList viaHeaders = null;

        CallIdHeader callIdHeader = null;
        CSeqHeader cSeqHeader = null;

        MaxForwardsHeader maxForwards = null;

        Request register = null;

     try {
        String host = "147.127.240.90";
        String proxy = "147.127.240.91";

        // create From Header
        fromAddress = addressFactory.createSipURI("300", proxy);  
        fromNameAddress = addressFactory.createAddress( fromAddress);
        fromHeader = headerFactory.createFromHeader(fromNameAddress, "12345678");

        // create To Header
        toAddress = addressFactory.createSipURI("300", proxy);
        toNameAddress = addressFactory.createAddress(toAddress);
        toHeader = headerFactory.createToHeader(toNameAddress, null);

        // create Request URI
        requestURI = addressFactory.createSipURI(null, proxy);

        // create Contact Header
        contactAddress = addressFactory.createSipURI("300", host);
        contactAddress.setPort(44464);

        contactNameAddress = addressFactory.createAddress(contactAddress);
        contactHeader = headerFactory.createContactHeader(contactNameAddress);
        contactHeader.setExpires(expireTime);
       
        // create Via Headers
        String transport = sipProvider.getListeningPoint().getTransport();
        viaHeader = headerFactory.createViaHeader(host, 44464, "udp", null);
        viaHeaders = new ArrayList();
        viaHeaders.add(viaHeader);

        // create the MaxForwarsHeaders
        maxForwards = headerFactory.createMaxForwardsHeader(70);

        // create and send Register Header
        callIdHeader = sipProvider.getNewCallId();

     if (callId.trim().length() > 0)
          callIdHeader.setCallId(callId);  // over write previous callId
       
        cSeqHeader = headerFactory.createCSeqHeader(++invco,Request.REGISTER);
        register =  messageFactory.createRequest(
                        requestURI,
                        Request.REGISTER,
                        callIdHeader,
                        cSeqHeader,
                        fromHeader,
                        toHeader,
                        viaHeaders,
                        maxForwards);       

        register.addHeader(contactHeader);
    }

    catch (Exception e) {
        System.err.println(e.getMessage());
           System.out.println("From process register");
        }
       
    return register;

   }

   ค่อนข้างยาวหน่อย แต่ความจริงแล้ว ผมดัดแปลง method นี้มาจาก createInvite() ในตัวอย่างครับ การทำงานก็คล้าย ๆ กัน เราลองมาดูการทำงานกันครับ  เริ่มแรกก็กำหนดค่าเริ่มต้นให้กับตัวแปรต่าง ๆ  ตัวแปร  host ก็คือเครื่อง SIP client ส่วน proxy ก็คือ SIP Server (Asterisk) ครับ  ผม add user: 300 และ pass: 300 ไว้ใน Asterisk เรียบร้อยแล้ว คราวนี้ ผมจะทดลอง register user 300 ผ่านโปรแกรม SIP Client ที่ผมทดลองเขียนขึ้นมาเอง 

   ก่อนจะส่ง SIP Register ไปหา Server เราก็ทำการ create SIP header ผ่าน method createXXX() ทั้งหลาย จนครบ ตามมาตรฐานของ SIP นะครับ มีข้อสังเกตนิดหน่อยว่า To กับ From จะเป็น user: 300 คนเดียวกัน เพราะเป็นการ Register ครับ  ผมเลือกใช้ port 44464 (จริง ๆ จะใช้ port อื่นก็ได้ครับ) แล้วก็  กำหนด expire เป็น 3600

   ในส่วน callId เราสามารถให้ sipProvider สร้างได้จาก getNewCallId()  ตรงนี้ผมใส่เงื่อนไข if ไว้ข้างล่างเนื่องจากว่าถ้ามี callId อยู่แล้ว เราก็จะใช้ callId จาก session เดิม (ซึ่งจะอธิบายอีกทีภายหลัง)

เมื่อเตรียม SIP Header เสร็จแล้ว เราก็เอา header พวกนี้ add เข้าไปใน register ซึ่งเป็น Request object

จากนั้น เราก็ส่ง Register message ไปให้ Server โดยชุดคำสั่งดังนี้

   // Create the client transaction.
   inviteTid = sipProvider.getNewClientTransaction(request);
   // send the request out.
   inviteTid.sendRequest();
   System.out
     .println("REGISTER with no Authorization sent:\n" + request); // Print SIP REGISTER message
   dialog = inviteTid.getDialog();

เริ่มต้นเราต้องสร้าง Transaction ขึ้นมาก่อน เนื่องจากเป็นการติดต่อแบบ stateful โดยสร้างจาก sipProvider เรียก method getNewClientTransaction แล้วก็ใส่ request object ลงไป จากนั้นก็เอา transaction ตัวนี้แหละ ส่ง Register Request หลังจากผ่านบรรทัดนี้ไป Register Request ก็จะถูกส่งไปยัง SIP Server เราสามารถตรวจสอบสถานะของการส่งจาก object dialog โดยการ getDialog มาดูได้ครับ

แนะนำว่าควรจะลง Sniffer เช่น Wireshark เอาไว้ตรวจสอบข้อความที่วิ่งไปในเครือข่ายด้วยนะครับ ว่า SIP Message ที่เราส่งไปนั้นถูกต้องหรือไม่

ยังไม่จบนะครับ แต่ผมเห็นว่ามันเริ่มยาวแล้ว ไปต่อตอนต่อไปดีกว่าครับ...