diff --git a/include/tins/icmp.h b/include/tins/icmp.h index 69808b2..a5f18b2 100644 --- a/include/tins/icmp.h +++ b/include/tins/icmp.h @@ -46,6 +46,7 @@ #include "pdu.h" #include "endianness.h" #include "ip_address.h" +#include "icmp_extension.h" namespace Tins { @@ -297,12 +298,19 @@ namespace Tins { return address_type(Endian::be_to_host(_icmp.un.gateway)); } - /** - * \brief Getter for the pointer field. - * - * \return Returns the pointer field value. - */ - uint8_t pointer() const { return this->_icmp.un.pointer; } + /** + * \brief Getter for the pointer field. + * + * \return Returns the pointer field value. + */ + uint8_t pointer() const { return this->_icmp.un.rfc4884.pointer; } + + /** + * \brief Getter for the length field. + * + * \return Returns the length field value. + */ + uint8_t length() const { return this->_icmp.un.rfc4884.length; } /** * \brief Getter for the mtu field. @@ -358,6 +366,8 @@ namespace Tins { */ bool matches_response(const uint8_t *ptr, uint32_t total_sz) const; + const ICMPExtensionsStructure& extensions() { return extensions_; } + /** * \brief Getter for the PDU's type. * @@ -372,6 +382,8 @@ namespace Tins { return new ICMP(*this); } private: + static const uint32_t EXTENSION_PAYLOAD_LIMIT; + TINS_BEGIN_PACK struct icmphdr { uint8_t type; @@ -387,7 +399,11 @@ namespace Tins { uint16_t unused; uint16_t mtu; } frag; - uint8_t pointer; + struct { + uint8_t pointer; + uint8_t length; + uint16_t unused; + } rfc4884; } un; } TINS_END_PACK; @@ -400,8 +416,11 @@ namespace Tins { */ void write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *parent); + void try_parse_extensions(const uint8_t* buffer, uint32_t& total_sz); + icmphdr _icmp; uint32_t _orig_timestamp_or_address_mask, _recv_timestamp, _trans_timestamp; + ICMPExtensionsStructure extensions_; }; } diff --git a/src/icmp.cpp b/src/icmp.cpp index 43145ec..12292b9 100644 --- a/src/icmp.cpp +++ b/src/icmp.cpp @@ -41,6 +41,9 @@ #include "icmp.h" namespace Tins { + +const uint32_t ICMP::EXTENSION_PAYLOAD_LIMIT = 128; + ICMP::ICMP(Flags flag) : _orig_timestamp_or_address_mask(), _recv_timestamp(), _trans_timestamp() { @@ -76,8 +79,11 @@ ICMP::ICMP(const uint8_t *buffer, uint32_t total_sz) total_sz -= sizeof(uint32_t); buffer += sizeof(uint32_t); } - if(total_sz) + // Attempt to parse ICMP extensions + try_parse_extensions(buffer, total_sz); + if (total_sz) { inner_pdu(new RawPDU(buffer, total_sz)); + } } void ICMP::code(uint8_t new_code) { @@ -109,7 +115,7 @@ void ICMP::mtu(uint16_t new_mtu) { } void ICMP::pointer(uint8_t new_pointer) { - _icmp.un.pointer = new_pointer; + _icmp.un.rfc4884.pointer = new_pointer; } void ICMP::original_timestamp(uint32_t new_timestamp) { @@ -222,6 +228,38 @@ void ICMP::write_serialization(uint8_t *buffer, uint32_t total_sz, const PDU *) memcpy(buffer + 2, &_icmp.check, sizeof(uint16_t)); } +void ICMP::try_parse_extensions(const uint8_t* buffer, uint32_t& total_sz) { + if (total_sz == 0) { + return; + } + // Check if this is one of the types defined in RFC 4884 + if (type() == DEST_UNREACHABLE || type() == TIME_EXCEEDED || type() == PARAM_PROBLEM) { + uint32_t actual_length = length() * sizeof(uint32_t); + // Check if we actually have this amount of data and whether it's more than + // the minimum encapsulated packet size + const uint8_t* extensions_ptr; + uint32_t extensions_size; + if (actual_length < total_sz && actual_length >= EXTENSION_PAYLOAD_LIMIT) { + extensions_ptr = buffer + actual_length; + extensions_size = total_sz - actual_length; + } + else if (total_sz > EXTENSION_PAYLOAD_LIMIT) { + // This packet might be non-rfc compliant. In that case the length + // field can contain garbage. + extensions_ptr = buffer + EXTENSION_PAYLOAD_LIMIT; + extensions_size = total_sz - EXTENSION_PAYLOAD_LIMIT; + } + else { + // No more special cases, this doesn't have extensions + return; + } + if (ICMPExtensionsStructure::validate_extensions(extensions_ptr, extensions_size)) { + extensions_ = ICMPExtensionsStructure(extensions_ptr, extensions_size); + total_sz -= extensions_size; + } + } +} + bool ICMP::matches_response(const uint8_t *ptr, uint32_t total_sz) const { if(total_sz < sizeof(icmphdr)) return false; diff --git a/tests/src/icmp.cpp b/tests/src/icmp.cpp index 9613532..b09668a 100644 --- a/tests/src/icmp.cpp +++ b/tests/src/icmp.cpp @@ -6,6 +6,7 @@ #include "ip.h" #include "ethernetII.h" #include "utils.h" +#include "rawpdu.h" using namespace std; using namespace Tins; @@ -286,3 +287,22 @@ TEST_F(ICMPTest, ConstructorFromBuffer) { test_equals(icmp1, icmp2); } } + +TEST_F(ICMPTest, ExtensionsParsingWithoutALengthField) { + const uint8_t packet[] = { 11, 0, 205, 4, 0, 0, 0, 0, 69, 0, 0, 40, 165, 76, 0, 0, 1, 17, 247, 111, 12, 4, 4, 4, 12, 1, 1, 1, 165, 75, 130, 155, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 197, 95, 0, 8, 1, 1, 24, 150, 1, 1 }; + const uint8_t encapsulated[] = { 69, 0, 0, 40, 165, 76, 0, 0, 1, 17, 247, 111, 12, 4, 4, 4, 12, 1, 1, 1, 165, 75, 130, 155, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + const uint8_t ext[] = { 0, 8, 1, 1, 24, 150, 1, 1 }; + ICMP icmp(packet, sizeof(packet)); + ICMPExtensionsStructure extensions = icmp.extensions(); + ASSERT_EQ(1, extensions.extensions().size()); + EXPECT_EQ( + ICMPExtension::payload_type(ext, ext + sizeof(ext)), + extensions.extensions().begin()->serialize() + ); + const RawPDU* raw = icmp.find_pdu(); + ASSERT_TRUE(raw != 0); + EXPECT_EQ( + RawPDU::payload_type(encapsulated, encapsulated + sizeof(encapsulated)), + raw->payload() + ); +}