Skip to content
60 changes: 60 additions & 0 deletions Packet++/header/SipLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "TextBasedProtocol.h"

#include <cstring>
#include <algorithm>

/// @file

/// @namespace pcpp
Expand Down Expand Up @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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
{
Expand Down
131 changes: 131 additions & 0 deletions Packet++/src/SipLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char*>(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<const char*>(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<int>(std::distance(start, it));
}

// -------- Class SipRequestFirstLine -----------------

SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest)
Expand Down Expand Up @@ -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<const char*>(memchr(data, ' ', dataLen));
if (firstSpace == nullptr)
{
PCPP_LOG_DEBUG("No space found after METHOD in SIP request line");
return "";
}

const char* lastSpace = static_cast<const char*>(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<size_t>(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<const char*>(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<size_t>(m_UriOffset))
Expand Down
5 changes: 4 additions & 1 deletion Packet++/src/UdpLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading