From f8b2d4d9cc8254ff7b3ca355930b6ac7eddbd25f Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Sat, 15 Nov 2025 10:41:04 +0000 Subject: [PATCH 1/8] add initial heuristic detection for SIP packets --- Packet++/header/SipLayer.h | 187 +++++++++++++++++++++++++++++++++++++ Packet++/src/UdpLayer.cpp | 6 ++ 2 files changed, 193 insertions(+) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index bd25e4cd83..56945f0a9c 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,190 @@ namespace pcpp return port == 5060 || port == 5061; } + static int find_char(const uint8_t* buf, int offset, char needle) + { + if (!buf || offset < 0) + return -1; + + const uint8_t* p = buf + offset; + + while (*p && *p != static_cast(needle)) + ++p; + + return (*p == static_cast(needle)) + ? static_cast(p - buf) + : -1; + } + + static int 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)); + } + + static bool startsWithSipVersion(const char* s, size_t len) + { + constexpr char pfx[] = "SIP/"; + constexpr std::size_t pfxLen = sizeof(pfx) - 1; + + if (len < pfxLen) + return false; + + return std::equal( + pfx, pfx + pfxLen, s, + [](char a, char b) + { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + } + ); + } + + // Checks if the token is exactly a 3-digit status code + static bool isThreeDigitCode(const char* s, size_t len) + { + if (len != 3) + return false; + + return std::all_of(s, s + 3, [](unsigned char ch) + { + return std::isdigit(ch) != 0; + }); + } + + // Returns true if there is ':' in [begin, end) + static bool hasColonInRange(const char* s, size_t begin, size_t end) + { + const char* first = s + begin; + const char* last = s + end; + + return std::find(first, last, ':') != last; + } + + // Find first space from start to len + static int findSpace(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + + auto it = std::find(begin, end, ' '); + if (it == end) + return -1; + + return static_cast(std::distance(s, it)); + } + + static int skipSpaces(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + const char* it = std::find_if(begin, end, [](unsigned char ch) + { + return ch != ' '; + }); + return static_cast(std::distance(s, it)); + } + + /* + * SIP Heuristic Detection: + * + * Request-Line: + * Method SP Request-URI SP SIP-Version + * + * Status-Line: + * SIP-Version SP Status-Code SP Reason-Phrase + * + * Returns: + * true = line looks like a valid SIP request or SIP response + * false = not SIP + */ + static bool 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; + + // ----------- Extract first 3 tokens ----------- + int token1_start = 0; + token1_start = skipSpaces(line, token1_start, len); + if (token1_start >= len) + return false; + + int space1 = findSpace(line, token1_start, len); + if (space1 == -1 || space1 == token1_start) + return false; // token1 invalid + + int token1_len = space1 - token1_start; + + int token2_start = skipSpaces(line, space1 + 1, len); + if (token2_start >= len) + return false; + + int space2 = findSpace(line, token2_start, len); + if (space2 == -1) + return false; // missing token3 + + int token2_len = space2 - token2_start; + + int token3_start = skipSpaces(line, space2 + 1, len); + if (token3_start >= len) + return false; + + int token3_len = len - token3_start; + + const char* t1 = line + token1_start; + const char* t2 = line + token2_start; + const char* t3 = line + token3_start; + + // ----------- Status-Line: "SIP/x.y SP nnn SP Reason" ----------- + if (startsWithSipVersion(t1, static_cast(token1_len))) + { + // second token must be 3-digit status code + if (!isThreeDigitCode(t2, static_cast(token2_len))) + return false; + + return true; // SIP Response detected + } + + // ----------- Request-Line: "METHOD SP URI SP SIP/x.y" ----------- + + // URI must have at least 3 characters + if (token2_len < 3) + return false; + + // URI must contain ':' before token3 starts + if (!hasColonInRange(line, + static_cast(token2_start + 1), + static_cast(space2))) + { + return false; + } + + // third token must be a SIP version string + if (!startsWithSipVersion(t3, static_cast(token3_len))) + return false; + + return true; // SIP Request detected + } + + protected: SipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, ProtocolType protocol) : TextBasedProtocolMessage(data, dataLen, prevLayer, packet, protocol) diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index 478d2893a3..affc2d1975 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -20,6 +20,7 @@ #include "PacketUtils.h" #include "Logger.h" #include +#include namespace pcpp { @@ -101,6 +102,11 @@ namespace pcpp uint8_t* udpData = m_Data + sizeof(udphdr); size_t udpDataLen = m_DataLen - sizeof(udphdr); + bool result = SipLayer::dissectSipHeuristic(udpData, udpDataLen); + std::cout << "######################" << std::endl; + std::cout << "#### is SIP?: " << (result ? "Yes" : "No") << std::endl; + std::cout << "######################" << std::endl; + if (DhcpLayer::isDhcpPorts(portSrc, portDst)) m_NextLayer = new DhcpLayer(udpData, udpDataLen, this, m_Packet); else if (VxlanLayer::isVxlanPort(portDst)) From 07ca6b71a13d5eba0d17aa4b9391d2a389bb2228 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Sun, 16 Nov 2025 15:11:21 +0000 Subject: [PATCH 2/8] add comment --- Packet++/header/SipLayer.h | 160 +++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 53 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index 56945f0a9c..bbe3164676 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -117,21 +117,14 @@ namespace pcpp return port == 5060 || port == 5061; } - static int find_char(const uint8_t* buf, int offset, char needle) - { - if (!buf || offset < 0) - return -1; - - const uint8_t* p = buf + offset; - - while (*p && *p != static_cast(needle)) - ++p; - - return (*p == static_cast(needle)) - ? static_cast(p - buf) - : -1; - } - + /// 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) { if (!data || dataLen == 0) @@ -149,16 +142,22 @@ namespace pcpp return static_cast(std::distance(start, it)); } + /// Checks whether a buffer starts with the SIP version prefix "SIP/". + /// Comparison is case-insensitive and requires the input length to be at least + /// the size of the prefix. + /// @param[in] s Pointer to the buffer to examine + /// @param[in] len Number of bytes available in the buffer + /// @return True if the buffer begins with "SIP/" (case-insensitive), false otherwise static bool startsWithSipVersion(const char* s, size_t len) { - constexpr char pfx[] = "SIP/"; - constexpr std::size_t pfxLen = sizeof(pfx) - 1; + constexpr char prefix[] = "SIP/"; + constexpr std::size_t prefixLen = sizeof(prefix) - 1; - if (len < pfxLen) + if (len < prefixLen) return false; return std::equal( - pfx, pfx + pfxLen, s, + prefix, prefix + prefixLen, s, [](char a, char b) { return std::tolower(static_cast(a)) == @@ -167,7 +166,12 @@ namespace pcpp ); } - // Checks if the token is exactly a 3-digit status code + /// Determines whether a buffer of length 3 contains only numeric digits. + /// This is primarily used to validate SIP response status codes, which must + /// always be 3-digit numeric values. + /// @param[in] s Pointer to the buffer to check + /// @param[in] len Must be exactly 3 to return true + /// @return True if all three characters are decimal digits, false otherwise static bool isThreeDigitCode(const char* s, size_t len) { if (len != 3) @@ -179,7 +183,13 @@ namespace pcpp }); } - // Returns true if there is ':' in [begin, end) + /// Checks for the presence of a colon (':') within a specific range of a string. + /// This is used to validate that a SIP Request-URI contains a scheme (e.g., sip:), + /// which is required for proper SIP request-line syntax. + /// @param[in] s Pointer to the string to search + /// @param[in] begin Starting index of the range (inclusive) + /// @param[in] end Ending index of the range (exclusive) + /// @return True if a ':' character exists within the specified range, false otherwise static bool hasColonInRange(const char* s, size_t begin, size_t end) { const char* first = s + begin; @@ -188,7 +198,12 @@ namespace pcpp return std::find(first, last, ':') != last; } - // Find first space from start to len + /// Finds the first space (' ') character in the string starting from a given index. + /// The search is limited to the range [start, len). If no space is found, -1 is returned. + /// @param[in] s Pointer to the string to search + /// @param[in] start Index from which to start scanning + /// @param[in] len Total valid length of the string + /// @return The index of the first space, or -1 if not found static int findSpace(const char* s, int start, int len) { const char* begin = s + start; @@ -201,6 +216,12 @@ namespace pcpp return static_cast(std::distance(s, it)); } + /// Finds the first space (' ') character in the string starting from a given index. + /// The search is limited to the range [start, len). If no space is found, -1 is returned. + /// @param[in] s Pointer to the string to search + /// @param[in] start Index from which to start scanning + /// @param[in] len Total valid length of the string + /// @return The index of the first space, or -1 if not found static int skipSpaces(const char* s, int start, int len) { const char* begin = s + start; @@ -212,80 +233,112 @@ namespace pcpp return static_cast(std::distance(s, it)); } - /* - * SIP Heuristic Detection: - * - * Request-Line: - * Method SP Request-URI SP SIP-Version - * - * Status-Line: - * SIP-Version SP Status-Code SP Reason-Phrase - * - * Returns: - * true = line looks like a valid SIP request or SIP response - * false = not SIP - */ + /// 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) { 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; - // ----------- Extract first 3 tokens ----------- + // --- Extract first three tokens from the first line --- int token1_start = 0; token1_start = skipSpaces(line, token1_start, len); if (token1_start >= len) + { return false; + } int space1 = findSpace(line, token1_start, len); if (space1 == -1 || space1 == token1_start) - return false; // token1 invalid + { + return false; + } int token1_len = space1 - token1_start; int token2_start = skipSpaces(line, space1 + 1, len); if (token2_start >= len) + { return false; + } int space2 = findSpace(line, token2_start, len); if (space2 == -1) - return false; // missing token3 + { + return false; + } int token2_len = space2 - token2_start; int token3_start = skipSpaces(line, space2 + 1, len); if (token3_start >= len) + { return false; + } int token3_len = len - token3_start; - const char* t1 = line + token1_start; - const char* t2 = line + token2_start; - const char* t3 = line + token3_start; + const char* token1 = line + token1_start; + const char* token2 = line + token2_start; + const char* token3 = line + token3_start; - // ----------- Status-Line: "SIP/x.y SP nnn SP Reason" ----------- - if (startsWithSipVersion(t1, static_cast(token1_len))) + // --- Check if it's a SIP response line: "SIP/x.y SP nnn SP Reason" --- + if (startsWithSipVersion(token1, static_cast(token1_len))) { // second token must be 3-digit status code - if (!isThreeDigitCode(t2, static_cast(token2_len))) + if (!isThreeDigitCode(token2, static_cast(token2_len))) return false; - return true; // SIP Response detected + return true; } - // ----------- Request-Line: "METHOD SP URI SP SIP/x.y" ----------- - - // URI must have at least 3 characters + // --- Check if it's a SIP request line: "METHOD SP URI SP SIP/x.y" --- if (token2_len < 3) + { return false; + } - // URI must contain ':' before token3 starts if (!hasColonInRange(line, static_cast(token2_start + 1), static_cast(space2))) @@ -293,11 +346,12 @@ namespace pcpp return false; } - // third token must be a SIP version string - if (!startsWithSipVersion(t3, static_cast(token3_len))) + if (!startsWithSipVersion(token3, static_cast(token3_len))) + { return false; + } - return true; // SIP Request detected + return true; } @@ -312,7 +366,7 @@ namespace pcpp SipLayer& operator=(const SipLayer& other) { TextBasedProtocolMessage::operator=(other); - return *this; + return///this; } // implementation of abstract methods From f4fba28cfe8a926210301ca2c2ce602d5be70f12 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Sun, 16 Nov 2025 15:17:52 +0000 Subject: [PATCH 3/8] refactor move helper methods to private, keep public API minimal --- Packet++/header/SipLayer.h | 233 +++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 116 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index bbe3164676..a40bc83877 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -117,122 +117,6 @@ namespace pcpp return port == 5060 || port == 5061; } - /// 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) - { - 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)); - } - - /// Checks whether a buffer starts with the SIP version prefix "SIP/". - /// Comparison is case-insensitive and requires the input length to be at least - /// the size of the prefix. - /// @param[in] s Pointer to the buffer to examine - /// @param[in] len Number of bytes available in the buffer - /// @return True if the buffer begins with "SIP/" (case-insensitive), false otherwise - static bool startsWithSipVersion(const char* s, size_t len) - { - constexpr char prefix[] = "SIP/"; - constexpr std::size_t prefixLen = sizeof(prefix) - 1; - - if (len < prefixLen) - return false; - - return std::equal( - prefix, prefix + prefixLen, s, - [](char a, char b) - { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - } - ); - } - - /// Determines whether a buffer of length 3 contains only numeric digits. - /// This is primarily used to validate SIP response status codes, which must - /// always be 3-digit numeric values. - /// @param[in] s Pointer to the buffer to check - /// @param[in] len Must be exactly 3 to return true - /// @return True if all three characters are decimal digits, false otherwise - static bool isThreeDigitCode(const char* s, size_t len) - { - if (len != 3) - return false; - - return std::all_of(s, s + 3, [](unsigned char ch) - { - return std::isdigit(ch) != 0; - }); - } - - /// Checks for the presence of a colon (':') within a specific range of a string. - /// This is used to validate that a SIP Request-URI contains a scheme (e.g., sip:), - /// which is required for proper SIP request-line syntax. - /// @param[in] s Pointer to the string to search - /// @param[in] begin Starting index of the range (inclusive) - /// @param[in] end Ending index of the range (exclusive) - /// @return True if a ':' character exists within the specified range, false otherwise - static bool hasColonInRange(const char* s, size_t begin, size_t end) - { - const char* first = s + begin; - const char* last = s + end; - - return std::find(first, last, ':') != last; - } - - /// Finds the first space (' ') character in the string starting from a given index. - /// The search is limited to the range [start, len). If no space is found, -1 is returned. - /// @param[in] s Pointer to the string to search - /// @param[in] start Index from which to start scanning - /// @param[in] len Total valid length of the string - /// @return The index of the first space, or -1 if not found - static int findSpace(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - - auto it = std::find(begin, end, ' '); - if (it == end) - return -1; - - return static_cast(std::distance(s, it)); - } - - /// Finds the first space (' ') character in the string starting from a given index. - /// The search is limited to the range [start, len). If no space is found, -1 is returned. - /// @param[in] s Pointer to the string to search - /// @param[in] start Index from which to start scanning - /// @param[in] len Total valid length of the string - /// @return The index of the first space, or -1 if not found - static int skipSpaces(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - const char* it = std::find_if(begin, end, [](unsigned char ch) - { - return ch != ' '; - }); - return static_cast(std::distance(s, it)); - } - /// Heuristically detects whether the first line of a buffer looks like a SIP /// Request-Line or Status-Line (RFC 3261). /// @@ -378,6 +262,123 @@ 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) + { + 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)); + } + + /// Checks whether a buffer starts with the SIP version prefix "SIP/". + /// Comparison is case-insensitive and requires the input length to be at least + /// the size of the prefix. + /// @param[in] s Pointer to the buffer to examine + /// @param[in] len Number of bytes available in the buffer + /// @return True if the buffer begins with "SIP/" (case-insensitive), false otherwise + static bool startsWithSipVersion(const char* s, size_t len) + { + constexpr char prefix[] = "SIP/"; + constexpr std::size_t prefixLen = sizeof(prefix) - 1; + + if (len < prefixLen) + return false; + + return std::equal( + prefix, prefix + prefixLen, s, + [](char a, char b) + { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + } + ); + } + + /// Determines whether a buffer of length 3 contains only numeric digits. + /// This is primarily used to validate SIP response status codes, which must + /// always be 3-digit numeric values. + /// @param[in] s Pointer to the buffer to check + /// @param[in] len Must be exactly 3 to return true + /// @return True if all three characters are decimal digits, false otherwise + static bool isThreeDigitCode(const char* s, size_t len) + { + if (len != 3) + return false; + + return std::all_of(s, s + 3, [](unsigned char ch) + { + return std::isdigit(ch) != 0; + }); + } + + /// Checks for the presence of a colon (':') within a specific range of a string. + /// This is used to validate that a SIP Request-URI contains a scheme (e.g., sip:), + /// which is required for proper SIP request-line syntax. + /// @param[in] s Pointer to the string to search + /// @param[in] begin Starting index of the range (inclusive) + /// @param[in] end Ending index of the range (exclusive) + /// @return True if a ':' character exists within the specified range, false otherwise + static bool hasColonInRange(const char* s, size_t begin, size_t end) + { + const char* first = s + begin; + const char* last = s + end; + + return std::find(first, last, ':') != last; + } + + /// Finds the first space (' ') character in the string starting from a given index. + /// The search is limited to the range [start, len). If no space is found, -1 is returned. + /// @param[in] s Pointer to the string to search + /// @param[in] start Index from which to start scanning + /// @param[in] len Total valid length of the string + /// @return The index of the first space, or -1 if not found + static int findSpace(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + + auto it = std::find(begin, end, ' '); + if (it == end) + return -1; + + return static_cast(std::distance(s, it)); + } + + /// Finds the first space (' ') character in the string starting from a given index. + /// The search is limited to the range [start, len). If no space is found, -1 is returned. + /// @param[in] s Pointer to the string to search + /// @param[in] start Index from which to start scanning + /// @param[in] len Total valid length of the string + /// @return The index of the first space, or -1 if not found + static int skipSpaces(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + const char* it = std::find_if(begin, end, [](unsigned char ch) + { + return ch != ' '; + }); + return static_cast(std::distance(s, it)); + } }; class SipRequestFirstLine; From d18abd086fd7349c360c894a309ee97bbbf1e965 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Mon, 17 Nov 2025 08:02:04 +0000 Subject: [PATCH 4/8] use function dissectSipHeuristic --- Packet++/header/SipLayer.h | 2 +- Packet++/src/UdpLayer.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index a40bc83877..1ef943eb37 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -250,7 +250,7 @@ namespace pcpp SipLayer& operator=(const SipLayer& other) { TextBasedProtocolMessage::operator=(other); - return///this; + return *this; } // implementation of abstract methods diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index affc2d1975..ff0e18ad4b 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -102,11 +102,6 @@ namespace pcpp uint8_t* udpData = m_Data + sizeof(udphdr); size_t udpDataLen = m_DataLen - sizeof(udphdr); - bool result = SipLayer::dissectSipHeuristic(udpData, udpDataLen); - std::cout << "######################" << std::endl; - std::cout << "#### is SIP?: " << (result ? "Yes" : "No") << std::endl; - std::cout << "######################" << std::endl; - if (DhcpLayer::isDhcpPorts(portSrc, portDst)) m_NextLayer = new DhcpLayer(udpData, udpDataLen, this, m_Packet); else if (VxlanLayer::isVxlanPort(portDst)) @@ -114,7 +109,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); From 6c6acdaf0bb6764c8772cfb514ff8e5816d4e116 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Mon, 17 Nov 2025 14:56:59 +0000 Subject: [PATCH 5/8] Remove unused #include --- Packet++/src/UdpLayer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index ff0e18ad4b..6f691bf902 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -20,7 +20,6 @@ #include "PacketUtils.h" #include "Logger.h" #include -#include namespace pcpp { From 2f2cc66b5713033fefad454e4a3f3f951eb46fb4 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Sat, 29 Nov 2025 07:55:22 +0000 Subject: [PATCH 6/8] move the implementation to SipLayer.cpp --- Packet++/header/SipLayer.h | 167 ++----------------------------------- Packet++/src/SipLayer.cpp | 166 ++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 160 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index 1ef943eb37..2a0d0bdaa8 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -149,95 +149,7 @@ namespace pcpp /// @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) - { - 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; - - // --- Extract first three tokens from the first line --- - int token1_start = 0; - token1_start = skipSpaces(line, token1_start, len); - if (token1_start >= len) - { - return false; - } - - int space1 = findSpace(line, token1_start, len); - if (space1 == -1 || space1 == token1_start) - { - return false; - } - - int token1_len = space1 - token1_start; - - int token2_start = skipSpaces(line, space1 + 1, len); - if (token2_start >= len) - { - return false; - } - - int space2 = findSpace(line, token2_start, len); - if (space2 == -1) - { - return false; - } - - int token2_len = space2 - token2_start; - - int token3_start = skipSpaces(line, space2 + 1, len); - if (token3_start >= len) - { - return false; - } - - int token3_len = len - token3_start; - - const char* token1 = line + token1_start; - const char* token2 = line + token2_start; - const char* token3 = line + token3_start; - - // --- Check if it's a SIP response line: "SIP/x.y SP nnn SP Reason" --- - if (startsWithSipVersion(token1, static_cast(token1_len))) - { - // second token must be 3-digit status code - if (!isThreeDigitCode(token2, static_cast(token2_len))) - return false; - - return true; - } - - // --- Check if it's a SIP request line: "METHOD SP URI SP SIP/x.y" --- - if (token2_len < 3) - { - return false; - } - - if (!hasColonInRange(line, - static_cast(token2_start + 1), - static_cast(space2))) - { - return false; - } - - if (!startsWithSipVersion(token3, static_cast(token3_len))) - { - return false; - } - - return true; - } - + static bool dissectSipHeuristic(const uint8_t* data, size_t dataLen); protected: SipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, ProtocolType protocol) @@ -272,22 +184,7 @@ namespace pcpp /// @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) - { - 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)); - } + static int findFirstLine(const uint8_t* data, size_t dataLen); /// Checks whether a buffer starts with the SIP version prefix "SIP/". /// Comparison is case-insensitive and requires the input length to be at least @@ -295,23 +192,7 @@ namespace pcpp /// @param[in] s Pointer to the buffer to examine /// @param[in] len Number of bytes available in the buffer /// @return True if the buffer begins with "SIP/" (case-insensitive), false otherwise - static bool startsWithSipVersion(const char* s, size_t len) - { - constexpr char prefix[] = "SIP/"; - constexpr std::size_t prefixLen = sizeof(prefix) - 1; - - if (len < prefixLen) - return false; - - return std::equal( - prefix, prefix + prefixLen, s, - [](char a, char b) - { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - } - ); - } + static bool startsWithSipVersion(const char* s, size_t len); /// Determines whether a buffer of length 3 contains only numeric digits. /// This is primarily used to validate SIP response status codes, which must @@ -319,16 +200,7 @@ namespace pcpp /// @param[in] s Pointer to the buffer to check /// @param[in] len Must be exactly 3 to return true /// @return True if all three characters are decimal digits, false otherwise - static bool isThreeDigitCode(const char* s, size_t len) - { - if (len != 3) - return false; - - return std::all_of(s, s + 3, [](unsigned char ch) - { - return std::isdigit(ch) != 0; - }); - } + static bool isThreeDigitCode(const char* s, size_t len); /// Checks for the presence of a colon (':') within a specific range of a string. /// This is used to validate that a SIP Request-URI contains a scheme (e.g., sip:), @@ -337,13 +209,7 @@ namespace pcpp /// @param[in] begin Starting index of the range (inclusive) /// @param[in] end Ending index of the range (exclusive) /// @return True if a ':' character exists within the specified range, false otherwise - static bool hasColonInRange(const char* s, size_t begin, size_t end) - { - const char* first = s + begin; - const char* last = s + end; - - return std::find(first, last, ':') != last; - } + static bool hasColonInRange(const char* s, size_t begin, size_t end); /// Finds the first space (' ') character in the string starting from a given index. /// The search is limited to the range [start, len). If no space is found, -1 is returned. @@ -351,17 +217,7 @@ namespace pcpp /// @param[in] start Index from which to start scanning /// @param[in] len Total valid length of the string /// @return The index of the first space, or -1 if not found - static int findSpace(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - - auto it = std::find(begin, end, ' '); - if (it == end) - return -1; - - return static_cast(std::distance(s, it)); - } + static int findSpace(const char* s, int start, int len); /// Finds the first space (' ') character in the string starting from a given index. /// The search is limited to the range [start, len). If no space is found, -1 is returned. @@ -369,16 +225,7 @@ namespace pcpp /// @param[in] start Index from which to start scanning /// @param[in] len Total valid length of the string /// @return The index of the first space, or -1 if not found - static int skipSpaces(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - const char* it = std::find_if(begin, end, [](unsigned char ch) - { - return ch != ' '; - }); - return static_cast(std::distance(s, it)); - } + static int skipSpaces(const char* s, int start, int len); }; class SipRequestFirstLine; diff --git a/Packet++/src/SipLayer.cpp b/Packet++/src/SipLayer.cpp index ac9acb1289..e2b1e6a6cf 100644 --- a/Packet++/src/SipLayer.cpp +++ b/Packet++/src/SipLayer.cpp @@ -103,6 +103,172 @@ 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; + + // --- Extract first three tokens from the first line --- + int token1_start = 0; + token1_start = skipSpaces(line, token1_start, len); + if (token1_start >= len) + { + return false; + } + + int space1 = findSpace(line, token1_start, len); + if (space1 == -1 || space1 == token1_start) + { + return false; + } + + int token1_len = space1 - token1_start; + + int token2_start = skipSpaces(line, space1 + 1, len); + if (token2_start >= len) + { + return false; + } + + int space2 = findSpace(line, token2_start, len); + if (space2 == -1) + { + return false; + } + + int token2_len = space2 - token2_start; + + int token3_start = skipSpaces(line, space2 + 1, len); + if (token3_start >= len) + { + return false; + } + + int token3_len = len - token3_start; + + const char* token1 = line + token1_start; + const char* token2 = line + token2_start; + const char* token3 = line + token3_start; + + // --- Check if it's a SIP response line: "SIP/x.y SP nnn SP Reason" --- + if (startsWithSipVersion(token1, static_cast(token1_len))) + { + // second token must be 3-digit status code + if (!isThreeDigitCode(token2, static_cast(token2_len))) + return false; + + return true; + } + + // --- Check if it's a SIP request line: "METHOD SP URI SP SIP/x.y" --- + if (token2_len < 3) + { + return false; + } + + if (!hasColonInRange(line, + static_cast(token2_start + 1), + static_cast(space2))) + { + return false; + } + + if (!startsWithSipVersion(token3, static_cast(token3_len))) + { + return false; + } + + return true; + } + + 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)); + } + + bool SipLayer::startsWithSipVersion(const char* s, size_t len) + { + constexpr char prefix[] = "SIP/"; + constexpr std::size_t prefixLen = sizeof(prefix) - 1; + + if (len < prefixLen) + return false; + + return std::equal( + prefix, prefix + prefixLen, s, + [](char a, char b) + { + return std::tolower(static_cast(a)) == + std::tolower(static_cast(b)); + } + ); + } + + bool SipLayer::isThreeDigitCode(const char* s, size_t len) + { + if (len != 3) + return false; + + return std::all_of(s, s + 3, [](unsigned char ch) + { + return std::isdigit(ch) != 0; + }); + } + + bool SipLayer::hasColonInRange(const char* s, size_t begin, size_t end) + { + const char* first = s + begin; + const char* last = s + end; + + return std::find(first, last, ':') != last; + } + + int SipLayer::findSpace(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + + auto it = std::find(begin, end, ' '); + if (it == end) + return -1; + + return static_cast(std::distance(s, it)); + } + + int SipLayer::skipSpaces(const char* s, int start, int len) + { + const char* begin = s + start; + const char* end = s + len; + const char* it = std::find_if(begin, end, [](unsigned char ch) + { + return ch != ' '; + }); + return static_cast(std::distance(s, it)); + } + // -------- Class SipRequestFirstLine ----------------- SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest) From dfc161c0acdd3393728b5c0caef7958410b4e4f0 Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Tue, 2 Dec 2025 13:40:12 +0000 Subject: [PATCH 7/8] refactor: use SipRequestFirstLine and SipResponseFirstLine static parsers in heuristic SIP detection --- Packet++/header/SipLayer.h | 12 +++ Packet++/src/SipLayer.cpp | 151 +++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 63 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index 2a0d0bdaa8..e78c39828b 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -620,6 +620,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 e2b1e6a6cf..be51dc90ad 100644 --- a/Packet++/src/SipLayer.cpp +++ b/Packet++/src/SipLayer.cpp @@ -119,77 +119,33 @@ namespace pcpp const char* line = reinterpret_cast(data); const int len = firstLineLen; - // --- Extract first three tokens from the first line --- - int token1_start = 0; - token1_start = skipSpaces(line, token1_start, len); - if (token1_start >= len) - { - return false; - } - - int space1 = findSpace(line, token1_start, len); - if (space1 == -1 || space1 == token1_start) - { - return false; - } - - int token1_len = space1 - token1_start; - - int token2_start = skipSpaces(line, space1 + 1, len); - if (token2_start >= len) - { - return false; - } - - int space2 = findSpace(line, token2_start, len); - if (space2 == -1) - { - return false; - } - - int token2_len = space2 - token2_start; - - int token3_start = skipSpaces(line, space2 + 1, len); - if (token3_start >= len) - { - return false; - } - - int token3_len = len - token3_start; - - const char* token1 = line + token1_start; - const char* token2 = line + token2_start; - const char* token3 = line + token3_start; - // --- Check if it's a SIP response line: "SIP/x.y SP nnn SP Reason" --- - if (startsWithSipVersion(token1, static_cast(token1_len))) + std::string responseSipVersion = SipResponseFirstLine::parseVersion(line, len); + if (!responseSipVersion.empty()) { - // second token must be 3-digit status code - if (!isThreeDigitCode(token2, static_cast(token2_len))) - return false; - - return true; + 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" --- - if (token2_len < 3) - { - return false; - } - - if (!hasColonInRange(line, - static_cast(token2_start + 1), - static_cast(space2))) - { - return false; - } - - if (!startsWithSipVersion(token3, static_cast(token3_len))) + SipRequestLayer::SipMethod method = SipRequestFirstLine::parseMethod(line, len); + if (method != SipRequestLayer::SipMethod::SipMethodUnknown) { - return false; + 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 true; + return false; } int SipLayer::findFirstLine(const uint8_t* data, size_t dataLen) @@ -374,6 +330,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)) From 9f5c52d9169b31544f1b60d4a965461fb01ce4cb Mon Sep 17 00:00:00 2001 From: Soroush mohammadi Date: Tue, 2 Dec 2025 14:06:29 +0000 Subject: [PATCH 8/8] Remove unused helper functions --- Packet++/header/SipLayer.h | 41 -------------------------- Packet++/src/SipLayer.cpp | 60 -------------------------------------- 2 files changed, 101 deletions(-) diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index e78c39828b..645604d853 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -185,47 +185,6 @@ namespace pcpp /// @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); - - /// Checks whether a buffer starts with the SIP version prefix "SIP/". - /// Comparison is case-insensitive and requires the input length to be at least - /// the size of the prefix. - /// @param[in] s Pointer to the buffer to examine - /// @param[in] len Number of bytes available in the buffer - /// @return True if the buffer begins with "SIP/" (case-insensitive), false otherwise - static bool startsWithSipVersion(const char* s, size_t len); - - /// Determines whether a buffer of length 3 contains only numeric digits. - /// This is primarily used to validate SIP response status codes, which must - /// always be 3-digit numeric values. - /// @param[in] s Pointer to the buffer to check - /// @param[in] len Must be exactly 3 to return true - /// @return True if all three characters are decimal digits, false otherwise - static bool isThreeDigitCode(const char* s, size_t len); - - /// Checks for the presence of a colon (':') within a specific range of a string. - /// This is used to validate that a SIP Request-URI contains a scheme (e.g., sip:), - /// which is required for proper SIP request-line syntax. - /// @param[in] s Pointer to the string to search - /// @param[in] begin Starting index of the range (inclusive) - /// @param[in] end Ending index of the range (exclusive) - /// @return True if a ':' character exists within the specified range, false otherwise - static bool hasColonInRange(const char* s, size_t begin, size_t end); - - /// Finds the first space (' ') character in the string starting from a given index. - /// The search is limited to the range [start, len). If no space is found, -1 is returned. - /// @param[in] s Pointer to the string to search - /// @param[in] start Index from which to start scanning - /// @param[in] len Total valid length of the string - /// @return The index of the first space, or -1 if not found - static int findSpace(const char* s, int start, int len); - - /// Finds the first space (' ') character in the string starting from a given index. - /// The search is limited to the range [start, len). If no space is found, -1 is returned. - /// @param[in] s Pointer to the string to search - /// @param[in] start Index from which to start scanning - /// @param[in] len Total valid length of the string - /// @return The index of the first space, or -1 if not found - static int skipSpaces(const char* s, int start, int len); }; class SipRequestFirstLine; diff --git a/Packet++/src/SipLayer.cpp b/Packet++/src/SipLayer.cpp index be51dc90ad..61f3b2440c 100644 --- a/Packet++/src/SipLayer.cpp +++ b/Packet++/src/SipLayer.cpp @@ -165,66 +165,6 @@ namespace pcpp return static_cast(std::distance(start, it)); } - bool SipLayer::startsWithSipVersion(const char* s, size_t len) - { - constexpr char prefix[] = "SIP/"; - constexpr std::size_t prefixLen = sizeof(prefix) - 1; - - if (len < prefixLen) - return false; - - return std::equal( - prefix, prefix + prefixLen, s, - [](char a, char b) - { - return std::tolower(static_cast(a)) == - std::tolower(static_cast(b)); - } - ); - } - - bool SipLayer::isThreeDigitCode(const char* s, size_t len) - { - if (len != 3) - return false; - - return std::all_of(s, s + 3, [](unsigned char ch) - { - return std::isdigit(ch) != 0; - }); - } - - bool SipLayer::hasColonInRange(const char* s, size_t begin, size_t end) - { - const char* first = s + begin; - const char* last = s + end; - - return std::find(first, last, ':') != last; - } - - int SipLayer::findSpace(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - - auto it = std::find(begin, end, ' '); - if (it == end) - return -1; - - return static_cast(std::distance(s, it)); - } - - int SipLayer::skipSpaces(const char* s, int start, int len) - { - const char* begin = s + start; - const char* end = s + len; - const char* it = std::find_if(begin, end, [](unsigned char ch) - { - return ch != ' '; - }); - return static_cast(std::distance(s, it)); - } - // -------- Class SipRequestFirstLine ----------------- SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest)