1
0
mirror of https://github.com/mfontanini/libtins synced 2026-01-23 02:35:57 +01:00

Add RadioTapParser class

This commit is contained in:
Matias Fontanini
2017-05-21 10:06:13 -07:00
parent d29296935e
commit b8e4c7248b
6 changed files with 467 additions and 0 deletions

View File

@@ -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

View File

@@ -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.
*

View File

@@ -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 <stdint.h>
#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<uint8_t, RadioTap> 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<uint8_t>& 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

View File

@@ -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

View File

@@ -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<uint8_t>& 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<RadioTap::PresentFlags>(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<const uint8_t*>(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<const FlagsUnion*>(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

View File

@@ -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<uint8_t> 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<uint64_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::FLAGS, parser.current_field());
EXPECT_EQ((uint32_t)RadioTap::FCS, parser.current_option().to<uint8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::RATE, parser.current_field());
EXPECT_EQ(12, parser.current_option().to<uint8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::DBM_SIGNAL, parser.current_field());
EXPECT_EQ(-38, parser.current_option().to<int8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::DBM_NOISE, parser.current_field());
EXPECT_EQ(-96, parser.current_option().to<int8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::ANTENNA, parser.current_field());
EXPECT_EQ(2, parser.current_option().to<uint8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::CHANNEL_PLUS,parser.current_field());
EXPECT_EQ(0x140U, parser.current_option().to<uint32_t>());
EXPECT_FALSE(parser.advance_field());
}
TEST_F(RadioTapTest, RadioTapParsingMultipleNamespaces) {
vector<uint8_t> 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<int8_t>());
EXPECT_TRUE(parser.advance_field());
EXPECT_EQ(RadioTap::ANTENNA, parser.current_field());
EXPECT_EQ(0, parser.current_option().to<uint8_t>());
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