diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index bd25e4cd83..645604d853 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -2,6 +2,9 @@ #include "TextBasedProtocol.h" +#include +#include + /// @file /// @namespace pcpp @@ -114,6 +117,40 @@ namespace pcpp return port == 5060 || port == 5061; } + /// Heuristically detects whether the first line of a buffer looks like a SIP + /// Request-Line or Status-Line (RFC 3261). + /// + /// Line is parsed as: + /// token1 SP(space1) token2 SP(space2) token3 + /// + /// SIP Request-Line: + /// token1 = Method + /// token2 = Request-URI (must contain ':') + /// token3 = SIP-Version (starts with "SIP/") + /// Example: INVITE sip:alice@example.com SIP/2.0 + /// + /// SIP Status-Line: + /// token1 = SIP-Version (starts with "SIP/") + /// token2 = Status-Code (3 digits) + /// token3 = Reason-Phrase + /// Example: SIP/2.0 200 OK + /// + /// RFC References: + /// From section 4.1 of RFC 2543: + /// Request-Line = Method SP Request-URI SP SIP-Version CRLF + /// + /// From section 5.1 of RFC 2543: + /// Status-Line = SIP-Version SP Status-Code SP Reason-Phrase CRLF + /// + /// From section 7.1 of RFC 3261: + /// Unlike HTTP, SIP treats the version number as a literal string. + /// In practice, this should make no difference. + /// + /// @param[in] data Pointer to the raw data buffer + /// @param[in] dataLen Length of the data buffer in bytes + /// @return True if the first line matches SIP request/response syntax, false otherwise + static bool dissectSipHeuristic(const uint8_t* data, size_t dataLen); + protected: SipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, ProtocolType protocol) : TextBasedProtocolMessage(data, dataLen, prevLayer, packet, protocol) @@ -137,6 +174,17 @@ namespace pcpp { return true; } + + private: + /// Finds the length of the first line in the buffer. + /// This method scans the input data for the first occurrence of '\r' or '\n', + /// marking the end of the first line. If no such character exists, the returned + /// value will be equal to the buffer length. Returns -1 if the buffer is null + /// or empty. + /// @param[in] data Pointer to the raw data buffer + /// @param[in] dataLen Length of the data buffer in bytes + /// @return The number of bytes until the first CR/LF, or -1 on invalid input + static int findFirstLine(const uint8_t* data, size_t dataLen); }; class SipRequestFirstLine; @@ -531,6 +579,18 @@ namespace pcpp /// @return The parsed SIP method static SipRequestLayer::SipMethod parseMethod(const char* data, size_t dataLen); + /// A static method for parsing the SIP version out of raw data + /// @param[in] data The raw data + /// @param[in] dataLen The raw data length + /// @return The parsed SIP version string or an empty string if parsing fails + static std::string parseVersion(const char* data, size_t dataLen); + + /// A static method for parsing the Request-URI out of raw data + /// @param[in] data The raw data + /// @param[in] dataLen The raw data length + /// @return The parsed Request-URI string or an empty string if parsing fails + static std::string parseUri(const char* data, size_t dataLen); + /// @return The size in bytes of the SIP request first line int getSize() const { diff --git a/Packet++/src/SipLayer.cpp b/Packet++/src/SipLayer.cpp index ac9acb1289..61f3b2440c 100644 --- a/Packet++/src/SipLayer.cpp +++ b/Packet++/src/SipLayer.cpp @@ -103,6 +103,68 @@ namespace pcpp } } + bool SipLayer::dissectSipHeuristic(const uint8_t* data, size_t dataLen) + { + if (!data || dataLen == 0) + { + return false; + } + + int firstLineLen = findFirstLine(data, dataLen); + if (firstLineLen <= 0) + { + return false; + } + + const char* line = reinterpret_cast(data); + const int len = firstLineLen; + + // --- Check if it's a SIP response line: "SIP/x.y SP nnn SP Reason" --- + std::string responseSipVersion = SipResponseFirstLine::parseVersion(line, len); + if (!responseSipVersion.empty()) + { + auto statusCode = SipResponseFirstLine::parseStatusCode(line, len); + if (statusCode != SipResponseLayer::SipStatusCodeUnknown) + { + return true; + } + } + + // --- Check if it's a SIP request line: "METHOD SP URI SP SIP/x.y" --- + SipRequestLayer::SipMethod method = SipRequestFirstLine::parseMethod(line, len); + if (method != SipRequestLayer::SipMethod::SipMethodUnknown) + { + std::string requestSipVersion = SipRequestFirstLine::parseVersion(line, len); + if (!requestSipVersion.empty()) + { + std::string requestUri = SipRequestFirstLine::parseUri(line, len); + if (!requestUri.empty() && requestUri.find(':') != std::string::npos) + { + return true; + } + } + } + + return false; + } + + int SipLayer::findFirstLine(const uint8_t* data, size_t dataLen) + { + if (!data || dataLen == 0) + return -1; + + const char* start = reinterpret_cast(data); + const char* end = start + dataLen; + + // Find CR or LF + auto it = std::find_if(start, end, [](char c) + { + return c == '\r' || c == '\n'; + }); + + return static_cast(std::distance(start, it)); + } + // -------- Class SipRequestFirstLine ----------------- SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest) @@ -208,6 +270,75 @@ namespace pcpp return methodAdEnum->second; } + std::string SipRequestFirstLine::parseUri(const char* data, size_t dataLen) + { + if (data == nullptr || dataLen == 0) + { + PCPP_LOG_DEBUG("Empty data in SIP request line"); + return ""; + } + + const char* firstSpace = static_cast(memchr(data, ' ', dataLen)); + if (firstSpace == nullptr) + { + PCPP_LOG_DEBUG("No space found after METHOD in SIP request line"); + return ""; + } + + const char* lastSpace = static_cast(memrchr(data, ' ', dataLen)); + if (lastSpace == nullptr || lastSpace <= firstSpace) + { + PCPP_LOG_DEBUG("Couldn't find proper space before SIP version in SIP request line"); + return ""; + } + + const char* uriStart = firstSpace + 1; + size_t uriLen = static_cast(lastSpace - uriStart); + + if (uriLen == 0) + { + PCPP_LOG_DEBUG("URI part is empty in SIP request line"); + return ""; + } + + return std::string(uriStart, uriLen); + } + + std::string SipRequestFirstLine::parseVersion(const char* data, size_t dataLen) + { + if (data == nullptr || dataLen == 0) + { + PCPP_LOG_DEBUG("Empty data in SIP request line for version parsing"); + return ""; + } + + const char* lastSpace = static_cast(memrchr(data, ' ', dataLen)); + + if (!lastSpace) + { + PCPP_LOG_DEBUG("No space found before SIP version in request line"); + return ""; + } + + const char* versionStart = lastSpace + 1; + size_t versionLen = dataLen - (versionStart - data); + + if (versionLen < 7) // "SIP/x.y" + { + PCPP_LOG_DEBUG("Version part too short"); + return ""; + } + + if (versionStart[0] != 'S' || versionStart[1] != 'I' || + versionStart[2] != 'P' || versionStart[3] != '/') + { + PCPP_LOG_DEBUG("SIP request version does not begin with 'SIP/'"); + return ""; + } + + return std::string(versionStart, versionLen); + } + void SipRequestFirstLine::parseVersion() { if (m_SipRequest->getDataLen() < static_cast(m_UriOffset)) diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index 478d2893a3..6f691bf902 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -108,7 +108,10 @@ namespace pcpp else if (DnsLayer::isDataValid(udpData, udpDataLen) && (DnsLayer::isDnsPort(portDst) || DnsLayer::isDnsPort(portSrc))) m_NextLayer = new DnsLayer(udpData, udpDataLen, this, m_Packet); - else if (SipLayer::isSipPort(portDst) || SipLayer::isSipPort(portSrc)) + else if (SipLayer::isSipPort(portDst) || + SipLayer::isSipPort(portSrc) || + SipLayer::dissectSipHeuristic(udpData, udpDataLen) + ) { if (SipRequestFirstLine::parseMethod((char*)udpData, udpDataLen) != SipRequestLayer::SipMethodUnknown) m_NextLayer = new SipRequestLayer(udpData, udpDataLen, this, m_Packet);