mirror of
https://github.com/mfontanini/libtins
synced 2026-01-23 02:35:57 +01:00
Add RadioTapParser class
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
134
include/tins/utils/radiotap_parser.h
Normal file
134
include/tins/utils/radiotap_parser.h
Normal 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
|
||||
@@ -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
|
||||
|
||||
264
src/utils/radiotap_parser.cpp
Normal file
264
src/utils/radiotap_parser.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user