def preprocessRoutingInformationForRequest(self, connectedSIPMessageToSend): ''' https://tools.ietf.org/html/rfc3261#section-16.4 ''' # TODO - for now, skip strict route stuff in the first paragraph of 16.4 sipMessage = connectedSIPMessageToSend.sipMessage requestURI = SIPURI.newParsedFrom(sipMessage.requestURI) maddr = requestURI.parameterNamed('maddr') if maddr: # TODO - for now, skip the maddr processing step. pass routeURIs = sipMessage.routeURIs if routeURIs: if self.sipURIMatchesUs(routeURIs[0]): # TODO - remove first route header, because it matches us. # TODO - need to test that removeFirstHeaderFieldOfClass() works correctly. Header field tests should be written for that. sipMessage.removeFirstHeaderFieldOfClass(RouteSIPHeaderField) # TODO: This line is a work-around for a problem that we really # need to address: when you hack a header or header field value, that # does not clear the SIPMessage's rawString. Fixing that is not trivial, # considering that we observe good layering practices in the message - header - hf # object complex. We will probably need to use events for that. Don't blow this # off, but for now, just manually clear the rawString. sipMessage.clearRawString()
def run_01_atlantaToBiloxi(self): self.aliceTransport.connections[0].sendString(self.aliceRequestString) self.assertEqual(0, len(self.aliceReceivedRequests)) # self.assertEqual(1, len(self.aliceReceivedResponses)) self.assertEqual(1, len(self.atlantaReceivedRequests)) self.assertEqual(1, len(self.biloxiReceivedRequests)) self.assertEqual(1, len(self.atlantaReceivedResponses)) self.assertEqual(0, len(self.biloxiReceivedResponses)) self.assertEqual(0, len(self.bobReceivedRequests)) self.assertEqual(0, len(self.bobReceivedResponses)) self.assertEqual(self.aliceBindAddress, self.atlantaReceivedRequests[0].connection.remoteAddress) self.assertEqual(self.aliceBindPort, self.atlantaReceivedRequests[0].connection.remotePort) self.assertEqual(self.atlantaBindAddress, self.atlantaReceivedRequests[0].connection.bindAddress) self.assertEqual(self.atlantaBindPort, self.atlantaReceivedRequests[0].connection.bindPort) atlantaReceivedRequest = self.atlantaReceivedRequests[0].sipMessage rURI = SIPURI.newParsedFrom(atlantaReceivedRequest.startLine.requestURI) self.assertEqual(self.aliceRequestString, atlantaReceivedRequest.rawString) self.assertEqual('INVITE', atlantaReceivedRequest.startLine.sipMethod) self.assertEqual(self.biloxiBindAddress, rURI.host) self.assertEqual(1, len(atlantaReceivedRequest.vias)) self.assertEqual(self.aliceRequestString, atlantaReceivedRequest.rawString) self.assertIsNone(atlantaReceivedRequest.header.toTag) self.assertEqual(self.atlantaBindAddress, self.biloxiReceivedRequests[0].connection.remoteAddress) self.assertEqual(self.atlantaBindPort, self.biloxiReceivedRequests[0].connection.remotePort) self.assertEqual(self.biloxiBindAddress, self.biloxiReceivedRequests[0].connection.bindAddress) self.assertEqual(self.biloxiBindPort, self.biloxiReceivedRequests[0].connection.bindPort) biloxiReceivedRequest = self.biloxiReceivedRequests[0].sipMessage self.assertEqual(atlantaReceivedRequest.startLine.requestURI, biloxiReceivedRequest.startLine.requestURI) self.assertEqual('INVITE', biloxiReceivedRequest.startLine.sipMethod) self.assertEqual(2, len(biloxiReceivedRequest.vias)) self.assertNotEqual(self.aliceRequestString, biloxiReceivedRequest.rawString) self.assertIsNone(biloxiReceivedRequest.header.toTag) self.assertEqual(self.biloxiBindAddress, self.atlantaReceivedResponses[0].connection.remoteAddress) self.assertEqual(self.biloxiBindPort, self.atlantaReceivedResponses[0].connection.remotePort) self.assertEqual(self.atlantaBindAddress, self.atlantaReceivedResponses[0].connection.bindAddress) self.assertEqual(self.atlantaBindPort, self.atlantaReceivedResponses[0].connection.bindPort) atlantaReceivedResponse = self.atlantaReceivedResponses[0].sipMessage self.assertIsNotNone(atlantaReceivedResponse.header.toTag) self.assertEqual(2, len(atlantaReceivedResponse.vias)) self.assertEqual(self.atlantaBindAddress, self.aliceReceivedResponses[0].connection.remoteAddress) self.assertEqual(self.atlantaBindPort, self.aliceReceivedResponses[0].connection.remotePort) self.assertEqual(self.aliceBindAddress, self.aliceReceivedResponses[0].connection.bindAddress) self.assertEqual(self.aliceBindPort, self.aliceReceivedResponses[0].connection.bindPort) aliceReceivedResponse = self.aliceReceivedResponses[0].sipMessage self.assertIsNotNone(aliceReceivedResponse.header.toTag) self.assertEqual(1, len(aliceReceivedResponse.vias)) self.assertEqual(self.aliceBindAddress, atlantaReceivedRequest.viaHeaderFields[0].host) # TODO: This 404 nonsense is temporary. Alice sends to a biloxi domain via atlanta, atlanta forwards her request to biloxi, # Biloxi sees that it is responsible for the request, and for right now, just answers 404. self.assertEqual(404, self.atlantaReceivedResponses[0].sipMessage.startLine.statusCode) self.assertEqual(404, self.aliceReceivedResponses[0].sipMessage.startLine.statusCode)
def determineTargetForRequest(self, connectedSIPMessageToSend): ''' https://tools.ietf.org/html/rfc3261#section-16.5 Special consideration for stateless proxies explained in section 16.11 - Choose only one target (not forking), based on time-invariant stuff. ''' sipMessage = connectedSIPMessageToSend.sipMessage requestURI = SIPURI.newParsedFrom(sipMessage.requestURI) maddr = requestURI.parameterNamed('maddr') if maddr: return requestURI if not self.sipURIMatchesUs(requestURI): return requestURI # TODO - we are responsible for this request. We will have a registrar # or location service, probably implemented using the Strategy pattern, # but for now, let's just make a degenerate behavior, by answering a 404. We will # implement that location service later. Also allow other developers to write their # own Strategy objects. raise SendResponseSIPEntityException(statusCodeInteger=404, reasonPhraseString='Not Found')
def validateRequest(self, receivedConnectedSIPMessage): ''' https://tools.ietf.org/html/rfc3261#section-16.3 Validate the request - isMalformed is False - Check for a merged request (i.e. 482 (Loop Detected)) - Check the request URI scheme, to ensure that we understand it (i.e. "sip" or "sips") 416 (Unsupported URI Scheme) - Check Max-Forwards. If 0, don't forward the request, 483 (Too Many Hops) - (optional) Loop check. Via header with sent-by value that's already been placed into previous requests by us - Maybe only appropriate for stateful proxy. - Proxy-Require test - 420 (Bad Extension) - Proxy-Authorization check - ''' sipMessage = receivedConnectedSIPMessage.sipMessage if sipMessage.isMalformed: # TODO - do we need to drop the connection if malformed? What does the RFC say about that? raise DropMessageSIPEntityException(descriptionString='Received SIP message was malformed.') # TODO - we instantiate a first-class SIPURI object here. We probably want to do that in the start line object instead. requestURI = SIPURI.newParsedFrom(sipMessage.requestURI) if requestURI.scheme not in ['sip', 'sips']: raise SendResponseSIPEntityException(statusCodeInteger=416, reasonPhraseString='Unsupported URI Scheme') if sipMessage.maxForwards is not None: if sipMessage.maxForwards <= 0: raise SendResponseSIPEntityException(statusCodeInteger=483, reasonPhraseString='Too many hops')
def forwardRequestToTarget(self, connectedSIPMessageToSend, targetURI=None, transportIDForHeader=None): ''' https://tools.ietf.org/html/rfc3261#section-16.6 Special consideration for stateless proxies explained in section 16.11 - The unique branch id must be invariant for requests with identical headers. - Item 10 will send the forwarded message directly to a transportConnection, not transaction. ''' sipMessage = connectedSIPMessageToSend.sipMessage # TODO: in progress # 1. Copy request # (already copied it) # 2. Update the Request-URI # TODO: need to remove any URI parameters that are not allowed in a Request URI. # TODO: When we set an attribute on the start line, we may need to manually mark the sip message as dirty. if targetURI: sipMessage.startLine.requestURI = targetURI.rawString # 3. Update the Max-Forwards header field if sipMessage.maxForwards is not None: # TODO: When you use -= 1 on an integer sip header field's integerValue parameter, does that work? Need to write a test. sipMessage.header.maxForwardsHeaderField.integerValue -= 1 else: # TODO: inserting a header field - we should make a method that inserts it using an aesthetically-pleasing order # relative to other header fields. Each header field concrete subclass would have an integer sort attribute for that. sipMessage.header.addHeaderField(MaxForwardsSIPHeaderField.newForIntegerValue(70)) # 4. Optionally add a Record-route header field value # TODO: is this the best way to get the URI host? # Are record-route header fields only used for INVITE? Should we only do this if it's an INVITE? # Answer: No. See RFC3261 16.6 point 4 # TODO: sip or sips scheme? Should that be derived from the transportConnection? For now, hard-code to 'sip' recordRouteURI = SIPURI.newForAttributes(host=self.transports[0].bindAddress, port=self.transports[0].bindPort, scheme='sip', parameterNamesAndValueStrings={'lr': None}) recordRouteHeaderField = RecordRouteSIPHeaderField.newForAttributes(recordRouteURI) if transportIDForHeader: # Do we want to put that state into this header or the Via? # Answer: we want it here. See RFC3261 16.6 point 4 recordRouteHeaderField.parameterNamedPut('bobstackTransportID', transportIDForHeader) sipMessage.header.addHeaderFieldBeforeHeaderFieldsOfSameClass(recordRouteHeaderField) # 5. Optionally add additional header fields # TODO: make the server header field a user-settable parameter. sipMessage.header.addHeaderField(ServerSIPHeaderField.newForValueString('BobStack')) # 6. Postprocess routing information routeURIs = sipMessage.header.routeURIs if routeURIs: if 'lr' not in routeURIs[0].parameterNames: # TODO: When we set an attribute on the start line, we may need to manually mark the sip message as dirty. sipMessage.header.addHeaderFieldAfterHeaderFieldsOfSameClass(RouteSIPHeaderField.newForAttributes(SIPURI.newParsedFrom(sipMessage.startLine.requestURI))) sipMessage.startLine.requestURI = routeURIs[0].rawString # TODO: Is the class actually the same object, considering that we're accessing it from a different directory? Trap for young players. # No, as a matter of fact, it is not the same object, and that's a problem. We've worked around it over there, # but we need to do better. sipMessage.header.removeFirstHeaderFieldOfClass(RouteSIPHeaderField) uriToDetermineNextHop = sipMessage.requestURI else: uriToDetermineNextHop = routeURIs[0] else: # TODO: seriously, we need to make start lines use first-class SIPURIs. uriToDetermineNextHop = SIPURI.newParsedFrom(sipMessage.requestURI) # 7. Determine the next-hop address, port, and transport nextHopConnectedTransportConnection = self.connectedTransportConnectionForSIPURI(uriToDetermineNextHop) if not nextHopConnectedTransportConnection: # TODO: we could not connect to the next-hop. Return some error code. Which error code and reason phrase? 400 and could not connect are NOT correct. raise SendResponseSIPEntityException(statusCodeInteger=400, reasonPhraseString='Could not connect') # 8. Add a Via header field value # TODO: For now, don't do the loop / spiral detection stuff. sipMessage.header.addHeaderFieldBeforeHeaderFieldsOfSameClass(self.newViaHeaderFieldForSIPMessage(sipMessage)) # 9. Add a Content-Length header field if necessary # TODO: if the target transport is stream-oriented, e.g. TLS or TCP, and the message has no Content-Length: header field, add one. # TODO: So we'll need to wait until step 7 is done, to know the transport. if not sipMessage.header.contentLengthHeaderField: if nextHopConnectedTransportConnection.isStateful: # TODO: if the target transport is stream-oriented, e.g. TLS or TCP, and the message has no Content-Length: header field, add one. sipMessage.header.addHeaderField(self.newContentLengthHeaderFieldForSIPMessage(sipMessage)) # TODO: This line is a work-around for a problem that we really # need to address: when you hack a header or header field value, that # does not clear the SIPMessage's rawString. Fixing that is not trivial, # considering that we observe good layering practices in the message - header - hf # object complex. We will probably need to use events for that. Don't blow this # off, but for now, just manually clear the rawString. sipMessage.clearRawString() # 10. Forward the new request # TODO: exception handling? nextHopConnectedTransportConnection.sendMessage(sipMessage)