Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
f8b2d4d
add initial heuristic detection for SIP packets
sorooshm78 Nov 15, 2025
07ca6b7
add comment
sorooshm78 Nov 16, 2025
f4fba28
refactor move helper methods to private, keep public API minimal
sorooshm78 Nov 16, 2025
d18abd0
use function dissectSipHeuristic
sorooshm78 Nov 17, 2025
6c6acda
Remove unused #include <iostream>
sorooshm78 Nov 17, 2025
2f2cc66
move the implementation to SipLayer.cpp
sorooshm78 Nov 29, 2025
09cb3f9
Merge branch 'dev' into add-sip-heuristic
sorooshm78 Nov 29, 2025
8362e31
Merge branch 'dev' into add-sip-heuristic
sorooshm78 Dec 2, 2025
dfc161c
refactor: use SipRequestFirstLine and SipResponseFirstLine static par…
sorooshm78 Dec 2, 2025
9f5c52d
Remove unused helper functions
sorooshm78 Dec 2, 2025
29063ca
Merge branch 'dev' into add-sip-heuristic
sorooshm78 Dec 7, 2025
fdae169
Revert add-sip-heuristic to match dev
sorooshm78 Dec 7, 2025
86eca5f
Add heuristic SIP message type detection in dissectSipHeuristic
sorooshm78 Dec 10, 2025
0e57e98
Merge branch 'dev' into add-sip-heuristic
sorooshm78 Dec 10, 2025
718582b
Resolve conflict
sorooshm78 Dec 10, 2025
c36d86b
add parameter and return descriptions to SIP parsing functions
sorooshm78 Dec 10, 2025
605d700
fix(spelling): correct "heristic" → "heuristic"
sorooshm78 Dec 10, 2025
4e1ef25
remove unnecessary blank line in SipLayer heuristic check
sorooshm78 Dec 10, 2025
7b1f3e5
Change data parameter to const uint8_t* in dissectSipHeuristic
sorooshm78 Dec 11, 2025
e8c6e61
style: trim trailing whitespace (fix CI)
sorooshm78 Dec 11, 2025
140eab5
Fix doxygen fails for CI
sorooshm78 Dec 11, 2025
56aaa9a
style: apply clang-format for CI
sorooshm78 Dec 11, 2025
8ac29ed
refactor SIP layer detection logic
sorooshm78 Dec 13, 2025
82c4f1c
Fix SIP version parsing bug and correctly extract version from reques…
sorooshm78 Dec 13, 2025
e72a1bf
Rename lineEnd to firstLineEnd
sorooshm78 Dec 13, 2025
508c436
Oops, fix mistake
sorooshm78 Dec 14, 2025
3c04854
replace c-style cast with reinterpret_cast for pointer conversion
sorooshm78 Dec 16, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Packet++/header/SipLayer.h
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add tests for this additional logic?

Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ namespace pcpp
class SipLayer : public TextBasedProtocolMessage
{
public:
/// SIP parse result types
enum class SipParseResult
{
/// Unknown or invalid format
Unknown = 0,
/// The message is a SIP request
Request = 1,
/// The message is a SIP response
Response = 2,
};
Comment on lines +72 to +81
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be a private enum, no?

If we move it to be private, doxygen documentation is not required


/// The length of the body of many SIP response messages is determined by a SIP header field called
/// "Content-Length". This method parses this field, extracts its value and return it. If this field doesn't
/// exist 0 is returned
Expand Down Expand Up @@ -114,6 +125,31 @@ namespace pcpp
return port == 5060 || port == 5061;
}

/// A heuristic method that attempts to identify SIP packets without fully parsing them
/// @param[in] data A pointer to the raw packet data
/// @param[in] dataLen Size of the data in bytes
/// @return A SipParseResult indicating whether the data is a SIP Request, Response, or Unknown
static SipParseResult detectSipMessageType(const uint8_t* data, size_t dataLen);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: this can be a private static method.

If we move it to be private, doxygen documentation is not required


/// A constructor that creates the layer from an existing packet raw data
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This is not a constructor but a static method that tries to create a SIP layer from existing raw data
  2. I'd mention that this method checks the ports first, if they match the SIP protocol it tries to parse the first line, otherwise it retrurns nullptr

/// @param[in] data A pointer to the raw data (will be casted to @ref arphdr)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is incorrect: (will be casted to @ref arphdr)

/// @param[in] dataLen Size of the data in bytes
/// @param[in] prevLayer A pointer to the previous layer
/// @param[in] packet A pointer to the Packet instance where layer will be stored in
/// @param[in] srcPort Source port number to check
/// @param[in] dstPort Dest port number to check
/// @return A newly allocated SIP layer of type request or response, or nullptr if parsing fails
static SipLayer* parseSipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet,
uint16_t srcPort, uint16_t dstPort);

/// A constructor that creates the layer from an existing packet raw data
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: this is not a constructor but a static method that tries to identify a SIP layer. I'd mention it doesn't look at ports, but tries some heuristics to verify if it's indeed a SIP request or response

