/* * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * Copyright (c) 1999-2008 Apple Inc. All Rights Reserved. * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ /* File: QTSSProxyModule.cpp Contains: Implementation of QTSSProxyModule class. This module is intended to be used in a stripped down version of QTSS/DSS in order to handle firewall proxy behaviour for the RTP protocol. */ #ifndef __Win32__ // // For gethostbyname #include #endif #include "QTSSProxyModule.h" #include "QTSSModuleUtils.h" #include "UDPSocketPool.h" #include "OSArrayObjectDeleter.h" #include "OSMemory.h" #include "RTSPClient.h" #include "ClientSocket.h" #include "SocketUtils.h" class ProxyTask : public Task { public: // // Task that just polls on all the sockets in the pool, sending data on all available sockets ProxyTask() : Task() {this->SetTaskName("ProxyTask"); this->Signal(Task::kStartEvent); } virtual ~ProxyTask() {} private: virtual SInt64 Run(); enum { kProxyTaskPollIntervalMsec = 10 }; }; class ProxySocketPool : public UDPSocketPool { public: // // Global pool of UDP sockets used by the Proxy for receiving // UDP data from the upstream servers. ProxySocketPool() {} virtual ~ProxySocketPool() {} virtual UDPSocketPair* ConstructUDPSocketPair(); virtual void DestructUDPSocketPair(UDPSocketPair *inPair); virtual void SetUDPSocketOptions(UDPSocketPair* inPair); }; class ProxyDemuxerTask : public UDPDemuxerTask { public: ProxyDemuxerTask(QTSS_RTPStreamObject inStream, UDPSocketPair* inSockets) : UDPDemuxerTask(), fStream(inStream), fQueueElem(this), fSockets(inSockets) {} virtual ~ProxyDemuxerTask() {} // // ACCESSORS QTSS_RTPStreamObject GetStream() { return fStream; } OSQueueElem* GetQueueElem() { return &fQueueElem; } UDPSocketPair* GetSockets() { return fSockets; } UInt16 GetOriginServerPort() { return fOriginServerPort; } // // SetOriginServerPort - // The origin server tells us what ports to send to on the SETUP // response. This object gets created on the SETUP request, so we // don't know the origin server ports when it is constructed. Once // we know what they are, call this method to let the object know what the ports are void SetOriginServerPort(UInt16 inPort) { fOriginServerPort = inPort; } private: QTSS_RTPStreamObject fStream; OSQueueElem fQueueElem; UDPSocketPair* fSockets; UInt16 fOriginServerPort; }; class ProxyClientInfo { public: // // This class contains all the proxy specific resources used by a single client of the // proxy. It is a UDP demuxer because it needs to get signalled when there is incoming // UDP data for this client ProxyClientInfo() : fStream(NULL), fClientSocket(Socket::kNonBlockingSocketType), fClient(&fClientSocket, false), fLastDemuxerTask(NULL) {} ~ProxyClientInfo(); // // There are generally several streams for a single client. Each stream must // have a different source port from the origin server. These functions // accomplish this mapping. ProxyDemuxerTask* AddStream(QTSS_RTPStreamObject inStream); ProxyDemuxerTask* GetDemuxerTaskForStream(QTSS_RTPStreamObject inStream); ProxyDemuxerTask* GetLastDemuxerTask() { return fLastDemuxerTask; } // // An RTSPClient object that handles the details of communicating with // the upstream server over RTSP RTSPClient* GetRTSPClient() { return &fClient; } // // A QTSS_SocketStream for doing async I/O in QTSS API with the RTSP socket QTSS_SocketStream GetSocketStream() { return fStream; } void SetSocketStream(QTSS_SocketStream inStream) { fStream = inStream; } private: // // All the resources needed by 1 client of the proxy stored here, so this all can be // stuffed into a ClientSession QTSS_SocketStream fStream; TCPClientSocket fClientSocket; RTSPClient fClient; ProxyDemuxerTask* fLastDemuxerTask; OSQueue fDemuxerTaskQueue; OSRef fRef; }; // ATTRIBUTES static QTSS_AttributeID sProxyClientInfoAttr = qtssIllegalAttrID; static QTSS_AttributeID sPortNumberTooBigErr = qtssIllegalAttrID; static QTSS_AttributeID sRemoteHostRefusedConnectionErr = qtssIllegalAttrID; static QTSS_AttributeID sNoTransportHeaderInSetupErr = qtssIllegalAttrID; // STATIC DATA static QTSS_PrefsObject sServerPrefs = NULL; static ProxySocketPool* sSocketPool = NULL; static ProxyTask* sProxyTask = NULL; static OSRefTable* sSessionMap = NULL; // FUNCTION PROTOTYPES static QTSS_Error QTSSProxyModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams); static QTSS_Error Register(QTSS_Register_Params* inParams); static QTSS_Error Initialize(QTSS_Initialize_Params* inParams); static QTSS_Error Shutdown(); static QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams); static QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams); static QTSS_Error ProcessIncomingRTCPPacket(QTSS_RTCPProcess_Params* inParams); static QTSS_Error HandleRTSPClientErr(QTSS_RTSPRequestObject inRequest, ProxyClientInfo* inClient, QTSS_Error inErr); static void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask); static QTSS_Error DoRequestPreProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, QTSS_ClientSessionObject inSession); static QTSS_Error DoRequestPostProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, QTSS_ClientSessionObject inClientSession); static UInt32 ReplaceSessionAndTransportHeaders(StrPtrLen* inResponse, iovec* outNewResponse, StrPtrLen* inNewSessionID, StrPtrLen* inNewTransportHeader, UInt32* outNewResponseLen); // FUNCTION IMPLEMENTATIONS QTSS_Error QTSSProxyModule_Main(void* inPrivateArgs) { return _stublibrary_main(inPrivateArgs, QTSSProxyModuleDispatch); } QTSS_Error QTSSProxyModuleDispatch(QTSS_Role inRole, QTSS_RoleParamPtr inParams) { switch (inRole) { case QTSS_Register_Role: return Register(&inParams->regParams); case QTSS_Initialize_Role: return Initialize(&inParams->initParams); case QTSS_RTSPRequest_Role: return ProcessRTSPRequest(&inParams->rtspRequestParams); case QTSS_ClientSessionClosing_Role: return DestroySession(&inParams->clientSessionClosingParams); case QTSS_RTCPProcess_Role: return ProcessIncomingRTCPPacket(&inParams->rtcpProcessParams); case QTSS_Shutdown_Role: return Shutdown(); } return QTSS_NoErr; } QTSS_Error Register(QTSS_Register_Params* inParams) { // Do role & attribute setup (void)QTSS_AddRole(QTSS_Initialize_Role); (void)QTSS_AddRole(QTSS_Shutdown_Role); (void)QTSS_AddRole(QTSS_RTSPRequest_Role); (void)QTSS_AddRole(QTSS_ClientSessionClosing_Role); (void)QTSS_AddRole(QTSS_RTCPProcess_Role); // Tell the server our name! static char* sModuleName = "QTSSProxyModule"; ::strcpy(inParams->outModuleName, sModuleName); static char* sProxyClientInfoName = "QTSSProxyModuleProxyClientInfo"; (void)QTSS_AddStaticAttribute(qtssClientSessionObjectType, sProxyClientInfoName, NULL, qtssAttrDataTypeVoidPointer); (void)QTSS_IDForAttr(qtssClientSessionObjectType, sProxyClientInfoName, &sProxyClientInfoAttr); static char* sPortNumberToBigErrName = "QTSSProxyModulePortNumberTooBig"; static char* sRemoteHostRefusedConnectionErrName = "QTSSProxyModuleRemoteHostRefusedConnection"; static char* sNoTransportHeaderInSetupErrName = "QTSSProxyModuleNoTransportHeaderInSetup"; (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sPortNumberToBigErrName, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sPortNumberToBigErrName, &sPortNumberTooBigErr); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionErrName, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sRemoteHostRefusedConnectionErrName, &sRemoteHostRefusedConnectionErr); (void)QTSS_AddStaticAttribute(qtssTextMessagesObjectType, sNoTransportHeaderInSetupErrName, NULL, qtssAttrDataTypeCharArray); (void)QTSS_IDForAttr(qtssTextMessagesObjectType, sNoTransportHeaderInSetupErrName, &sNoTransportHeaderInSetupErr); return QTSS_NoErr; } QTSS_Error Initialize(QTSS_Initialize_Params* inParams) { // Setup module utils QTSSModuleUtils::Initialize(inParams->inMessages, inParams->inServer, inParams->inErrorLogStream); // // Setup global data structures sServerPrefs = inParams->inPrefs; sSocketPool = NEW ProxySocketPool(); sProxyTask = NEW ProxyTask(); sSessionMap = NEW OSRefTable(); // Report to the server that this module handles DESCRIBE, SETUP, PLAY, PAUSE, and TEARDOWN static QTSS_RTSPMethod sSupportedMethods[] = { qtssDescribeMethod, qtssSetupMethod, qtssTeardownMethod, qtssPlayMethod, qtssPauseMethod }; QTSSModuleUtils::SetupSupportedMethods(inParams->inServer, sSupportedMethods, 5); return QTSS_NoErr; } QTSS_Error Shutdown() { return QTSS_NoErr; } QTSS_Error DestroySession(QTSS_ClientSessionClosing_Params* inParams) { ProxyClientInfo* theClient = NULL; UInt32 theLen = sizeof(theClient); QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, &theClient, &theLen); if (theErr != QTSS_NoErr) return theErr; delete theClient; // // NULL out the attribute so in case there is a race condition no one will // get this dangling pointer (void)QTSS_SetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, NULL, 0); return QTSS_NoErr; } QTSS_Error ProcessRTSPRequest(QTSS_StandardRTSP_Params* inParams) { ProxyClientInfo* theClient = NULL; UInt32 theLen = sizeof(theClient); QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, &theClient, &theLen); // // If there is no client yet, this is the first request made on this session. // Create an RTSPClient if (theErr != QTSS_NoErr) { theClient = NEW ProxyClientInfo(); // // Parse out the DNS name of the origin server and the port StrPtrLen theDNSNameAndPort; theErr = QTSS_GetValuePtr(inParams->inRTSPHeaders, qtssHostHeader, 0, (void**)&theDNSNameAndPort.Ptr, &theDNSNameAndPort.Len); Assert(theErr == QTSS_NoErr); StringParser extractPortNumber(&theDNSNameAndPort); StrPtrLen theDNSNamePtr; extractPortNumber.GetThru(&theDNSNamePtr, ':'); UInt32 thePort = extractPortNumber.ConsumeInteger(NULL); // // For now, if there was no port specified, use 554 if (thePort == 0) thePort = 554; // // Make sure the port is in the range of a 2-byte integer if (thePort > 65536) return QTSSModuleUtils::SendErrorResponse(inParams->inRTSPRequest, qtssClientBadRequest, sPortNumberTooBigErr); // // gethostbyname takes a NULL terminated C-string OSCharArrayDeleter theDNSName(theDNSNamePtr.GetAsCString()); // // Do a DNS lookup on the host to find its IP address struct hostent* theHostent = ::gethostbyname(theDNSName); UInt32 theIPAddr = 0; if (theHostent != NULL) theIPAddr = ntohl(*(UInt32*)(theHostent->h_addr_list[0])); else theIPAddr = SocketUtils::ConvertStringToAddr(theDNSName); // // Give this information to the ClientSocket object theClient->GetRTSPClient()->GetSocket()->Set(theIPAddr, (UInt16)thePort); theErr = QTSS_SetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, &theClient, sizeof(theClient)); Assert(theErr == QTSS_NoErr); // // Start the process of connecting to the origin server, first // just by telling the ClientSocket to send an empty message theErr = theClient->GetRTSPClient()->GetSocket()->Send(NULL, 0); if (theErr != QTSS_NoErr) // // we are connecting. This function will be called when connection is set up return HandleRTSPClientErr(inParams->inRTSPRequest, theClient, theErr); } // // If we aren't in the middle of sending a request, we have to set it up if (!theClient->GetRTSPClient()->IsTransactionInProgress()) theErr = DoRequestPreProcessing(theClient, inParams->inRTSPRequest, inParams->inClientSession); else // // Continue our attempt to send this request theErr = theClient->GetRTSPClient()->DoTransaction(); if (theErr != QTSS_NoErr) HandleRTSPClientErr(inParams->inRTSPRequest, theClient, theErr); else // // The response has come back from the origin server. Do whatever // processing we need on it, and send the response to the proxy client. theErr = DoRequestPostProcessing(theClient, inParams->inRTSPRequest, inParams->inClientSession); return theErr; } QTSS_Error HandleRTSPClientErr(QTSS_RTSPRequestObject inRequest, ProxyClientInfo* inClient, QTSS_Error inErr) { if (inClient->GetSocketStream() == NULL) { QTSS_SocketStream theSockStream = NULL; // Create a socket stream for the TCP socket in the RTSPClient object. The socket stream will // allow this module to receive events on the socket QTSS_Error theErr = QTSS_CreateStreamFromSocket(inClient->GetRTSPClient()->GetSocket()->GetSocket()->GetSocketFD(), &theSockStream); Assert(theErr == QTSS_NoErr); Assert(theSockStream != NULL); // // Cache it for future use inClient->SetSocketStream(theSockStream); } if ((inErr == EAGAIN) || (inErr == EINPROGRESS)) { // // Still not done. Return back to the server and wait for an event // We're making an assumption here that inClient only uses one socket to connect to // the server. We only have one stream, so we have to make that assumption. // Note that it is not necessary to have any kind of timeout here, because the server // naturally times out idle connections. If the server doesn't respond for awhile, // this session will naturally go away inClient->GetRTSPClient()->GetSocket()->GetSocket()->DontAutoCleanup(); RequestSocketEvent(inClient->GetSocketStream(), inClient->GetRTSPClient()->GetSocket()->GetEventMask()); return QTSS_NoErr; // We'll get called in the same method again when there is more work to do } if (inErr == QTSS_RequestFailed) { // // The remote host responded with a non 200 response. Forward it onto // the proxy client as normal, let it figure out what to do. // // Or this might just be that we got back an error from QTSS_AddRTPStream, // in which case we don't need to do anything special. return QTSS_NoErr; } else return QTSSModuleUtils::SendErrorResponse(inRequest, qtssServerGatewayTimeout, sRemoteHostRefusedConnectionErr); } void RequestSocketEvent(QTSS_StreamRef inStream, UInt32 inEventMask) { // // Job of this function is to convert a CommonUtilitiesLib event mask to a QTSS Event mask QTSS_EventType theEvent = 0; if (inEventMask & EV_RE) theEvent |= QTSS_ReadableEvent; if (inEventMask & EV_WR) theEvent |= QTSS_WriteableEvent; QTSS_Error theErr = QTSS_RequestEvent(inStream, theEvent); Assert(theErr == QTSS_NoErr); } QTSS_Error DoRequestPreProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, QTSS_ClientSessionObject inSession) { QTSS_RTSPMethod* theMethod = NULL; UInt32 theLen = 0; QTSS_Error theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqMethod, 0, (void**)&theMethod, &theLen); Assert(theErr == QTSS_NoErr); char theTransportHeaderBuf[128]; StrPtrLen theTransportHeader(theTransportHeaderBuf, 0); // // The session ID passed to us is this server's session ID. Make sure to replace it // with the session ID passed to us by the upstream server, or else the upstream // server will not be able to properly handle this request. StrPtrLen* theProxyClientSessionIDPtr = inProxyClientInfo->GetRTSPClient()->GetSessionID(); // // Truncate the \r\n at the end of this StrPtrLen theProxyClientSessionID(*theProxyClientSessionIDPtr); if (theProxyClientSessionID.Len > 0) { Assert(theProxyClientSessionID.Len >= 2); // // Truncate off the EOL if ((theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\r') || (theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\n')) theProxyClientSessionID.Len--; if ((theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\r') || (theProxyClientSessionID.Ptr[theProxyClientSessionID.Len - 1] == '\n')) theProxyClientSessionID.Len--; // // Truncate off "Session: " Assert(theProxyClientSessionID.Len > 9); theProxyClientSessionID.Ptr += 9; theProxyClientSessionID.Len -= 9; } StrPtrLen theRequest; theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqFullRequest, 0, (void**)&theRequest.Ptr, &theRequest.Len); Assert(theErr == QTSS_NoErr); if (*theMethod == qtssSetupMethod) { // // If this is a SETUP, we need to do some special processing, // and rewrite the Transport header before sending it on. // // First, tell the server to setup a new QTSS_RTPStreamObject // The proxy only supports UDP transport right now, so make sure that that is what we get QTSS_RTPStreamObject theStream = NULL; theErr = QTSS_AddRTPStream(inSession, inRequest, &theStream, qtssASFlagsForceUDPTransport); if (theErr != QTSS_NoErr) { Assert(theErr == QTSS_RequestFailed); // if any other error is returned, // our error handling logic will get confused. return theErr; } // // Tell the ProxyClientInfo object about this new stream. // This will also allocate client ports on this proxy. ProxyDemuxerTask* theProxyDemuxerTask = inProxyClientInfo->AddStream(theStream); // // Build a new Transport header UInt16 theRTPPort = theProxyDemuxerTask->GetSockets()->GetSocketA()->GetLocalPort(); qtss_sprintf(theTransportHeaderBuf, "Transport: RTP/AVP;unicast;client_port=%d-%d\r\n", theRTPPort, theRTPPort + 1); theTransportHeader.Len = ::strlen(theTransportHeaderBuf); } // // Build the new request and send it iovec theNewRequest[5]; UInt32 totalResponseLen = 0; UInt32 theNumVecs = ReplaceSessionAndTransportHeaders(&theRequest, theNewRequest, &theProxyClientSessionID, &theTransportHeader, &totalResponseLen); return inProxyClientInfo->GetRTSPClient()->SendRTSPRequest(theNewRequest, theNumVecs); } QTSS_Error DoRequestPostProcessing(ProxyClientInfo* inProxyClientInfo, QTSS_RTSPRequestObject inRequest, QTSS_ClientSessionObject inClientSession) { QTSS_RTSPMethod* theMethod = NULL; UInt32 theLen = 0; QTSS_Error theErr = QTSS_GetValuePtr(inRequest, qtssRTSPReqMethod, 0, (void**)&theMethod, &theLen); Assert(theErr == QTSS_NoErr); char theTransportHeaderBuf[128]; StrPtrLen theTransportHeader(theTransportHeaderBuf, 0); StrPtrLen theSessionID; theErr = QTSS_GetValuePtr(inClientSession, qtssCliSesRTSPSessionID, 0, (void**)&theSessionID.Ptr, &theSessionID.Len); Assert(theErr == QTSS_NoErr); StrPtrLen theResponse(inProxyClientInfo->GetRTSPClient()->GetResponse(), inProxyClientInfo->GetRTSPClient()->GetResponseLen()); if ((*theMethod == qtssSetupMethod) && (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) { ProxyDemuxerTask* thisStreamsDemuxer = inProxyClientInfo->GetLastDemuxerTask(); thisStreamsDemuxer->SetOriginServerPort(inProxyClientInfo->GetRTSPClient()->GetServerPort()); // // Build a new Transport header. This should basically be exactly what the // server would be sending back if the proxy module wasn't generating the response UInt16 theSvrRTPPort = 0; UInt16 theCliRTPPort = 0; UInt32 theLen = sizeof(theSvrRTPPort); theErr = QTSS_GetValue(thisStreamsDemuxer->GetStream(), qtssRTPStrSvrRTPPort, 0, &theSvrRTPPort, &theLen); Assert(theErr == QTSS_NoErr); theErr = QTSS_GetValue(thisStreamsDemuxer->GetStream(), qtssRTPStrClientRTPPort, 0, &theCliRTPPort, &theLen); Assert(theErr == QTSS_NoErr); char theIPAddrStr[20]; theLen = 20; theErr = QTSS_GetValue(inClientSession, qtssCliRTSPSessLocalAddrStr, 0, &theIPAddrStr[0], &theLen); Assert(theErr == QTSS_NoErr); theIPAddrStr[theLen] = '\0'; qtss_sprintf(theTransportHeader.Ptr, "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d;source=%s\r\n", theCliRTPPort, theCliRTPPort + 1, theSvrRTPPort, theSvrRTPPort + 1, theIPAddrStr); theTransportHeader.Len = ::strlen(theTransportHeader.Ptr); } else if ((*theMethod == qtssPlayMethod) && (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) { // // The client session needs to know that we are entering the playing state. // Otherwise, when we write RTP packet data to the QTSS_RTPStreamObjects, // the server won't really be able to figure out what to do with the packets. theErr = QTSS_Play(inClientSession, inRequest, 0); if (theErr != QTSS_NoErr) { Assert(theErr == QTSS_RequestFailed); return theErr; } } else if ((*theMethod == qtssPauseMethod) && (inProxyClientInfo->GetRTSPClient()->GetStatus() == 200)) { // // Same thing as above, just let the server know that we are no longer playing theErr = QTSS_Pause(inClientSession); Assert(theErr == QTSS_NoErr); } // // Build our ioVec with the new response // When building the iovec used for the response, we need to leave the first // iovec in the array empty, per QTSS API conventions. iovec theNewResponse[7]; theNewResponse[0].iov_len = 0; UInt32 totalResponseLen = 0; // // Internally, the server keeps an RTSP Session ID for this QTSS_ClientSessionObject. // We need to replace the one the upstream server has given us with this server's // RTSP Session ID. Otherwise, when the server receives the next RTSP request, // this server will not be able to map the RTSP session ID properly. UInt32 theNumVecs = ReplaceSessionAndTransportHeaders(&theResponse, &theNewResponse[1], &theSessionID, &theTransportHeader, &totalResponseLen); theNumVecs++; // Take into account that the first one is empty // // Also add in the content body of the response if there is one theNewResponse[theNumVecs].iov_base = inProxyClientInfo->GetRTSPClient()->GetContentBody(); theNewResponse[theNumVecs].iov_len = inProxyClientInfo->GetRTSPClient()->GetContentLength(); totalResponseLen += theNewResponse[theNumVecs].iov_len; theNumVecs++; UInt32 lenWritten = 0; theErr = QTSS_WriteV(inRequest, theNewResponse, theNumVecs, totalResponseLen, &lenWritten); Assert(lenWritten == totalResponseLen); Assert(theErr == QTSS_NoErr); return QTSS_NoErr; } UInt32 ReplaceSessionAndTransportHeaders(StrPtrLen* inResponse, iovec* outNewResponse, StrPtrLen* inNewSessionID, StrPtrLen* inNewTransportHeader, UInt32* outNewResponseLen) { static StrPtrLen sTransportHeader("Transport"); static StrPtrLen sSessionHeader("Session"); StringParser reqParser(inResponse); StrPtrLen theHeaderName; UInt32 curVecIndex = 0; outNewResponse[0].iov_base = inResponse->Ptr; *outNewResponseLen = 0; while (reqParser.GetDataRemaining() > 0) { reqParser.ConsumeWord(&theHeaderName); if (theHeaderName.EqualIgnoreCase(sTransportHeader.Ptr, sTransportHeader.Len)) { // // Mark off the length of the last section of the header outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) theHeaderName.Ptr) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base ); *outNewResponseLen += outNewResponse[curVecIndex].iov_len; // // Insert the new Transport header outNewResponse[curVecIndex+1].iov_base = inNewTransportHeader->Ptr; outNewResponse[curVecIndex+1].iov_len = inNewTransportHeader->Len; *outNewResponseLen += inNewTransportHeader->Len; // // Move onto the next iovec curVecIndex+=2; // // Mark the start of the next section reqParser.GetThruEOL(NULL); outNewResponse[curVecIndex].iov_base = reqParser.GetCurrentPosition(); } else if (theHeaderName.EqualIgnoreCase(sSessionHeader.Ptr, sSessionHeader.Len)) { reqParser.ConsumeLength(NULL, 2); //skip over ": " // // Mark off the length of the last section of the header outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) reqParser.GetCurrentPosition()) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base); *outNewResponseLen += outNewResponse[curVecIndex].iov_len; // // Insert new session ID outNewResponse[curVecIndex+1].iov_base = inNewSessionID->Ptr; outNewResponse[curVecIndex+1].iov_len = inNewSessionID->Len; *outNewResponseLen += inNewSessionID->Len; // // Move onto the next empty vec curVecIndex+=2; // // Move past the session ID. Be careful, there may be a ';' that we need to look for StrPtrLen theSessionID; reqParser.GetThruEOL(&theSessionID); outNewResponse[curVecIndex].iov_base = &theSessionID.Ptr[theSessionID.Len]; while (theSessionID.Ptr < outNewResponse[curVecIndex].iov_base) { if (*theSessionID.Ptr == ';') { outNewResponse[curVecIndex].iov_base = theSessionID.Ptr; break; } theSessionID.Ptr++; } } else reqParser.GetThruEOL(NULL); } // // Finish off the vec by marking the len of the last section outNewResponse[curVecIndex].iov_len = (UInt32) ((PointerSizedUInt) reqParser.GetCurrentPosition()) - ((PointerSizedUInt) outNewResponse[curVecIndex].iov_base ); // // And finish off the total length *outNewResponseLen += outNewResponse[curVecIndex].iov_len; // // Return the number of vecs written return curVecIndex+1; } QTSS_Error ProcessIncomingRTCPPacket(QTSS_RTCPProcess_Params* inParams) { ProxyClientInfo* theClient = NULL; UInt32 theLen = sizeof(theClient); QTSS_Error theErr = QTSS_GetValue(inParams->inClientSession, sProxyClientInfoAttr, 0, &theClient, &theLen); // // This role receives ALL RTCP packets. We are only interested in RTCP packets // sent to proxy sessions. So we figure this out based on whether there // is a ProxyClientInfo object in the client session if (theErr != QTSS_NoErr) return QTSS_NoErr; // // Let's forward this RTCP packet to the right upstream server ProxyDemuxerTask* theTask = theClient->GetDemuxerTaskForStream(inParams->inRTPStream); Assert(theTask != NULL); if (theTask == NULL) return QTSS_NoErr; // // Using the RTCP socket (SocketB) of the pair, send the packet to the origin server's // RTCP port (the port number stored is the RTP port) (void)theTask->GetSockets()->GetSocketB()-> SendTo(theTask->GetRemoteAddr(), theTask->GetOriginServerPort() + 1, inParams->inRTCPPacketData, inParams->inRTCPPacketDataLen); return QTSS_NoErr; } SInt64 ProxyTask::Run() { const UInt32 kMaxRTCPPacketSize = 2048; char thePacketBuffer[kMaxRTCPPacketSize]; QTSS_PacketStruct thePacketStruct; thePacketStruct.packetTransmitTime = QTSS_Milliseconds(); thePacketStruct.packetData = thePacketBuffer; (void)this->GetEvents(); OSMutexLocker locker(sSocketPool->GetMutex()); for (OSQueueIter iter(sSocketPool->GetSocketQueue()); !iter.IsDone(); iter.Next()) { UInt32 theRemoteAddr = 0; UInt16 theRemotePort = 0; UDPSocketPair* thePair = (UDPSocketPair*)iter.GetCurrent()->GetEnclosingObject(); Assert(thePair != NULL); for (UInt32 x = 0; x < 2; x++) { QTSS_WriteFlags theFlags = qtssWriteFlagsNoFlags; UDPSocket* theSocket = NULL; if (x == 0) { theFlags = qtssWriteFlagsIsRTP; theSocket = thePair->GetSocketA(); } else { theFlags = qtssWriteFlagsIsRTCP; theSocket = thePair->GetSocketB(); } Assert(theSocket->GetDemuxer() != NULL); OSMutexLocker locker(theSocket->GetDemuxer()->GetMutex()); //get all the outstanding packets for this socket while (true) { UInt32 thePacketLen = 0; theSocket->RecvFrom(&theRemoteAddr, &theRemotePort, thePacketStruct.packetData, kMaxRTCPPacketSize, &thePacketLen); if (thePacketLen == 0) break;//no more packets on this socket! ProxyDemuxerTask* theDemuxerTask = (ProxyDemuxerTask*)theSocket->GetDemuxer()->GetTask(theRemoteAddr, 0); if (theDemuxerTask != NULL) { QTSS_RTPStreamObject theStream = theDemuxerTask->GetStream(); (void)QTSS_Write(theStream, &thePacketStruct, thePacketLen, NULL, theFlags); } } } } return kProxyTaskPollIntervalMsec; } UDPSocketPair* ProxySocketPool::ConstructUDPSocketPair() { Assert(sProxyTask != NULL); //construct a pair of UDP sockets, the lower one for RTP data (outgoing only, no demuxer //necessary), and one for RTCP data (incoming, so definitely need a demuxer). //These are nonblocking sockets that DON'T receive events (we are going to poll for data) return NEW UDPSocketPair( NEW UDPSocket(sProxyTask, UDPSocket::kWantsDemuxer | Socket::kNonBlockingSocketType), NEW UDPSocket(sProxyTask, UDPSocket::kWantsDemuxer | Socket::kNonBlockingSocketType)); } void ProxySocketPool::DestructUDPSocketPair(UDPSocketPair* inPair) { delete inPair->GetSocketA(); delete inPair->GetSocketB(); delete inPair; } void ProxySocketPool::SetUDPSocketOptions(UDPSocketPair* inPair) { #ifdef __Win32__ // // On Win32, apparently the socket buffer size matters even though this is UDP and being // used for sending... on UNIX typically the socket buffer size doesn't matter because the // packet goes right down to the driver. On Win32, unless this is really big, we get packet loss. inPair->GetSocketA()->SetSocketBufSize(256 * 1024); inPair->GetSocketB()->SetSocketBufSize(256 * 1024); #endif // // Always set the Rcv buf size for the RTCP sockets. This is important because the // server is going to be getting many packets on these sockets. inPair->GetSocketA()->SetSocketRcvBufSize(512 * 1024); inPair->GetSocketB()->SetSocketRcvBufSize(512 * 1024); } ProxyClientInfo::~ProxyClientInfo() { UInt32 theHostAddr = fClient.GetSocket()->GetHostAddr(); for (OSQueueIter iter(&fDemuxerTaskQueue); !iter.IsDone(); ) { OSQueueElem* theElem = iter.GetCurrent(); ProxyDemuxerTask* theTask = (ProxyDemuxerTask*)theElem->GetEnclosingObject(); // // Move onto the next element becase we are going to be deleting this one iter.Next(); theElem->Remove(); // Get off the queue before deleting // // Clean up UDPSocketPair* thePair = theTask->GetSockets(); thePair->GetSocketA()->GetDemuxer()->UnregisterTask(theHostAddr, 0, theTask); thePair->GetSocketB()->GetDemuxer()->UnregisterTask(theHostAddr, 0, theTask); delete theTask; sSocketPool->ReleaseUDPSocketPair(thePair); } } ProxyDemuxerTask* ProxyClientInfo::GetDemuxerTaskForStream(QTSS_RTPStreamObject inStream) { // // Iterate through the queue of demuxer tasks, finding the one with // the stream that matches this input for (OSQueueIter iter(&fDemuxerTaskQueue); !iter.IsDone(); iter.Next()) { ProxyDemuxerTask* theTask = (ProxyDemuxerTask*)iter.GetCurrent()->GetEnclosingObject(); if (theTask->GetStream() == inStream) return theTask; } return NULL; } ProxyDemuxerTask* ProxyClientInfo::AddStream(QTSS_RTPStreamObject inStream) { // // Allocate some UDP sockets out of our pool to receive the UDP data from // the server. Demuxing is based on the origin server's (host's) IP addr, so // pass that to the socket pool so it can properly allocate a pair of UDP sockets. // We don't know what the remote port is yet (we only find out when we get the // SETUP response from the origin), so just pass in 0. UInt32 theHostAddr = fClient.GetSocket()->GetHostAddr(); UInt32 theLocalAddr = fClient.GetSocket()->GetLocalAddr(); UDPSocketPair* thePair = sSocketPool->GetUDPSocketPair(theLocalAddr, 0, theHostAddr, 0); fLastDemuxerTask = NEW ProxyDemuxerTask(inStream, thePair); fDemuxerTaskQueue.EnQueue(fLastDemuxerTask->GetQueueElem()); // // Tell the demuxers for these sockets to send any packets from this IP addr // to the ProxyDemuxerTask for this stream. This is how incoming UDP packets // will be routed to the proper QTSS_RTPStreamObject thePair->GetSocketA()->GetDemuxer()->RegisterTask(theHostAddr, 0, fLastDemuxerTask); thePair->GetSocketB()->GetDemuxer()->RegisterTask(theHostAddr, 0, fLastDemuxerTask); // // return the newly created ProxyDemuxerTask return fLastDemuxerTask; }