-
Notifications
You must be signed in to change notification settings - Fork 728
Improve SIP packet detection using heuristic parsing #2024
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
f8b2d4d
07ca6b7
f4fba28
d18abd0
6c6acda
2f2cc66
09cb3f9
8362e31
dfc161c
9f5c52d
29063ca
fdae169
86eca5f
0e57e98
718582b
c36d86b
605d700
4e1ef25
7b1f3e5
e8c6e61
140eab5
56aaa9a
8ac29ed
82c4f1c
e72a1bf
508c436
3c04854
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
@@ -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); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| /// @param[in] data A pointer to the raw data (will be casted to @ref arphdr) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This part is incorrect: |
||
| /// @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 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto This part is incorrect: |
||
| /// @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) | ||
|
|
@@ -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 | ||
| { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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) != "") | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you can check: |
||
| { | ||
| return new SipResponseLayer(data, dataLen, prevLayer, packet); | ||
| } | ||
|
|
||
| return nullptr; | ||
| } | ||
|
|
||
| SipLayer* SipLayer::parseSipLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto: you can use |
||
| { | ||
| 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
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Instead of separate |
||
| { | ||
| return new SipRequestLayer(data, dataLen, prevLayer, packet); | ||
| } | ||
| return nullptr; | ||
| } | ||
|
|
||
| if (sipParseResult == SipLayer::SipParseResult::Response) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
| { | ||
| 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) | ||
|
|
@@ -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); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: you can use |
||
| } | ||
|
|
||
| void SipRequestFirstLine::parseVersion() | ||
| { | ||
| if (m_SipRequest->getDataLen() < static_cast<size_t>(m_UriOffset)) | ||
|
|
||
There was a problem hiding this comment.
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?