diff --git a/Packet++/header/SipLayer.h b/Packet++/header/SipLayer.h index bd25e4cd83..c7ed934155 100644 --- a/Packet++/header/SipLayer.h +++ b/Packet++/header/SipLayer.h @@ -114,6 +114,28 @@ namespace pcpp return port == 5060 || port == 5061; } + /// A static factory method that attempts to create a SIP layer from existing packet raw data + /// The method first checks whether the source or destination port matches the SIP protocol. + /// @param[in] data A pointer to the raw data + /// @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 static factory method that attempts to create a SIP layer from existing packet raw data + /// This method does not check source or destination ports. Instead, it uses heuristics + /// to determine whether the data represents a SIP request or response. + /// @param[in] data A pointer to the raw data + /// @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) @@ -137,6 +159,16 @@ namespace pcpp { return true; } + + private: + enum class SipParseResult + { + Unknown = 0, + Request = 1, + Response = 2, + }; + + static SipParseResult detectSipMessageType(const uint8_t* data, size_t dataLen); }; class SipRequestFirstLine; @@ -498,6 +530,15 @@ namespace pcpp friend class SipRequestLayer; public: + /// A structure containing parsed components from a SIP request first line. + /// All string fields are empty if parsing fails + struct SipFirstLineData + { + std::string method; ///< The SIP method (e.g., INVITE, REGISTER, BYE) + std::string uri; ///< The Request-URI destination + std::string version; ///< The SIP protocol version (e.g., SIP/2.0) + }; + /// @return The SIP request method SipRequestLayer::SipMethod getMethod() const { @@ -531,6 +572,13 @@ namespace pcpp /// @return The parsed SIP method static SipRequestLayer::SipMethod parseMethod(const char* data, size_t dataLen); + /// A static method for parsing the complete SIP request first line from raw data + /// @param[in] data The raw data containing the SIP request line + /// @param[in] dataLen The raw data length + /// @return A pair where first indicates success/failure, and second contains the parsed data. + /// If parsing fails, first is false and second contains empty strings + static std::pair parseFirstLine(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 179ec8bff0..b870bbe6bc 100644 --- a/Packet++/src/SipLayer.cpp +++ b/Packet++/src/SipLayer.cpp @@ -12,6 +12,17 @@ namespace pcpp { + constexpr uint32_t pack4(const char* data, size_t len) + { + return ((len > 0 ? static_cast(data[0]) << 24 : 0) | + (len > 1 ? static_cast(data[1]) << 16 : 0) | + (len > 2 ? static_cast(data[2]) << 8 : 0) | (len > 3 ? static_cast(data[3]) : 0)); + } + + constexpr uint32_t operator""_packed4(const char* str, size_t len) + { + return pack4(str, len); + } const std::string SipMethodEnumToString[14] = { "INVITE", "ACK", "BYE", "CANCEL", "REGISTER", "PRACK", "OPTIONS", "SUBSCRIBE", "NOTIFY", "PUBLISH", @@ -103,6 +114,92 @@ namespace pcpp } } + SipLayer::SipParseResult SipLayer::detectSipMessageType(const uint8_t* data, size_t dataLen) + { + if (!data || dataLen < 3) + { + return SipLayer::SipParseResult::Unknown; + } + + uint32_t key = pack4(reinterpret_cast(data), dataLen); + + switch (key) + { + case "INVI"_packed4: // INVITE + case "ACK "_packed4: // ACK + case "BYE "_packed4: // BYE + case "CANC"_packed4: // CANCEL + case "REGI"_packed4: // REGISTER + case "PRAC"_packed4: // PRACK + case "OPTI"_packed4: // OPTIONS + case "SUBS"_packed4: // SUBSCRIBE + case "NOTI"_packed4: // NOTIFY + case "PUBL"_packed4: // PUBLISH + case "INFO"_packed4: // INFO + case "REFE"_packed4: // REFER + case "MESS"_packed4: // MESSAGE + case "UPDA"_packed4: // UPDATE + return SipLayer::SipParseResult::Request; + + case "SIP/"_packed4: + return SipLayer::SipParseResult::Response; + + default: + 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(data), dataLen) != + SipRequestLayer::SipMethodUnknown) + { + return new SipRequestLayer(data, dataLen, prevLayer, packet); + } + + if (SipResponseFirstLine::parseStatusCode(reinterpret_cast(data), dataLen) != + SipResponseLayer::SipStatusCodeUnknown && + !SipResponseFirstLine::parseVersion(reinterpret_cast(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) + { + SipLayer::SipParseResult sipParseResult = detectSipMessageType(data, dataLen); + + if (sipParseResult == SipLayer::SipParseResult::Unknown) + { + return nullptr; + } + + if (sipParseResult == SipLayer::SipParseResult::Request) + { + if (SipRequestFirstLine::parseFirstLine(reinterpret_cast(data), dataLen).first) + { + return new SipRequestLayer(data, dataLen, prevLayer, packet); + } + return nullptr; + } + + if (SipResponseFirstLine::parseStatusCode(reinterpret_cast(data), dataLen) != + SipResponseLayer::SipStatusCodeUnknown && + !SipResponseFirstLine::parseVersion(reinterpret_cast(data), dataLen).empty()) + { + return new SipResponseLayer(data, dataLen, prevLayer, packet); + } + return nullptr; + } + // -------- Class SipRequestFirstLine ----------------- SipRequestFirstLine::SipRequestFirstLine(SipRequestLayer* sipRequest) : m_SipRequest(sipRequest) @@ -208,6 +305,84 @@ namespace pcpp return methodAdEnum->second; } + std::pair SipRequestFirstLine::parseFirstLine(const char* data, + size_t dataLen) + { + SipFirstLineData result = { "", "", "" }; + + if (data == nullptr || dataLen == 0) + { + PCPP_LOG_DEBUG("Empty data in SIP request line"); + return { false, result }; + } + + // Find first space (end of METHOD) + size_t firstSpaceIndex = 0; + while (firstSpaceIndex < dataLen && data[firstSpaceIndex] != ' ') + { + firstSpaceIndex++; + } + + if (firstSpaceIndex == 0 || firstSpaceIndex == dataLen) + { + PCPP_LOG_DEBUG("Invalid METHOD in SIP request line"); + return { false, result }; + } + + // Validate method exists in SipMethodStringToEnum + std::string methodStr{ data, firstSpaceIndex }; + if (SipMethodStringToEnum.find(methodStr) == SipMethodStringToEnum.end()) + { + PCPP_LOG_DEBUG("Unknown SIP method"); + return { false, result }; + } + + // Find second space (end of URI) + size_t secondSpaceIndex = firstSpaceIndex + 1; + while (secondSpaceIndex < dataLen && data[secondSpaceIndex] != ' ') + secondSpaceIndex++; + + if (secondSpaceIndex == dataLen) + { + PCPP_LOG_DEBUG("No space before version"); + return { false, result }; + } + + size_t uriLen = secondSpaceIndex - firstSpaceIndex - 1; + if (uriLen == 0) + { + PCPP_LOG_DEBUG("Empty URI"); + return { false, result }; + } + + // Find end of line + size_t lineEnd = secondSpaceIndex + 1; + while (lineEnd < dataLen && data[lineEnd] != '\r' && data[lineEnd] != '\n') + lineEnd++; + + // Minimum length for "SIP/x.y" + size_t versionLen = lineEnd - secondSpaceIndex - 1; + if (versionLen < 7) + { + PCPP_LOG_DEBUG("Version too short"); + return { false, result }; + } + + const char* versionStart = data + secondSpaceIndex + 1; + if (versionStart[0] != 'S' || versionStart[1] != 'I' || versionStart[2] != 'P' || versionStart[3] != '/') + { + PCPP_LOG_DEBUG("Invalid SIP version format"); + return { false, result }; + } + + // All validations passed + result.method = std::move(methodStr); + result.uri = std::string{ data + firstSpaceIndex + 1, uriLen }; + result.version = std::string{ versionStart, versionLen }; + + return { true, result }; + } + void SipRequestFirstLine::parseVersion() { if (m_SipRequest->getDataLen() < static_cast(m_UriOffset)) diff --git a/Packet++/src/UdpLayer.cpp b/Packet++/src/UdpLayer.cpp index d7cc1985c6..f32e7731bf 100644 --- a/Packet++/src/UdpLayer.cpp +++ b/Packet++/src/UdpLayer.cpp @@ -110,14 +110,11 @@ namespace pcpp 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); + if (!m_NextLayer) + { + constructNextLayer(udpData, udpDataLen, getAttachedPacket()); + } } else if ((RadiusLayer::isRadiusPort(portDst) || RadiusLayer::isRadiusPort(portSrc)) && RadiusLayer::isDataValid(udpData, udpDataLen)) @@ -152,8 +149,20 @@ 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()); + } } void UdpLayer::computeCalculateFields() diff --git a/Tests/Packet++Test/PacketExamples/sip_non_default_port.pcap b/Tests/Packet++Test/PacketExamples/sip_non_default_port.pcap new file mode 100644 index 0000000000..cd5e3ae70f Binary files /dev/null and b/Tests/Packet++Test/PacketExamples/sip_non_default_port.pcap differ diff --git a/Tests/Packet++Test/PacketExamples/sip_non_default_port1.dat b/Tests/Packet++Test/PacketExamples/sip_non_default_port1.dat new file mode 100644 index 0000000000..3c931ee2a6 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/sip_non_default_port1.dat @@ -0,0 +1 @@ +5056c2a5e282ac49c3b41e570800450003e60000400040118c1fc0a81444c0a81553d03dcc9c03d20000494e56495445207369703a31303036403139322e3136382e32312e38333a3532333830205349502f322e300d0a5669613a205349502f322e302f554450203139322e3136382e32302e36383a35333330393b72706f72743b6272616e63683d7a39684734624b506a39336161623839353831623034623239383762353561353332303331643731320d0a4d61782d466f7277617264733a2037300d0a46726f6d3a203c7369703a31303035403139322e3136382e32312e38333e3b7461673d61353062666662356464643334323665386638343865636537643064333839620d0a546f3a203c7369703a31303036403139322e3136382e32312e38333e0d0a436f6e746163743a203c7369703a31303035403139322e3136382e32302e36383a35333330393b6f623e0d0a43616c6c2d49443a2030633362616232636562653334656339393761383064616134333436623339330d0a435365713a20313230303320494e564954450d0a416c6c6f773a20505241434b2c20494e564954452c2041434b2c204259452c2043414e43454c2c205550444154452c20494e464f2c205355425343524942452c204e4f544946592c2052454645522c204d4553534147452c204f5054494f4e530d0a537570706f727465643a207265706c616365732c2031303072656c2c2074696d65722c206e6f72656665727375620d0a53657373696f6e2d457870697265733a20313830300d0a4d696e2d53453a2039300d0a557365722d4167656e743a204d6963726f5349502f332e32322e330d0a436f6e74656e742d547970653a206170706c69636174696f6e2f7364700d0a436f6e74656e742d4c656e6774683a2020203334320d0a0d0a763d300d0a6f3d2d2033393735343231383631203339373534323138363120494e20495034203139322e3136382e32302e36380d0a733d706a6d656469610d0a623d41533a38340d0a743d3020300d0a613d582d6e61743a300d0a6d3d617564696f2034303034205254502f41565020382030203130310d0a633d494e20495034203139322e3136382e32302e36380d0a623d544941533a36343030300d0a613d727463703a3430303520494e20495034203139322e3136382e32302e36380d0a613d73656e64726563760d0a613d7274706d61703a382050434d412f383030300d0a613d7274706d61703a302050434d552f383030300d0a613d7274706d61703a3130312074656c6570686f6e652d6576656e742f383030300d0a613d666d74703a31303120302d31360d0a613d737372633a31333038393936333420636e616d653a343938653434356430336530346262360d0a \ No newline at end of file diff --git a/Tests/Packet++Test/PacketExamples/sip_non_default_port2.dat b/Tests/Packet++Test/PacketExamples/sip_non_default_port2.dat new file mode 100644 index 0000000000..befc1710d4 --- /dev/null +++ b/Tests/Packet++Test/PacketExamples/sip_non_default_port2.dat @@ -0,0 +1 @@ +5056c2a5e282ac49c3b41e570800450003960000400040118c6fc0a81444c0a81553e0f2cc9c038200005349502f322e3020323030204f4b0d0a5669613a205349502f322e302f554450203139322e3136382e32312e38333a35323338303b72706f72743d35323338303b72656365697665643d3139322e3136382e32312e38333b6272616e63683d7a39684734624b396a6a676536483430356335610d0a43616c6c2d49443a2030633166623163352d353966352d313233662d316162302d3030353035366135383034390d0a46726f6d3a2022457874656e73696f6e203130303522203c7369703a31303035403139322e3136382e32312e38333e3b7461673d3963745174324e355a6d3546440d0a546f3a203c7369703a31303036403139322e3136382e32302e36383b6f623e3b7461673d35323065343635313963653934333532626566323630366161616436333333630d0a435365713a2031303836353634333820494e564954450d0a416c6c6f773a20505241434b2c20494e564954452c2041434b2c204259452c2043414e43454c2c205550444154452c20494e464f2c205355425343524942452c204e4f544946592c2052454645522c204d4553534147452c204f5054494f4e530d0a436f6e746163743a203c7369703a31303036403139322e3136382e32302e36383a35373538363b6f623e0d0a537570706f727465643a207265706c616365732c2031303072656c2c2074696d65722c206e6f72656665727375620d0a436f6e74656e742d547970653a206170706c69636174696f6e2f7364700d0a436f6e74656e742d4c656e6774683a2020203331390d0a0d0a763d300d0a6f3d2d2033393735343231383631203339373534323138363220494e20495034203139322e3136382e32302e36380d0a733d706a6d656469610d0a623d41533a38340d0a743d3020300d0a613d582d6e61743a300d0a6d3d617564696f2034303036205254502f4156502038203130310d0a633d494e20495034203139322e3136382e32302e36380d0a623d544941533a36343030300d0a613d727463703a3430303720494e20495034203139322e3136382e32302e36380d0a613d73656e64726563760d0a613d7274706d61703a382050434d412f383030300d0a613d7274706d61703a3130312074656c6570686f6e652d6576656e742f383030300d0a613d666d74703a31303120302d31360d0a613d737372633a3134393431323030333120636e616d653a313739363565373334373065373364390d0a \ No newline at end of file diff --git a/Tests/Packet++Test/TestDefinition.h b/Tests/Packet++Test/TestDefinition.h index 9248e028d4..c5c88b2707 100644 --- a/Tests/Packet++Test/TestDefinition.h +++ b/Tests/Packet++Test/TestDefinition.h @@ -181,6 +181,7 @@ PTF_TEST_CASE(Igmpv3ReportCreateAndEditTest); // Implemented in SipSdpTests.cpp PTF_TEST_CASE(SipRequestParseMethodTest); PTF_TEST_CASE(SipRequestLayerParsingTest); +PTF_TEST_CASE(SipDetectionByContentOnNonStandardPort); PTF_TEST_CASE(SipRequestLayerCreationTest); PTF_TEST_CASE(SipRequestLayerEditTest); PTF_TEST_CASE(SipResponseParseStatusCodeTest); diff --git a/Tests/Packet++Test/Tests/SipSdpTests.cpp b/Tests/Packet++Test/Tests/SipSdpTests.cpp index 697d6a8620..6ec1e73057 100644 --- a/Tests/Packet++Test/Tests/SipSdpTests.cpp +++ b/Tests/Packet++Test/Tests/SipSdpTests.cpp @@ -166,6 +166,39 @@ PTF_TEST_CASE(SipRequestLayerParsingTest) } // SipRequestLayerParsingTest +PTF_TEST_CASE(SipDetectionByContentOnNonStandardPort) +{ + timeval time; + gettimeofday(&time, nullptr); + + // Load SIP Request packet with non-standard ports: UDP src=53309, dst=52380 + auto rawPacket1 = createPacketFromHexResource("PacketExamples/sip_non_default_port1.dat"); + pcpp::Packet sipReqNonStandardPort(rawPacket1.get()); + + PTF_ASSERT_TRUE(sipReqNonStandardPort.isPacketOfType(pcpp::SIPRequest)); + + auto sipReqLayer = sipReqNonStandardPort.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sipReqLayer); + + PTF_ASSERT_EQUAL(sipReqLayer->getFirstLine()->getMethod(), pcpp::SipRequestLayer::SipINVITE, enum); + PTF_ASSERT_EQUAL(sipReqLayer->getFirstLine()->getUri(), "sip:1006@192.168.21.83:52380"); + PTF_ASSERT_EQUAL(sipReqLayer->getFirstLine()->getVersion(), "SIP/2.0"); + + // Load SIP Response packet with non-standard ports: UDP src=53309, dst=52380 + auto rawPacket2 = createPacketFromHexResource("PacketExamples/sip_non_default_port2.dat"); + pcpp::Packet sipResNonStandardPort(rawPacket2.get()); + + PTF_ASSERT_TRUE(sipResNonStandardPort.isPacketOfType(pcpp::SIPResponse)); + + auto sipRespLayer = sipResNonStandardPort.getLayerOfType(); + PTF_ASSERT_NOT_NULL(sipRespLayer); + + PTF_ASSERT_EQUAL(sipRespLayer->getFirstLine()->getStatusCode(), pcpp::SipResponseLayer::Sip200OK, enum); + PTF_ASSERT_EQUAL(sipRespLayer->getFirstLine()->getStatusCodeAsInt(), 200); + PTF_ASSERT_EQUAL(sipRespLayer->getFirstLine()->getStatusCodeString(), "OK"); + PTF_ASSERT_EQUAL(sipRespLayer->getFirstLine()->getVersion(), "SIP/2.0"); +} // SipDetectionByContentOnNonStandardPort + PTF_TEST_CASE(SipRequestLayerCreationTest) { auto rawPacketAndBuf1 = createPacketAndBufferFromHexResource("PacketExamples/sip_req1.dat"); diff --git a/Tests/Packet++Test/main.cpp b/Tests/Packet++Test/main.cpp index 5a6fd6cf1e..01eb4aa980 100644 --- a/Tests/Packet++Test/main.cpp +++ b/Tests/Packet++Test/main.cpp @@ -278,6 +278,7 @@ int main(int argc, char* argv[]) PTF_RUN_TEST(SipRequestParseMethodTest, "sip"); PTF_RUN_TEST(SipRequestLayerParsingTest, "sip"); + PTF_RUN_TEST(SipDetectionByContentOnNonStandardPort, "sip"); PTF_RUN_TEST(SipRequestLayerCreationTest, "sip"); PTF_RUN_TEST(SipRequestLayerEditTest, "sip"); PTF_RUN_TEST(SipResponseParseStatusCodeTest, "sip");