# Copyright (c) 2007, Kundan Singh. All rights reserved. See LICENSING for details.
''' The session initiation protocol (SIP) as per RFC 3261. In my code there is no performance optimization, if it hurts the style and compactness of the code. ''' import re, socket from kutil import getlocaladdr from rfc2396 import isIPv4, isMulticast, URI, Address from rfc2617 import createAuthorization from socket import gethostbyname # TODO: should replace with getifaddr, SRV, NAPTR or similar _debug = False #----------------------- Header and Message ------------------------------- _quote = lambda s: '"' + s + '"' if s[0] != '"' != s[-1] else s _unquote = lambda s: s[1:-1] if s[0] == '"' == s[-1] else s # various header types: standard (default), address, comma and unstructured _address = ['contact', 'from', 'record-route', 'refer-to', 'referred-by', 'route', 'to'] _comma = ['authorization', 'proxy-authenticate', 'proxy-authorization', 'www-authenticate'] _unstructured = ['call-id', 'cseq', 'date', 'expires', 'max-forwards', 'organization', 'server', 'subject', 'timestamp', 'user-agent'] # short form of header names _short = ['allow-events', 'u', 'call-id', 'i', 'contact', 'm', 'content-encoding', 'e', 'content-length', 'l', 'content-type', 'c', 'event', 'o', 'from', 'f', 'subject', 's', 'supported', 'k', 'to', 't', 'via', 'v'] # exception for canonicalization of header names _exception = {'call-id':'Call-ID','cseq':'CSeq','www-authenticate':'WWW-Authenticate'} #_canon = lambda s: '-'.join([x.capitalize() for x in s.split('-')]) if s.lower() not in ['cseq','call-id','www-authenticate'] else {'cseq':'CSeq','call-id':'Call-ID','www-authenticate':'WWW-Authenticate'}[s.lower()] def _canon(s): '''Return the canonical form of the header. >>> print _canon('call-Id'), _canon('fRoM'), _canon('refer-to') Call-ID From Refer-To ''' s = s.lower() return ((len(s)==1) and s in _short and _canon(_short[_short.index(s)-1])) \ or (s in _exception and _exception[s]) or '-'.join([x.capitalize() for x in s.split('-')]) class Header(object): '''A SIP header object with dynamic properties. Attributes such as name, and various parameters can be accessed on the object. >>> print repr(Header('"Kundan Singh" <sip:kundan@example.net>', 'To')) To: "Kundan Singh" <sip:kundan@example.net> >>> print repr(Header('"Kundan"<sip:kundan99@example.net>', 'To')) To: "Kundan" <sip:kundan99@example.net> >>> print repr(Header('Sanjay <sip:sanjayc77@example.net>', 'fRoM')) From: "Sanjay" <sip:sanjayc77@example.net> >>> print repr(Header('application/sdp', 'conTenT-tyPe')) Content-Type: application/sdp >>> print repr(Header('presence; param=value;param2=another', 'Event')) Event: presence;param=value;param2=another >>> print repr(Header('78 INVITE', 'CSeq')) CSeq: 78 INVITE ''' def __init__(self, value=None, name=None): '''Construct a Header from optional value and optional name.''' self.name = name and _canon(name.strip()) or None self.value = self._parse(value.strip(), self.name and self.name.lower() or None) def _parse(self, value, name): '''Parse a header string value for the given header name.''' if name in _address: # address header addr = Address(); addr.mustQuote = True count = addr.parse(value) value, rest = addr, value[count:] if rest: for n,sep,v in map(lambda x: x.partition('='), rest.split(';') if rest else []): if n.strip(): self.__dict__[n.lower().strip()] = v.strip() elif name not in _comma and name not in _unstructured: # standard value, sep, rest = value.partition(';') for n,sep,v in map(lambda x: x.partition('='), rest.split(';') if rest else []): # TODO: add checks for token self.__dict__[n.lower().strip()] = v.strip() if name in _comma: self.authMethod, sep, rest = value.strip().partition(' ') for n,v in map(lambda x: x.strip().split('='), rest.split(',') if rest else []): self.__dict__[n.lower().strip()] = _unquote(v.strip()) elif name == 'cseq': n, sep, self.method = map(lambda x: x.strip(), value.partition(' ')) self.number = int(n); value = n + ' ' + self.method return value def __str__(self): '''Return a string representation of the header value.''' # TODO: use reduce instead of join+map name = self.name.lower() rest = '' if ((name in _comma) or (name in _unstructured)) \ else (';'.join(map(lambda x: self.__dict__[x] and '%s=%s'%(x.lower(),self.__dict__[x]) or x, filter(lambda x: x.lower() not in ['name','value', '_viauri'], self.__dict__)))) return str(self.value) + (rest and (';'+rest) or ''); def __repr__(self): '''Return the string representation of header's "name: value"''' return self.name + ": " + str(self) def dup(self): '''Duplicate this object.''' return Header(self.__str__(), self.name) # container access for parameters: use lower-case key in __dict__ def __getitem__(self, name): return self.__dict__.get(name.lower(), None) def __setitem__(self, name, value): self.__dict__[name.lower()] = value def __contains__(self, name): return name.lower() in self.__dict__ @property def viaUri(self): '''Read-only URI representing Via header's value. >>> print Header('SIP/2.0/UDP example.net:5090;ttl=1', 'Via').viaUri sip:example.net:5090;transport=udp >>> print Header('SIP/2.0/UDP 192.1.2.3;rport=1078;received=76.17.12.18;branch=0', 'Via').viaUri sip:76.17.12.18:1078;transport=udp >>> print Header('SIP/2.0/UDP 192.1.2.3;maddr=224.0.1.75', 'Via').viaUri sip:224.0.1.75:5060;transport=udp ''' if not hasattr(self, '_viaUri'): if self.name != 'Via': raise ValueError, 'viaUri available only on Via header' proto, addr = self.value.split(' ') type = proto.split('/')[2].lower() # udp, tcp, tls self._viaUri = URI('sip:' + addr + ';transport=' + type) if self._viaUri.port == None: self._viaUri.port = 5060 if 'rport' in self: try: self._viaUri.port = int(self.rport) except: pass # probably not an int if type not in ['tcp','sctp','tls']: if 'maddr' in self: self._viaUri.host = self.maddr elif 'received' in self: self._viaUri.host = self.received return self._viaUri @staticmethod def createHeaders(value): '''Parse a header line and return (name, [Header, Header, Header]) where name represents the header name, and the list has list of Header objects, typically one but for comma separated header line there can be multiple. >>> print Header.createHeaders('Event: presence, reg') ('Event', [Event: presence, Event: reg]) ''' name, value = map(str.strip, value.split(':', 1)) return (_canon(name), map(lambda x: Header(x, name), value.split(',') if name.lower() not in _comma else [value])) class Message(object): '''A SIP message object with dynamic properties. The header names can be accessed as attributes or items and are case-insensitive. Attributes such as method, uri (URI), response (int), responsetext, protocol, and body are available. Accessing an unavailable header gives None instead of exception. >>> m = Message() >>> m.method = 'INVITE' ''' # non-header attributes or items _keywords = ['method','uri','response','responsetext','protocol','_body','body'] # headers that can appear only atmost once. subsequent occurance ignored. _single = ['call-id', 'content-disposition', 'content-length', 'content-type', 'cseq', 'date', 'expires', 'event', 'max-forwards', 'organization', 'refer-to', 'referred-by', 'server', 'session-expires', 'subject', 'timestamp', 'to', 'user-agent'] def __init__(self, value=None): self.method = self.uri = self.response = self.responsetext = self.protocol = self._body = None if value: self._parse(value) # attribute access: use lower-case name, and use container if not found def __getattr__(self, name): return self.__getitem__(name) def __getattribute__(self, name): return object.__getattribute__(self, name.lower()) def __setattr__(self, name, value): object.__setattr__(self, name.lower(), value) def __delattr__(self, name): object.__delattr__(self, name.lower()) def __hasattr__(self, name): object.__hasattr__(self, name.lower()) # container access: use lower-case key in __dict__ def __getitem__(self, name): return self.__dict__.get(name.lower(), None) def __setitem__(self, name, value): self.__dict__[name.lower()] = value def __contains__(self, name): return name.lower() in self.__dict__ def _parse(self, value): '''Parse a SIP message as this object. Throws exception on error''' # TODO: perform all error checking: # 1. no \r\n\r\n in the message. # 2. no headers. # 3. first line has less than three parts. # 4. syntax for protocol, and must be SIP/2.0 # 5. syntax for method or response attributes # 6. first header must not start with a space or tab. # 7. detect and ignore header parsing and multiple instance errors. # 8. Content-Length if present must match the length of body. # 9. mandatory headers are To, From, Call-ID and CSeq. # 10. syntax for top Via header and fields: ttl, maddr, received, branch. firstheaders, body = value.split('\r\n\r\n', 1) firstline, headers = firstheaders.split('\r\n', 1) a, b, c = firstline.split(' ', 2) try: # try as response self.response, self.responsetext, self.protocol = int(b), c, a # throws error if b is not int. except: # probably a request self.method, self.uri, self.protocol = a, URI(b), c for h in headers.split('\r\n'): if h.startswith(r'[ \t]'): pass try: name, values = Header.createHeaders(h) if name not in self: # doesn't already exist self[name] = values if len(values) > 1 else values[0] elif name not in Message._single: # valid multiple-instance header if not isinstance(self[name],list): self[name] = [self[name]] self[name] += values except: if _debug: print 'error parsing', h continue bodyLen = int(self['Content-Length'].value) if 'Content-Length' in self else 0 if body: self.body = body if self.body != None and bodyLen != len(body): raise ValueError, 'Invalid content-length %d!=%d'%(bodyLen, len(body)) for h in ['To','From','CSeq','Call-ID']: if h not in self: raise ValueError, 'Mandatory header %s missing'%(h) def __repr__(self): '''Return the formatted message string.''' if self.method != None: m = self.method + ' ' + str(self.uri) + ' ' + self.protocol + '\r\n' elif self.response != None: m = self.protocol + ' ' + str(self.response) + ' ' + self.responsetext + '\r\n' else: return None # invalid message for h in self: m += repr(h) + '\r\n' m+= '\r\n' if self.body != None: m += self.body return m def dup(self): '''Duplicate this object.''' return Message(self.__repr__()) def __iter__(self): '''Return iterator to iterate over all Header objects.''' h = list() for n in filter(lambda x: not x.startswith('_') and x not in Message._keywords, self.__dict__): h += filter(lambda x: isinstance(x, Header), self[n] if isinstance(self[n],list) else [self[n]]) return iter(h) def first(self, name): '''Return the first Header object for this name, or None.''' result = self[name] return isinstance(result,list) and result[0] or result def all(self, *args): '''Return list of the Header object (or empty list) for all the header names in args.''' args = map(lambda x: x.lower(), args) h = list() for n in filter(lambda x: x in args and not x.startswith('_') and x not in Message._keywords, self.__dict__): h += filter(lambda x: isinstance(x, Header), self[n] if isinstance(self[n],list) else [self[n]]) return h def insert(self, header, append=False): if header and header.name: if header.name not in self: self[header.name] = header elif isinstance(self[header.name], Header): self[header.name] = (append and [self[header.name], header] or [header, self[header.name]]) else: if append: self[header.name].append(header) else: self[header.name].insert(0, header) # TODO: don't insert multi-instance if single header type. def delete(self, name, position=None): '''Delete a named header, either all (default) or at given position (0 for first, -1 for last).''' if position is None: del self[name] # remove all headers with this name else: h = self.all(name) # get all headers try: del h[position] # and remove at given position except: pass # ignore any error in index if len(h) == 0: del self[name] else: self[name] = h[0] if len(h) == 1 else h def body(): '''The body property, when set also sets the Content-Length header field.''' def fset(self, value): self._body = value self['Content-Length'] = Header('%d'%(value and len(value) or 0), 'Content-Length') def fget(self): return self._body return locals() body = property(**body()) @staticmethod def _populateMessage(m, headers=None, content=None): '''Modify m to add headers (list of Header objects) and content (str body)''' if headers: for h in headers: m.insert(h, True) # append the header instead of overriding if content: m.body = content else: m['Content-Length'] = Header('0', 'Content-Length') @staticmethod def createRequest(method, uri, headers=None, content=None): '''Create a new request Message with given attributes.''' m = Message() m.method, m.uri, m.protocol = method, URI(uri), 'SIP/2.0' Message._populateMessage(m, headers, content) if m.CSeq != None and m.CSeq.method != method: m.CSeq = Header(str(m.CSeq.number) + ' ' + method, 'CSeq') #if _debug: print 'createRequest returned\n', m return m @staticmethod def createResponse(response, responsetext, headers=None, content=None, r=None): '''Create a new response Message with given attributes. The original request may be specified as the r parameter.''' m = Message() m.response, m.responsetext, m.protocol = response, responsetext, 'SIP/2.0' if r: m.To, m.From, m.CSeq, m['Call-ID'], m.Via = r.To, r.From, r.CSeq, r['Call-ID'], r.Via if response == 100: m.Timestamp = r.Timestamp Message._populateMessage(m, headers, content) return m # define is1xx, is2xx, ... is6xx and isfinal for x in range(1,7): exec 'def is%dxx(self): return self.response and (self.response / 100 == %d)'%(x,x) exec 'is%dxx = property(is%dxx)'%(x,x) @property def isfinal(self): return self.response and (self.response >= 200) #---------------------------Stack------------------------------------------ import random class Stack(object): '''The SIP stack is associated with transport layer and controls message flow among different layers. The application must provide an app instance with following signature: class App(): def send(self, data, dest): pass 'to send data (str) to dest ('192.1.2.3', 5060).' def sending(self, data, dest): pass 'to indicate that a given data (Message) will be sent to the dest (host, port).' def createServer(self, request, uri): return UserAgent(stack, request) 'to ask the application to create a UAS for this request (Message) from source uri (Uri).' def receivedRequest(self, ua, request): pass 'to inform that the UAS or Dialog has recived a new request (Message).' def receivedResponse(self, ua, request): pass 'to inform that the UAC or Dialog has recived a new response (Message).' def cancelled(self, ua, request): pass 'to inform that the UAS or Dialog has received a cancel for original request (Message).' def dialogCreated(self, dialog, ua): pass 'to inform that the a new Dialog is created from the old UserAgent.' def authenticate(self, ua, header): header.password='mypass'; return True 'to ask the application for credentials for this challenge header (Header).' def createTimer(self, cbObj): return timerObject 'the returned timer object must have start() and stop() methods, a delay (int) attribute, and should invoke cbObj.timedout(timer) when the timer expires.' The application must invoke the following callback on the stack: stack.received(data, src) 'when incoming data (str) received on underlying transport from src ('192.2.2.2', 5060).' The application must provide a Transport object which is an object with these attributes: host, port, type, secure, reliable, congestionControlled, where host: a string representing listening IP address, e.g., '192.1.2.3' port: a int representing listening port number, e.g., 5060. type: a string of the form 'udp', 'tcp', 'tls', or 'sctp' indicating the transport type. secure: a boolean indicating whether this is secure or not? reliable: a boolean indicating whether the transport is reliable or not? congestionControlled: a boolean indicating whether the transport is congestion controlled? ''' def __init__(self, app, transport): '''Construct a stack using the specified application (higher) layer and transport (lower) data.''' self.tag = str(random.randint(0,2**31)) self.app, self.transport = app, transport self.closing = False self.dialogs, self.transactions = dict(), dict() self.serverMethods = ['INVITE','BYE','MESSAGE','SUBSCRIBE','NOTIFY'] def __del__(self): self.closing = True for d in self.dialogs: del self.dialogs[d] for t in self.transactions: del self.transactions[t] del self.dialogs; del self.transactions @property def uri(self): '''Construct a URI for the transport.''' transport = self.transport return URI(((transport.type == 'tls') and 'sips' or 'sip') + ':' + transport.host + ':' + str(transport.port)) @property def newCallId(self): return str(random.randint(0,2**31)) + '@' + (self.transport.host or 'localhost') def createVia(self, secure=False): if not self.transport: raise ValueError, 'No transport in stack' if secure and not self.transport.secure: raise ValueError, 'Cannot find a secure transport' return Header('SIP/2.0/' + self.transport.type.upper() + ' ' + self.transport.host + ':' + str(self.transport.port) + ';rport', 'Via') def send(self, data, dest=None, transport=None): '''Send a data (Message) to given dest (URI or hostPort), or using the Via header of response message if dest is missing.''' if dest and isinstance(dest, URI): if not uri.host: raise ValueError, 'No host in destination uri' dest = (dest.host, dest.port or self.transport.type == 'tls' and self.transport.secure and 5061 or 5060) if isinstance(data, Message): if data.method: # request
A client that sends a request to a multicast address MUST add the "maddr" parameter to its Via header field value containing the destination multicast address, and for IPv4, SHOULD add the "ttl" parameter with a value of 1. Usage of IPv6 multicast is not defined in this specification, and will be a subject of future standardization when the need arises.
if dest and isMulticast(dest[0]): data.first('Via')['maddr'], data.first('Via')['ttl'] = dest[0], 1 elif data.response: # response: use Via if dest missing if not dest: dest = data.first('Via').viaUri.hostPort self.app.send(str(data), dest, stack=self) def received(self, data, src): '''Callback when received some data (str) from the src ('host', port).''' try: m = Message(data) uri = URI((self.transport.secure and 'sips' or 'sip') + ':' + str(src[0]) + ':' + str(src[1])) if m.method: # request: update Via and call receivedRequest if m.Via == None: raise ValueError, 'No Via header in request' via = m.first('Via') if via.viaUri.host != src[0] or via.viaUri.port != src[1]: via['received'], via.viaUri.host = src[0], src[0] if 'rport' in via: via['rport'] = src[1] via.viaUri.port = src[1] self._receivedRequest(m, uri) elif m.response: # response: call receivedResponse self._receivedResponse(m, uri) else: raise ValueError, 'Received invalid message' except ValueError, E: # TODO: send 400 response to non-ACK request if _debug: print 'Error in received message:', E def _receivedRequest(self, r, uri): '''Received a SIP request r (Message) from the uri (URI).''' branch = r.first('Via').branch if r.method == 'ACK' and branch == '0': # TODO: this is a hack to work around iptel.org which puts branch=0 in all ACK # hence it matches the previous transaction's ACK for us, which is not good. # We need to fix our code to handle end-to-end ACK correctly in findTransaction. t = None else: t = self.findTransaction(Transaction.createId(branch, r.method)) if not t: # no transaction found app = None # the application layer for further processing if r.method != 'CANCEL' and 'tag' in r.To: # for existing dialog d = self.findDialog(r) if not d: # no dialog found if r.method != 'ACK': u = self.createServer(r, uri) if u: app = u else: self.send(Message.createResponse(481, 'Dialog does not exist', None, None, r)) return else: # hack to locate original t for ACK if _debug: print 'no dialog for ACK, finding transaction' if not t and branch != '0': t = self.findTransaction(Transaction.createId(branch, 'INVITE')) if t: t.receivedRequest(r) return else: if _debug: print 'No existing transaction for ACK' u = self.createServer(r, uri) if u: app = u else: if _debug: print 'Ignoring ACK without transaction' return else: # dialog found app = d elif r.method != 'CANCEL': # process all other out-of-dialog request except CANCEL u = self.createServer(r, uri) if u: app = u elif r.method == 'OPTIONS': m = Message.createResponse(200, 'OK', None, None, r) m.Allow = Header('INVITE, ACK, CANCEL, BYE, OPTIONS', 'Allow') self.send(m) return elif r.method != 'ACK': self.send(Message.createResponse(405, 'Method not allowed', None, None, r)) return else: # Process a CANCEL request o = self.findTransaction(Transaction.createId(r.first('Via').branch, 'INVITE')) # original transaction if not o: self.send(Message.createResponse(481, "Original transaction does not exist", None, None, r)) return else: app = o.app if app: t = app.createTransaction(r) #t = Transaction.createServer(self, app, r, self.transport, self.tag) elif r.method != 'ACK': self.send(Message.createResponse(404, "Not found", None, None, r)) else: t.receivedRequest(r) def _receivedResponse(self, r, uri): '''Received a SIP response r (Message) from the uri (URI).''' if not r.Via: raise ValueError, 'No Via header in received response' branch = r.first('Via').branch method = r.CSeq.method t = self.findTransaction(Transaction.createId(branch, method)) if not t: if method == 'INVITE' and r.is2xx: # success of INVITE d = self.findDialog(r) if not d: raise ValueError, 'No transaction or dialog for 2xx of INVITE' else: d.receivedResponse(None, r) else: raise ValueError, 'No transaction for response' else: t.receivedResponse(r) # following are the main API methods to indicate events from UAS/UAC/Dialog def createServer(self, request, uri): return self.app.createServer(request, uri, self) def sending(self, ua, message): self.app.sending(ua, message, self) def receivedRequest(self, ua, request): self.app.receivedRequest(ua, request, self) def receivedResponse(self, ua, response): self.app.receivedResponse(ua, response, self) def cancelled(self, ua, request): self.app.cancelled(ua, request, self) def dialogCreated(self, dialog, ua): self.app.dialogCreated(dialog, ua, self) def authenticate(self, ua, header): return self.app.authenticate(ua, header, self) def createTimer(self, obj): return self.app.createTimer(obj, self) def findDialog(self, arg): '''Find an existing dialog for given id (str) or received message (Message).''' return self.dialogs.get(isinstance(arg, Message) and Dialog.extractId(arg) or str(arg), None) def findTransaction(self, id): '''Find an existing transaction for given id (str).''' return self.transactions.get(id, None) def findOtherTransaction(self, r, orig): '''Find another transaction other than orig (Transaction) for this request r (Message).''' for t in self.transactions.values(): if t != orig and Transaction.equals(t, r, orig): return t return None class TransportInfo: '''Transport information needed by Stack constructor''' def __init__(self, sock, secure=False): '''The sock argument is the bound socket.''' addr = getlocaladdr(sock) self.host, self.port, self.type, self.secure, self.reliable, self.congestionControlled = addr[0], addr[1], (sock.type==socket.SOCK_DGRAM and 'udp' or 'tcp'), secure, (sock.type==socket.SOCK_STREAM), (sock.type==socket.SOCK_STREAM) #---------------------------Transaction------------------------------------ from hashlib import md5 from base64 import urlsafe_b64encode
17 Transactions SIP is a transactional protocol: interactions between components take place in a series of independent message exchanges. Specifically, a SIP transaction consists of a single request and any responses to that request, which include zero or more provisional responses and one or more final responses. In the case of a transaction where the request was an INVITE (known as an INVITE transaction), the transaction also includes the ACK only if the final response was not a 2xx response. If the response was a 2xx, the ACK is not considered part of the transaction. The reason for this separation is rooted in the importance of delivering all 200 (OK) responses to an INVITE to the UAC. To deliver them all to the UAC, the UAS alone takes responsibility for retransmitting them (see Section 13.3.1.4), and the UAC alone takes responsibility for acknowledging them with ACK (see Section 13.2.2.4). Since this ACK is retransmitted only by the UAC, it is effectively considered its own transaction. Transactions have a client side and a server side. The client side is known as a client transaction and the server side as a server transaction. The client transaction sends the request, and the server transaction sends the response. The client and server transactions are logical functions that are embedded in any number of elements. Specifically, they exist within user agents and stateful proxy servers. Consider the example in Section 4. In this example, the UAC executes the client transaction, and its outbound proxy executes the server transaction. The outbound proxy also executes a client transaction, which sends the request to a server transaction in the inbound proxy. That proxy also executes a client transaction, which in turn sends the request to a server transaction in the UAS. This is shown in Figure 4. +---------+ +---------+ +---------+ +---------+ | +-+|Request |+-+ +-+|Request |+-+ +-+|Request |+-+ | | |C||------->||S| |C||------->||S| |C||------->||S| | | |l|| ||e| |l|| ||e| |l|| ||e| | | |i|| ||r| |i|| ||r| |i|| ||r| | | |e|| ||v| |e|| ||v| |e|| ||v| | | |n|| ||e| |n|| ||e| |n|| ||e| | | |t|| ||r| |t|| ||r| |t|| ||r| | | | || || | | || || | | || || | | | |T|| ||T| |T|| ||T| |T|| ||T| | | |r|| ||r| |r|| ||r| |r|| ||r| | | |a|| ||a| |a|| ||a| |a|| ||a| | | |n|| ||n| |n|| ||n| |n|| ||n| | | |s||Response||s| |s||Response||s| |s||Response||s| | | +-+|<-------|+-+ +-+|<-------|+-+ +-+|<-------|+-+ | +---------+ +---------+ +---------+ +---------+ UAC Outbound Inbound UAS Proxy Proxy Figure 4: Transaction relationships A stateless proxy does not contain a client or server transaction. The transaction exists between the UA or stateful proxy on one side, and the UA or stateful proxy on the other side. As far as SIP transactions are concerned, stateless proxies are effectively transparent. The purpose of the client transaction is to receive a request from the element in which the client is embedded (call this element the "Transaction User" or TU; it can be a UA or a stateful proxy), and reliably deliver the request to a server transaction. The client transaction is also responsible for receiving responses and delivering them to the TU, filtering out any response retransmissions or disallowed responses (such as a response to ACK). Additionally, in the case of an INVITE request, the client transaction is responsible for generating the ACK request for any final response accepting a 2xx response. Similarly, the purpose of the server transaction is to receive requests from the transport layer and deliver them to the TU. The server transaction filters any request retransmissions from the network. The server transaction accepts responses from the TU and delivers them to the transport layer for transmission over the network. In the case of an INVITE transaction, it absorbs the ACK request for any final response excepting a 2xx response. The 2xx response and its ACK receive special treatment. This response is retransmitted only by a UAS, and its ACK generated only by the UAC. This end-to-end treatment is needed so that a caller knows the entire set of users that have accepted the call. Because of this special handling, retransmissions of the 2xx response are handled by the UA core, not the transaction layer. Similarly, generation of the ACK for the 2xx is handled by the UA core. Each proxy along the path merely forwards each 2xx response to INVITE and its corresponding ACK.
class Transaction(object): def __init__(self, server): '''Construct a transaction for the SIP method (str) and server (True or False) parameters, and uses the Invite/Non-invite Server/Client state machine accordingly.''' self.branch = self.id = self.stack = self.app = self.request = self.transport = self.remote = self.tag = None self.server, self.timers, self.timer = server, {}, Timer() def close(self): '''Stop the timers and remove from the lists.''' self.stopTimers() if self.stack: if self.id in self.stack.transactions: del self.stack.transactions[self.id] def state(): def fset(self, value): self._state = value if self._state == 'terminating': self.close() # automatically close when state goes terminating def fget(self): return self._state return locals() state = property(**state()) @property def headers(self): '''Read-only list of transaction Header objects (To, From, CSeq, Call-ID)''' return map(lambda x: self.request[x], ['To', 'From', 'CSeq', 'Call-ID']) @staticmethod def createBranch(request, server): '''Static method to create a branch parameter from request (Message) and server (Boolean) or using [To, From, Call-ID, CSeq-number(int)] and server (Boolean).''' To, From, CallId, CSeq = (request.To.value, request.From.value, request['Call-ID'].value, request.CSeq.number) if isinstance(request, Message) else (request[0], request[1], request[2], request[3]) data = str(To).lower() + '|' + str(From).lower() + '|' + str(CallId) + '|' + str(CSeq) + '|' + str(server) return 'z9hG4bK' + str(urlsafe_b64encode(md5(data).digest())).replace('=','.') @staticmethod def createId(branch, method): '''Static method to create a transaction identifier form branch and method''' return branch if method != 'ACK' and method != 'CANCEL' else branch + '|' + method @staticmethod def createServer(stack, app, request, transport, tag, start=True): '''Static method to create a server transaction.''' t = request.method == 'INVITE' and InviteServerTransaction() or ServerTransaction() t.stack, t.app, t.request, t.transport, t.tag = stack, app, request, transport, tag t.remote = request.first('Via').viaUri.hostPort t.branch = request.first('Via').branch if request.Via != None and 'branch' in request.first('Via') else Transaction.createBranch(request, True) t.id = Transaction.createId(t.branch, request.method) stack.transactions[t.id] = t if start: t.start() # invoke callback in UAS else: t.state = 'trying' # already invoked callback in UAS return t @staticmethod def createClient(stack, app, request, transport, remote): '''Static method to create a client transaction.''' t = request.method == 'INVITE' and InviteClientTransaction() or ClientTransaction() t.stack, t.app, t.request, t.remote, t.transport = stack, app, request, remote, transport t.branch = request.first('Via').branch if request.Via != None and 'branch' in request.first('Via') else Transaction.createBranch(request, False) t.id = Transaction.createId(t.branch, request.method) stack.transactions[t.id] = t t.start() return t @staticmethod def equals(t1, r, t2): '''Compare transaction t1 with new request r and original transaction t2.''' t = t1.request #return r.To.value.uri == t.To.value.uri and r.From.value.uri == t.From.value.uri \ # and r['Call-ID'].value == t['Call-ID'].value and r.CSeq.value == t.CSeq.value \ # and r.From.tag == t.From.tag and t2.server == t1.server a = r.To.value.uri == t.To.value.uri if _debug: print r.From.value.uri, t.From.value.uri a = a and (r.From.value.uri == t.From.value.uri) a = a and (r['Call-ID'].value == t['Call-ID'].value) a = a and (r.CSeq.value == t.CSeq.value) a = a and (r.From.tag == t.From.tag) a = a and (t2.server == t1.server) return a def createAck(self): '''Create an ACK request (Message) in this client transaction, else None.''' return Message.createRequest('ACK', str(self.request.uri), self.headers) if self.request and not self.server else None def createCancel(self): '''Create a CANCEL request (Message) in this client transaction, else None.''' m = Message.createRequest('CANCEL', str(self.request.uri), self.headers) if self.request and not self.server else None if m and self.request.Route: m.Route = self.request.Route if m: m.Via = self.request.first('Via') # only top Via included return m def createResponse(self, response, responsetext): '''Create a response (Message) in this server transaction, else None.''' m = Message.createResponse(response, responsetext, None, None, self.request) if self.request and self.server else None if response != 100 and 'tag' not in m.To: m.To['tag'] = self.tag # TODO: move this to UAS (?) return m def startTimer(self, name, timeout): '''Start a named timer with timeout (int).''' if timeout > 0: if name in self.timers: timer = self.timers[name] else: timer = self.timers[name] = self.stack.createTimer(self) timer.delay = timeout timer.start() def stopTimers(self): '''Stop all the named timers''' for v in self.timers.values(): v.stop() del self.timers def timedout(self, timer): '''Callback invoked by Timer returned by stack.createTimer().''' if timer.running: timer.stop() found = filter(lambda x: self.timers[x] == timer, self.timers.keys()) if len(found): for f in found: del self.timers[f] self.timeout(found[0], timer.delay)
A Table of Timer Values Table 4 summarizes the meaning and defaults of the various timers used by this specification. Timer Value Section Meaning ---------------------------------------------------------------------- T1 500ms default Section 17.1.1.1 RTT Estimate T2 4s Section 17.1.2.2 The maximum retransmit interval for non-INVITE requests and INVITE responses T4 5s Section 17.1.2.2 Maximum duration a message will remain in the network Timer A initially T1 Section 17.1.1.2 INVITE request retransmit interval, for UDP only Timer B 64*T1 Section 17.1.1.2 INVITE transaction timeout timer Timer C > 3min Section 16.6 proxy INVITE transaction bullet 11 timeout Timer D > 32s for UDP Section 17.1.1.2 Wait time for response 0s for TCP/SCTP retransmits Timer E initially T1 Section 17.1.2.2 non-INVITE request retransmit interval, UDP only Timer F 64*T1 Section 17.1.2.2 non-INVITE transaction timeout timer Timer G initially T1 Section 17.2.1 INVITE response retransmit interval Timer H 64*T1 Section 17.2.1 Wait time for ACK receipt Timer I T4 for UDP Section 17.2.1 Wait time for 0s for TCP/SCTP ACK retransmits Timer J 64*T1 for UDP Section 17.2.2 Wait time for 0s for TCP/SCTP non-INVITE request retransmits Timer K T4 for UDP Section 17.1.2.2 Wait time for 0s for TCP/SCTP response retransmits
class Timer(object): '''Various transaction timers as defined in RFC 3261.''' def __init__(self, T1=500, T2=4000, T4=5000): self.T1, self.T2, self.T4 = T1, T2, T4 def A(self): return self.T1 def B(self): return 64*self.T1 def D(self): return max(64*self.T1, 32000) def I(self): return self.T4 A, B, D, E, F, G, H, I, J, K = map(lambda x: property(x), [A, B, D, A, B, A, B, I, B, I])
17.1.2 Non-INVITE Client Transaction 17.1.2.1 Overview of the non-INVITE Transaction Non-INVITE transactions do not make use of ACK. They are simple request-response interactions. For unreliable transports, requests are retransmitted at an interval which starts at T1 and doubles until it hits T2. If a provisional response is received, retransmissions continue for unreliable transports, but at an interval of T2. The server transaction retransmits the last response it sent, which can be a provisional or final response, only when a retransmission of the request is received. This is why request retransmissions need to continue even after a provisional response; they are to ensure reliable delivery of the final response. Unlike an INVITE transaction, a non-INVITE transaction has no special handling for the 2xx response. The result is that only a single 2xx response to a non-INVITE is ever delivered to a UAC. 17.1.2.2 Formal Description The state machine for the non-INVITE client transaction is shown in Figure 6. It is very similar to the state machine for INVITE. The "Trying" state is entered when the TU initiates a new client transaction with a request. When entering this state, the client transaction SHOULD set timer F to fire in 64*T1 seconds. The request MUST be passed to the transport layer for transmission. If an unreliable transport is in use, the client transaction MUST set timer E to fire in T1 seconds. If timer E fires while still in this state, the timer is reset, but this time with a value of MIN(2*T1, T2). When the timer fires again, it is reset to a MIN(4*T1, T2). This process continues so that retransmissions occur with an exponentially increasing interval that caps at T2. The default value of T2 is 4s, and it represents the amount of time a non-INVITE server transaction will take to respond to a request, if it does not respond immediately. For the default values of T1 and T2, this results in intervals of 500 ms, 1 s, 2 s, 4 s, 4 s, 4 s, etc. If Timer F fires while the client transaction is still in the "Trying" state, the client transaction SHOULD inform the TU about the timeout, and then it SHOULD enter the "Terminated" state. If a provisional response is received while in the "Trying" state, the response MUST be passed to the TU, and then the client transaction SHOULD move to the "Proceeding" state. If a final response (status codes 200-699) is received while in the "Trying" state, the response MUST be passed to the TU, and the client transaction MUST transition to the "Completed" state. If Timer E fires while in the "Proceeding" state, the request MUST be passed to the transport layer for retransmission, and Timer E MUST be reset with a value of T2 seconds. If timer F fires while in the "Proceeding" state, the TU MUST be informed of a timeout, and the client transaction MUST transition to the terminated state. If a final response (status codes 200-699) is received while in the "Proceeding" state, the response MUST be passed to the TU, and the client transaction MUST transition to the "Completed" state. Once the client transaction enters the "Completed" state, it MUST set Timer K to fire in T4 seconds for unreliable transports, and zero seconds for reliable transports. The "Completed" state exists to buffer any additional response retransmissions that may be received (which is why the client transaction remains there only for unreliable transports). T4 represents the amount of time the network will take to clear messages between client and server transactions. The default value of T4 is 5s. A response is a retransmission when it matches the same transaction, using the rules specified in Section 17.1.3. If Timer K fires while in this state, the client transaction MUST transition to the "Terminated" state. Once the transaction is in the terminated state, it MUST be destroyed immediately. 17.1.3 Matching Responses to Client Transactions When the transport layer in the client receives a response, it has to determine which client transaction will handle the response, so that the processing of Sections 17.1.1 and 17.1.2 can take place. The branch parameter in the top Via header field is used for this purpose. A response matches a client transaction under two conditions: 1. If the response has the same value of the branch parameter in the top Via header field as the branch parameter in the top Via header field of the request that created the transaction. 2. If the method parameter in the CSeq header field matches the method of the request that created the transaction. The method is needed since a CANCEL request constitutes a different transaction, but shares the same value of the branch parameter. If a request is sent via multicast, it is possible that it will generate multiple responses from different servers. These responses will all have the same branch parameter in the topmost Via, but vary in the To tag. The first response received, based on the rules above, will be used, and others will be viewed as retransmissions. That is not an error; multicast SIP provides only a rudimentary "single-hop-discovery-like" service that is limited to processing a single response. See Section 18.1.1 for details. 17.1.4 Handling Transport Errors |Request from TU |send request Timer E V send request +-----------+ +---------| |-------------------+ | | Trying | Timer F | +-------->| | or Transport Err.| +-----------+ inform TU | 200-699 | | | resp. to TU | |1xx | +---------------+ |resp. to TU | | | | | Timer E V Timer F | | send req +-----------+ or Transport Err. | | +---------| | inform TU | | | |Proceeding |------------------>| | +-------->| |-----+ | | +-----------+ |1xx | | | ^ |resp to TU | | 200-699 | +--------+ | | resp. to TU | | | | | | V | | +-----------+ | | | | | | | Completed | | | | | | | +-----------+ | | ^ | | | | | Timer K | +--------------+ | - | | | V | NOTE: +-----------+ | | | | transitions | Terminated|<------------------+ labeled with | | the event +-----------+ over the action to take Figure 6: non-INVITE client transaction When the client transaction sends a request to the transport layer to be sent, the following procedures are followed if the transport layer indicates a failure. The client transaction SHOULD inform the TU that a transport failure has occurred, and the client transaction SHOULD transition directly to the "Terminated" state. The TU will handle the failover mechanisms described in [4]. 17.2 Server Transaction
class ClientTransaction(Transaction): '''Non-INVITE client transaction''' def __init__(self): Transaction.__init__(self, False) def start(self): self.state = 'trying' if not self.transport.reliable: self.startTimer('E', self.timer.E) self.startTimer('F', self.timer.F) self.stack.send(self.request, self.remote, self.transport) def receivedResponse(self, response): if response.is1xx: if self.state == 'trying': self.state = 'proceeding' self.app.receivedResponse(self, response) elif self.state == 'proceeding': self.app.receivedResponse(self, response) elif response.isfinal: if self.state == 'trying' or self.state == 'proceeding': self.state = 'completed' self.app.receivedResponse(self, response) if not self.transport.reliable: self.startTimer('K', self.timer.K) else: self.timeout('K', 0) def timeout(self, name, timeout): if self.state == 'trying' or self.state == 'proceeding': if name == 'E': timeout = min(2*timeout, self.timer.T2) if self.state == 'trying' else self.timer.T2 self.startTimer('E', timeout) self.stack.send(self.request, self.remote, self.transport) elif name == 'F': self.state = 'terminated' self.app.timeout(self) elif self.state == 'completed': if name == 'K': self.state = 'terminated' def error(self, error): if self.state == 'trying' or self.state == 'proceeding': self.state = 'terminated' self.app.error(self, error)
17.2.2 Non-INVITE Server Transaction The state machine for the non-INVITE server transaction is shown in Figure 8. The state machine is initialized in the "Trying" state and is passed a request other than INVITE or ACK when initialized. This request is passed up to the TU. Once in the "Trying" state, any further request retransmissions are discarded. A request is a retransmission if it matches the same server transaction, using the rules specified in Section 17.2.3. While in the "Trying" state, if the TU passes a provisional response to the server transaction, the server transaction MUST enter the "Proceeding" state. The response MUST be passed to the transport layer for transmission. Any further provisional responses that are received from the TU while in the "Proceeding" state MUST be passed to the transport layer for transmission. If a retransmission of the request is received while in the "Proceeding" state, the most recently sent provisional response MUST be passed to the transport layer for retransmission. If the TU passes a final response (status codes 200-699) to the server while in the "Proceeding" state, the transaction MUST enter the "Completed" state, and the response MUST be passed to the transport layer for transmission. When the server transaction enters the "Completed" state, it MUST set Timer J to fire in 64*T1 seconds for unreliable transports, and zero seconds for reliable transports. While in the "Completed" state, the server transaction MUST pass the final response to the transport layer for retransmission whenever a retransmission of the request is received. Any other final responses passed by the TU to the server transaction MUST be discarded while in the "Completed" state. The server transaction remains in this state until Timer J fires, at which point it MUST transition to the "Terminated" state. The server transaction MUST be destroyed the instant it enters the "Terminated" state. 17.2.3 Matching Requests to Server Transactions
|Request received |pass to TU V +-----------+ | | | Trying |-------------+ | | | +-----------+ |200-699 from TU | |send response |1xx from TU | |send response | | | Request V 1xx from TU | send response+-----------+send response| +--------| |--------+ | | | Proceeding| | | +------->| |<-------+ | +<--------------| | | |Trnsprt Err +-----------+ | |Inform TU | | | | | | |200-699 from TU | | |send response | | Request V | | send response+-----------+ | | +--------| | | | | | Completed |<------------+ | +------->| | +<--------------| | |Trnsprt Err +-----------+ |Inform TU | | |Timer J fires | |- | | | V | +-----------+ | | | +-------------->| Terminated| | | +-----------+ Figure 8: non-INVITE server transaction
class ServerTransaction(Transaction): '''Non-INVITE server transaction''' def __init__(self): Transaction.__init__(self, True) def start(self): self.state = 'trying' self.app.receivedRequest(self, self.request) def receivedRequest(self, request): if self.request.method == request.method: # retransmitted if self.state == 'proceeding' or self.state == 'completed': self.stack.send(self.lastResponse, self.remote, self.transport) elif self.state == 'trying': pass # just ignore the retransmitted request def timeout(self, name, timeout): if self.state == 'completed': if name == 'J': self.state = 'terminated' def error(self, error): if self.state == 'completed': self.state = 'terminated' self.app.error(self, error) def sendResponse(self, response): self.lastResponse = response; if response.is1xx: if self.state == 'trying' or self.state == 'proceedings': self.state = 'proceeding' self.stack.send(response, self.remote, self.transport) elif response.isfinal: if self.state == 'proceeding' or self.state == 'trying': self.state = 'completed' self.stack.send(response, self.remote, self.transport) if not self.transport.reliable: self.startTimer('J', self.timer.J) else: self.timeout('J', 0)
17.1.1 INVITE Client Transaction 17.1.1.1 Overview of INVITE Transaction The INVITE transaction consists of a three-way handshake. The client transaction sends an INVITE, the server transaction sends responses, and the client transaction sends an ACK. For unreliable transports (such as UDP), the client transaction retransmits requests at an interval that starts at T1 seconds and doubles after every retransmission. T1 is an estimate of the round-trip time (RTT), and it defaults to 500 ms. Nearly all of the transaction timers described here scale with T1, and changing T1 adjusts their values. The request is not retransmitted over reliable transports. After receiving a 1xx response, any retransmissions cease altogether, and the client waits for further responses. The server transaction can send additional 1xx responses, which are not transmitted reliably by the server transaction. Eventually, the server transaction decides to send a final response. For unreliable transports, that response is retransmitted periodically, and for reliable transports, it is sent once. For each final response that is received at the client transaction, the client transaction sends an ACK, the purpose of which is to quench retransmissions of the response. 17.1.1.2 Formal Description The state machine for the INVITE client transaction is shown in Figure 5. The initial state, "calling", MUST be entered when the TU initiates a new client transaction with an INVITE request. The client transaction MUST pass the request to the transport layer for transmission (see Section 18). If an unreliable transport is being used, the client transaction MUST start timer A with a value of T1. If a reliable transport is being used, the client transaction SHOULD NOT start timer A (Timer A controls request retransmissions). For any transport, the client transaction MUST start timer B with a value of 64*T1 seconds (Timer B controls transaction timeouts). When timer A fires, the client transaction MUST retransmit the request by passing it to the transport layer, and MUST reset the timer with a value of 2*T1. The formal definition of retransmit within the context of the transaction layer is to take the message previously sent to the transport layer and pass it to the transport layer once more. When timer A fires 2*T1 seconds later, the request MUST be retransmitted again (assuming the client transaction is still in this state). This process MUST continue so that the request is retransmitted with intervals that double after each transmission. These retransmissions SHOULD only be done while the client transaction is in the "calling" state. The default value for T1 is 500 ms. T1 is an estimate of the RTT between the client and server transactions. Elements MAY (though it is NOT RECOMMENDED) use smaller values of T1 within closed, private networks that do not permit general Internet connection. T1 MAY be chosen larger, and this is RECOMMENDED if it is known in advance (such as on high latency access links) that the RTT is larger. Whatever the value of T1, the exponential backoffs on retransmissions described in this section MUST be used. If the client transaction is still in the "Calling" state when timer B fires, the client transaction SHOULD inform the TU that a timeout has occurred. The client transaction MUST NOT generate an ACK. The value of 64*T1 is equal to the amount of time required to send seven requests in the case of an unreliable transport. If the client transaction receives a provisional response while in the "Calling" state, it transitions to the "Proceeding" state. In the "Proceeding" state, the client transaction SHOULD NOT retransmit the request any longer. Furthermore, the provisional response MUST be passed to the TU. Any further provisional responses MUST be passed up to the TU while in the "Proceeding" state. When in either the "Calling" or "Proceeding" states, reception of a response with status code from 300-699 MUST cause the client transaction to transition to "Completed". The client transaction MUST pass the received response up to the TU, and the client transaction MUST generate an ACK request, even if the transport is reliable (guidelines for constructing the ACK from the response are given in Section 17.1.1.3) and then pass the ACK to the transport layer for transmission. The ACK MUST be sent to the same address, port, and transport to which the original request was sent. The client transaction SHOULD start timer D when it enters the "Completed" state, with a value of at least 32 seconds for unreliable transports, and a value of zero seconds for reliable transports. Timer D reflects the amount of time that the server transaction can remain in the "Completed" state when unreliable transports are used. This is equal to Timer H in the INVITE server transaction, whose default is 64*T1. However, the client transaction does not know the value of T1 in use by the server transaction, so an absolute minimum of 32s is used instead of basing Timer D on T1. Any retransmissions of the final response that are received while in the "Completed" state MUST cause the ACK to be re-passed to the transport layer for retransmission, but the newly received response MUST NOT be passed up to the TU. A retransmission of the response is defined as any response which would match the same client transaction based on the rules of Section 17.1.3. |INVITE from TU Timer A fires |INVITE sent Reset A, V Timer B fires INVITE sent +-----------+ or Transport Err. +---------| |---------------+inform TU | | Calling | | +-------->| |-------------->| +-----------+ 2xx | | | 2xx to TU | | |1xx | 300-699 +---------------+ |1xx to TU | ACK sent | | | resp. to TU | 1xx V | | 1xx to TU -----------+ | | +---------| | | | | |Proceeding |-------------->| | +-------->| | 2xx | | +-----------+ 2xx to TU | | 300-699 | | | ACK sent, | | | resp. to TU| | | | | NOTE: | 300-699 V | | ACK sent +-----------+Transport Err. | transitions | +---------| |Inform TU | labeled with | | | Completed |-------------->| the event | +-------->| | | over the action | +-----------+ | to take | ^ | | | | | Timer D fires | +--------------+ | - | | | V | +-----------+ | | | | | Terminated|<--------------+ | | +-----------+ Figure 5: INVITE client transaction If timer D fires while the client transaction is in the "Completed" state, the client transaction MUST move to the terminated state. When in either the "Calling" or "Proceeding" states, reception of a 2xx response MUST cause the client transaction to enter the "Terminated" state, and the response MUST be passed up to the TU. The handling of this response depends on whether the TU is a proxy core or a UAC core. A UAC core will handle generation of the ACK for this response, while a proxy core will always forward the 200 (OK) upstream. The differing treatment of 200 (OK) between proxy and UAC is the reason that handling of it does not take place in the transaction layer. The client transaction MUST be destroyed the instant it enters the "Terminated" state. This is actually necessary to guarantee correct operation. The reason is that 2xx responses to an INVITE are treated differently; each one is forwarded by proxies, and the ACK handling in a UAC is different. Thus, each 2xx needs to be passed to a proxy core (so that it can be forwarded) and to a UAC core (so it can be acknowledged). No transaction layer processing takes place. Whenever a response is received by the transport, if the transport layer finds no matching client transaction (using the rules of Section 17.1.3), the response is passed directly to the core. Since the matching client transaction is destroyed by the first 2xx, subsequent 2xx will find no match and therefore be passed to the core.
class InviteClientTransaction(Transaction): '''INVITE client transaction''' def __init__(self): Transaction.__init__(self, False) def start(self): self.state = 'calling' if not self.transport.reliable: self.startTimer('A', self.timer.A) self.startTimer('B', self.timer.B) self.stack.send(self.request, self.remote, self.transport) def receivedResponse(self, response): if response.is1xx: if self.state == 'calling': self.state = 'proceeding' self.app.receivedResponse(self, response) elif self.state == 'proceeding': self.app.receivedResponse(self, response) elif response.is2xx: if self.state == 'calling' or self.state == 'proceeding': self.state = 'terminated' self.app.receivedResponse(self, response) else: # failure if self.state == 'calling' or self.state == 'proceeding': self.state = 'completed' self.stack.send(self.createAck(response), self.remote, self.transport) self.app.receivedResponse(self, response) if not self.transport.reliable: self.startTimer('D', self.timer.D) else: self.timeout('D', 0) elif self.state == 'completed': self.stack.send(self.createAck(response), self.remote, self.transport) def timeout(self, name, timeout): if self.state == 'calling': if name == 'A': self.startTimer('A', 2*timeout) self.stack.send(self.request, self.remote, self.transport) elif name == 'B': self.state = 'terminated' self.app.timeout(self) elif self.state == 'completed': if name == 'D': self.state = 'terminated' def error(self, error): if self.state == 'calling' or self.state == 'completed': self.state = 'terminated' self.app.error(self, error)
17.1.1.3 Construction of the ACK Request This section specifies the construction of ACK requests sent within the client transaction. A UAC core that generates an ACK for 2xx MUST instead follow the rules described in Section 13. The ACK request constructed by the client transaction MUST contain values for the Call-ID, From, and Request-URI that are equal to the values of those header fields in the request passed to the transport by the client transaction (call this the "original request"). The To header field in the ACK MUST equal the To header field in the response being acknowledged, and therefore will usually differ from the To header field in the original request by the addition of the tag parameter. The ACK MUST contain a single Via header field, and this MUST be equal to the top Via header field of the original request. The CSeq header field in the ACK MUST contain the same value for the sequence number as was present in the original request, but the method parameter MUST be equal to "ACK". If the INVITE request whose response is being acknowledged had Route header fields, those header fields MUST appear in the ACK. This is to ensure that the ACK can be routed properly through any downstream stateless proxies. Although any request MAY contain a body, a body in an ACK is special since the request cannot be rejected if the body is not understood. Therefore, placement of bodies in ACK for non-2xx is NOT RECOMMENDED, but if done, the body types are restricted to any that appeared in the INVITE, assuming that the response to the INVITE was not 415. If it was, the body in the ACK MAY be any type listed in the Accept header field in the 415.
def createAck(self, response): if not self.request: raise ValueError, 'No transaction request found' m = Message.createRequest('ACK', str(self.request.uri)) m['Call-ID'] = self.request['Call-ID'] m.From = self.request.From m.To = response.To if response else self.request.To m.Via = self.request.first("Via") # only top Via m.CSeq = Header(str(self.request.CSeq.number) + ' ACK', 'CSeq') if self.request.Route: m.Route = self.request.Route return m;
17.2.1 INVITE Server Transaction The state diagram for the INVITE server transaction is shown in Figure 7. When a server transaction is constructed for a request, it enters the "Proceeding" state. The server transaction MUST generate a 100 (Trying) response unless it knows that the TU will generate a provisional or final response within 200 ms, in which case it MAY generate a 100 (Trying) response. This provisional response is needed to quench request retransmissions rapidly in order to avoid network congestion. The 100 (Trying) response is constructed according to the procedures in Section 8.2.6, except that the insertion of tags in the To header field of the response (when none was present in the request) is downgraded from MAY to SHOULD NOT. The request MUST be passed to the TU. The TU passes any number of provisional responses to the server transaction. So long as the server transaction is in the "Proceeding" state, each of these MUST be passed to the transport layer for transmission. They are not sent reliably by the transaction layer (they are not retransmitted by it) and do not cause a change in the state of the server transaction. If a request retransmission is received while in the "Proceeding" state, the most recent provisional response that was received from the TU MUST be passed to the transport layer for retransmission. A request is a retransmission if it matches the same server transaction based on the rules of Section 17.2.3. If, while in the "Proceeding" state, the TU passes a 2xx response to the server transaction, the server transaction MUST pass this response to the transport layer for transmission. It is not retransmitted by the server transaction; retransmissions of 2xx responses are handled by the TU. The server transaction MUST then transition to the "Terminated" state. While in the "Proceeding" state, if the TU passes a response with status code from 300 to 699 to the server transaction, the response MUST be passed to the transport layer for transmission, and the state machine MUST enter the "Completed" state. For unreliable transports, timer G is set to fire in T1 seconds, and is not set to fire for reliable transports. This is a change from RFC 2543, where responses were always retransmitted, even over reliable transports. When the "Completed" state is entered, timer H MUST be set to fire in 64*T1 seconds for all transports. Timer H determines when the server transaction abandons retransmitting the response. Its value is chosen to equal Timer B, the amount of time a client transaction will continue to retry sending a request. If timer G fires, the response is passed to the transport layer once more for retransmission, and timer G is set to fire in MIN(2*T1, T2) seconds. From then on, when timer G fires, the response is passed to the transport again for transmission, and timer G is reset with a value that doubles, unless that value exceeds T2, in which case it is reset with the value of T2. This is identical to the retransmit behavior for requests in the "Trying" state of the non-INVITE client transaction. Furthermore, while in the "Completed" state, if a request retransmission is received, the server SHOULD pass the response to the transport for retransmission. If an ACK is received while the server transaction is in the "Completed" state, the server transaction MUST transition to the "Confirmed" state. As Timer G is ignored in this state, any retransmissions of the response will cease. If timer H fires while in the "Completed" state, it implies that the ACK was never received. In this case, the server transaction MUST transition to the "Terminated" state, and MUST indicate to the TU that a transaction failure has occurred. |INVITE |pass INV to TU INVITE V send 100 if TU won't in 200ms send response+-----------+ +--------| |--------+101-199 from TU | | Proceeding| |send response +------->| |<-------+ | | Transport Err. | | Inform TU | |--------------->+ +-----------+ | 300-699 from TU | |2xx from TU | send response | |send response | | +------------------>+ | | INVITE V Timer G fires | send response+-----------+ send response | +--------| |--------+ | | | Completed | | | +------->| |<-------+ | +-----------+ | | | | ACK | | | - | +------------------>+ | Timer H fires | V or Transport Err.| +-----------+ Inform TU | | | | | Confirmed | | | | | +-----------+ | | | |Timer I fires | |- | | | V | +-----------+ | | | | | Terminated|<---------------+ | | +-----------+ Figure 7: INVITE server transaction The purpose of the "Confirmed" state is to absorb any additional ACK messages that arrive, triggered from retransmissions of the final response. When this state is entered, timer I is set to fire in T4 seconds for unreliable transports, and zero seconds for reliable transports. Once timer I fires, the server MUST transition to the "Terminated" state. Once the transaction is in the "Terminated" state, it MUST be destroyed immediately. As with client transactions, this is needed to ensure reliability of the 2xx responses to INVITE.
# I modified to also have a trying state needed for proxy mode when 100 is not sent immediately. class InviteServerTransaction(Transaction): '''INVITE server transaction''' def __init__(self): Transaction.__init__(self, True) def start(self): self.state = 'proceeding' self.sendResponse(self.createResponse(100, 'Trying')) self.app.receivedRequest(self, self.request) def receivedRequest(self, request): if self.request.method == request.method: # retransmitted if self.state == 'proceeding' or self.state == 'completed': self.stack.send(self.lastResponse, self.remote, self.transport) elif request.method == 'ACK': if self.state == 'completed': self.state = 'confirmed' if not self.transport.reliable: self.startTimer('I', self.timer.I) else: self.timeout('I', 0) elif self.state == 'confirmed': pass # ignore the retransmitted ACK def timeout(self, name, timeout): if self.state == 'completed': if name == 'G': self.startTimer('G', min(2*timeout, self.timer.T2)) self.stack.send(self.lastResponse, self.remote, self.transport) elif name == 'H': self.state = 'terminated' self.app.timeout(self) elif self.state == 'confirmed': if name == 'I': self.state = 'terminated' def error(self, error): if self.state == 'proceeding' or self.state == 'trying' or self.state == 'confirmed': self.state = 'terminated' self.app.error(self, error) def sendResponse(self, response): self.lastResponse = response if response.is1xx: if self.state == 'proceeding' or self.state == 'trying': self.stack.send(response, self.remote, self.transport) elif response.is2xx: if self.state == 'proceeding' or self.state == 'trying': self.state = 'terminated' self.stack.send(response, self.remote, self.transport) else: # failure if self.state == 'proceeding' or self.state == 'trying': self.state = 'completed' if not self.transport.reliable: self.startTimer('G', self.timer.G) self.startTimer('H', self.timer.H) self.stack.send(response, self.remote, self.transport) if __name__ == '__main__': m = Message('INVITE sip:kundan@example.net SIP/2.0\r\n' + 'CSeq: 1 INVITE\r\n' + 'To: sip:kundan@example.net\r\n' + 'From: sip:sanjayc77@example.net\r\n' + 'Call-ID: 783713917681\r\n' + '\r\n') print Transaction.createBranch(m, True) #---------------------------UserAgent and Dialog---------------------------
8 General User Agent Behavior A user agent represents an end system. It contains a user agent client (UAC), which generates requests, and a user agent server (UAS), which responds to them. A UAC is capable of generating a request based on some external stimulus (the user clicking a button, or a signal on a PSTN line) and processing a response. A UAS is capable of receiving a request and generating a response based on user input, external stimulus, the result of a program execution, or some other mechanism. When a UAC sends a request, the request passes through some number of proxy servers, which forward the request towards the UAS. When the UAS generates a response, the response is forwarded towards the UAC. UAC and UAS procedures depend strongly on two factors. First, based on whether the request or response is inside or outside of a dialog, and second, based on the method of a request. Dialogs are discussed thoroughly in Section 12; they represent a peer-to-peer relationship between user agents and are established by specific SIP methods, such as INVITE.
class UserAgent(object): '''Represents both UAS and UAC.''' def __init__(self, stack, request=None, server=None): '''Construct as UAS (if incoming request Message is supplied) or UAC.''' self.stack, self.request = stack, request self.server = server if server != None else (request != None) self.transaction, self.cancelRequest = None, None self.callId = request['Call-ID'].value if request and request['Call-ID'] else stack.newCallId self.remoteParty = request.From.value if request and request.From else None self.localParty = request.To.value if request and request.To else None self.localTag, self.remoteTag = stack.tag + str(random.randint(0,10*10)), None self.subject = request.Subject.value if request and request.Subject else None self.secure = (request and request.uri.scheme == 'sips') self.maxForwards, self.routeSet = 70, [] self.localTarget, self.remoteTarget, self.remoteCandidates = None, None, None self.localSeq, self.remoteSeq = 0, 0 self.contact = Address(str(stack.uri)) if self.localParty and self.localParty.uri.user: self.contact.uri.user = self.localParty.uri.user self.autoack = True# whether to send an ACK to 200 OK of INVITE automatically or let application send it. self.auth = dict() # to store authentication context def __repr__(self): '''Just a textual representation of the UserAgent''' return '<%s call-id=%s>'%(isinstance(self, Dialog) and 'Dialog' or 'UserAgent', self.callId) def createTransaction(self, request): '''Create a new server transaction for the UAS request. A stateless proxy may not create a transaction.''' return Transaction.createServer(self.stack, self, request, self.stack.transport, self.stack.tag)
8.1.1 Generating the Request A valid SIP request formulated by a UAC MUST, at a minimum, contain the following header fields: To, From, CSeq, Call-ID, Max-Forwards, and Via; all of these header fields are mandatory in all SIP requests. These six header fields are the fundamental building blocks of a SIP message, as they jointly provide for most of the critical message routing services including the addressing of messages, the routing of responses, limiting message propagation, ordering of messages, and the unique identification of transactions. These header fields are in addition to the mandatory request line, which contains the method, Request-URI, and SIP version. Examples of requests sent outside of a dialog include an INVITE to establish a session (Section 13) and an OPTIONS to query for capabilities (Section 11). 8.1.1.1 Request-URI The initial Request-URI of the message SHOULD be set to the value of the URI in the To field. One notable exception is the REGISTER method; behavior for setting the Request-URI of REGISTER is given in Section 10. It may also be undesirable for privacy reasons or convenience to set these fields to the same value (especially if the originating UA expects that the Request-URI will be changed during transit). In some special circumstances, the presence of a pre-existing route set can affect the Request-URI of the message. A pre-existing route set is an ordered set of URIs that identify a chain of servers, to which a UAC will send outgoing requests that are outside of a dialog. Commonly, they are configured on the UA by a user or service provider manually, or through some other non-SIP mechanism. When a provider wishes to configure a UA with an outbound proxy, it is RECOMMENDED that this be done by providing it with a pre-existing route set with a single URI, that of the outbound proxy. When a pre-existing route set is present, the procedures for populating the Request-URI and Route header field detailed in Section 12.2.1.1 MUST be followed (even though there is no dialog), using the desired Request-URI as the remote target URI. 8.1.1.2 To The To header field first and foremost specifies the desired "logical" recipient of the request, or the address-of-record of the user or resource that is the target of this request. This may or may not be the ultimate recipient of the request. The To header field MAY contain a SIP or SIPS URI, but it may also make use of other URI schemes (the tel URL (RFC 2806 [9]), for example) when appropriate. All SIP implementations MUST support the SIP URI scheme. Any implementation that supports TLS MUST support the SIPS URI scheme. The To header field allows for a display name. A UAC may learn how to populate the To header field for a particular request in a number of ways. Usually the user will suggest the To header field through a human interface, perhaps inputting the URI manually or selecting it from some sort of address book. Frequently, the user will not enter a complete URI, but rather a string of digits or letters (for example, "bob"). It is at the discretion of the UA to choose how to interpret this input. Using the string to form the user part of a SIP URI implies that the UA wishes the name to be resolved in the domain to the right-hand side (RHS) of the at-sign in the SIP URI (for instance, sip:bob@example.com). Using the string to form the user part of a SIPS URI implies that the UA wishes to communicate securely, and that the name is to be resolved in the domain to the RHS of the at-sign. The RHS will frequently be the home domain of the requestor, which allows for the home domain to process the outgoing request. This is useful for features like "speed dial" that require interpretation of the user part in the home domain. The tel URL may be used when the UA does not wish to specify the domain that should interpret a telephone number that has been input by the user. Rather, each domain through which the request passes would be given that opportunity. As an example, a user in an airport might log in and send requests through an outbound proxy in the airport. If they enter "411" (this is the phone number for local directory assistance in the United States), that needs to be interpreted and processed by the outbound proxy in the airport, not the user's home domain. In this case, tel:411 would be the right choice. A request outside of a dialog MUST NOT contain a To tag; the tag in the To field of a request identifies the peer of the dialog. Since no dialog is established, no tag is present. For further information on the To header field, see Section 20.39. The following is an example of a valid To header field: To: Carol <sip:carol@chicago.com> 8.1.1.3 From The From header field indicates the logical identity of the initiator of the request, possibly the user's address-of-record. Like the To header field, it contains a URI and optionally a display name. It is used by SIP elements to determine which processing rules to apply to a request (for example, automatic call rejection). As such, it is very important that the From URI not contain IP addresses or the FQDN of the host on which the UA is running, since these are not logical names. The From header field allows for a display name. A UAC SHOULD use the display name "Anonymous", along with a syntactically correct, but otherwise meaningless URI (like sip:thisis@anonymous.invalid), if the identity of the client is to remain hidden. Usually, the value that populates the From header field in requests generated by a particular UA is pre-provisioned by the user or by the administrators of the user's local domain. If a particular UA is used by multiple users, it might have switchable profiles that include a URI corresponding to the identity of the profiled user. Recipients of requests can authenticate the originator of a request in order to ascertain that they are who their From header field claims they are (see Section 22 for more on authentication). The From field MUST contain a new "tag" parameter, chosen by the UAC. See Section 19.3 for details on choosing a tag. For further information on the From header field, see Section 20.20. Examples: From: "Bob" <sips:bob@biloxi.com> ;tag=a48s From: sip:+12125551212@phone2net.com;tag=887s From: Anonymous <sip:c8oqz84zk7z@privacy.org>;tag=hyh8 8.1.1.4 Call-ID The Call-ID header field acts as a unique identifier to group together a series of messages. It MUST be the same for all requests and responses sent by either UA in a dialog. It SHOULD be the same in each registration from a UA. In a new request created by a UAC outside of any dialog, the Call-ID header field MUST be selected by the UAC as a globally unique identifier over space and time unless overridden by method-specific behavior. All SIP UAs must have a means to guarantee that the Call- ID header fields they produce will not be inadvertently generated by any other UA. Note that when requests are retried after certain failure responses that solicit an amendment to a request (for example, a challenge for authentication), these retried requests are not considered new requests, and therefore do not need new Call-ID header fields; see Section 8.1.3.5. Use of cryptographically random identifiers (RFC 1750 [12]) in the generation of Call-IDs is RECOMMENDED. Implementations MAY use the form "localid@host". Call-IDs are case-sensitive and are simply compared byte-by-byte. Using cryptographically random identifiers provides some protection against session hijacking and reduces the likelihood of unintentional Call-ID collisions. No provisioning or human interface is required for the selection of the Call-ID header field value for a request. For further information on the Call-ID header field, see Section 20.8. Example: Call-ID: f81d4fae-7dec-11d0-a765-00a0c91e6bf6@foo.bar.com 8.1.1.5 CSeq The CSeq header field serves as a way to identify and order transactions. It consists of a sequence number and a method. The method MUST match that of the request. For non-REGISTER requests outside of a dialog, the sequence number value is arbitrary. The sequence number value MUST be expressible as a 32-bit unsigned integer and MUST be less than 2**31. As long as it follows the above guidelines, a client may use any mechanism it would like to select CSeq header field values. Section 12.2.1.1 discusses construction of the CSeq for requests within a dialog. Example: CSeq: 4711 INVITE 8.1.1.6 Max-Forwards The Max-Forwards header field serves to limit the number of hops a request can transit on the way to its destination. It consists of an integer that is decremented by one at each hop. If the Max-Forwards value reaches 0 before the request reaches its destination, it will be rejected with a 483(Too Many Hops) error response. A UAC MUST insert a Max-Forwards header field into each request it originates with a value that SHOULD be 70. This number was chosen to be sufficiently large to guarantee that a request would not be dropped in any SIP network when there were no loops, but not so large as to consume proxy resources when a loop does occur. Lower values should be used with caution and only in networks where topologies are known by the UA. 8.1.1.7 Via The Via header field indicates the transport used for the transaction and identifies the location where the response is to be sent. A Via header field value is added only after the transport that will be used to reach the next hop has been selected (which may involve the usage of the procedures in [4]). When the UAC creates a request, it MUST insert a Via into that request. The protocol name and protocol version in the header field MUST be SIP and 2.0, respectively. The Via header field value MUST contain a branch parameter. This parameter is used to identify the transaction created by that request. This parameter is used by both the client and the server. The branch parameter value MUST be unique across space and time for all requests sent by the UA. The exceptions to this rule are CANCEL and ACK for non-2xx responses. As discussed below, a CANCEL request will have the same value of the branch parameter as the request it cancels. As discussed in Section 17.1.1.3, an ACK for a non-2xx response will also have the same branch ID as the INVITE whose response it acknowledges. The uniqueness property of the branch ID parameter, to facilitate its use as a transaction ID, was not part of RFC 2543. The branch ID inserted by an element compliant with this specification MUST always begin with the characters "z9hG4bK". These 7 characters are used as a magic cookie (7 is deemed sufficient to ensure that an older RFC 2543 implementation would not pick such a value), so that servers receiving the request can determine that the branch ID was constructed in the fashion described by this specification (that is, globally unique). Beyond this requirement, the precise format of the branch token is implementation-defined. The Via header maddr, ttl, and sent-by components will be set when the request is processed by the transport layer (Section 18). Via processing for proxies is described in Section 16.6 Item 8 and Section 16.7 Item 3. 8.1.1.8 Contact The Contact header field provides a SIP or SIPS URI that can be used to contact that specific instance of the UA for subsequent requests. The Contact header field MUST be present and contain exactly one SIP or SIPS URI in any request that can result in the establishment of a dialog. For the methods defined in this specification, that includes only the INVITE request. For these requests, the scope of the Contact is global. That is, the Contact header field value contains the URI at which the UA would like to receive requests, and this URI MUST be valid even if used in subsequent requests outside of any dialogs. If the Request-URI or top Route header field value contains a SIPS URI, the Contact header field MUST contain a SIPS URI as well. For further information on the Contact header field, see Section 20.10. 8.1.1.9 Supported and Require If the UAC supports extensions to SIP that can be applied by the server to the response, the UAC SHOULD include a Supported header field in the request listing the option tags (Section 19.2) for those extensions. The option tags listed MUST only refer to extensions defined in standards-track RFCs. This is to prevent servers from insisting that clients implement non-standard, vendor-defined features in order to receive service. Extensions defined by experimental and informational RFCs are explicitly excluded from usage with the Supported header field in a request, since they too are often used to document vendor-defined extensions. If the UAC wishes to insist that a UAS understand an extension that the UAC will apply to the request in order to process the request, it MUST insert a Require header field into the request listing the option tag for that extension. If the UAC wishes to apply an extension to the request and insist that any proxies that are traversed understand that extension, it MUST insert a Proxy-Require header field into the request listing the option tag for that extension. As with the Supported header field, the option tags in the Require and Proxy-Require header fields MUST only refer to extensions defined in standards-track RFCs. 8.1.1.10 Additional Message Components After a new request has been created, and the header fields described above have been properly constructed, any additional optional header fields are added, as are any header fields specific to the method. SIP requests MAY contain a MIME-encoded message-body. Regardless of the type of body that a request contains, certain header fields must be formulated to characterize the contents of the body. For further information on these header fields, see Sections 20.11 through 20.15.
def createRequest(self, method, content=None, contentType=None): '''Create new UAC request.''' self.server = False if not self.remoteParty: raise ValueError, 'No remoteParty for UAC' if not self.localParty: self.localParty = Address('"Anonymous" <sip:anonymous@anonymous.invalid>') uri = URI(str(self.remoteTarget if self.remoteTarget else self.remoteParty.uri)) # TODO: use original URI for ACK if method == 'REGISTER': uri.user = None # no uri.user in REGISTER if not self.secure and uri.secure: self.secure = True if method != 'ACK' and method != 'CANCEL': self.localSeq = self.localSeq + 1 # initial headers To = Header(str(self.remoteParty), 'To') To.value.uri.secure = self.secure From = Header(str(self.localParty), 'From') From.value.uri.secure = self.secure From.tag = self.localTag CSeq = Header(str(self.localSeq) + ' ' + method, 'CSeq') CallId = Header(self.callId, 'Call-ID') MaxForwards = Header(str(self.maxForwards), 'Max-Forwards') Via = self.stack.createVia(self.secure) Via.branch = Transaction.createBranch([To.value, From.value, CallId.value, CSeq.number], False) # Transport adds other parameters such as maddr, ttl if not self.localTarget: self.localTarget = self.stack.uri.dup() self.localTarget.user = self.localParty.uri.user # put Contact is every request. app may remove or override it. Contact = Header(str(self.localTarget), 'Contact') Contact.value.uri.secure = self.secure headers = [To, From, CSeq, CallId, MaxForwards, Via, Contact] if self.routeSet: for route in map(lambda x: Header(str(x), 'Route'), self.routeSet): route.value.uri.secure = self.secure #print 'adding route header', route headers.append(route) # app adds other headers such as Supported, Require and Proxy-Require if contentType: headers.append(Header(contentType, 'Content-Type')) self.request = Message.createRequest(method, str(uri), headers, content) return self.request
10.2 Constructing the REGISTER Request REGISTER requests add, remove, and query bindings. A REGISTER request can add a new binding between an address-of-record and one or more contact addresses. Registration on behalf of a particular address-of-record can be performed by a suitably authorized third party. A client can also remove previous bindings or query to determine which bindings are currently in place for an address-of- record. Except as noted, the construction of the REGISTER request and the behavior of clients sending a REGISTER request is identical to the general UAC behavior described in Section 8.1 and Section 17.1. A REGISTER request does not establish a dialog. A UAC MAY include a Route header field in a REGISTER request based on a pre-existing route set as described in Section 8.1. The Record-Route header field has no meaning in REGISTER requests or responses, and MUST be ignored if present. In particular, the UAC MUST NOT create a new route set based on the presence or absence of a Record-Route header field in any response to a REGISTER request. The following header fields, except Contact, MUST be included in a REGISTER request. A Contact header field MAY be included: Request-URI: The Request-URI names the domain of the location service for which the registration is meant (for example, "sip:chicago.com"). The "userinfo" and "@" components of the SIP URI MUST NOT be present. To: The To header field contains the address of record whose registration is to be created, queried, or modified. The To header field and the Request-URI field typically differ, as the former contains a user name. This address-of-record MUST be a SIP URI or SIPS URI. From: The From header field contains the address-of-record of the person responsible for the registration. The value is the same as the To header field unless the request is a third- party registration. Call-ID: All registrations from a UAC SHOULD use the same Call-ID header field value for registrations sent to a particular registrar. If the same client were to use different Call-ID values, a registrar could not detect whether a delayed REGISTER request might have arrived out of order. CSeq: The CSeq value guarantees proper ordering of REGISTER requests. A UA MUST increment the CSeq value by one for each REGISTER request with the same Call-ID. Contact: REGISTER requests MAY contain a Contact header field with zero or more values containing address bindings. UAs MUST NOT send a new registration (that is, containing new Contact header field values, as opposed to a retransmission) until they have received a final response from the registrar for the previous one or the previous REGISTER request has timed out. bob +----+ | UA | | | +----+ | |3)INVITE | carol@chicago.com chicago.com +--------+ V +---------+ 2)Store|Location|4)Query +-----+ |Registrar|=======>| Service|<=======|Proxy|sip.chicago.com +---------+ +--------+=======>+-----+ A 5)Resp | | | | | 1)REGISTER| | | | +----+ | | UA |<-------------------------------+ cube2214a| | 6)INVITE +----+ carol@cube2214a.chicago.com carol Figure 2: REGISTER example The following Contact header parameters have a special meaning in REGISTER requests: action: The "action" parameter from RFC 2543 has been deprecated. UACs SHOULD NOT use the "action" parameter. expires: The "expires" parameter indicates how long the UA would like the binding to be valid. The value is a number indicating seconds. If this parameter is not provided, the value of the Expires header field is used instead. Implementations MAY treat values larger than 2**32-1 (4294967295 seconds or 136 years) as equivalent to 2**32-1. Malformed values SHOULD be treated as equivalent to 3600.
def createRegister(self, aor): '''Create a REGISTER request for given aor (Address).''' if aor: self.remoteParty = Address(str(aor)) if not self.localParty: self.localParty = Address(str(self.remoteParty)) return self.createRequest('REGISTER')
8.1.2 Sending the Request The destination for the request is then computed. Unless there is local policy specifying otherwise, the destination MUST be determined by applying the DNS procedures described in [4] as follows. If the first element in the route set indicated a strict router (resulting in forming the request as described in Section 12.2.1.1), the procedures MUST be applied to the Request-URI of the request. Otherwise, the procedures are applied to the first Route header field value in the request (if one exists), or to the request's Request-URI if there is no Route header field present. These procedures yield an ordered set of address, port, and transports to attempt. Independent of which URI is used as input to the procedures of [4], if the Request-URI specifies a SIPS resource, the UAC MUST follow the procedures of [4] as if the input URI were a SIPS URI. Local policy MAY specify an alternate set of destinations to attempt. If the Request-URI contains a SIPS URI, any alternate destinations MUST be contacted with TLS. Beyond that, there are no restrictions on the alternate destinations if the request contains no Route header field. This provides a simple alternative to a pre-existing route set as a way to specify an outbound proxy. However, that approach for configuring an outbound proxy is NOT RECOMMENDED; a pre-existing route set with a single URI SHOULD be used instead. If the request contains a Route header field, the request SHOULD be sent to the locations derived from its topmost value, but MAY be sent to any server that the UA is certain will honor the Route and Request-URI policies specified in this document (as opposed to those in RFC 2543). In particular, a UAC configured with an outbound proxy SHOULD attempt to send the request to the location indicated in the first Route header field value instead of adopting the policy of sending all messages to the outbound proxy. This ensures that outbound proxies that do not add Record-Route header field values will drop out of the path of subsequent requests. It allows endpoints that cannot resolve the first Route URI to delegate that task to an outbound proxy. The UAC SHOULD follow the procedures defined in [4] for stateful elements, trying each address until a server is contacted. Each try constitutes a new transaction, and therefore each carries a different topmost Via header field value with a new branch parameter. Furthermore, the transport value in the Via header field is set to whatever transport was determined for the target server.
def sendRequest(self, request): '''Send a UAC request Message.''' if not self.request and request.method == 'REGISTER': if not self.transaction and self.transaction.state != 'completed' and self.transaction.state != 'terminated': raise ValueError, 'Cannot re-REGISTER since pending registration' self.request = request # store for future if not request.Route: self.remoteTarget = request.uri target = self.remoteTarget if request.Route: routes = request.all('Route') if len(routes) > 0: target = routes[0].value.uri if not target or 'lr' not in target.param: # strict route if _debug: print 'strict route target=', target, 'routes=', routes del routes[0] # ignore first route if len(routes) > 0: if _debug: print 'appending our route' routes.append(Header(str(request.uri), 'Route')) request.Route = routes request.uri = target; # TODO: remove any Route header in REGISTER request self.stack.sending(self, request) # TODO: replace the following with RFC3263 to return multiple candidates. Add TCP and UDP and is possible TLS. dest = target.dup() dest.port = target.port or target.secure and 5061 or 5060 if not isIPv4(dest.host): try: dest.host = gethostbyname(dest.host) except: pass if isIPv4(dest.host): self.remoteCandidates = [dest] # continue processing as if we received multiple candidates if not self.remoteCandidates or len(self.remoteCandidates) == 0: self.error(None, 'cannot resolve DNS target') return target = self.remoteCandidates.pop(0) if self.request.method != 'ACK': # start a client transaction to send the request self.transaction = Transaction.createClient(self.stack, self, self.request, self.stack.transport, target.hostPort) else: # directly send ACK on transport layer self.stack.send(self.request, target.hostPort) def retryNextCandidate(self): '''Retry next DNS resolved address.''' if not self.remoteCandidates or len(self.remoteCandidates) == 0: raise ValueError, 'No more DNS resolved address to try' target = URI(self.remoteCandiates.pop(0)) self.request.first('Via').branch += 'A' # so that we create a different new transaction transaction = Transaction.createClient(self.stack, self, self.request, self.stack.transport, target.hostPort) @staticmethod def canCreateDialog(request, response): '''Whether we can create a dialog for this response of this request? Default is to create dialog for 2xx response to INVITE or SUBSCRIBE.''' return response.is2xx and (request.method == 'INVITE' or request.method == 'SUBSCRIBE')
8.1.3 Processing Responses Responses are first processed by the transport layer and then passed up to the transaction layer. The transaction layer performs its processing and then passes the response up to the TU. The majority of response processing in the TU is method specific. However, there are some general behaviors independent of the method. 8.1.3.1 Transaction Layer Errors In some cases, the response returned by the transaction layer will not be a SIP message, but rather a transaction layer error. When a timeout error is received from the transaction layer, it MUST be treated as if a 408 (Request Timeout) status code has been received. If a fatal transport error is reported by the transport layer (generally, due to fatal ICMP errors in UDP or connection failures in TCP), the condition MUST be treated as a 503 (Service Unavailable) status code. 8.1.3.2 Unrecognized Responses A UAC MUST treat any final response it does not recognize as being equivalent to the x00 response code of that class, and MUST be able to process the x00 response code for all classes. For example, if a UAC receives an unrecognized response code of 431, it can safely assume that there was something wrong with its request and treat the response as if it had received a 400 (Bad Request) response code. A UAC MUST treat any provisional response different than 100 that it does not recognize as 183 (Session Progress). A UAC MUST be able to process 100 and 183 responses. 8.1.3.3 Vias If more than one Via header field value is present in a response, the UAC SHOULD discard the message. The presence of additional Via header field values that precede the originator of the request suggests that the message was misrouted or possibly corrupted. 8.1.3.4 Processing 3xx Responses Upon receipt of a redirection response (for example, a 301 response status code), clients SHOULD use the URI(s) in the Contact header field to formulate one or more new requests based on the redirected request. This process is similar to that of a proxy recursing on a 3xx class response as detailed in Sections 16.5 and 16.6. A client starts with an initial target set containing exactly one URI, the Request-URI of the original request. If a client wishes to formulate new requests based on a 3xx class response to that request, it places the URIs to try into the target set. Subject to the restrictions in this specification, a client can choose which Contact URIs it places into the target set. As with proxy recursion, a client processing 3xx class responses MUST NOT add any given URI to the target set more than once. If the original request had a SIPS URI in the Request- URI, the client MAY choose to recurse to a non-SIPS URI, but SHOULD inform the user of the redirection to an insecure URI. Any new request may receive 3xx responses themselves containing the original URI as a contact. Two locations can be configured to redirect to each other. Placing any given URI in the target set only once prevents infinite redirection loops. As the target set grows, the client MAY generate new requests to the URIs in any order. A common mechanism is to order the set by the "q" parameter value from the Contact header field value. Requests to the URIs MAY be generated serially or in parallel. One approach is to process groups of decreasing q-values serially and process the URIs in each q-value group in parallel. Another is to perform only serial processing in decreasing q-value order, arbitrarily choosing between contacts of equal q-value. If contacting an address in the list results in a failure, as defined in the next paragraph, the element moves to the next address in the list, until the list is exhausted. If the list is exhausted, then the request has failed. Failures SHOULD be detected through failure response codes (codes greater than 399); for network errors the client transaction will report any transport layer failures to the transaction user. Note that some response codes (detailed in 8.1.3.5) indicate that the request can be retried; requests that are reattempted should not be considered failures. When a failure for a particular contact address is received, the client SHOULD try the next contact address. This will involve creating a new client transaction to deliver a new request. In order to create a request based on a contact address in a 3xx response, a UAC MUST copy the entire URI from the target set into the Request-URI, except for the "method-param" and "header" URI parameters (see Section 19.1.1 for a definition of these parameters). It uses the "header" parameters to create header field values for the new request, overwriting header field values associated with the redirected request in accordance with the guidelines in Section 19.1.5. Note that in some instances, header fields that have been communicated in the contact address may instead append to existing request header fields in the original redirected request. As a general rule, if the header field can accept a comma-separated list of values, then the new header field value MAY be appended to any existing values in the original redirected request. If the header field does not accept multiple values, the value in the original redirected request MAY be overwritten by the header field value communicated in the contact address. For example, if a contact address is returned with the following value: sip:user@host?Subject=foo&Call-Info=<http://www.foo.com> Then any Subject header field in the original redirected request is overwritten, but the HTTP URL is merely appended to any existing Call-Info header field values. It is RECOMMENDED that the UAC reuse the same To, From, and Call-ID used in the original redirected request, but the UAC MAY also choose to update the Call-ID header field value for new requests, for example. Finally, once the new request has been constructed, it is sent using a new client transaction, and therefore MUST have a new branch ID in the top Via field as discussed in Section 8.1.1.7. In all other respects, requests sent upon receipt of a redirect response SHOULD re-use the header fields and bodies of the original request. In some instances, Contact header field values may be cached at UAC temporarily or permanently depending on the status code received and the presence of an expiration interval; see Sections 21.3.2 and 21.3.3. 8.1.3.5 Processing 4xx Responses Certain 4xx response codes require specific UA processing, independent of the method. If a 401 (Unauthorized) or 407 (Proxy Authentication Required) response is received, the UAC SHOULD follow the authorization procedures of Section 22.2 and Section 22.3 to retry the request with credentials. If a 413 (Request Entity Too Large) response is received (Section 21.4.11), the request contained a body that was longer than the UAS was willing to accept. If possible, the UAC SHOULD retry the request, either omitting the body or using one of a smaller length. If a 415 (Unsupported Media Type) response is received (Section 21.4.13), the request contained media types not supported by the UAS. The UAC SHOULD retry sending the request, this time only using content with types listed in the Accept header field in the response, with encodings listed in the Accept-Encoding header field in the response, and with languages listed in the Accept-Language in the response. If a 416 (Unsupported URI Scheme) response is received (Section 21.4.14), the Request-URI used a URI scheme not supported by the server. The client SHOULD retry the request, this time, using a SIP URI. If a 420 (Bad Extension) response is received (Section 21.4.15), the request contained a Require or Proxy-Require header field listing an option-tag for a feature not supported by a proxy or UAS. The UAC SHOULD retry the request, this time omitting any extensions listed in the Unsupported header field in the response. In all of the above cases, the request is retried by creating a new request with the appropriate modifications. This new request constitutes a new transaction and SHOULD have the same value of the Call-ID, To, and From of the previous request, but the CSeq should contain a new sequence number that is one higher than the previous. With other 4xx responses, including those yet to be defined, a retry may or may not be possible depending on the method and the use case.
def receivedResponse(self, transaction, response): '''Received a new response from the transaction.''' if transaction and transaction != self.transaction: if _debug: print 'Invalid transaction received %r!=%r'%(transaction, self.transaction) return if len(response.all('Via')) > 1: raise ValueError, 'More than one Via header in response' if response.is1xx: if self.cancelRequest: cancel = Transaction.createClient(self.stack, self, self.cancelRequest, transaction.transport, transaction.remote) self.cancelRequest = None else: self.stack.receivedResponse(self, response) elif response.response == 401 or response.response == 407: # authentication challenge if not self.authenticate(response, self.transaction): # couldn't authenticate self.stack.receivedResponse(self, response) else: if self.canCreateDialog(self.request, response): dialog = Dialog.createClient(self.stack, self.request, response, transaction) self.stack.dialogCreated(dialog, self) self.stack.receivedResponse(dialog, response) if self.autoack and self.request.method == 'INVITE': dialog.sendRequest(dialog.createRequest('ACK')) else: self.stack.receivedResponse(self, response)
8.2 UAS Behavior When a request outside of a dialog is processed by a UAS, there is a set of processing rules that are followed, independent of the method. Section 12 gives guidance on how a UAS can tell whether a request is inside or outside of a dialog. Note that request processing is atomic. If a request is accepted, all state changes associated with it MUST be performed. If it is rejected, all state changes MUST NOT be performed. UASs SHOULD process the requests in the order of the steps that follow in this section (that is, starting with authentication, then inspecting the method, the header fields, and so on throughout the remainder of this section). 8.2.1 Method Inspection Once a request is authenticated (or authentication is skipped), the UAS MUST inspect the method of the request. If the UAS recognizes but does not support the method of a request, it MUST generate a 405 (Method Not Allowed) response. Procedures for generating responses are described in Section 8.2.6. The UAS MUST also add an Allow header field to the 405 (Method Not Allowed) response. The Allow header field MUST list the set of methods supported by the UAS generating the message. The Allow header field is presented in Section 20.5. If the method is one supported by the server, processing continues. 8.2.2 Header Inspection If a UAS does not understand a header field in a request (that is, the header field is not defined in this specification or in any supported extension), the server MUST ignore that header field and continue processing the message. A UAS SHOULD ignore any malformed header fields that are not necessary for processing requests. 8.2.2.1 To and Request-URI The To header field identifies the original recipient of the request designated by the user identified in the From field. The original recipient may or may not be the UAS processing the request, due to call forwarding or other proxy operations. A UAS MAY apply any policy it wishes to determine whether to accept requests when the To header field is not the identity of the UAS. However, it is RECOMMENDED that a UAS accept requests even if they do not recognize the URI scheme (for example, a tel: URI) in the To header field, or if the To header field does not address a known or current user of this UAS. If, on the other hand, the UAS decides to reject the request, it SHOULD generate a response with a 403 (Forbidden) status code and pass it to the server transaction for transmission. However, the Request-URI identifies the UAS that is to process the request. If the Request-URI uses a scheme not supported by the UAS, it SHOULD reject the request with a 416 (Unsupported URI Scheme) response. If the Request-URI does not identify an address that the UAS is willing to accept requests for, it SHOULD reject the request with a 404 (Not Found) response. Typically, a UA that uses the REGISTER method to bind its address-of-record to a specific contact address will see requests whose Request-URI equals that contact address. Other potential sources of received Request-URIs include the Contact header fields of requests and responses sent by the UA that establish or refresh dialogs. 8.2.2.2 Merged Requests If the request has no tag in the To header field, the UAS core MUST check the request against ongoing transactions. If the From tag, Call-ID, and CSeq exactly match those associated with an ongoing transaction, but the request does not match that transaction (based on the matching rules in Section 17.2.3), the UAS core SHOULD generate a 482 (Loop Detected) response and pass it to the server transaction. The same request has arrived at the UAS more than once, following different paths, most likely due to forking. The UAS processes the first such request received and responds with a 482 (Loop Detected) to the rest of them. 8.2.2.3 Require Assuming the UAS decides that it is the proper element to process the request, it examines the Require header field, if present. The Require header field is used by a UAC to tell a UAS about SIP extensions that the UAC expects the UAS to support in order to process the request properly. Its format is described in Section 20.32. If a UAS does not understand an option-tag listed in a Require header field, it MUST respond by generating a response with status code 420 (Bad Extension). The UAS MUST add an Unsupported header field, and list in it those options it does not understand amongst those in the Require header field of the request. Note that Require and Proxy-Require MUST NOT be used in a SIP CANCEL request, or in an ACK request sent for a non-2xx response. These header fields MUST be ignored if they are present in these requests. An ACK request for a 2xx response MUST contain only those Require and Proxy-Require values that were present in the initial request. Example: UAC->UAS: INVITE sip:watson@bell-telephone.com SIP/2.0 Require: 100rel UAS->UAC: SIP/2.0 420 Bad Extension Unsupported: 100rel This behavior ensures that the client-server interaction will proceed without delay when all options are understood by both sides, and only slow down if options are not understood (as in the example above). For a well-matched client-server pair, the interaction proceeds quickly, saving a round-trip often required by negotiation mechanisms. In addition, it also removes ambiguity when the client requires features that the server does not understand. Some features, such as call handling fields, are only of interest to end systems. 8.2.3 Content Processing Assuming the UAS understands any extensions required by the client, the UAS examines the body of the message, and the header fields that describe it. If there are any bodies whose type (indicated by the Content-Type), language (indicated by the Content-Language) or encoding (indicated by the Content-Encoding) are not understood, and that body part is not optional (as indicated by the Content- Disposition header field), the UAS MUST reject the request with a 415 (Unsupported Media Type) response. The response MUST contain an Accept header field listing the types of all bodies it understands, in the event the request contained bodies of types not supported by the UAS. If the request contained content encodings not understood by the UAS, the response MUST contain an Accept-Encoding header field listing the encodings understood by the UAS. If the request contained content with languages not understood by the UAS, the response MUST contain an Accept-Language header field indicating the languages understood by the UAS. Beyond these checks, body handling depends on the method and type. For further information on the processing of content-specific header fields, see Section 7.4 as well as Section 20.11 through 20.15. 8.2.4 Applying Extensions A UAS that wishes to apply some extension when generating the response MUST NOT do so unless support for that extension is indicated in the Supported header field in the request. If the desired extension is not supported, the server SHOULD rely only on baseline SIP and any other extensions supported by the client. In rare circumstances, where the server cannot process the request without the extension, the server MAY send a 421 (Extension Required) response. This response indicates that the proper response cannot be generated without support of a specific extension. The needed extension(s) MUST be included in a Require header field in the response. This behavior is NOT RECOMMENDED, as it will generally break interoperability. Any extensions applied to a non-421 response MUST be listed in a Require header field included in the response. Of course, the server MUST NOT apply extensions not listed in the Supported header field in the request. As a result of this, the Require header field in a response will only ever contain option tags defined in standards- track RFCs. 8.2.5 Processing the Request Assuming all of the checks in the previous subsections are passed, the UAS processing becomes method-specific. Section 10 covers the REGISTER request, Section 11 covers the OPTIONS request, Section 13 covers the INVITE request, and Section 15 covers the BYE request.
def receivedRequest(self, transaction, request): '''New incoming request in this transaction.''' if transaction and self.transaction and transaction != self.transaction and request.method != 'CANCEL': raise ValueError, 'Invalid transaction for received request' self.server = True # this becomes a UAS #if request.method == 'REGISTER': # response = transaction.createResponse(405, 'Method not allowed') # response.Allow = Header('INVITE, ACK, CANCEL, BYE', 'Allow') # TODO make this configurable # transaction.sendResponse(response) # return if request.uri.scheme not in ['sip', 'sips']: transaction.sendResponse(transaction.createResponse(416, 'Unsupported URI scheme')) return if 'tag' not in request.To: # out of dialog request if self.stack.findOtherTransaction(request, transaction): # request merging? transaction.sendResponse(transaction.createResponse(482, "Loop detected - found another transaction")) return if request.Require: # TODO let the application handle Require header if request.method != 'CANCEL' and request.method != 'ACK': response = transaction.createResponse(420, 'Bad extension') response.Unsupported = Header(str(request.Require.value), 'Unsupported') transaction.sendResponse(response) return if transaction: self.transaction = transaction # store it if request.method == 'CANCEL': original = self.stack.findTransaction(Transaction.createId(transaction.branch, 'INVITE')) if not original: transaction.sendResponse(transaction.createResponse(481, 'Original transaction not found')) return if original.state == 'proceeding' or original.state == 'trying': original.sendResponse(original.createResponse(487, 'Request terminated')) transaction.sendResponse(transaction.createResponse(200, 'OK')) # CANCEL response # TODO: the To tag must be same in the two responses self.stack.receivedRequest(self, request)
8.2.6 Generating the Response When a UAS wishes to construct a response to a request, it follows the general procedures detailed in the following subsections. Additional behaviors specific to the response code in question, which are not detailed in this section, may also be required. Once all procedures associated with the creation of a response have been completed, the UAS hands the response back to the server transaction from which it received the request. 8.2.6.1 Sending a Provisional Response One largely non-method-specific guideline for the generation of responses is that UASs SHOULD NOT issue a provisional response for a non-INVITE request. Rather, UASs SHOULD generate a final response to a non-INVITE request as soon as possible. When a 100 (Trying) response is generated, any Timestamp header field present in the request MUST be copied into this 100 (Trying) response. If there is a delay in generating the response, the UAS SHOULD add a delay value into the Timestamp value in the response. This value MUST contain the difference between the time of sending of the response and receipt of the request, measured in seconds. 8.2.6.2 Headers and Tags The From field of the response MUST equal the From header field of the request. The Call-ID header field of the response MUST equal the Call-ID header field of the request. The CSeq header field of the response MUST equal the CSeq field of the request. The Via header field values in the response MUST equal the Via header field values in the request and MUST maintain the same ordering. If a request contained a To tag in the request, the To header field in the response MUST equal that of the request. However, if the To header field in the request did not contain a tag, the URI in the To header field in the response MUST equal the URI in the To header field; additionally, the UAS MUST add a tag to the To header field in the response (with the exception of the 100 (Trying) response, in which a tag MAY be present). This serves to identify the UAS that is responding, possibly resulting in a component of a dialog ID. The same tag MUST be used for all responses to that request, both final and provisional (again excepting the 100 (Trying)). Procedures for the generation of tags are defined in Section 19.3.
def sendResponse(self, response, responsetext=None, content=None, contentType=None, createDialog=True): if not self.request: raise ValueError, 'Invalid request in sending a response' if isinstance(response, int): response = self.createResponse(response, responsetext, content, contentType) if createDialog and self.canCreateDialog(self.request, response): if self.request['Record-Route']: response['Record-Route'] = self.request['Record-Route'] if not response.Contact: contact = Address(str(self.contact)) if not contact.uri.user: contact.uri.user = self.request.To.value.uri.user contact.uri.secure = self.secure response.Contact = Header(str(contact), 'Contact') dialog = Dialog.createServer(self.stack, self.request, response, self.transaction) self.stack.dialogCreated(dialog, self) self.stack.sending(dialog, response) else: self.stack.sending(self, response) if not self.transaction: # send on transport self.stack.send(response, response.first('Via').viaUri.hostPort) else: self.transaction.sendResponse(response) def createResponse(self, response, responsetext, content=None, contentType=None): if not self.request: raise ValueError, 'Invalid request in creating a response' response = Message.createResponse(response, responsetext, None, content, self.request) if contentType: response['Content-Type'] = Header(contentType, 'Content-Type') if response.response != 100 and 'tag' not in response.To: response.To['tag'] = self.localTag return response;
9 Canceling a Request The previous section has discussed general UA behavior for generating requests and processing responses for requests of all methods. In this section, we discuss a general purpose method, called CANCEL. The CANCEL request, as the name implies, is used to cancel a previous request sent by a client. Specifically, it asks the UAS to cease processing the request and to generate an error response to that request. CANCEL has no effect on a request to which a UAS has already given a final response. Because of this, it is most useful to CANCEL requests to which it can take a server long time to respond. For this reason, CANCEL is best for INVITE requests, which can take a long time to generate a response. In that usage, a UAS that receives a CANCEL request for an INVITE, but has not yet sent a final response, would "stop ringing", and then respond to the INVITE with a specific error response (a 487). CANCEL requests can be constructed and sent by both proxies and user agent clients. Section 15 discusses under what conditions a UAC would CANCEL an INVITE request, and Section 16.10 discusses proxy usage of CANCEL. A stateful proxy responds to a CANCEL, rather than simply forwarding a response it would receive from a downstream element. For that reason, CANCEL is referred to as a "hop-by-hop" request, since it is responded to at each stateful proxy hop. 9.1 Client Behavior A CANCEL request SHOULD NOT be sent to cancel a request other than INVITE. Since requests other than INVITE are responded to immediately, sending a CANCEL for a non-INVITE request would always create a race condition. The following procedures are used to construct a CANCEL request. The Request-URI, Call-ID, To, the numeric part of CSeq, and From header fields in the CANCEL request MUST be identical to those in the request being cancelled, including tags. A CANCEL constructed by a client MUST have only a single Via header field value matching the top Via value in the request being cancelled. Using the same values for these header fields allows the CANCEL to be matched with the request it cancels (Section 9.2 indicates how such matching occurs). However, the method part of the CSeq header field MUST have a value of CANCEL. This allows it to be identified and processed as a transaction in its own right (See Section 17). If the request being cancelled contains a Route header field, the CANCEL request MUST include that Route header field's values. This is needed so that stateless proxies are able to route CANCEL requests properly. The CANCEL request MUST NOT contain any Require or Proxy-Require header fields. Once the CANCEL is constructed, the client SHOULD check whether it has received any response (provisional or final) for the request being cancelled (herein referred to as the "original request"). If no provisional response has been received, the CANCEL request MUST NOT be sent; rather, the client MUST wait for the arrival of a provisional response before sending the request. If the original request has generated a final response, the CANCEL SHOULD NOT be sent, as it is an effective no-op, since CANCEL has no effect on requests that have already generated a final response. When the client decides to send the CANCEL, it creates a client transaction for the CANCEL and passes it the CANCEL request along with the destination address, port, and transport. The destination address, port, and transport for the CANCEL MUST be identical to those used to send the original request. If it was allowed to send the CANCEL before receiving a response for the previous request, the server could receive the CANCEL before the original request. Note that both the transaction corresponding to the original request and the CANCEL transaction will complete independently. However, a UAC canceling a request cannot rely on receiving a 487 (Request Terminated) response for the original request, as an RFC 2543- compliant UAS will not generate such a response. If there is no final response for the original request in 64*T1 seconds (T1 is defined in Section 17.1.1.1), the client SHOULD then consider the original transaction cancelled and SHOULD destroy the client transaction handling the original request. 9.2 Server Behavior The CANCEL method requests that the TU at the server side cancel a pending transaction. The TU determines the transaction to be cancelled by taking the CANCEL request, and then assuming that the request method is anything but CANCEL or ACK and applying the transaction matching procedures of Section 17.2.3. The matching transaction is the one to be cancelled. The processing of a CANCEL request at a server depends on the type of server. A stateless proxy will forward it, a stateful proxy might respond to it and generate some CANCEL requests of its own, and a UAS will respond to it. See Section 16.10 for proxy treatment of CANCEL. A UAS first processes the CANCEL request according to the general UAS processing described in Section 8.2. However, since CANCEL requests are hop-by-hop and cannot be resubmitted, they cannot be challenged by the server in order to get proper credentials in an Authorization header field. Note also that CANCEL requests do not contain a Require header field. If the UAS did not find a matching transaction for the CANCEL according to the procedure above, it SHOULD respond to the CANCEL with a 481 (Call Leg/Transaction Does Not Exist). If the transaction for the original request still exists, the behavior of the UAS on receiving a CANCEL request depends on whether it has already sent a final response for the original request. If it has, the CANCEL request has no effect on the processing of the original request, no effect on any session state, and no effect on the responses generated for the original request. If the UAS has not issued a final response for the original request, its behavior depends on the method of the original request. If the original request was an INVITE, the UAS SHOULD immediately respond to the INVITE with a 487 (Request Terminated). A CANCEL request has no impact on the processing of transactions with any other method defined in this specification. Regardless of the method of the original request, as long as the CANCEL matched an existing transaction, the UAS answers the CANCEL request itself with a 200 (OK) response. This response is constructed following the procedures described in Section 8.2.6 noting that the To tag of the response to the CANCEL and the To tag in the response to the original request SHOULD be the same. The response to CANCEL is passed to the server transaction for transmission.
def sendCancel(self): '''Cancel a request.''' if not self.transaction: raise ValueError, 'No transaction for sending CANCEL' self.cancelRequest = self.transaction.createCancel() if self.transaction.state != 'trying' and self.transaction.state != 'calling': if self.transaction.state == 'proceeding': transaction = Transaction.createClient(self.stack, self, self.cancelRequest, self.transaction.transport, self.transaction.remote) self.cancelRequest = None # else don't send until 1xx is received def timeout(self, transaction): '''A client transaction was timedout.''' if transaction and transaction != self.transaction: # invalid transaction return self.transaction = None if not self.server: # UAC if self.remoteCandidates and len(self.remoteCandidates)>0: self.retryNextCandidate() else: self.receivedResponse(None, Message.createResponse(408, 'Request timeout', None, None, self.request)) def error(self, transaction, error): '''A transaction gave transport error.''' if transaction and transaction != self.transaction: # invalid transaction return self.transaction = None if not self.server: # UAC if self.remoteCandidates and len(self.remoteCandidates)>0: self.retryNextCandidate() else: self.receivedResponse(None, Message.createResponse(503, 'Service unavailable - ' + error, None, None, self.request)) def authenticate(self, response, transaction): '''Whether we can supply the credentials locally to authenticate or not? If we can, then re-send the request in new transaction and return true, else return false''' a = response.first('WWW-Authenticate') or response.first('Proxy-Authenticate') or None if not a: return False request = Message(str(transaction.request)) # construct a new message resend, present = False, False for b in request.all('Authorization', 'Proxy-Authorization'): if a.realm == b.realm and (a.name == 'WWW-Authenticate' and b.name == 'Authorization' or a.name == 'Proxy-Authenticate' and b.name == 'Proxy-Authorization'): present = True break if not present and 'realm' in a: # prompt for password result = self.stack.authenticate(self, a) if not result or 'password' not in a and 'hashValue' not in a: return False value = createAuthorization(a.value, a.username, a.password, str(request.uri), self.request.method, self.request.body, self.auth) if value: request.insert(Header(value, (a.name == 'WWW-Authenticate') and 'Authorization' or 'Proxy-Authorization'), True) resend = True if resend: self.localSeq = self.localSeq + 1 request.CSeq = Header(str(self.localSeq) + ' ' + request.method, 'CSeq') request.first('Via').branch = Transaction.createBranch(request, False) self.request = request self.transaction = Transaction.createClient(self.stack, self, self.request, self.transaction.transport, self.transaction.remote) return True else: return False;
12 Dialogs A key concept for a user agent is that of a dialog. A dialog represents a peer-to-peer SIP relationship between two user agents that persists for some time. The dialog facilitates sequencing of messages between the user agents and proper routing of requests between both of them. The dialog represents a context in which to interpret SIP messages. Section 8 discussed method independent UA processing for requests and responses outside of a dialog. This section discusses how those requests and responses are used to construct a dialog, and then how subsequent requests and responses are sent within a dialog. A dialog is identified at each UA with a dialog ID, which consists of a Call-ID value, a local tag and a remote tag. The dialog ID at each UA involved in the dialog is not the same. Specifically, the local tag at one UA is identical to the remote tag at the peer UA. The tags are opaque tokens that facilitate the generation of unique dialog IDs. A dialog ID is also associated with all responses and with any request that contains a tag in the To field. The rules for computing the dialog ID of a message depend on whether the SIP element is a UAC or UAS. For a UAC, the Call-ID value of the dialog ID is set to the Call-ID of the message, the remote tag is set to the tag in the To field of the message, and the local tag is set to the tag in the From field of the message (these rules apply to both requests and responses). As one would expect for a UAS, the Call-ID value of the dialog ID is set to the Call-ID of the message, the remote tag is set to the tag in the From field of the message, and the local tag is set to the tag in the To field of the message. A dialog contains certain pieces of state needed for further message transmissions within the dialog. This state consists of the dialog ID, a local sequence number (used to order requests from the UA to its peer), a remote sequence number (used to order requests from its peer to the UA), a local URI, a remote URI, remote target, a boolean flag called "secure", and a route set, which is an ordered list of URIs. The route set is the list of servers that need to be traversed to send a request to the peer. A dialog can also be in the "early" state, which occurs when it is created with a provisional response, and then transition to the "confirmed" state when a 2xx final response arrives. For other responses, or if no response arrives at all on that dialog, the early dialog terminates.
class Dialog(UserAgent): '''A SIP dialog'''
12.1 Creation of a Dialog Dialogs are created through the generation of non-failure responses to requests with specific methods. Within this specification, only 2xx and 101-199 responses with a To tag, where the request was INVITE, will establish a dialog. A dialog established by a non-final response to a request is in the "early" state and it is called an early dialog. Extensions MAY define other means for creating dialogs. Section 13 gives more details that are specific to the INVITE method. Here, we describe the process for creation of dialog state that is not dependent on the method. UAs MUST assign values to the dialog ID components as described below. 12.1.1 UAS behavior When a UAS responds to a request with a response that establishes a dialog (such as a 2xx to INVITE), the UAS MUST copy all Record-Route header field values from the request into the response (including the URIs, URI parameters, and any Record-Route header field parameters, whether they are known or unknown to the UAS) and MUST maintain the order of those values. The UAS MUST add a Contact header field to the response. The Contact header field contains an address where the UAS would like to be contacted for subsequent requests in the dialog (which includes the ACK for a 2xx response in the case of an INVITE). Generally, the host portion of this URI is the IP address or FQDN of the host. The URI provided in the Contact header field MUST be a SIP or SIPS URI. If the request that initiated the dialog contained a SIPS URI in the Request-URI or in the top Record-Route header field value, if there was any, or the Contact header field if there was no Record-Route header field, the Contact header field in the response MUST be a SIPS URI. The URI SHOULD have global scope (that is, the same URI can be used in messages outside this dialog). The same way, the scope of the URI in the Contact header field of the INVITE is not limited to this dialog either. It can therefore be used in messages to the UAC even outside this dialog. The UAS then constructs the state of the dialog. This state MUST be maintained for the duration of the dialog. If the request arrived over TLS, and the Request-URI contained a SIPS URI, the "secure" flag is set to TRUE. The route set MUST be set to the list of URIs in the Record-Route header field from the request, taken in order and preserving all URI parameters. If no Record-Route header field is present in the request, the route set MUST be set to the empty set. This route set, even if empty, overrides any pre-existing route set for future requests in this dialog. The remote target MUST be set to the URI from the Contact header field of the request. The remote sequence number MUST be set to the value of the sequence number in the CSeq header field of the request. The local sequence number MUST be empty. The call identifier component of the dialog ID MUST be set to the value of the Call-ID in the request. The local tag component of the dialog ID MUST be set to the tag in the To field in the response to the request (which always includes a tag), and the remote tag component of the dialog ID MUST be set to the tag from the From field in the request. A UAS MUST be prepared to receive a request without a tag in the From field, in which case the tag is considered to have a value of null. This is to maintain backwards compatibility with RFC 2543, which did not mandate From tags. The remote URI MUST be set to the URI in the From field, and the local URI MUST be set to the URI in the To field.
@staticmethod def createServer(stack, request, response, transaction): '''Create a dialog from UAS while sending response to request in the transaction.''' d = Dialog(stack, request, True) d.request = request d.routeSet = request.all('Record-Route') if request['Record-Route'] else None while d.routeSet and isMulticast(d.routeSet[0].value.uri.host): # remove any multicast address from top of the list. if _debug: print 'deleting top multicast routeSet', d.routeSet[0] del d.routeSet[0] if len(d.routeSet) == 0: d.routeSet = None d.secure = request.uri.secure d.localSeq, d.localSeq = 0, request.CSeq.number d.callId = request['Call-ID'].value d.localTag, d.remoteTag = response.To.tag, request.From.tag d.localParty, d.remoteParty = Address(str(request.To.value)), Address(str(request.From.value)) d.remoteTarget = URI(str(request.first('Contact').value.uri)) # TODO: retransmission timer for 2xx in UAC stack.dialogs[d.id] = d return d
12.1.2 UAC Behavior When a UAC sends a request that can establish a dialog (such as an INVITE) it MUST provide a SIP or SIPS URI with global scope (i.e., the same SIP URI can be used in messages outside this dialog) in the Contact header field of the request. If the request has a Request- URI or a topmost Route header field value with a SIPS URI, the Contact header field MUST contain a SIPS URI. When a UAC receives a response that establishes a dialog, it constructs the state of the dialog. This state MUST be maintained for the duration of the dialog. If the request was sent over TLS, and the Request-URI contained a SIPS URI, the "secure" flag is set to TRUE. The route set MUST be set to the list of URIs in the Record-Route header field from the response, taken in reverse order and preserving all URI parameters. If no Record-Route header field is present in the response, the route set MUST be set to the empty set. This route set, even if empty, overrides any pre-existing route set for future requests in this dialog. The remote target MUST be set to the URI from the Contact header field of the response. The local sequence number MUST be set to the value of the sequence number in the CSeq header field of the request. The remote sequence number MUST be empty (it is established when the remote UA sends a request within the dialog). The call identifier component of the dialog ID MUST be set to the value of the Call-ID in the request. The local tag component of the dialog ID MUST be set to the tag in the From field in the request, and the remote tag component of the dialog ID MUST be set to the tag in the To field of the response. A UAC MUST be prepared to receive a response without a tag in the To field, in which case the tag is considered to have a value of null. This is to maintain backwards compatibility with RFC 2543, which did not mandate To tags. The remote URI MUST be set to the URI in the To field, and the local URI MUST be set to the URI in the From field.
@staticmethod def createClient(stack, request, response, transaction): '''Create a dialog from UAC on receiving response to request in the transaction.''' d = Dialog(stack, request, False) d.request = request d.routeSet = [x for x in reversed(response.all('Record-Route'))] if response['Record-Route'] else None #print 'UAC routeSet=', d.routeSet d.secure = request.uri.secure d.localSeq, d.remoteSeq = request.CSeq.number, 0 d.callId = request['Call-ID'].value d.localTag, d.remoteTag = request.From.tag, response.To.tag d.localParty, d.remoteParty = Address(str(request.From.value)), Address(str(request.To.value)) d.remoteTarget = URI(str(response.first("Contact").value.uri)) stack.dialogs[d.id] = d return d @staticmethod def extractId(m): '''Extract dialog identifier string from a Message m.''' return m['Call-ID'].value + '|' + (m.To.tag if m.method else m.From.tag) + '|' + (m.From.tag if m.method else m.To.tag) def __init__(self, stack, request, server, transaction=None): '''Create a dialog for the request in server (True) or client (False) mode for given transaction.''' UserAgent.__init__(self, stack, request, server) # base class method self.servers, self.clients = [], [] # pending server and client transactions self._id = None if transaction: transaction.app = self # this is higher layer of transaction def close(self): if self.stack: if self.id in self.stack.dialogs: del self.stack.dialogs[self.id] # self.stack = None # TODO: uncomment this to clear reference, but then it causes problem in receivedResponse where self.stack becomes None @property def id(self): '''Dialog identifier string.''' if not self._id: self._id = self.callId + '|' + self.localTag + '|' + self.remoteTag return self._id
12.2.1 UAC Behavior 12.2.1.1 Generating the Request A request within a dialog is constructed by using many of the components of the state stored as part of the dialog. The URI in the To field of the request MUST be set to the remote URI from the dialog state. The tag in the To header field of the request MUST be set to the remote tag of the dialog ID. The From URI of the request MUST be set to the local URI from the dialog state. The tag in the From header field of the request MUST be set to the local tag of the dialog ID. If the value of the remote or local tags is null, the tag parameter MUST be omitted from the To or From header fields, respectively. Usage of the URI from the To and From fields in the original request within subsequent requests is done for backwards compatibility with RFC 2543, which used the URI for dialog identification. In this specification, only the tags are used for dialog identification. It is expected that mandatory reflection of the original To and From URI in mid-dialog requests will be deprecated in a subsequent revision of this specification. The Call-ID of the request MUST be set to the Call-ID of the dialog. Requests within a dialog MUST contain strictly monotonically increasing and contiguous CSeq sequence numbers (increasing-by-one) in each direction (excepting ACK and CANCEL of course, whose numbers equal the requests being acknowledged or cancelled). Therefore, if the local sequence number is not empty, the value of the local sequence number MUST be incremented by one, and this value MUST be placed into the CSeq header field. If the local sequence number is empty, an initial value MUST be chosen using the guidelines of Section 8.1.1.5. The method field in the CSeq header field value MUST match the method of the request. With a length of 32 bits, a client could generate, within a single call, one request a second for about 136 years before needing to wrap around. The initial value of the sequence number is chosen so that subsequent requests within the same call will not wrap around. A non-zero initial value allows clients to use a time- based initial sequence number. A client could, for example, choose the 31 most significant bits of a 32-bit second clock as an initial sequence number. The UAC uses the remote target and route set to build the Request-URI and Route header field of the request. If the route set is empty, the UAC MUST place the remote target URI into the Request-URI. The UAC MUST NOT add a Route header field to the request. If the route set is not empty, and the first URI in the route set contains the lr parameter (see Section 19.1.1), the UAC MUST place the remote target URI into the Request-URI and MUST include a Route header field containing the route set values in order, including all parameters. If the route set is not empty, and its first URI does not contain the lr parameter, the UAC MUST place the first URI from the route set into the Request-URI, stripping any parameters that are not allowed in a Request-URI. The UAC MUST add a Route header field containing the remainder of the route set values in order, including all parameters. The UAC MUST then place the remote target URI into the Route header field as the last value. For example, if the remote target is sip:user@remoteua and the route set contains: <sip:proxy1>,<sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4> The request will be formed with the following Request-URI and Route header field: METHOD sip:proxy1 Route: <sip:proxy2>,<sip:proxy3;lr>,<sip:proxy4>,<sip:user@remoteua> If the first URI of the route set does not contain the lr parameter, the proxy indicated does not understand the routing mechanisms described in this document and will act as specified in RFC 2543, replacing the Request-URI with the first Route header field value it receives while forwarding the message. Placing the Request-URI at the end of the Route header field preserves the information in that Request-URI across the strict router (it will be returned to the Request-URI when the request reaches a loose- router). A UAC SHOULD include a Contact header field in any target refresh requests within a dialog, and unless there is a need to change it, the URI SHOULD be the same as used in previous requests within the dialog. If the "secure" flag is true, that URI MUST be a SIPS URI. As discussed in Section 12.2.2, a Contact header field in a target refresh request updates the remote target URI. This allows a UA to provide a new contact address, should its address change during the duration of the dialog. However, requests that are not target refresh requests do not affect the remote target URI for the dialog. The rest of the request is formed as described in Section 8.1.1. Once the request has been constructed, the address of the server is computed and the request is sent, using the same procedures for requests outside of a dialog (Section 8.1.2). The procedures in Section 8.1.2 will normally result in the request being sent to the address indicated by the topmost Route header field value or the Request-URI if no Route header field is present. Subject to certain restrictions, they allow the request to be sent to an alternate address (such as a default outbound proxy not represented in the route set).
def createRequest(self, method, content=None, contentType=None): '''Create a new SIP request in this dialog.''' request = UserAgent.createRequest(self, method, content, contentType) if self.remoteTag: request.To.tag = self.remoteTag if self.routeSet and len(self.routeSet)>0 and 'lr' not in self.routeSet[0].value.uri.param: # strict route request.uri = self.routeSet[0].value.uri.dup() del request.uri.param['lr'] return request def createResponse(self, response, responsetext, content=None, contentType=None): '''Create a new SIP response in this dialog''' if len(self.servers) == 0: raise ValueError, 'No server transaction to create response' request = self.servers[0].request response = Message.createResponse(response, responsetext, None, content, request) if contentType: response['Content-Type'] = Header(contentType, 'Content-Type') if response.response != 100 and 'tag' not in response.To: response.To.tag = self.localTag return response def sendResponse(self, response, responsetext=None, content=None, contentType=None, createDialog=True): '''Send a new response in this dialog for first pending server transaction.''' if len(self.servers) == 0: raise ValueError, 'No server transaction to send response' self.transaction, self.request = self.servers[0], self.servers[0].request UserAgent.sendResponse(self, response, responsetext, content, contentType, False) code = response if isinstance(response, int) else response.response if code >= 200: self.servers.pop(0) # no more pending if final response sent def sendCancel(self): '''Send a CANCEL request for the first pending client transaction.''' if len(self.clients) == 0: if _debug: print 'No client transaction to send cancel' return self.transaction, self.request = self.clients[0], self.clients[0].request UserAgent.sendCancel(self)
12.2.2 UAS Behavior Requests sent within a dialog, as any other requests, are atomic. If a particular request is accepted by the UAS, all the state changes associated with it are performed. If the request is rejected, none of the state changes are performed. Note that some requests, such as INVITEs, affect several pieces of state. The UAS will receive the request from the transaction layer. If the request has a tag in the To header field, the UAS core computes the dialog identifier corresponding to the request and compares it with existing dialogs. If there is a match, this is a mid-dialog request. In that case, the UAS first applies the same processing rules for requests outside of a dialog, discussed in Section 8.2. If the request has a tag in the To header field, but the dialog identifier does not match any existing dialogs, the UAS may have crashed and restarted, or it may have received a request for a different (possibly failed) UAS (the UASs can construct the To tags so that a UAS can identify that the tag was for a UAS for which it is providing recovery). Another possibility is that the incoming request has been simply misrouted. Based on the To tag, the UAS MAY either accept or reject the request. Accepting the request for acceptable To tags provides robustness, so that dialogs can persist even through crashes. UAs wishing to support this capability must take into consideration some issues such as choosing monotonically increasing CSeq sequence numbers even across reboots, reconstructing the route set, and accepting out-of-range RTP timestamps and sequence numbers. If the UAS wishes to reject the request because it does not wish to recreate the dialog, it MUST respond to the request with a 481 (Call/Transaction Does Not Exist) status code and pass that to the server transaction. Requests that do not change in any way the state of a dialog may be received within a dialog (for example, an OPTIONS request). They are processed as if they had been received outside the dialog. If the remote sequence number is empty, it MUST be set to the value of the sequence number in the CSeq header field value in the request. If the remote sequence number was not empty, but the sequence number of the request is lower than the remote sequence number, the request is out of order and MUST be rejected with a 500 (Server Internal Error) response. If the remote sequence number was not empty, and the sequence number of the request is greater than the remote sequence number, the request is in order. It is possible for the CSeq sequence number to be higher than the remote sequence number by more than one. This is not an error condition, and a UAS SHOULD be prepared to receive and process requests with CSeq values more than one higher than the previous received request. The UAS MUST then set the remote sequence number to the value of the sequence number in the CSeq header field value in the request. If a proxy challenges a request generated by the UAC, the UAC has to resubmit the request with credentials. The resubmitted request will have a new CSeq number. The UAS will never see the first request, and thus, it will notice a gap in the CSeq number space. Such a gap does not represent any error condition. When a UAS receives a target refresh request, it MUST replace the dialog's remote target URI with the URI from the Contact header field in that request, if present.
def receivedRequest(self, transaction, request): '''Incoming request in the dialog.''' if self.remoteSeq != 0 and request.CSeq.number < self.remoteSeq: if _debug: print 'Dialog.receivedRequest() CSeq is old', request.CSeq.number, '<', self.remoteSeq self.sendResponse(500, 'Internal server error - invalid CSeq') return self.remoteSeq = request.CSeq.number if request.method == 'INVITE' and request.Contect: self.remoteTarget = request.first('Contact').value.uri.dup() if request.method == 'ACK' or request.method == 'CANCEL': self.servers = filter(lambda x: x != transaction, self.servers) # remove from pending if request.method == 'ACK': self.stack.receivedRequest(self, request) else: self.stack.cancelled(self, transaction.request) return self.servers.append(transaction) # make it pending self.stack.receivedRequest(self, request)
12.2.1.2 Processing the Responses The UAC will receive responses to the request from the transaction layer. If the client transaction returns a timeout, this is treated as a 408 (Request Timeout) response. The behavior of a UAC that receives a 3xx response for a request sent within a dialog is the same as if the request had been sent outside a dialog. This behavior is described in Section 8.1.3.4. Note, however, that when the UAC tries alternative locations, it still uses the route set for the dialog to build the Route header of the request. When a UAC receives a 2xx response to a target refresh request, it MUST replace the dialog's remote target URI with the URI from the Contact header field in that response, if present. If the response for a request within a dialog is a 481 (Call/Transaction Does Not Exist) or a 408 (Request Timeout), the UAC SHOULD terminate the dialog. A UAC SHOULD also terminate a dialog if no response at all is received for the request (the client transaction would inform the TU about the timeout.) For INVITE initiated dialogs, terminating the dialog consists of sending a BYE.
def receivedResponse(self, transaction, response): '''Incoming response in a dialog.''' if response.is2xx and response.Contact and transaction and transaction.request.method == 'INVITE': self.remoteTarget = response.first('Contact').value.uri.dup() if not response.is1xx: # final response self.clients = filter(lambda x: x != transaction, self.clients) # remove from pending if response.response == 408 or response.response == 481: # remote doesn't recognize the dialog self.close() if response.response == 401 or response.response == 407: if not self.authenticate(response, transaction): self.stack.receivedResponse(self, response) elif transaction: self.stack.receivedResponse(self, response) if self.autoack and response.is2xx and (transaction and transaction.request.method == 'INVITE' or response.CSeq.method == 'INVITE'): self.sendRequest(self.createRequest('ACK')) #---------------------------Proxy-------------------------------------------
16 Proxy Behavior 16.1 Overview SIP proxies are elements that route SIP requests to user agent servers and SIP responses to user agent clients. A request may traverse several proxies on its way to a UAS. Each will make routing decisions, modifying the request before forwarding it to the next element. Responses will route through the same set of proxies traversed by the request in the reverse order. Being a proxy is a logical role for a SIP element. When a request arrives, an element that can play the role of a proxy first decides if it needs to respond to the request on its own. For instance, the request may be malformed or the element may need credentials from the client before acting as a proxy. The element MAY respond with any appropriate error code. When responding directly to a request, the element is playing the role of a UAS and MUST behave as described in Section 8.2. A proxy can operate in either a stateful or stateless mode for each new request. When stateless, a proxy acts as a simple forwarding element. It forwards each request downstream to a single element determined by making a targeting and routing decision based on the request. It simply forwards every response it receives upstream. A stateless proxy discards information about a message once the message has been forwarded. A stateful proxy remembers information (specifically, transaction state) about each incoming request and any requests it sends as a result of processing the incoming request. It uses this information to affect the processing of future messages associated with that request. A stateful proxy MAY choose to "fork" a request, routing it to multiple destinations. Any request that is forwarded to more than one location MUST be handled statefully. In some circumstances, a proxy MAY forward requests using stateful transports (such as TCP) without being transaction-stateful. For instance, a proxy MAY forward a request from one TCP connection to another transaction statelessly as long as it places enough information in the message to be able to forward the response down the same connection the request arrived on. Requests forwarded between different types of transports where the proxy's TU must take an active role in ensuring reliable delivery on one of the transports MUST be forwarded transaction statefully. A stateful proxy MAY transition to stateless operation at any time during the processing of a request, so long as it did not do anything that would otherwise prevent it from being stateless initially (forking, for example, or generation of a 100 response). When performing such a transition, all state is simply discarded. The proxy SHOULD NOT initiate a CANCEL request. Much of the processing involved when acting statelessly or statefully for a request is identical. The next several subsections are written from the point of view of a stateful proxy. The last section calls out those places where a stateless proxy behaves differently. 16.2 Stateful Proxy When stateful, a proxy is purely a SIP transaction processing engine. Its behavior is modeled here in terms of the server and client transactions defined in Section 17. A stateful proxy has a server transaction associated with one or more client transactions by a higher layer proxy processing component (see figure 3), known as a proxy core. An incoming request is processed by a server transaction. Requests from the server transaction are passed to a proxy core. The proxy core determines where to route the request, choosing one or more next-hop locations. An outgoing request for each next-hop location is processed by its own associated client transaction. The proxy core collects the responses from the client transactions and uses them to send responses to the server transaction. A stateful proxy creates a new server transaction for each new request received. Any retransmissions of the request will then be handled by that server transaction per Section 17. The proxy core MUST behave as a UAS with respect to sending an immediate provisional on that server transaction (such as 100 Trying) as described in Section 8.2.6. Thus, a stateful proxy SHOULD NOT generate 100 (Trying) responses to non-INVITE requests. This is a model of proxy behavior, not of software. An implementation is free to take any approach that replicates the external behavior this model defines. For all new requests, including any with unknown methods, an element intending to proxy the request MUST: 1. Validate the request (Section 16.3) 2. Preprocess routing information (Section 16.4) 3. Determine target(s) for the request (Section 16.5) +--------------------+ | | +---+ | | | C | | | | T | | | +---+ +---+ | Proxy | +---+ CT = Client Transaction | S | | "Higher" Layer | | C | | T | | | | T | ST = Server Transaction +---+ | | +---+ | | +---+ | | | C | | | | T | | | +---+ +--------------------+ Figure 3: Stateful Proxy Model 4. Forward the request to each target (Section 16.6) 5. Process all responses (Section 16.7) 16.3 Request Validation Before an element can proxy a request, it MUST verify the message's validity. A valid message must pass the following checks: 1. Reasonable Syntax 2. URI scheme 3. Max-Forwards 4. (Optional) Loop Detection 5. Proxy-Require 6. Proxy-Authorization If any of these checks fail, the element MUST behave as a user agent server (see Section 8.2) and respond with an error code. Notice that a proxy is not required to detect merged requests and MUST NOT treat merged requests as an error condition. The endpoints receiving the requests will resolve the merge as described in Section 8.2.2.2. 1. Reasonable syntax check The request MUST be well-formed enough to be handled with a server transaction. Any components involved in the remainder of these Request Validation steps or the Request Forwarding section MUST be well-formed. Any other components, well-formed or not, SHOULD be ignored and remain unchanged when the message is forwarded. For instance, an element would not reject a request because of a malformed Date header field. Likewise, a proxy would not remove a malformed Date header field before forwarding a request. This protocol is designed to be extended. Future extensions may define new methods and header fields at any time. An element MUST NOT refuse to proxy a request because it contains a method or header field it does not know about. 2. URI scheme check If the Request-URI has a URI whose scheme is not understood by the proxy, the proxy SHOULD reject the request with a 416 (Unsupported URI Scheme) response. 3. Max-Forwards check The Max-Forwards header field (Section 20.22) is used to limit the number of elements a SIP request can traverse. If the request does not contain a Max-Forwards header field, this check is passed. If the request contains a Max-Forwards header field with a field value greater than zero, the check is passed. If the request contains a Max-Forwards header field with a field value of zero (0), the element MUST NOT forward the request. If the request was for OPTIONS, the element MAY act as the final recipient and respond per Section 11. Otherwise, the element MUST return a 483 (Too many hops) response. 4. Optional Loop Detection check An element MAY check for forwarding loops before forwarding a request. If the request contains a Via header field with a sent- by value that equals a value placed into previous requests by the proxy, the request has been forwarded by this element before. The request has either looped or is legitimately spiraling through the element. To determine if the request has looped, the element MAY perform the branch parameter calculation described in Step 8 of Section 16.6 on this message and compare it to the parameter received in that Via header field. If the parameters match, the request has looped. If they differ, the request is spiraling, and processing continues. If a loop is detected, the element MAY return a 482 (Loop Detected) response. 5. Proxy-Require check Future extensions to this protocol may introduce features that require special handling by proxies. Endpoints will include a Proxy-Require header field in requests that use these features, telling the proxy not to process the request unless the feature is understood. If the request contains a Proxy-Require header field (Section 20.29) with one or more option-tags this element does not understand, the element MUST return a 420 (Bad Extension) response. The response MUST include an Unsupported (Section 20.40) header field listing those option-tags the element did not understand. 6. Proxy-Authorization check If an element requires credentials before forwarding a request, the request MUST be inspected as described in Section 22.3. That section also defines what the element must do if the inspection fails. 16.4 Route Information Preprocessing The proxy MUST inspect the Request-URI of the request. If the Request-URI of the request contains a value this proxy previously placed into a Record-Route header field (see Section 16.6 item 4), the proxy MUST replace the Request-URI in the request with the last value from the Route header field, and remove that value from the Route header field. The proxy MUST then proceed as if it received this modified request. This will only happen when the element sending the request to the proxy (which may have been an endpoint) is a strict router. This rewrite on receive is necessary to enable backwards compatibility with those elements. It also allows elements following this specification to preserve the Request-URI through strict-routing proxies (see Section 12.2.1.1). This requirement does not obligate a proxy to keep state in order to detect URIs it previously placed in Record-Route header fields. Instead, a proxy need only place enough information in those URIs to recognize them as values it provided when they later appear. If the Request-URI contains a maddr parameter, the proxy MUST check to see if its value is in the set of addresses or domains the proxy is configured to be responsible for. If the Request-URI has a maddr parameter with a value the proxy is responsible for, and the request was received using the port and transport indicated (explicitly or by default) in the Request-URI, the proxy MUST strip the maddr and any non-default port or transport parameter and continue processing as if those values had not been present in the request. A request may arrive with a maddr matching the proxy, but on a port or transport different from that indicated in the URI. Such a request needs to be forwarded to the proxy using the indicated port and transport. If the first value in the Route header field indicates this proxy, the proxy MUST remove that value from the request. 16.5 Determining Request Targets Next, the proxy calculates the target(s) of the request. The set of targets will either be predetermined by the contents of the request or will be obtained from an abstract location service. Each target in the set is represented as a URI. If the Request-URI of the request contains an maddr parameter, the Request-URI MUST be placed into the target set as the only target URI, and the proxy MUST proceed to Section 16.6. If the domain of the Request-URI indicates a domain this element is not responsible for, the Request-URI MUST be placed into the target set as the only target, and the element MUST proceed to the task of Request Forwarding (Section 16.6). There are many circumstances in which a proxy might receive a request for a domain it is not responsible for. A firewall proxy handling outgoing calls (the way HTTP proxies handle outgoing requests) is an example of where this is likely to occur. If the target set for the request has not been predetermined as described above, this implies that the element is responsible for the domain in the Request-URI, and the element MAY use whatever mechanism it desires to determine where to send the request. Any of these mechanisms can be modeled as accessing an abstract Location Service. This may consist of obtaining information from a location service created by a SIP Registrar, reading a database, consulting a presence server, utilizing other protocols, or simply performing an algorithmic substitution on the Request-URI. When accessing the location service constructed by a registrar, the Request-URI MUST first be canonicalized as described in Section 10.3 before being used as an index. The output of these mechanisms is used to construct the target set. If the Request-URI does not provide sufficient information for the proxy to determine the target set, it SHOULD return a 485 (Ambiguous) response. This response SHOULD contain a Contact header field containing URIs of new addresses to be tried. For example, an INVITE to sip:John.Smith@company.com may be ambiguous at a proxy whose location service has multiple John Smiths listed. See Section 21.4.23 for details. Any information in or about the request or the current environment of the element MAY be used in the construction of the target set. For instance, different sets may be constructed depending on contents or the presence of header fields and bodies, the time of day of the request's arrival, the interface on which the request arrived, failure of previous requests, or even the element's current level of utilization. As potential targets are located through these services, their URIs are added to the target set. Targets can only be placed in the target set once. If a target URI is already present in the set (based on the definition of equality for the URI type), it MUST NOT be added again. A proxy MUST NOT add additional targets to the target set if the Request-URI of the original request does not indicate a resource this proxy is responsible for. A proxy can only change the Request-URI of a request during forwarding if it is responsible for that URI. If the proxy is not responsible for that URI, it will not recurse on 3xx or 416 responses as described below. If the Request-URI of the original request indicates a resource this proxy is responsible for, the proxy MAY continue to add targets to the set after beginning Request Forwarding. It MAY use any information obtained during that processing to determine new targets. For instance, a proxy may choose to incorporate contacts obtained in a redirect response (3xx) into the target set. If a proxy uses a dynamic source of information while building the target set (for instance, if it consults a SIP Registrar), it SHOULD monitor that source for the duration of processing the request. New locations SHOULD be added to the target set as they become available. As above, any given URI MUST NOT be added to the set more than once. Allowing a URI to be added to the set only once reduces unnecessary network traffic, and in the case of incorporating contacts from redirect requests prevents infinite recursion. For example, a trivial location service is a "no-op", where the target URI is equal to the incoming request URI. The request is sent to a specific next hop proxy for further processing. During request forwarding of Section 16.6, Item 6, the identity of that next hop, expressed as a SIP or SIPS URI, is inserted as the top-most Route header field value into the request. If the Request-URI indicates a resource at this proxy that does not exist, the proxy MUST return a 404 (Not Found) response. If the target set remains empty after applying all of the above, the proxy MUST return an error response, which SHOULD be the 480 (Temporarily Unavailable) response. 16.6 Request Forwarding As soon as the target set is non-empty, a proxy MAY begin forwarding the request. A stateful proxy MAY process the set in any order. It MAY process multiple targets serially, allowing each client transaction to complete before starting the next. It MAY start client transactions with every target in parallel. It also MAY arbitrarily divide the set into groups, processing the groups serially and processing the targets in each group in parallel. A common ordering mechanism is to use the qvalue parameter of targets obtained from Contact header fields (see Section 20.10). Targets are processed from highest qvalue to lowest. Targets with equal qvalues may be processed in parallel. A stateful proxy must have a mechanism to maintain the target set as responses are received and associate the responses to each forwarded request with the original request. For the purposes of this model, this mechanism is a "response context" created by the proxy layer before forwarding the first request. For each target, the proxy forwards the request following these steps: 1. Make a copy of the received request 2. Update the Request-URI 3. Update the Max-Forwards header field 4. Optionally add a Record-route header field value 5. Optionally add additional header fields 6. Postprocess routing information 7. Determine the next-hop address, port, and transport 8. Add a Via header field value 9. Add a Content-Length header field if necessary 10. Forward the new request 11. Set timer C Each of these steps is detailed below: 1. Copy request The proxy starts with a copy of the received request. The copy MUST initially contain all of the header fields from the received request. Fields not detailed in the processing described below MUST NOT be removed. The copy SHOULD maintain the ordering of the header fields as in the received request. The proxy MUST NOT reorder field values with a common field name (See Section 7.3.1). The proxy MUST NOT add to, modify, or remove the message body. An actual implementation need not perform a copy; the primary requirement is that the processing for each next hop begin with the same request. 2. Request-URI The Request-URI in the copy's start line MUST be replaced with the URI for this target. If the URI contains any parameters not allowed in a Request-URI, they MUST be removed. This is the essence of a proxy's role. This is the mechanism through which a proxy routes a request toward its destination. In some circumstances, the received Request-URI is placed into the target set without being modified. For that target, the replacement above is effectively a no-op. 3. Max-Forwards If the copy contains a Max-Forwards header field, the proxy MUST decrement its value by one (1). If the copy does not contain a Max-Forwards header field, the proxy MUST add one with a field value, which SHOULD be 70. Some existing UAs will not provide a Max-Forwards header field in a request. 4. Record-Route If this proxy wishes to remain on the path of future requests in a dialog created by this request (assuming the request creates a dialog), it MUST insert a Record-Route header field value into the copy before any existing Record-Route header field values, even if a Route header field is already present. Requests establishing a dialog may contain a preloaded Route header field. If this request is already part of a dialog, the proxy SHOULD insert a Record-Route header field value if it wishes to remain on the path of future requests in the dialog. In normal endpoint operation as described in Section 12, these Record- Route header field values will not have any effect on the route sets used by the endpoints. The proxy will remain on the path if it chooses to not insert a Record-Route header field value into requests that are already part of a dialog. However, it would be removed from the path when an endpoint that has failed reconstitutes the dialog. A proxy MAY insert a Record-Route header field value into any request. If the request does not initiate a dialog, the endpoints will ignore the value. See Section 12 for details on how endpoints use the Record-Route header field values to construct Route header fields. Each proxy in the path of a request chooses whether to add a Record-Route header field value independently - the presence of a Record-Route header field in a request does not obligate this proxy to add a value. The URI placed in the Record-Route header field value MUST be a SIP or SIPS URI. This URI MUST contain an lr parameter (see Section 19.1.1). This URI MAY be different for each destination the request is forwarded to. The URI SHOULD NOT contain the transport parameter unless the proxy has knowledge (such as in a private network) that the next downstream element that will be in the path of subsequent requests supports that transport. The URI this proxy provides will be used by some other element to make a routing decision. This proxy, in general, has no way of knowing the capabilities of that element, so it must restrict itself to the mandatory elements of a SIP implementation: SIP URIs and either the TCP or UDP transports. The URI placed in the Record-Route header field MUST resolve to the element inserting it (or a suitable stand-in) when the server location procedures of [4] are applied to it, so that subsequent requests reach the same SIP element. If the Request-URI contains a SIPS URI, or the topmost Route header field value (after the post processing of bullet 6) contains a SIPS URI, the URI placed into the Record-Route header field MUST be a SIPS URI. Furthermore, if the request was not received over TLS, the proxy MUST insert a Record-Route header field. In a similar fashion, a proxy that receives a request over TLS, but generates a request without a SIPS URI in the Request-URI or topmost Route header field value (after the post processing of bullet 6), MUST insert a Record-Route header field that is not a SIPS URI. A proxy at a security perimeter must remain on the perimeter throughout the dialog. If the URI placed in the Record-Route header field needs to be rewritten when it passes back through in a response, the URI MUST be distinct enough to locate at that time. (The request may spiral through this proxy, resulting in more than one Record-Route header field value being added). Item 8 of Section 16.7 recommends a mechanism to make the URI sufficiently distinct. The proxy MAY include parameters in the Record-Route header field value. These will be echoed in some responses to the request such as the 200 (OK) responses to INVITE. Such parameters may be useful for keeping state in the message rather than the proxy. If a proxy needs to be in the path of any type of dialog (such as one straddling a firewall), it SHOULD add a Record-Route header field value to every request with a method it does not understand since that method may have dialog semantics. The URI a proxy places into a Record-Route header field is only valid for the lifetime of any dialog created by the transaction in which it occurs. A dialog-stateful proxy, for example, MAY refuse to accept future requests with that value in the Request-URI after the dialog has terminated. Non-dialog- stateful proxies, of course, have no concept of when the dialog has terminated, but they MAY encode enough information in the value to compare it against the dialog identifier of future requests and MAY reject requests not matching that information. Endpoints MUST NOT use a URI obtained from a Record-Route header field outside the dialog in which it was provided. See Section 12 for more information on an endpoint's use of Record-Route header fields. Record-routing may be required by certain services where the proxy needs to observe all messages in a dialog. However, it slows down processing and impairs scalability and thus proxies should only record-route if required for a particular service. The Record-Route process is designed to work for any SIP request that initiates a dialog. INVITE is the only such request in this specification, but extensions to the protocol MAY define others. 5. Add Additional Header Fields The proxy MAY add any other appropriate header fields to the copy at this point. 6. Postprocess routing information A proxy MAY have a local policy that mandates that a request visit a specific set of proxies before being delivered to the destination. A proxy MUST ensure that all such proxies are loose routers. Generally, this can only be known with certainty if the proxies are within the same administrative domain. This set of proxies is represented by a set of URIs (each of which contains the lr parameter). This set MUST be pushed into the Route header field of the copy ahead of any existing values, if present. If the Route header field is absent, it MUST be added, containing that list of URIs. If the proxy has a local policy that mandates that the request visit one specific proxy, an alternative to pushing a Route value into the Route header field is to bypass the forwarding logic of item 10 below, and instead just send the request to the address, port, and transport for that specific proxy. If the request has a Route header field, this alternative MUST NOT be used unless it is known that next hop proxy is a loose router. Otherwise, this approach MAY be used, but the Route insertion mechanism above is preferred for its robustness, flexibility, generality and consistency of operation. Furthermore, if the Request-URI contains a SIPS URI, TLS MUST be used to communicate with that proxy. If the copy contains a Route header field, the proxy MUST inspect the URI in its first value. If that URI does not contain an lr parameter, the proxy MUST modify the copy as follows: - The proxy MUST place the Request-URI into the Route header field as the last value. - The proxy MUST then place the first Route header field value into the Request-URI and remove that value from the Route header field. Appending the Request-URI to the Route header field is part of a mechanism used to pass the information in that Request-URI through strict-routing elements. "Popping" the first Route header field value into the Request-URI formats the message the way a strict-routing element expects to receive it (with its own URI in the Request-URI and the next location to visit in the first Route header field value). 7. Determine Next-Hop Address, Port, and Transport The proxy MAY have a local policy to send the request to a specific IP address, port, and transport, independent of the values of the Route and Request-URI. Such a policy MUST NOT be used if the proxy is not certain that the IP address, port, and transport correspond to a server that is a loose router. However, this mechanism for sending the request through a specific next hop is NOT RECOMMENDED; instead a Route header field should be used for that purpose as described above. In the absence of such an overriding mechanism, the proxy applies the procedures listed in [4] as follows to determine where to send the request. If the proxy has reformatted the request to send to a strict-routing element as described in step 6 above, the proxy MUST apply those procedures to the Request-URI of the request. Otherwise, the proxy MUST apply the procedures to the first value in the Route header field, if present, else the Request-URI. The procedures will produce an ordered set of (address, port, transport) tuples. Independently of which URI is being used as input to the procedures of [4], if the Request-URI specifies a SIPS resource, the proxy MUST follow the procedures of [4] as if the input URI were a SIPS URI. As described in [4], the proxy MUST attempt to deliver the message to the first tuple in that set, and proceed through the set in order until the delivery attempt succeeds. For each tuple attempted, the proxy MUST format the message as appropriate for the tuple and send the request using a new client transaction as detailed in steps 8 through 10. Since each attempt uses a new client transaction, it represents a new branch. Thus, the branch parameter provided with the Via header field inserted in step 8 MUST be different for each attempt. If the client transaction reports failure to send the request or a timeout from its state machine, the proxy continues to the next address in that ordered set. If the ordered set is exhausted, the request cannot be forwarded to this element in the target set. The proxy does not need to place anything in the response context, but otherwise acts as if this element of the target set returned a 408 (Request Timeout) final response. 8. Add a Via header field value The proxy MUST insert a Via header field value into the copy before the existing Via header field values. The construction of this value follows the same guidelines of Section 8.1.1.7. This implies that the proxy will compute its own branch parameter, which will be globally unique for that branch, and contain the requisite magic cookie. Note that this implies that the branch parameter will be different for different instances of a spiraled or looped request through a proxy. Proxies choosing to detect loops have an additional constraint in the value they use for construction of the branch parameter. A proxy choosing to detect loops SHOULD create a branch parameter separable into two parts by the implementation. The first part MUST satisfy the constraints of Section 8.1.1.7 as described above. The second is used to perform loop detection and distinguish loops from spirals. Loop detection is performed by verifying that, when a request returns to a proxy, those fields having an impact on the processing of the request have not changed. The value placed in this part of the branch parameter SHOULD reflect all of those fields (including any Route, Proxy-Require and Proxy- Authorization header fields). This is to ensure that if the request is routed back to the proxy and one of those fields changes, it is treated as a spiral and not a loop (see Section 16.3). A common way to create this value is to compute a cryptographic hash of the To tag, From tag, Call-ID header field, the Request-URI of the request received (before translation), the topmost Via header, and the sequence number from the CSeq header field, in addition to any Proxy-Require and Proxy-Authorization header fields that may be present. The algorithm used to compute the hash is implementation-dependent, but MD5 (RFC 1321 [35]), expressed in hexadecimal, is a reasonable choice. (Base64 is not permissible for a token.) If a proxy wishes to detect loops, the "branch" parameter it supplies MUST depend on all information affecting processing of a request, including the incoming Request-URI and any header fields affecting the request's admission or routing. This is necessary to distinguish looped requests from requests whose routing parameters have changed before returning to this server. The request method MUST NOT be included in the calculation of the branch parameter. In particular, CANCEL and ACK requests (for non-2xx responses) MUST have the same branch value as the corresponding request they cancel or acknowledge. The branch parameter is used in correlating those requests at the server handling them (see Sections 17.2.3 and 9.2). 9. Add a Content-Length header field if necessary If the request will be sent to the next hop using a stream- based transport and the copy contains no Content-Length header field, the proxy MUST insert one with the correct value for the body of the request (see Section 20.14). 10. Forward Request A stateful proxy MUST create a new client transaction for this request as described in Section 17.1 and instructs the transaction to send the request using the address, port and transport determined in step 7. 11. Set timer C In order to handle the case where an INVITE request never generates a final response, the TU uses a timer which is called timer C. Timer C MUST be set for each client transaction when an INVITE request is proxied. The timer MUST be larger than 3 minutes. Section 16.7 bullet 2 discusses how this timer is updated with provisional responses, and Section 16.8 discusses processing when it fires. 16.7 Response Processing When a response is received by an element, it first tries to locate a client transaction (Section 17.1.3) matching the response. If none is found, the element MUST process the response (even if it is an informational response) as a stateless proxy (described below). If a match is found, the response is handed to the client transaction. Forwarding responses for which a client transaction (or more generally any knowledge of having sent an associated request) is not found improves robustness. In particular, it ensures that "late" 2xx responses to INVITE requests are forwarded properly. As client transactions pass responses to the proxy layer, the following processing MUST take place: 1. Find the appropriate response context 2. Update timer C for provisional responses 3. Remove the topmost Via 4. Add the response to the response context 5. Check to see if this response should be forwarded immediately 6. When necessary, choose the best final response from the response context If no final response has been forwarded after every client transaction associated with the response context has been terminated, the proxy must choose and forward the "best" response from those it has seen so far. The following processing MUST be performed on each response that is forwarded. It is likely that more than one response to each request will be forwarded: at least each provisional and one final response. 7. Aggregate authorization header field values if necessary 8. Optionally rewrite Record-Route header field values 9. Forward the response 10. Generate any necessary CANCEL requests Each of the above steps are detailed below: 1. Find Context The proxy locates the "response context" it created before forwarding the original request using the key described in Section 16.6. The remaining processing steps take place in this context. 2. Update timer C for provisional responses For an INVITE transaction, if the response is a provisional response with status codes 101 to 199 inclusive (i.e., anything but 100), the proxy MUST reset timer C for that client transaction. The timer MAY be reset to a different value, but this value MUST be greater than 3 minutes. 3. Via The proxy removes the topmost Via header field value from the response. If no Via header field values remain in the response, the response was meant for this element and MUST NOT be forwarded. The remainder of the processing described in this section is not performed on this message, the UAC processing rules described in Section 8.1.3 are followed instead (transport layer processing has already occurred). This will happen, for instance, when the element generates CANCEL requests as described in Section 10. 4. Add response to context Final responses received are stored in the response context until a final response is generated on the server transaction associated with this context. The response may be a candidate for the best final response to be returned on that server transaction. Information from this response may be needed in forming the best response, even if this response is not chosen. If the proxy chooses to recurse on any contacts in a 3xx response by adding them to the target set, it MUST remove them from the response before adding the response to the response context. However, a proxy SHOULD NOT recurse to a non-SIPS URI if the Request-URI of the original request was a SIPS URI. If the proxy recurses on all of the contacts in a 3xx response, the proxy SHOULD NOT add the resulting contactless response to the response context. Removing the contact before adding the response to the response context prevents the next element upstream from retrying a location this proxy has already attempted. 3xx responses may contain a mixture of SIP, SIPS, and non-SIP URIs. A proxy may choose to recurse on the SIP and SIPS URIs and place the remainder into the response context to be returned, potentially in the final response. If a proxy receives a 416 (Unsupported URI Scheme) response to a request whose Request-URI scheme was not SIP, but the scheme in the original received request was SIP or SIPS (that is, the proxy changed the scheme from SIP or SIPS to something else when it proxied a request), the proxy SHOULD add a new URI to the target set. This URI SHOULD be a SIP URI version of the non-SIP URI that was just tried. In the case of the tel URL, this is accomplished by placing the telephone-subscriber part of the tel URL into the user part of the SIP URI, and setting the hostpart to the domain where the prior request was sent. See Section 19.1.6 for more detail on forming SIP URIs from tel URLs. As with a 3xx response, if a proxy "recurses" on the 416 by trying a SIP or SIPS URI instead, the 416 response SHOULD NOT be added to the response context. 5. Check response for forwarding Until a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any provisional response other than 100 (Trying) - Any 2xx response If a 6xx response is received, it is not immediately forwarded, but the stateful proxy SHOULD cancel all client pending transactions as described in Section 10, and it MUST NOT create any new branches in this context. This is a change from RFC 2543, which mandated that the proxy was to forward the 6xx response immediately. For an INVITE transaction, this approach had the problem that a 2xx response could arrive on another branch, in which case the proxy would have to forward the 2xx. The result was that the UAC could receive a 6xx response followed by a 2xx response, which should never be allowed to happen. Under the new rules, upon receiving a 6xx, a proxy will issue a CANCEL request, which will generally result in 487 responses from all outstanding client transactions, and then at that point the 6xx is forwarded upstream. After a final response has been sent on the server transaction, the following responses MUST be forwarded immediately: - Any 2xx response to an INVITE request A stateful proxy MUST NOT immediately forward any other responses. In particular, a stateful proxy MUST NOT forward any 100 (Trying) response. Those responses that are candidates for forwarding later as the "best" response have been gathered as described in step "Add Response to Context". Any response chosen for immediate forwarding MUST be processed as described in steps "Aggregate Authorization Header Field Values" through "Record-Route". This step, combined with the next, ensures that a stateful proxy will forward exactly one final response to a non-INVITE request, and either exactly one non-2xx response or one or more 2xx responses to an INVITE request. 6. Choosing the best response A stateful proxy MUST send a final response to a response context's server transaction if no final responses have been immediately forwarded by the above rules and all client transactions in this response context have been terminated. The stateful proxy MUST choose the "best" final response among those received and stored in the response context. If there are no final responses in the context, the proxy MUST send a 408 (Request Timeout) response to the server transaction. Otherwise, the proxy MUST forward a response from the responses stored in the response context. It MUST choose from the 6xx class responses if any exist in the context. If no 6xx class responses are present, the proxy SHOULD choose from the lowest response class stored in the response context. The proxy MAY select any response within that chosen class. The proxy SHOULD give preference to responses that provide information affecting resubmission of this request, such as 401, 407, 415, 420, and 484 if the 4xx class is chosen. A proxy which receives a 503 (Service Unavailable) response SHOULD NOT forward it upstream unless it can determine that any subsequent requests it might proxy will also generate a 503. In other words, forwarding a 503 means that the proxy knows it cannot service any requests, not just the one for the Request- URI in the request which generated the 503. If the only response that was received is a 503, the proxy SHOULD generate a 500 response and forward that upstream. The forwarded response MUST be processed as described in steps "Aggregate Authorization Header Field Values" through "Record- Route". For example, if a proxy forwarded a request to 4 locations, and received 503, 407, 501, and 404 responses, it may choose to forward the 407 (Proxy Authentication Required) response. 1xx and 2xx responses may be involved in the establishment of dialogs. When a request does not contain a To tag, the To tag in the response is used by the UAC to distinguish multiple responses to a dialog creating request. A proxy MUST NOT insert a tag into the To header field of a 1xx or 2xx response if the request did not contain one. A proxy MUST NOT modify the tag in the To header field of a 1xx or 2xx response. Since a proxy may not insert a tag into the To header field of a 1xx response to a request that did not contain one, it cannot issue non-100 provisional responses on its own. However, it can branch the request to a UAS sharing the same element as the proxy. This UAS can return its own provisional responses, entering into an early dialog with the initiator of the request. The UAS does not have to be a discreet process from the proxy. It could be a virtual UAS implemented in the same code space as the proxy. 3-6xx responses are delivered hop-by-hop. When issuing a 3-6xx response, the element is effectively acting as a UAS, issuing its own response, usually based on the responses received from downstream elements. An element SHOULD preserve the To tag when simply forwarding a 3-6xx response to a request that did not contain a To tag. A proxy MUST NOT modify the To tag in any forwarded response to a request that contains a To tag. While it makes no difference to the upstream elements if the proxy replaced the To tag in a forwarded 3-6xx response, preserving the original tag may assist with debugging. When the proxy is aggregating information from several responses, choosing a To tag from among them is arbitrary, and generating a new To tag may make debugging easier. This happens, for instance, when combining 401 (Unauthorized) and 407 (Proxy Authentication Required) challenges, or combining Contact values from unencrypted and unauthenticated 3xx responses. 7. Aggregate Authorization Header Field Values If the selected response is a 401 (Unauthorized) or 407 (Proxy Authentication Required), the proxy MUST collect any WWW- Authenticate and Proxy-Authenticate header field values from all other 401 (Unauthorized) and 407 (Proxy Authentication Required) responses received so far in this response context and add them to this response without modification before forwarding. The resulting 401 (Unauthorized) or 407 (Proxy Authentication Required) response could have several WWW- Authenticate AND Proxy-Authenticate header field values. This is necessary because any or all of the destinations the request was forwarded to may have requested credentials. The client needs to receive all of those challenges and supply credentials for each of them when it retries the request. Motivation for this behavior is provided in Section 26. 8. Record-Route If the selected response contains a Record-Route header field value originally provided by this proxy, the proxy MAY choose to rewrite the value before forwarding the response. This allows the proxy to provide different URIs for itself to the next upstream and downstream elements. A proxy may choose to use this mechanism for any reason. For instance, it is useful for multi-homed hosts. If the proxy received the request over TLS, and sent it out over a non-TLS connection, the proxy MUST rewrite the URI in the Record-Route header field to be a SIPS URI. If the proxy received the request over a non-TLS connection, and sent it out over TLS, the proxy MUST rewrite the URI in the Record-Route header field to be a SIP URI. The new URI provided by the proxy MUST satisfy the same constraints on URIs placed in Record-Route header fields in requests (see Step 4 of Section 16.6) with the following modifications: The URI SHOULD NOT contain the transport parameter unless the proxy has knowledge that the next upstream (as opposed to downstream) element that will be in the path of subsequent requests supports that transport. When a proxy does decide to modify the Record-Route header field in the response, one of the operations it performs is locating the Record-Route value that it had inserted. If the request spiraled, and the proxy inserted a Record-Route value in each iteration of the spiral, locating the correct value in the response (which must be the proper iteration in the reverse direction) is tricky. The rules above recommend that a proxy wishing to rewrite Record-Route header field values insert sufficiently distinct URIs into the Record-Route header field so that the right one may be selected for rewriting. A RECOMMENDED mechanism to achieve this is for the proxy to append a unique identifier for the proxy instance to the user portion of the URI. When the response arrives, the proxy modifies the first Record-Route whose identifier matches the proxy instance. The modification results in a URI without this piece of data appended to the user portion of the URI. Upon the next iteration, the same algorithm (find the topmost Record-Route header field value with the parameter) will correctly extract the next Record-Route header field value inserted by that proxy. Not every response to a request to which a proxy adds a Record-Route header field value will contain a Record-Route header field. If the response does contain a Record-Route header field, it will contain the value the proxy added. 9. Forward response After performing the processing described in steps "Aggregate Authorization Header Field Values" through "Record-Route", the proxy MAY perform any feature specific manipulations on the selected response. The proxy MUST NOT add to, modify, or remove the message body. Unless otherwise specified, the proxy MUST NOT remove any header field values other than the Via header field value discussed in Section 16.7 Item 3. In particular, the proxy MUST NOT remove any "received" parameter it may have added to the next Via header field value while processing the request associated with this response. The proxy MUST pass the response to the server transaction associated with the response context. This will result in the response being sent to the location now indicated in the topmost Via header field value. If the server transaction is no longer available to handle the transmission, the element MUST forward the response statelessly by sending it to the server transport. The server transaction might indicate failure to send the response or signal a timeout in its state machine. These errors would be logged for diagnostic purposes as appropriate, but the protocol requires no remedial action from the proxy. The proxy MUST maintain the response context until all of its associated transactions have been terminated, even after forwarding a final response. 10. Generate CANCELs If the forwarded response was a final response, the proxy MUST generate a CANCEL request for all pending client transactions associated with this response context. A proxy SHOULD also generate a CANCEL request for all pending client transactions associated with this response context when it receives a 6xx response. A pending client transaction is one that has received a provisional response, but no final response (it is in the proceeding state) and has not had an associated CANCEL generated for it. Generating CANCEL requests is described in Section 9.1. The requirement to CANCEL pending client transactions upon forwarding a final response does not guarantee that an endpoint will not receive multiple 200 (OK) responses to an INVITE. 200 (OK) responses on more than one branch may be generated before the CANCEL requests can be sent and processed. Further, it is reasonable to expect that a future extension may override this requirement to issue CANCEL requests. 16.8 Processing Timer C If timer C should fire, the proxy MUST either reset the timer with any value it chooses, or terminate the client transaction. If the client transaction has received a provisional response, the proxy MUST generate a CANCEL request matching that transaction. If the client transaction has not received a provisional response, the proxy MUST behave as if the transaction received a 408 (Request Timeout) response. Allowing the proxy to reset the timer allows the proxy to dynamically extend the transaction's lifetime based on current conditions (such as utilization) when the timer fires. 16.9 Handling Transport Errors If the transport layer notifies a proxy of an error when it tries to forward a request (see Section 18.4), the proxy MUST behave as if the forwarded request received a 503 (Service Unavailable) response. If the proxy is notified of an error when forwarding a response, it drops the response. The proxy SHOULD NOT cancel any outstanding client transactions associated with this response context due to this notification. If a proxy cancels its outstanding client transactions, a single malicious or misbehaving client can cause all transactions to fail through its Via header field. 16.10 CANCEL Processing A stateful proxy MAY generate a CANCEL to any other request it has generated at any time (subject to receiving a provisional response to that request as described in section 9.1). A proxy MUST cancel any pending client transactions associated with a response context when it receives a matching CANCEL request. A stateful proxy MAY generate CANCEL requests for pending INVITE client transactions based on the period specified in the INVITE's Expires header field elapsing. However, this is generally unnecessary since the endpoints involved will take care of signaling the end of the transaction. While a CANCEL request is handled in a stateful proxy by its own server transaction, a new response context is not created for it. Instead, the proxy layer searches its existing response contexts for the server transaction handling the request associated with this CANCEL. If a matching response context is found, the element MUST immediately return a 200 (OK) response to the CANCEL request. In this case, the element is acting as a user agent server as defined in Section 8.2. Furthermore, the element MUST generate CANCEL requests for all pending client transactions in the context as described in Section 16.7 step 10. If a response context is not found, the element does not have any knowledge of the request to apply the CANCEL to. It MUST statelessly forward the CANCEL request (it may have statelessly forwarded the associated request previously). 16.11 Stateless Proxy When acting statelessly, a proxy is a simple message forwarder. Much of the processing performed when acting statelessly is the same as when behaving statefully. The differences are detailed here. A stateless proxy does not have any notion of a transaction, or of the response context used to describe stateful proxy behavior. Instead, the stateless proxy takes messages, both requests and responses, directly from the transport layer (See section 18). As a result, stateless proxies do not retransmit messages on their own. They do, however, forward all retransmissions they receive (they do not have the ability to distinguish a retransmission from the original message). Furthermore, when handling a request statelessly, an element MUST NOT generate its own 100 (Trying) or any other provisional response. A stateless proxy MUST validate a request as described in Section 16.3 A stateless proxy MUST follow the request processing steps described in Sections 16.4 through 16.5 with the following exception: o A stateless proxy MUST choose one and only one target from the target set. This choice MUST only rely on fields in the message and time-invariant properties of the server. In particular, a retransmitted request MUST be forwarded to the same destination each time it is processed. Furthermore, CANCEL and non-Routed ACK requests MUST generate the same choice as their associated INVITE. A stateless proxy MUST follow the request processing steps described in Section 16.6 with the following exceptions: o The requirement for unique branch IDs across space and time applies to stateless proxies as well. However, a stateless proxy cannot simply use a random number generator to compute the first component of the branch ID, as described in Section 16.6 bullet 8. This is because retransmissions of a request need to have the same value, and a stateless proxy cannot tell a retransmission from the original request. Therefore, the component of the branch parameter that makes it unique MUST be the same each time a retransmitted request is forwarded. Thus for a stateless proxy, the branch parameter MUST be computed as a combinatoric function of message parameters which are invariant on retransmission. The stateless proxy MAY use any technique it likes to guarantee uniqueness of its branch IDs across transactions. However, the following procedure is RECOMMENDED. The proxy examines the branch ID in the topmost Via header field of the received request. If it begins with the magic cookie, the first component of the branch ID of the outgoing request is computed as a hash of the received branch ID. Otherwise, the first component of the branch ID is computed as a hash of the topmost Via, the tag in the To header field, the tag in the From header field, the Call-ID header field, the CSeq number (but not method), and the Request-URI from the received request. One of these fields will always vary across two different transactions. o All other message transformations specified in Section 16.6 MUST result in the same transformation of a retransmitted request. In particular, if the proxy inserts a Record-Route value or pushes URIs into the Route header field, it MUST place the same values in retransmissions of the request. As for the Via branch parameter, this implies that the transformations MUST be based on time-invariant configuration or retransmission-invariant properties of the request. o A stateless proxy determines where to forward the request as described for stateful proxies in Section 16.6 Item 10. The request is sent directly to the transport layer instead of through a client transaction. Since a stateless proxy must forward retransmitted requests to the same destination and add identical branch parameters to each of them, it can only use information from the message itself and time-invariant configuration data for those calculations. If the configuration state is not time-invariant (for example, if a routing table is updated) any requests that could be affected by the change may not be forwarded statelessly during an interval equal to the transaction timeout window before or after the change. The method of processing the affected requests in that interval is an implementation decision. A common solution is to forward them transaction statefully. Stateless proxies MUST NOT perform special processing for CANCEL requests. They are processed by the above rules as any other requests. In particular, a stateless proxy applies the same Route header field processing to CANCEL requests that it applies to any other request. Response processing as described in Section 16.7 does not apply to a proxy behaving statelessly. When a response arrives at a stateless proxy, the proxy MUST inspect the sent-by value in the first (topmost) Via header field value. If that address matches the proxy, (it equals a value this proxy has inserted into previous requests) the proxy MUST remove that header field value from the response and forward the result to the location indicated in the next Via header field value. The proxy MUST NOT add to, modify, or remove the message body. Unless specified otherwise, the proxy MUST NOT remove any other header field values. If the address does not match the proxy, the message MUST be silently discarded. 16.12 Summary of Proxy Route Processing In the absence of local policy to the contrary, the processing a proxy performs on a request containing a Route header field can be summarized in the following steps. 1. The proxy will inspect the Request-URI. If it indicates a resource owned by this proxy, the proxy will replace it with the results of running a location service. Otherwise, the proxy will not change the Request-URI. 2. The proxy will inspect the URI in the topmost Route header field value. If it indicates this proxy, the proxy removes it from the Route header field (this route node has been reached). 3. The proxy will forward the request to the resource indicated by the URI in the topmost Route header field value or in the Request-URI if no Route header field is present. The proxy determines the address, port and transport to use when forwarding the request by applying the procedures in [4] to that URI. If no strict-routing elements are encountered on the path of the request, the Request-URI will always indicate the target of the request. 16.12.1 Examples 16.12.1.1 Basic SIP Trapezoid This scenario is the basic SIP trapezoid, U1 -> P1 -> P2 -> U2, with both proxies record-routing. Here is the flow. U1 sends: INVITE sip:callee@domain.com SIP/2.0 Contact: sip:caller@u1.example.com to P1. P1 is an outbound proxy. P1 is not responsible for domain.com, so it looks it up in DNS and sends it there. It also adds a Record-Route header field value: INVITE sip:callee@domain.com SIP/2.0 Contact: sip:caller@u1.example.com Record-Route: <sip:p1.example.com;lr> P2 gets this. It is responsible for domain.com so it runs a location service and rewrites the Request-URI. It also adds a Record-Route header field value. There is no Route header field, so it resolves the new Request-URI to determine where to send the request: INVITE sip:callee@u2.domain.com SIP/2.0 Contact: sip:caller@u1.example.com Record-Route: <sip:p2.domain.com;lr> Record-Route: <sip:p1.example.com;lr> The callee at u2.domain.com gets this and responds with a 200 OK: SIP/2.0 200 OK Contact: sip:callee@u2.domain.com Record-Route: <sip:p2.domain.com;lr> Record-Route: <sip:p1.example.com;lr> The callee at u2 also sets its dialog state's remote target URI to sip:caller@u1.example.com and its route set to: (<sip:p2.domain.com;lr>,<sip:p1.example.com;lr>) This is forwarded by P2 to P1 to U1 as normal. Now, U1 sets its dialog state's remote target URI to sip:callee@u2.domain.com and its route set to: (<sip:p1.example.com;lr>,<sip:p2.domain.com;lr>) Since all the route set elements contain the lr parameter, U1 constructs the following BYE request: BYE sip:callee@u2.domain.com SIP/2.0 Route: <sip:p1.example.com;lr>,<sip:p2.domain.com;lr> As any other element (including proxies) would do, it resolves the URI in the topmost Route header field value using DNS to determine where to send the request. This goes to P1. P1 notices that it is not responsible for the resource indicated in the Request-URI so it doesn't change it. It does see that it is the first value in the Route header field, so it removes that value, and forwards the request to P2: BYE sip:callee@u2.domain.com SIP/2.0 Route: <sip:p2.domain.com;lr> P2 also notices it is not responsible for the resource indicated by the Request-URI (it is responsible for domain.com, not u2.domain.com), so it doesn't change it. It does see itself in the first Route header field value, so it removes it and forwards the following to u2.domain.com based on a DNS lookup against the Request-URI: BYE sip:callee@u2.domain.com SIP/2.0 16.12.1.2 Traversing a Strict-Routing Proxy In this scenario, a dialog is established across four proxies, each of which adds Record-Route header field values. The third proxy implements the strict-routing procedures specified in RFC 2543 and many works in progress. U1->P1->P2->P3->P4->U2 The INVITE arriving at U2 contains: 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> Record-Route: <sip:p2.example.com;lr> Record-Route: <sip:p1.example.com;lr> Which U2 responds to with a 200 OK. Later, U2 sends the following BYE request to P4 based on the first Route header field value. BYE sip:caller@u1.example.com SIP/2.0 Route: <sip:p4.domain.com;lr> Route: <sip:p3.middle.com> Route: <sip:p2.example.com;lr> Route: <sip:p1.example.com;lr> P4 is not responsible for the resource indicated in the Request-URI so it will leave it alone. It notices that it is the element in the first Route header field value so it removes it. It then prepares to send the request based on the now first Route header field value of sip:p3.middle.com, but it notices that this URI does not contain the lr parameter, so before sending, it reformats the request to be: BYE sip:p3.middle.com SIP/2.0 Route: <sip:p2.example.com;lr> Route: <sip:p1.example.com;lr> Route: <sip:caller@u1.example.com> P3 is a strict router, so it forwards the following to P2: BYE sip:p2.example.com;lr SIP/2.0 Route: <sip:p1.example.com;lr> Route: <sip:caller@u1.example.com> P2 sees the request-URI is a value it placed into a Record-Route header field, so before further processing, it rewrites the request to be: BYE sip:caller@u1.example.com SIP/2.0 Route: <sip:p1.example.com;lr> P2 is not responsible for u1.example.com, so it sends the request to P1 based on the resolution of the Route header field value. P1 notices itself in the topmost Route header field value, so it removes it, resulting in: BYE sip:caller@u1.example.com SIP/2.0 Since P1 is not responsible for u1.example.com and there is no Route header field, P1 will forward the request to u1.example.com based on the Request-URI. 16.12.1.3 Rewriting Record-Route Header Field Values In this scenario, U1 and U2 are in different private namespaces and they enter a dialog through a proxy P1, which acts as a gateway between the namespaces. U1->P1->U2 U1 sends: INVITE sip:callee@gateway.leftprivatespace.com SIP/2.0 Contact: <sip:caller@u1.leftprivatespace.com> P1 uses its location service and sends the following to U2: INVITE sip:callee@rightprivatespace.com SIP/2.0 Contact: <sip:caller@u1.leftprivatespace.com> Record-Route: <sip:gateway.rightprivatespace.com;lr> U2 sends this 200 (OK) back to P1: SIP/2.0 200 OK Contact: <sip:callee@u2.rightprivatespace.com> Record-Route: <sip:gateway.rightprivatespace.com;lr> P1 rewrites its Record-Route header parameter to provide a value that U1 will find useful, and sends the following to U1: SIP/2.0 200 OK Contact: <sip:callee@u2.rightprivatespace.com> Record-Route: <sip:gateway.leftprivatespace.com;lr> Later, U1 sends the following BYE request to P1: BYE sip:callee@u2.rightprivatespace.com SIP/2.0 Route: <sip:gateway.leftprivatespace.com;lr> which P1 forwards to U2 as: BYE sip:callee@u2.rightprivatespace.com SIP/2.0 17 Transactions SIP is a transactional protocol: interactions between components take place in a series of independent message exchanges. Specifically, a SIP transaction consists of a single request and any responses to that request, which include zero or more provisional responses and one or more final responses. In the case of a transaction where the request was an INVITE (known as an INVITE transaction), the transaction also includes the ACK only if the final response was not a 2xx response. If the response was a 2xx, the ACK is not considered part of the transaction. The reason for this separation is rooted in the importance of delivering all 200 (OK) responses to an INVITE to the UAC. To deliver them all to the UAC, the UAS alone takes responsibility for retransmitting them (see Section 13.3.1.4), and the UAC alone takes responsibility for acknowledging them with ACK (see Section 13.2.2.4). Since this ACK is retransmitted only by the UAC, it is effectively considered its own transaction. Transactions have a client side and a server side. The client side is known as a client transaction and the server side as a server transaction. The client transaction sends the request, and the server transaction sends the response. The client and server transactions are logical functions that are embedded in any number of elements. Specifically, they exist within user agents and stateful proxy servers. Consider the example in Section 4. In this example, the UAC executes the client transaction, and its outbound proxy executes the server transaction. The outbound proxy also executes a client transaction, which sends the request to a server transaction in the inbound proxy. That proxy also executes a client transaction, which in turn sends the request to a server transaction in the UAS. This is shown in Figure 4. +---------+ +---------+ +---------+ +---------+ | +-+|Request |+-+ +-+|Request |+-+ +-+|Request |+-+ | | |C||------->||S| |C||------->||S| |C||------->||S| | | |l|| ||e| |l|| ||e| |l|| ||e| | | |i|| ||r| |i|| ||r| |i|| ||r| | | |e|| ||v| |e|| ||v| |e|| ||v| | | |n|| ||e| |n|| ||e| |n|| ||e| | | |t|| ||r| |t|| ||r| |t|| ||r| | | | || || | | || || | | || || | | | |T|| ||T| |T|| ||T| |T|| ||T| | | |r|| ||r| |r|| ||r| |r|| ||r| | | |a|| ||a| |a|| ||a| |a|| ||a| | | |n|| ||n| |n|| ||n| |n|| ||n| | | |s||Response||s| |s||Response||s| |s||Response||s| | | +-+|<-------|+-+ +-+|<-------|+-+ +-+|<-------|+-+ | +---------+ +---------+ +---------+ +---------+ UAC Outbound Inbound UAS Proxy Proxy Figure 4: Transaction relationships A stateless proxy does not contain a client or server transaction. The transaction exists between the UA or stateful proxy on one side, and the UA or stateful proxy on the other side. As far as SIP transactions are concerned, stateless proxies are effectively transparent. The purpose of the client transaction is to receive a request from the element in which the client is embedded (call this element the "Transaction User" or TU; it can be a UA or a stateful proxy), and reliably deliver the request to a server transaction. The client transaction is also responsible for receiving responses and delivering them to the TU, filtering out any response retransmissions or disallowed responses (such as a response to ACK). Additionally, in the case of an INVITE request, the client transaction is responsible for generating the ACK request for any final response accepting a 2xx response. Similarly, the purpose of the server transaction is to receive requests from the transport layer and deliver them to the TU. The server transaction filters any request retransmissions from the network. The server transaction accepts responses from the TU and delivers them to the transport layer for transmission over the network. In the case of an INVITE transaction, it absorbs the ACK request for any final response excepting a 2xx response. The 2xx response and its ACK receive special treatment. This response is retransmitted only by a UAS, and its ACK generated only by the UAC. This end-to-end treatment is needed so that a caller knows the entire set of users that have accepted the call. Because of this special handling, retransmissions of the 2xx response are handled by the UA core, not the transaction layer. Similarly, generation of the ACK for the 2xx is handled by the UA core. Each proxy along the path merely forwards each 2xx response to INVITE and its corresponding ACK.
class Proxy(UserAgent): '''Extends UserAgent to represents a stateless and stateful proxy. The base object represents original UAS.''' def __init__(self, stack, request=None, server=None): '''Construct as Proxy UAS for the incoming request.''' if request is None: raise ValueError('Cannot create Proxy without incoming request') UserAgent.__init__(self, stack, request, server) self.branches = [] # all the client branches containing Transaction objects def createTransaction(self, request): '''Delay the creation of transaction when sendResponse or sendRequest is invoked.''' self.receivedRequest(None, request) # don't create a transaction, but just invoke the callback return None def receivedRequest(self, transaction, request): '''New incoming request. Transaction may be empty at this point.''' if transaction and self.transaction and transaction != self.transaction and request.method != 'CANCEL': raise ValueError, 'Invalid transaction for received request' self.server = True # this becomes a UAS # 16.3 request validation if request.uri.scheme not in ['sip', 'sips']: self.sendResponse(416, 'Unsupported URI scheme') return if request['Max-Forwards'] and int(request.first('Max-Forwards').value) < 0: self.sendResponse(483, 'Too many hops') return if 'tag' not in request.To: # out of dialog request if self.stack.findOtherTransaction(request, transaction): # request merging? self.sendResponse(482, "Loop detected - found another transaction") return if request['Proxy-Require']: # TODO let the application handle Require header if request.method != 'CANCEL' and request.method != 'ACK': response = self.createResponse(420, 'Bad extension') response.Unsupported = Header(str(request['Proxy-Require'].value), 'Unsupported') self.sendResponse(response) return if transaction: self.transaction = transaction # store it if request.method == 'CANCEL': original = self.stack.findTransaction(Transaction.createId(transaction.branch, 'INVITE')) if original: if original.state == 'proceeding' or original.state == 'trying': original.sendResponse(original.createResponse(487, 'Request terminated')) self.createResponse(200, 'OK') # CANCEL response # TODO: the To tag must be same in the two responses # 16.4 route information processing if not request.uri.user and self.isLocal(request.uri) and 'lr' in request.uri.param and request.Route: lastRoute = request.all('Route')[-1]; request.delete('Route', position=-1) request.uri = lastRoute.value.uri #if 'maddr' in request.uri.param: TODO: handle this case if request.Route and self.isLocal(request.first('Route').value.uri): request.delete('Route', position=0) # delete first Route header self.stack.receivedRequest(self, request) def isLocal(self, uri): '''Check whether the give uri represents local address (host:port) ?''' return self.stack.transport.host == uri.host and self.stack.transport.port == uri.port def sendResponse(self, response, responsetext=None, content=None, contentType=None, createDialog=True): '''Invoke the base class to send a response to original UAS. Create a transaction beforehand if needed.''' if not self.transaction: # create a transaction if doesn't exist self.transaction = Transaction.createServer(self.stack, self, self.request, self.stack.transport, self.stack.tag, start=False) UserAgent.sendResponse(self, response, responsetext, content, contentType, False) # never create dialog def createRequest(self, method, dest, stateless=False, recordRoute=False, headers=[], route=[]): '''Create a proxied request from the original request, using destination (host, port). Additional arguments modify how the proxied request is generated. The caller must invoke sendRequest to send the returned request.''' if method != self.request.method: raise ValueError('method in createRequest must be same as original UAS for proxy') request = self.request.dup() # so that original is not modified if not stateless and not self.transaction: # need to create a transaction self.transaction = Transaction.createServer(self.stack, self, self.request, self.stack.transport, self.stack.tag, start=False) if isinstance(dest, Address): request.uri = dest.uri.dup() elif isinstance(dest, tuple): request.uri = URI(request.uri.scheme + ':' + request.uri.user + '@' + dest[0] + ':' + str(dest[1])) else: request.uri = dest.dup() # so that original is not modified request['Max-Forwards'] = Header(str(int(request.first('Max-Forwards').value)-1) if request['Max-Forwards'] else '70', 'Max-Forwards') if recordRoute: rr = Address(str(self.stack.uri)) rr.uri.param['lr'] = None rr.mustQuote = True # TODO: take care of sips URI request.insert(Header(str(rr), 'Record-Route')) for h in headers: request.insert(h, append=True) # insert additional headers for h in reversed(route): request.insert(h, append=False) # insert the routes Via = self.stack.createVia(self.secure) Via.branch = Transaction.createBranch(request, False) request.insert(Via) return request def sendRequest(self, request): '''Proxy a request in a new client transaction.''' if not request.Route: target = request.uri else: routes = request.all('Route') if len(routes) > 0: target = routes[0].value.uri if not target or 'lr' not in target.param: # strict route if _debug: print 'strict route target=', target, 'routes=', routes del routes[0] # ignore first route if len(routes) > 0: if _debug: print 'appending our route' routes.append(Header(str(request.uri), 'Route')) request.Route = routes request.uri = target; self.stack.sending(self, request) class Branch(object): __slots__ = ('request', 'response', 'remoteCandidates', 'transaction', 'cancelRequest') def __init__(self): self.request = self.response = self.remoteCandidates = self.transaction = self.cancelRequest = None branch = Branch() # TODO: replace the following with RFC3263 to return multiple candidates. Add TCP and UDP and if possible TLS. dest = target.dup() dest.port = target.port or target.secure and 5061 or 5060 if not isIPv4(dest.host): try: dest.host = gethostbyname(dest.host) except: pass if isIPv4(dest.host): branch.remoteCandidates = [dest] # continue processing as if we received multiple candidates if not branch.remoteCandidates or len(branch.remoteCandidates) == 0: self.error(None, 'cannot resolve DNS target') return target = branch.remoteCandidates.pop(0) if request.method != 'ACK': # start a client transaction to send the request branch.transaction = Transaction.createClient(self.stack, self, request, self.stack.transport, target.hostPort) branch.request = request self.branches.append(branch) else: # directly send ACK on transport layer self.stack.send(request, target.hostPort) def retryNextCandidate(self, branch): '''Retry next DNS resolved address.''' if not branch.remoteCandidates or len(branch.remoteCandidates) == 0: raise ValueError, 'No more DNS resolved address to try' target = URI(branch.remoteCandiates.pop(0)) branch.request.first('Via').branch += 'A' # so that we create a different new transaction branch.transaction = Transaction.createClient(self.stack, self, branch.request, self.stack.transport, target.hostPort) def getBranch(self, transaction): for branch in self.branches: if branch.transaction == transaction: return branch return None def receivedResponse(self, transaction, response): '''Received a new response from the transaction.''' branch = self.getBranch(transaction) if not branch: if _debug: print 'Invalid transaction received %r'%(transaction) return if response.is1xx and branch.cancelRequest: cancel = Transaction.createClient(self.stack, self, branch.cancelRequest, transaction.transport, transaction.remote) branch.cancelRequest = None else: if response.isfinal: branch.response = response # TODO: self.stack.receivedResponse(self, response) self.sendResponseIfPossible() def sendResponseIfPossible(self): branches = filter(lambda x: x.response and x.response.isfinal, self.branches) branches2xx = filter(lambda x: x.response.is2xx, branches) if _debug: print 'received %d responses out of %d'%(len(branches), len(self.branches)) response = None if branches2xx: response = branches[0].response elif len(branches) == len(self.branches): response = branches[0].response # TODO select best instead of first if response: self.branches[:] = [] # clear the list so that no more responses are accepted. response.delete('Via', position=0) # remove topmost Via header self.sendResponse(response) def sendCancel(self): '''Cancel a request.''' for branch in self.branches: branch.cancelRequest = branch.transaction.createCancel() if branch.transaction.state != 'trying' and branch.transaction.state != 'calling': if branch.transaction.state == 'proceeding': transaction = Transaction.createClient(self.stack, self, branch.cancelRequest, branch.transaction.transport, branch.transaction.remote) branch.cancelRequest = None # else don't send until 1xx is received def timeout(self, transaction): '''A client transaction was timedout.''' branch = self.getBranch(transaction) if not branch: return # invalid transaction branch.transaction = None if branch.remoteCandidates and len(branch.remoteCandidates)>0: self.retryNextCandidate(branch) else: self.receivedResponse(None, Message.createResponse(408, 'Request timeout', None, None, branch.request)) def error(self, transaction, error): '''A transaction gave transport error.''' branch = self.getBranch(transaction) if not branch: return # invalid transaction self.transaction = None branch.transaction = None if branch.remoteCandidates and len(branch.remoteCandidates)>0: self.retryNextCandidate(branch) else: self.receivedResponse(None, Message.createResponse(503, 'Service unavailable - ' + error, None, None, branch.request)) #--------------------------- Testing -------------------------------------- if __name__ == '__main__': import doctest doctest.testmod()