diff --git a/CMakeLists.txt b/CMakeLists.txt index 29d7d2c..91237ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -291,6 +291,7 @@ IF(LIBTINS_BUILD_TESTS) BINARY_DIR ${GOOGLETEST_BINARY_DIR} CMAKE_CACHE_ARGS "-DBUILD_GTEST:bool=ON" "-DBUILD_GMOCK:bool=OFF" "-Dgtest_force_shared_crt:bool=ON" + "-DCMAKE_CXX_COMPILER:path=${CMAKE_CXX_COMPILER}" INSTALL_COMMAND "" ) # Make sure we build googletest before anything else diff --git a/include/tins/radiotap.h b/include/tins/radiotap.h index 0855146..14cf6e9 100644 --- a/include/tins/radiotap.h +++ b/include/tins/radiotap.h @@ -54,6 +54,11 @@ public: */ static const PDU::PDUType pdu_flag = PDU::RADIOTAP; + /** + * RadioTap is little endian + */ + static const endian_type endianness = LE; + /** * \brief Enumeration of the different channel type flags. * diff --git a/include/tins/utils/radiotap_parser.h b/include/tins/utils/radiotap_parser.h new file mode 100644 index 0000000..05b0026 --- /dev/null +++ b/include/tins/utils/radiotap_parser.h @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2017, Matias Fontanini + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef TINS_RADIOTAP_PARSER_H +#define TINS_RADIOTAP_PARSER_H + +#include +#include "macros.h" +#include "radiotap.h" +#include "pdu_option.h" + +namespace Tins { +namespace Utils { + +struct RadioTapFlags; + +/** + * \brief Allows parsing RadioTap options + * + * RadioTap is a somehow tricky protocol to be parsed, as it has ordered flags, + * alignment between options, etc. This class allows parsing options in a RadioTap + * header without much trouble. + */ +class RadioTapParser { +public: + /** + * Represents the RadioTap namespace currently being parsed + */ + enum NamespaceType { + RADIOTAP_NS, + VENDOR_NS, + UNKNOWN_NS + }; + + typedef PDUOption option; + + /** + * The RadioTap header + */ + TINS_BEGIN_PACK + struct RadiotapHeader { + #if TINS_IS_LITTLE_ENDIAN + uint8_t version; + uint8_t padding; + #else + uint8_t padding; + uint8_t version; + #endif // TINS_IS_LITTLE_ENDIAN + uint16_t length; + uint32_t flags; + } TINS_END_PACK; + + /** + * \brief Constructs a RadioTap parser around a payload + * + * Note that the payload is not copied, hence it must be kept in + * scope while the parser is still being used. + * + * The buffer should contain an entire RadioTap header, with optionally + * extra data at the end, which will be ignored. + * + * \param buffer The buffer to be parsed + */ + RadioTapParser(const std::vector& buffer); + + /** + * Gets the current namespace being parsed + */ + NamespaceType current_namespace() const; + + /** + * Gets the current field being parsed + */ + RadioTap::PresentFlags current_field() const; + + /** + * Gets the option the parsed is currently pointing at + */ + option current_option(); + + /** + * \brief Advances to the next option + * + * If there's a namespace change, this will handle that as well. + * + * \returns true iff advancing was successfull (e.g. false if we reached + * the end of the header) + */ + bool advance_field(); +private: + const uint8_t* find_options_start() const; + bool advance_to_next_field(bool start_from_zero); + bool advance_to_next_namespace(); + const RadioTapFlags* get_flags_ptr() const; + bool is_field_set(uint32_t bit, const RadioTapFlags* flags) const; + + const uint8_t* start_; + const uint8_t* end_; + const uint8_t* current_ptr_; + NamespaceType current_namespace_; + uint32_t current_bit_; + uint32_t namespace_index_; +}; + +} // Utils +} // Tins + +#endif // TINS_RADIOTAP_PARSER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d2dbcb4..8068b7c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,7 @@ set(SOURCES dot11/dot11_control.cpp utils/checksum_utils.cpp utils/frequency_utils.cpp + utils/radiotap_parser.cpp utils/routing_utils.cpp utils/resolve_utils.cpp utils/pdu_utils.cpp diff --git a/src/utils/radiotap_parser.cpp b/src/utils/radiotap_parser.cpp new file mode 100644 index 0000000..1aed722 --- /dev/null +++ b/src/utils/radiotap_parser.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2017, Matias Fontanini + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "utils/radiotap_parser.h" +#include "exceptions.h" + +using std::vector; + +namespace Tins { +namespace Utils { + +struct FieldMetadata { + uint32_t size; + uint32_t alignment; +}; + +static const FieldMetadata RADIOTAP_METADATA[] = { + { 8, 8 }, /// TSFT + { 1, 1 }, // FLAGS + { 1, 1 }, // RATE + { 4, 2 }, // CHANNEL + { 2, 2 }, // FHSS + { 1, 1 }, // DBM_SIGNAL + { 1, 1 }, // DBM_NOISE + { 2, 2 }, // LOCK_QUALITY + { 2, 2 }, // TX_ATTENUATION + { 2, 2 }, // DB_TX_ATTENUATION + { 1, 1 }, // DBM_TX_ATTENUATION + { 1, 1 }, // ANTENNA + { 1, 1 }, // DB_SIGNAL + { 1, 1 }, // DB_NOISE + { 2, 2 }, // RX_FLAGS + { 2, 2 }, // TX_FLAGS + { 1, 1 }, // RTS_RETRIES + { 1, 1 }, // DATA_RETRIES + { 4, 4 }, // CHANNEL_PLUS + { 3, 1 }, // MCS +}; + +static const uint64_t BIT_LIMIT = sizeof(RADIOTAP_METADATA) / sizeof(FieldMetadata) + 1; + +#if TINS_IS_LITTLE_ENDIAN +TINS_BEGIN_PACK +struct RadioTapFlags { + uint32_t + tsft:1, + flags:1, + rate:1, + channel:1, + fhss:1, + dbm_signal:1, + dbm_noise:1, + lock_quality:1, + + tx_attenuation:1, + db_tx_attenuation:1, + dbm_tx_power:1, + antenna:1, + db_signal:1, + db_noise:1, + rx_flags:1, + tx_flags:1, + + reserved1:1, + data_retries:1, + channel_plus:1, + mcs:1, + reserved2:4, + + reserved3:7, + ext:1; +} TINS_END_PACK; +#else +TINS_BEGIN_PACK +struct RadioTapFlags { + uint32_t + lock_quality:1, + dbm_noise:1, + dbm_signal:1, + fhss:1, + channel:1, + rate:1, + flags:1, + tsft:1, + + tx_flags:1, + rx_flags:1, + db_noise:1, + db_signal:1, + antenna:1, + dbm_tx_power:1, + db_tx_attenuation:1, + tx_attenuation:1, + + reserved2:4, + mcs:1, + channel_plus:1, + data_retries:1, + reserved1:1, + + ext:1, + reserved3:7; +} TINS_END_PACK; +#endif + +void align_buffer(const uint8_t* buffer_start, const uint8_t*& buffer, uint32_t size, size_t n) { + uint32_t offset = ((buffer - buffer_start) % n); + if (offset) { + offset = n - offset; + if (offset > size) { + throw malformed_packet(); + } + buffer += offset; + } +} + +RadioTapParser::RadioTapParser(const vector& buffer) +: current_namespace_(RADIOTAP_NS), current_bit_(0), namespace_index_(0) { + if (TINS_UNLIKELY(buffer.empty())) { + throw malformed_packet(); + } + start_ = &*buffer.begin(); + end_ = start_ + buffer.size(); + const size_t max_size = end_ - start_; + if (TINS_UNLIKELY(max_size < sizeof(RadiotapHeader))) { + throw malformed_packet(); + } + const RadiotapHeader* radio = (const RadiotapHeader*)start_; + end_ = start_ + Endian::le_to_host(radio->length); + current_ptr_ = find_options_start(); + // Skip all fields and make this point to the first flags one + advance_to_next_field(true /* start from bit zero */); +} + +RadioTapParser::NamespaceType RadioTapParser::current_namespace() const { + return current_namespace_; +} + +RadioTap::PresentFlags RadioTapParser::current_field() const { + return static_cast(1 << current_bit_); +} + +RadioTapParser::option RadioTapParser::current_option() { + const uint32_t size = RADIOTAP_METADATA[current_bit_].size; + if (TINS_UNLIKELY(current_ptr_ + size > end_)) { + throw malformed_packet(); + } + return option(current_field(), size, current_ptr_); +} + +bool RadioTapParser::advance_field() { + // If we manage to advance the field, return true + if (advance_to_next_field(false /* keep going from current */)) { + return true; + } + // Otherwise, let's try advancing the namespace. If we fail, then we failed + if (!advance_to_next_namespace()) { + return false; + } + // Otherwise restart bit and try to find the first field in this namespace + current_bit_ = 0; + return advance_to_next_field(true /* start from 0*/); +} + +const uint8_t* RadioTapParser::find_options_start() const { + uint32_t total_sz = end_ - start_; + if (TINS_UNLIKELY(total_sz < sizeof(RadiotapHeader))) { + throw malformed_packet(); + } + // Skip fields before the flags one + const RadioTapFlags* flags = get_flags_ptr(); + while (flags->ext == 1) { + if (TINS_UNLIKELY(total_sz < sizeof(RadioTapFlags))) { + throw malformed_packet(); + } + ++flags; + total_sz -= sizeof(RadioTapFlags); + } + return reinterpret_cast(flags) + sizeof(RadioTapFlags); +} + +bool RadioTapParser::advance_to_next_field(bool start_from_zero) { + const RadioTapFlags* flags = get_flags_ptr(); + uint64_t bit; + if (start_from_zero) { + bit = 0; + } + else { + // Skip the payload + current_ptr_ += RADIOTAP_METADATA[current_bit_].size; + bit = current_bit_ + 1; + } + while (!is_field_set(1 << bit, flags) && bit < BIT_LIMIT) { + bit++; + } + if (bit < BIT_LIMIT) { + // Skip and align the buffer + align_buffer(start_, current_ptr_, end_ - start_, RADIOTAP_METADATA[bit].alignment); + current_bit_ = bit; + return true; + } + return false; +} + +bool RadioTapParser::advance_to_next_namespace() { + const uint32_t initial_index = namespace_index_; + while (get_flags_ptr()->ext == 1) { + const RadioTapFlags* flags = get_flags_ptr(); + if (is_field_set(29, flags)) { + current_namespace_ = RADIOTAP_NS; + } + else if (is_field_set(30, flags)) { + current_namespace_ = VENDOR_NS; + } + else { + current_namespace_ = UNKNOWN_NS; + } + namespace_index_++; + } + return initial_index != namespace_index_; +} + +bool RadioTapParser::is_field_set(uint32_t bit, const RadioTapFlags* flags) const { + TINS_BEGIN_PACK + union FlagsUnion { + RadioTapFlags* flags; + uint32_t flags_32; + } TINS_END_PACK; + const FlagsUnion* flags_union = reinterpret_cast(flags); + return (Endian::le_to_host(flags_union->flags_32) & bit) != 0; +} + +const RadioTapFlags* RadioTapParser::get_flags_ptr() const { + return (const RadioTapFlags*)(start_ + sizeof(uint32_t) * (namespace_index_ + 1)); +} + +} // Utils +} // Tins diff --git a/tests/src/radiotap_test.cpp b/tests/src/radiotap_test.cpp index 6618d36..2f215cb 100644 --- a/tests/src/radiotap_test.cpp +++ b/tests/src/radiotap_test.cpp @@ -12,9 +12,11 @@ #include "snap.h" #include "eapol.h" #include "utils.h" +#include "utils/radiotap_parser.h" using namespace std; using namespace Tins; +using Tins::Utils::RadioTapParser; class RadioTapTest : public testing::Test { public: @@ -400,4 +402,64 @@ TEST_F(RadioTapTest, SerializationWorksFine) { ); } +// RadioTapParser + +TEST_F(RadioTapTest, RadioTapParsing) { + vector buffer(expected_packet, expected_packet + sizeof(expected_packet)); + RadioTapParser parser(buffer); + EXPECT_EQ(RadioTap::TSTF, parser.current_field()); + EXPECT_EQ(616089172U, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::FLAGS, parser.current_field()); + EXPECT_EQ((uint32_t)RadioTap::FCS, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::RATE, parser.current_field()); + EXPECT_EQ(12, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::DBM_SIGNAL, parser.current_field()); + EXPECT_EQ(-38, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::DBM_NOISE, parser.current_field()); + EXPECT_EQ(-96, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::ANTENNA, parser.current_field()); + EXPECT_EQ(2, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::CHANNEL_PLUS,parser.current_field()); + EXPECT_EQ(0x140U, parser.current_option().to()); + + EXPECT_FALSE(parser.advance_field()); +} + +TEST_F(RadioTapTest, RadioTapParsingMultipleNamespaces) { + vector buffer(expected_packet4, expected_packet4 + sizeof(expected_packet4)); + RadioTapParser parser(buffer); + EXPECT_EQ(RadioTapParser::RADIOTAP_NS, parser.current_namespace()); + while (parser.current_field() != RadioTap::MCS) { + ASSERT_TRUE(parser.advance_field()); + } + // MCS is the last option in this namespace. After this, we should jump to the next one + EXPECT_TRUE(parser.advance_field()); + + // These are on the second namespace + EXPECT_EQ(RadioTap::DBM_SIGNAL, parser.current_field()); + EXPECT_EQ(-70, parser.current_option().to()); + EXPECT_TRUE(parser.advance_field()); + + EXPECT_EQ(RadioTap::ANTENNA, parser.current_field()); + EXPECT_EQ(0, parser.current_option().to()); + EXPECT_FALSE(parser.advance_field()); + + // Subsequent calls shouldn't change anything + EXPECT_FALSE(parser.advance_field()); + EXPECT_FALSE(parser.advance_field()); + EXPECT_EQ(RadioTapParser::RADIOTAP_NS, parser.current_namespace()); +} + #endif // TINS_HAVE_DOT11