/// @param[in] data A pointer to the raw data (will be casted to @ref arphdr)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto This part is incorrect: (will be casted to @ref arphdr)

/// @param[in] dataLen Size of the data in bytes
/// @param[in] prevLayer A pointer to the previous layer
/// @param[in] packet A pointer to the Packet instance where layer will be stored in
/// @return A newly allocated SIP layer of type request or response, or nullptr if parsing fails
static SipLayer* parseSipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet);

protected:
SipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, ProtocolType protocol)
: TextBasedProtocolMessage(data, dataLen, prevLayer, packet, protocol)
Expand Down Expand Up @@ -531,6 +567,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
198 changes: 198 additions & 0 deletions Packet++/src/SipLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ namespace pcpp
{ "UPDATE", SipRequestLayer::SipMethod::SipUPDATE },
};

const std::unordered_map<std::string, SipRequestLayer::SipMethod> SipMethodShortMap{
{ "INV", SipRequestLayer::SipMethod::SipINVITE },
{ "ACK", SipRequestLayer::SipMethod::SipACK },
{ "BYE", SipRequestLayer::SipMethod::SipBYE },
{ "CAN", SipRequestLayer::SipMethod::SipCANCEL },
{ "REG", SipRequestLayer::SipMethod::SipREGISTER },
{ "PRA", SipRequestLayer::SipMethod::SipPRACK },
{ "OPT", SipRequestLayer::SipMethod::SipOPTIONS },
{ "SUB", SipRequestLayer::SipMethod::SipSUBSCRIBE },
{ "NOT", SipRequestLayer::SipMethod::SipNOTIFY },
{ "PUB", SipRequestLayer::SipMethod::SipPUBLISH },
{ "INF", SipRequestLayer::SipMethod::SipINFO },
{ "REF", SipRequestLayer::SipMethod::SipREFER },
{ "MES", SipRequestLayer::SipMethod::SipMESSAGE },
{ "UPD", SipRequestLayer::SipMethod::SipUPDATE },
};

// -------- Class SipLayer -----------------

int SipLayer::getContentLength() const
Expand Down Expand Up @@ -103,6 +120,85 @@ namespace pcpp
}
}

SipLayer::SipParseResult SipLayer::detectSipMessageType(const uint8_t* data, size_t dataLen)
{
if (!data || dataLen < 3)
{
return SipLayer::SipParseResult::Unknown;
}

if (data[0] == 'S' && data[1] == 'I' && data[2] == 'P')
{
return SipLayer::SipParseResult::Response;
}

auto it = SipMethodShortMap.find(std::string(data, data + 3));
if (it != SipMethodShortMap.end())
{
return SipLayer::SipParseResult::Request;
}
return SipLayer::SipParseResult::Unknown;
}

SipLayer* SipLayer::parseSipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet, uint16_t srcPort,
uint16_t dstPort)
{
if (!(SipLayer::isSipPort(srcPort) || SipLayer::isSipPort(dstPort)))
{
return nullptr;
}

if (SipRequestFirstLine::parseMethod(reinterpret_cast<char*>(data), dataLen) !=
SipRequestLayer::SipMethodUnknown)
{
return new SipRequestLayer(data, dataLen, prevLayer, packet);
}

if (SipResponseFirstLine::parseStatusCode(reinterpret_cast<char*>(data), dataLen) !=
SipResponseLayer::SipStatusCodeUnknown &&
SipResponseFirstLine::parseVersion(reinterpret_cast<char*>(data), dataLen) != "")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can check:

!SipResponseFirstLine::parseVersion(reinterpret_cast<char*>(data), dataLen).empty()

{
return new SipResponseLayer(data, dataLen, prevLayer, packet);
}

return nullptr;
}

SipLayer* SipLayer::parseSipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto: you can use empty() in this method instead of != ""

{
SipLayer::SipParseResult sipParseResult = detectSipMessageType(data, dataLen);

if (sipParseResult == SipLayer::SipParseResult::Unknown)
{
return nullptr;
}

if (sipParseResult == SipLayer::SipParseResult::Request)
{
if (SipRequestFirstLine::parseMethod(reinterpret_cast<char*>(data), dataLen) !=
SipRequestLayer::SipMethodUnknown &&
SipRequestFirstLine::parseUri(reinterpret_cast<char*>(data), dataLen) != "" &&
SipRequestFirstLine::parseVersion(reinterpret_cast<char*>(data), dataLen) != "")
Comment on lines +180 to +181
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can do something more efficient which is to try to parse both URI and version in the same method: go over the first line, find a space, then make sure the URI has length of 1 or more, search for another space and then verify there is SIP/x.y and end-of-line.

Instead of separate SipRequestFirstLine::parseUri and SipRequestFirstLine::parseVersion we can have one version: SipRequestFirstLine::isValidData that can return a bool instead of a string

{
return new SipRequestLayer(data, dataLen, prevLayer, packet);
}
return nullptr;
}

if (sipParseResult == SipLayer::SipParseResult::Response)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement is not needed. If we got here this statement must be true

{
if (SipResponseFirstLine::parseStatusCode(reinterpret_cast<char*>(data), dataLen) !=
SipResponseLayer::SipStatusCodeUnknown &&
SipResponseFirstLine::parseVersion(reinterpret_cast<char*>(data), dataLen) != "")
{
return new SipResponseLayer(data, dataLen, prevLayer, packet);
}
return nullptr;
}

return nullptr;
}

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

SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest)
Expand Down Expand Up @@ -208,6 +304,108 @@ 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 "";
}

// Find the position of the first space (end of METHOD)
size_t firstSpaceIndex = 0;
while (firstSpaceIndex < dataLen && data[firstSpaceIndex] != ' ')
{
firstSpaceIndex++;
}

if (firstSpaceIndex == dataLen || firstSpaceIndex == 0)
{
PCPP_LOG_DEBUG("No space found after METHOD in SIP request line");
return "";
}

// URI starts right after the first space
const char* uriStart = data + firstSpaceIndex + 1;

// Continue searching from after the first space to find the second space
size_t secondSpaceIndex = firstSpaceIndex + 1;
while (secondSpaceIndex < dataLen && data[secondSpaceIndex] != ' ')
{
secondSpaceIndex++;
}

if (secondSpaceIndex == dataLen)
{
PCPP_LOG_DEBUG("Couldn't find space before SIP version in SIP request line");
return "";
}

size_t uriLen = static_cast<size_t>(secondSpaceIndex - (firstSpaceIndex + 1));

if (uriLen == 0)
{
PCPP_LOG_DEBUG("URI part is empty in SIP request line");
return "";
}

return std::string(uriStart, uriLen);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

}

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 "";
}

size_t firstLineEnd = 0;
while (firstLineEnd < dataLen && data[firstLineEnd] != '\n' && data[firstLineEnd] != '\r')
{
firstLineEnd++;
}

if (firstLineEnd == 0)
{
PCPP_LOG_DEBUG("Empty request line");
return "";
}

// Find the last space by searching from the end manually
size_t lastSpaceIndex = firstLineEnd;
while (lastSpaceIndex > 0 && data[lastSpaceIndex - 1] != ' ')
{
lastSpaceIndex--;
}

if (lastSpaceIndex == 0 || lastSpaceIndex == firstLineEnd)
{
PCPP_LOG_DEBUG("No space found before SIP version in request line");
return "";
}

// Version starts right after the last space
const char* versionStart = data + lastSpaceIndex;
size_t versionLen = firstLineEnd - lastSpaceIndex;

// Minimum length for "SIP/x.y"
if (versionLen < 7)
{
PCPP_LOG_DEBUG("Version part too short");
return "";
}

// Basic validation: must start with "SIP/"
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);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: you can use return {versionStart, versionLen};

}

void SipRequestFirstLine::parseVersion()
{
if (m_SipRequest->getDataLen() < static_cast<size_t>(m_UriOffset))
Expand Down
21 changes: 10 additions & 11 deletions Packet++/src/UdpLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,7 @@ namespace pcpp
(DnsLayer::isDnsPort(portDst) || DnsLayer::isDnsPort(portSrc)))
m_NextLayer = new DnsLayer(udpData, udpDataLen, this, getAttachedPacket());
else if (SipLayer::isSipPort(portDst) || SipLayer::isSipPort(portSrc))
{
if (SipRequestFirstLine::parseMethod((char*)udpData, udpDataLen) != SipRequestLayer::SipMethodUnknown)
m_NextLayer = new SipRequestLayer(udpData, udpDataLen, this, getAttachedPacket());
else if (SipResponseFirstLine::parseStatusCode((char*)udpData, udpDataLen) !=
SipResponseLayer::SipStatusCodeUnknown &&
SipResponseFirstLine::parseVersion((char*)udpData, udpDataLen) != "")
m_NextLayer = new SipResponseLayer(udpData, udpDataLen, this, getAttachedPacket());
else
m_NextLayer = new PayloadLayer(udpData, udpDataLen, this, getAttachedPacket());
}
m_NextLayer = SipLayer::parseSipLayer(udpData, udpDataLen, this, getAttachedPacket(), portSrc, portDst);
else if ((RadiusLayer::isRadiusPort(portDst) || RadiusLayer::isRadiusPort(portSrc)) &&
RadiusLayer::isDataValid(udpData, udpDataLen))
m_NextLayer = new RadiusLayer(udpData, udpDataLen, this, getAttachedPacket());
Expand Down Expand Up @@ -152,7 +143,15 @@ namespace pcpp
if (!m_NextLayer)
m_NextLayer = new PayloadLayer(udpData, udpDataLen, this, getAttachedPacket());
}
else

// If a valid layer was found, return immediately
if (m_NextLayer)
return;

// Here, heuristics for all protocols should be invoked to determine the correct layer
m_NextLayer = SipLayer::parseSipLayer(udpData, udpDataLen, this, getAttachedPacket());

if (!m_NextLayer)
m_NextLayer = new PayloadLayer(udpData, udpDataLen, this, getAttachedPacket());
}

Expand Down
Loading