3. Nội dung báo cáo
1.2.2. Cấu trúc giao thức
1.2.2.1. SIP stack
SIP là một giao thức phân lớp (layered). Hình dưới đây là các lớp của một thực thể SIP.
Transaction user (TU)
Điều khiển logic của thực thể. Lớp TU quản lý các TU object theo Call-ID Transaction Quản lý và điều khiển các transaction:
phát lại, gửi sự kiện lên TU... S ynt ax & E nc od in g
Định dạng và phân tích các bản tin SIP
Transport Xác định các phương thức vận chuyển các bản tin SIP giữa các thực thể
Khái niệm transaction được định nghĩa trong RFC3261 gồm một request đơn và các đáp ứng cho request đó (có thể có 1xx, và ít nhất một đáp ứng hoàn thành?). Thông thường transaction tính cả bản tin ACK, tuy nhiên trong trường hợp request là INVITE và đáp ứng hoàn thành là 2xx (success) thì ACK có thể không được tính vào transaction này.
RFC3261 phân biệt hai loại transaction là Client Transaction và Server Transaction, tương ứng với hoạt động của UAC và UAS. Trong mỗi loại lại phân biệt giữa INVITE transaction và non-INVITE transaction.
Lớp transaction quản lý các transaction trong một thực thể SIP. Các client transaction được tạo ra theo chỉ thị từ TU, trong khi các server transaction được tạo ra dựa trên các bản tin request nhận được. Mỗi transaction hoạt động dưới dạng state-machine, có nhiệm vụ điều khiển việc phát lại các bản tin và chuyển các bản tin chính (hợp lệ) lên các TU để xử lý. Các sự kiện khác cũng được gửi lên TU để giám sát và đồng bộ.
So với phiên bản SIP/1.0, khái niệm transaction trong SIP/2.0 đã được bổ sung nhiều nội dung, trong đó chủ yếu tạo điều kiện thuận lợi cho việc tương ứng (matching) các bản tin với các transaction (sử dụng tham số branch trong trường Via, với giá trị đặc biệt "z9hG4bK").
1.2.2.2. Định tuyến message
1.2.2.2.1. Định tuyến response
Một request có thể sẽ được chuyển qua nhiều proxy khác nhau trước khi đến được client bị gọi. Đáp ứng cho request này cần được chuyển theo đúng vết cũ để đảm bảo logic cho transaction tại các proxy. Để làm được điều này, SIP sử dụng trường header Via theo kịch bản sau:
Mỗi khi request đi qua một proxy thì proxy đó sẽ chèn thêm một trường Via vào đầu danh sách (Via list) trong bản tin request đó, trường Via này chứa địa chỉ của proxy.
Client bị gọi khi tạo bản tin đáp ứng cần sao chép nguyên xi danh sách Via từ bản tin request, và gửi theo địa chỉ trong trường Via đầu tiên (top-most Via).
Khi proxy nhận được một đáp ứng, nó loại bỏ trường Via đầu tiên, sau đó forward đáp ứng theo địa chỉ trong Via tiếp theo (lúc này là top-most Via).
Trên thực tế Via list được sử dụng để các stateless proxy cũng có thể hoạt động được. Các stateful proxy không nên sử dụng top-most Via để xác định địa chỉ forward đáp ứng, mà nên lưu lại địa chỉ đã gửi request tới nó để phục vụ cho thao tác này.
Trong quá trình thiết lập phiên, thông thường sau khi chủ gọi nhận được đáp ứng khác với 100, các request tiếp theo như CANCEL, ACK, BYE hay re-INVITE sẽ được trao đổi trực tiếp giữa hai client mà không đi qua proxy. Khả năng này có được là do trong các đáp ứng non-100 đã có trường Contact chứa địa chỉ của bị gọi. Tuy nhiên các trao đổi vẫn phải đảm bảo nguyên tắc là đáp ứng cho request nào thì phải đi ngược lại vết mà request đó đã đi qua.
Tuy nhiên một số proxy có thể yêu cầu request tiếp theo trong cùng dialog (F2) vẫn đi qua proxy. Để làm được điều này, proxy chèn thêm trường Record-Route trong bản tin request trước đó (F1) trước khi forward đi, còn client nhận request sẽ sử dụng các Record- Route này trong đáp ứng cho F1 (không tính 100 Trying). Sau trao đổi này, cả hai client đều có tập route cho request tiếp theo (thứ tự các proxy trong tập route tại mỗi client là ngược nhau).
Chú ý: Tập route có thể nhỏ hơn tập Via, vì không phải proxy nào trên tuyến cũng có nhu cầu route.
Khi một client gửi F2, nó kiểm tra tập route hiện thời. Nếu tập route này rỗng thì F2 sẽ có Request-URI là Contact đã nhận được từ trước, và được gửi thẳng cho client phía bên kia theo giá trị trong trường Contact đó. Nếu tập route không rỗng, client sẽ lấy route đầu tiên làm Request-URI, các route còn lại để tạo nên các trường Route trong F2, và add thêm một trường Route với giá trị của Contact đã nhận từ trước, sau đó gửi tới địa chỉ trong route đầu tiên.
Về phía proxy, khi nhận được F2 có danh sách các trường Route, proxy sẽ kiểm tra Request-URI (nếu không phù hợp sẽ trả lời 400 Bad Request), thay thế Request-URI bằng giá trị của Route đầu tiên, loại bỏ Route này khỏi danh sách và forward F2 tới địa chỉ trong Route đó.
Ví dụ một cuộc gọi từ U1 đến U2 qua 4 proxy, trong đó P1, P3 và P4 yêu cầu route: U1 -> P1 -> P2 -> P3 -> P4 -> U2
Bản tin INVITE đến U2: INVITE sip:callee@u2.domain.com SIP/2.0 Contact: sip:caller@u1.example.com Record-Route: <sip:p4.domain.com> Record-Route: <sip:p3.middle.com> Record-Route: <sip:p1.example.com>
Giả sử U2 chủ động kết thúc cuộc gọi. Tập route tại U2 lúc này gồm P4, P3 và P1. Khi tạo bản tin BYE, U2 lấy Request-URI theo route đầu tiên (sip:p4.domain.com), các route còn lại tạo thành các trường Route, và lấy Contact từ INVITE trước đó tạo thêm một trường Route nữa. Bản tin BYE này được gửi tới P4:
BYE sip:p4.domain.com SIP/2.0 Route: <sip:p3.middle.com> Route: <sip:p1.example.com> Route: <sip:caller@u1.example.com>
P4 nhận thấy Request-URI là phù hợp với nó, nên thay thế Request-URI bằng trường Route đầu tiên (sip:p3.middle.com), loại bỏ trường Route này và forward tới P3:
BYE sip:p3.middle.com SIP/2.0 Route: <sip:p1.example.com> Route: <sip:caller@u1.example.com>
P3 nhận thấy Request-URI là phù hợp với nó, nên thay thế Request-URI bằng trường Route đầu tiên (sip:p1.example.com), loại bỏ trường Route này và forward tới P1:
BYE sip:p1.example.com SIP/2.0 Route: <sip:caller@u1.example.com>
P1 nhận thấy Request-URI là phù hợp với nó, nên thay thế Request-URI bằng trường Route đầu tiên (sip:caller@u1.example.com), loại bỏ trường Route này và forward tới U1:
BYE sip:caller@u1.example.com SIP/2.0
1.2.2.2.3. Loose Routing
Kỹ thuật nói trên được định nghĩa từ SIP/1.0 (RFC2543), còn gọi là Strict Routing. Đặc điểm của strict routing là khi forward giữa các chặng thì Request-URI phải thay đổi liên tục, và có nhiều ý kiến cho rằng định tuyến như vậy là hơi quá "strict". Vì vậy khi thiết kế RFC3261 (SIP/2.0), một quy tắc định tuyến mới gọi là Loose Routing đã được đưa vào, với nguyên tắc căn bản là không thay đổi Request-URI. Tuy nhiên để tương thích với version cũ thì Loose Routing cũng khá phức tạp:
Khi một proxy muốn F2 sẽ được gửi qua nó, proxy chèn thêm trường Record-Route trong bản tin F1 với giá trị là địa chỉ của proxy và tham số "lr" (viết tắt của loose routing). Đáp ứng của F1 giữ nguyên các trường Record-Route, để sau trao đổi thì hai bên đều có tập route giống nhau (nhưng thứ tự ngược lại, tất nhiên!).
Khi client tạo F2, nếu thấy route đầu tiên không có tham số "lr" (hoặc tập route rỗng) thì sẽ xử lý theo kiểu strict routing như đã nói ở trên. Nếu route đầu tiên là "loose", Request-URI của F2 sẽ là Contact trong bản tin nhận được trước đó, còn các route sẽ được dùng để tạo nên các trường Route, và bản tin F2 sẽ được chuyển đi theo địa chỉ trong route đầu tiên. Tuy nhiên client vẫn có thể sử dụng quy tắc của Strict Routing như ở mục trên cũng không sao.
Khi một proxy loại loose nhận được F2, nó kiểm tra Request-URI. Nếu Request-URI phù hợp với nó (nghĩa là proxy trước đó là strict) thì sẽ thay thế Request-URI bằng giá trị của trường Route cuối cùng, rồi loại bỏ trường Route này. Nếu không phù hợp (proxy
trước đó cũng là loose), thì kiểm tra trường Route đầu tiên và loại bỏ nếu phù hợp (nếu trường Route đầu tiên không phù hợp thì đáp ứng 400 và dừng tại đây).
Bây giờ proxy kiểm tra trường Route đầu tiên (next hop), nếu có tham số "lr" thì forward F2 theo địa chỉ trong trường Route đó. Nếu không có nghĩa là proxy tiếp theo sẽ là strict, và cần phải xử lý tương thích như sau: đưa Request-URI xuống thành Route cuối cùng, đưa Route đầu tiên lên làm Request-URI và forward theo địa chỉ của Request-URI mới.
Ví dụ một cuộc gọi từ U1 đến U2 qua 4 proxy, trong đó P1, P3 và P4 yêu cầu loose routing:
U1 -> P1 -> P2 -> P3 -> P4 -> U2 Bản tin INVITE đến U2: INVITE sip:callee@u2.domain.com SIP/2.0 Contact: sip:caller@u1.example.com Record-Route: <sip:p4.domain.com;lr> Record-Route: <sip:p3.middle.com;lr> Record-Route: <sip:p1.example.com;lr>
Giả sử U2 chủ động kết thúc cuộc gọi. Khi tạo bản tin BYE, U2 vẫn sử dụng quy tắc cũ và gửi tới P4 như sau:
BYE sip:p4.domain.com;lr SIP/2.0 Route: <sip:p3.middle.com;lr> Route: <sip:p1.example.com;lr> Route: <sip:caller@u1.example.com;lr>
P4 nhận thấy Request-URI là phù hợp với nó, nên thay thế Request-URI bằng trường Route cuối cùng (sip:caller@u1.example.com) và loại bỏ trường Route này. Sau đó nó nhận thấy trường Route đầu tiên (sip:p3.middle.com;lr) có tham số "lr", nên forward luôn bản tin theo địa chỉ trong Route đó (chính là địa chỉ của P3):
BYE sip:caller@u1.example.com SIP/2.0 Route: <sip:p3.middle.com;lr>
Route: <sip:p1.example.com;lr>
P3 nhận thấy Request-URI là không phù hợp với nó, nên giữ nguyên Request-URI và kiểm tra trường Route đầu tiên (sip:p3.middle.com;lr). Do trường Route đầu tiên phù hợp nên P3 loại bỏ trường Route đó. Bây giờ Route đầu tiên sẽ là <sip:p1.example.com;lr>, P3 nhận thấy Route này có tham số "lr" nên forward bản tin BYE theo địa chỉ trong Route đó (tới P1):
BYE sip:caller@u1.example.com SIP/2.0 Route: <sip:p1.example.com;lr>
P1 nhận thấy Request-URI là không phù hợp với nó, nên giữ nguyên Request-URI và kiểm tra trường Route đầu tiên (sip:p1.example.com;lr). Do trường Route đầu tiên phù
hợp nên P1 loại bỏ trường Route đó. Bây giờ trong bản tin không còn trường Route nào nữa, nên P1 sẽ forward theo địa chỉ trong Request-URI (tới U1):
Chƣơng 2: CÁC VẤN ĐỀ VÀ KHẢO SÁT THỰC NGHIỆM