diff --git a/include/tcp.h b/include/tcp.h index c091a66..2668d44 100644 --- a/include/tcp.h +++ b/include/tcp.h @@ -66,10 +66,23 @@ namespace Tins { */ enum Options { - EOL = 0, - NOP = 1, - MSS = 2, - TSOPT = 8 + EOL = 0, + NOP = 1, + MSS = 2, + WSCALE = 3, + SACK_OK = 4, + SACK = 5, + TSOPT = 8, + ALTCHK = 14 + }; + + /** + * \brief Alternate checksum enum. + */ + enum AltChecksums { + CHK_TCP, + CHK_8FLETCHER, + CHK_16FLETCHER }; /** @@ -82,8 +95,8 @@ namespace Tins { * \param olength The option's data length. * \param odata The option's data(if any). */ - TCPOption(uint8_t okind = 0, uint8_t olength = 0, uint8_t *odata = 0) : - kind(okind), length(olength), data(odata) { } + TCPOption(uint8_t opt = 0, uint8_t olength = 0, uint8_t *ovalue = 0) : + option(opt), length(olength), value(ovalue) { } /** * \brief Writes the option into a buffer. @@ -92,8 +105,8 @@ namespace Tins { */ uint8_t *write(uint8_t *buffer); - uint8_t kind, length; - uint8_t *data; + uint8_t option, length; + uint8_t *value; }; /** @@ -275,19 +288,87 @@ namespace Tins { void payload(uint8_t *new_payload, uint32_t new_payload_size); /** - * \brief Set the maximum segment size. + * \brief Add a maximum segment size option. * * \param value The new maximum segment size. */ - void set_mss(uint16_t value); + void add_mss_option(uint16_t value); /** - * \brief Set the timestamp. + * \brief Searchs for a maximum segment size option. + * \param value A pointer in which the option's value will be stored. + * \return True if the option was found, false otherwise. + */ + bool search_mss_option(uint16_t *value); + + /** + * \brief Add a window scale option. + * + * \param value The new window scale. + */ + void add_winscale_option(uint8_t value); + + /** + * \brief Searchs for a window scale option. + * \param value A pointer in which the option's value will be stored. + * \return True if the option was found, false otherwise. + */ + bool search_winscale_option(uint8_t *value); + + /** + * \brief Add a sack permitted option. + */ + void add_sack_permitted_option(); + + /** + * \brief Searchs for a sack permitted option. + * \return True if the option was found, false otherwise. + */ + bool search_sack_permitted_option(); + + /** + * \brief Add a sack option. + * + * \param value The new window scale. + */ + void add_sack_option(const std::list &edges); + + /** + * \brief Searchs for a sack option. + * \param value A pointer in which the option's value will be stored. + * \return True if the option was found, false otherwise. + */ + bool search_sack_option(std::list *edges); + + /** + * \brief Add a timestamp option. * * \param value The current value of the timestamp clock. * \param reply The echo reply field. */ - void set_timestamp(uint32_t value, uint32_t reply); + void add_timestamp_option(uint32_t value, uint32_t reply); + + /** + * \brief Searchs for a timestamp option. + * \param value A pointer in which the option's value will be stored. + * \param reply A pointer in which the option's reply value will be stored. + * \return True if the option was found, false otherwise. + */ + bool search_timestamp_option(uint32_t *value, uint32_t *reply); + + /** + * \brief Add a alternate checksum option. + * + * \param value The new alternate checksum scale. + */ + void add_altchecksum_option(AltChecksums value); + + /** + * \brief Searchs for a alternate checksum option. + * \param value A pointer in which the option's value will be stored. + * \return True if the option was found, false otherwise. + */ + bool search_altchecksum_option(uint8_t *value); /** * \brief Set a TCP flag value. @@ -323,6 +404,13 @@ namespace Tins { */ PDUType pdu_type() const { return PDU::TCP; } + /** + * \brief Searchs for an option that matchs the given flag. + * \param opt_flag The flag to be searched. + * \return A pointer to the option, or 0 if it was not found. + */ + const TCPOption *search_option(Options opt) const; + /** * \brief Clones this PDU. * @@ -369,6 +457,15 @@ namespace Tins { void copy_fields(const TCP *other); + template bool generic_search(Options opt, T *value) { + const TCPOption *option = search_option(opt); + if(option && option->length == sizeof(T)) { + *value = *(T*)option->value; + return true; + } + return false; + } + void cleanup(); /** \brief Serialices this TCP PDU. * \param buffer The buffer in which the PDU will be serialized. diff --git a/src/tcp.cpp b/src/tcp.cpp index b0768e1..ce6b9d9 100644 --- a/src/tcp.cpp +++ b/src/tcp.cpp @@ -65,19 +65,17 @@ Tins::TCP::TCP(const uint8_t *buffer, uint32_t total_sz) : PDU(Constants::IP::PR uint8_t args[2] = {0}; while(index < header_end) { for(unsigned i(0); i < 2 && args[0] != NOP; ++i) { - args[i] = buffer[index++]; if(index == header_end) - throw std::runtime_error("Not enought size for a TCP header in the buffer."); + throw std::runtime_error("Not enough size for a TCP header in the buffer."); + args[i] = buffer[index++]; } // We don't want to store NOPs and EOLs if(args[0] != NOP && args[0] != EOL) { args[1] -= (sizeof(uint8_t) << 1); - if(args[1]) { - // Not enough size for this option - if(header_end - index < args[1]) - throw std::runtime_error("Not enought size for a TCP header in the buffer."); - add_option((Options)args[0], args[1], buffer + index); - } + // Not enough size for this option + if(header_end - index < args[1]) + throw std::runtime_error("Not enough size for a TCP header in the buffer."); + add_option((Options)args[0], args[1], buffer + index); index += args[1]; } else if(args[0] == EOL) @@ -103,7 +101,7 @@ Tins::TCP::~TCP() { void Tins::TCP::cleanup() { for(std::list::iterator it = _options.begin(); it != _options.end(); ++it) - delete[] it->data; + delete[] it->value; _options.clear(); } @@ -143,16 +141,80 @@ void Tins::TCP::data_offset(uint8_t new_doff) { this->_tcp.doff = new_doff; } -void Tins::TCP::set_mss(uint16_t value) { +void Tins::TCP::add_mss_option(uint16_t value) { value = Utils::net_to_host_s(value); add_option(MSS, 2, (uint8_t*)&value); } -void Tins::TCP::set_timestamp(uint32_t value, uint32_t reply) { +bool Tins::TCP::search_mss_option(uint16_t *value) { + if(!generic_search(MSS, value)) + return false; + *value = Utils::net_to_host_s(*value); + return true; +} + +void Tins::TCP::add_winscale_option(uint8_t value) { + add_option(WSCALE, 1, &value); +} + +bool Tins::TCP::search_winscale_option(uint8_t *value) { + return generic_search(WSCALE, value); +} + +void Tins::TCP::add_sack_permitted_option() { + add_option(SACK_OK, 0, 0); +} + +bool Tins::TCP::search_sack_permitted_option() { + return search_option(SACK_OK); +} + +void Tins::TCP::add_sack_option(const std::list &edges) { + uint32_t *value = 0; + if(edges.size()) { + value = new uint32_t[edges.size()]; + uint32_t *ptr = value; + for(std::list::const_iterator it = edges.begin(); it != edges.end(); ++it) + *(ptr++) = Utils::net_to_host_l(*it); + } + add_option(SACK, (uint8_t)(sizeof(uint32_t) * edges.size()), (const uint8_t*)value); + delete[] value; +} + +bool Tins::TCP::search_sack_option(std::list *edges) { + const TCPOption *option = search_option(SACK); + if(!option || (option->length % sizeof(uint32_t)) != 0) + return false; + const uint32_t *ptr = (const uint32_t*)option->value; + const uint32_t *end = ptr + (option->length / sizeof(uint32_t)); + while(ptr < end) + edges->push_back(Utils::net_to_host_l(*(ptr++))); + return true; +} + +void Tins::TCP::add_timestamp_option(uint32_t value, uint32_t reply) { uint64_t buffer = ((uint64_t)Utils::net_to_host_l(reply) << 32) | Utils::net_to_host_l(value); add_option(TSOPT, 8, (uint8_t*)&buffer); } +bool Tins::TCP::search_timestamp_option(uint32_t *value, uint32_t *reply) { + const TCPOption *option = search_option(TSOPT); + if(!option || option->length != (sizeof(uint32_t) << 1)) + return false; + const uint32_t *ptr = (const uint32_t*)option->value; + *value = Utils::net_to_host_l(*(ptr++)); + *reply = Utils::net_to_host_l(*(ptr)); + return true; +} + +void Tins::TCP::add_altchecksum_option(AltChecksums value) { + add_option(ALTCHK, 1, (const uint8_t*)&value); +} + +bool Tins::TCP::search_altchecksum_option(uint8_t *value) { + return generic_search(ALTCHK, value); +} + uint8_t Tins::TCP::get_flag(Flags tcp_flag) { switch(tcp_flag) { case FIN: @@ -239,8 +301,8 @@ void Tins::TCP::write_serialization(uint8_t *buffer, uint32_t total_sz, const PD buffer = it->write(buffer); if(_options_size < _total_options_size) { - uint8_t padding = _total_options_size; - while(padding < _options_size) { + uint8_t padding = _options_size; + while(padding < _total_options_size) { *(buffer++) = 1; padding++; } @@ -258,19 +320,27 @@ void Tins::TCP::write_serialization(uint8_t *buffer, uint32_t total_sz, const PD _tcp.check = 0; } +const Tins::TCP::TCPOption *Tins::TCP::search_option(Options opt) const { + for(std::list::const_iterator it = _options.begin(); it != _options.end(); ++it) { + if(it->option == opt) + return &(*it); + } + return 0; +} + /* TCPOptions */ uint8_t *Tins::TCP::TCPOption::write(uint8_t *buffer) { - if(kind == 1) { - *buffer = kind; + if(option == 1) { + *buffer = option; return buffer + 1; } else { - buffer[0] = kind; + buffer[0] = option; buffer[1] = length + (sizeof(uint8_t) << 1); - if(data) - memcpy(buffer + 2, data, length); + if(value) + memcpy(buffer + 2, value, length); return buffer + buffer[1]; } } @@ -278,10 +348,10 @@ uint8_t *Tins::TCP::TCPOption::write(uint8_t *buffer) { void Tins::TCP::copy_fields(const TCP *other) { std::memcpy(&_tcp, &other->_tcp, sizeof(_tcp)); for(std::list::const_iterator it = other->_options.begin(); it != other->_options.end(); ++it) { - TCPOption opt(it->kind, it->length); - if(it->data) { - opt.data = new uint8_t[it->length]; - std::memcpy(opt.data, it->data, it->length); + TCPOption opt(it->option, it->length); + if(it->value) { + opt.value = new uint8_t[it->length]; + std::memcpy(opt.value, it->value, it->length); } _options.push_back(opt); } diff --git a/tests/src/tcp.cpp b/tests/src/tcp.cpp new file mode 100644 index 0000000..ae77b94 --- /dev/null +++ b/tests/src/tcp.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include +#include "tcp.h" +#include "utils.h" + +using namespace std; +using namespace Tins; + +class TCPTest : public testing::Test { +public: + static const uint8_t expected_packet[]; + + void test_equals(const TCP &tcp1, const TCP &tcp2); +}; + +const uint8_t TCPTest::expected_packet[] = {'\x7f', 'M', 'O', '\x1d', '\xf1', '\xda', '\xe5', +'F', '_', '\xae', '\xd1', '#', '\xd0', '\x02', 'q', '\xda', '\x00', '\x00', '\x1f', '\xae', +'\x02', '\x04', '\x98', '\xfa', '\x08', '\n', 'O', '\xd2', ':', '\xcb', '\x89', '\xfe', +'\x12', '4', '\x03', '\x03', 'z', '\x04', '\x02', '\x05', '\n', '\x00', '\x01', '\x02', +'\x03', '\x04', '\x05', '\x06', '\x07', '\x00', '\x00', '\x00'}; + + +TEST_F(TCPTest, DefaultConstructor) { + TCP tcp; + EXPECT_EQ(tcp.dport(), 0); + EXPECT_EQ(tcp.sport(), 0); + EXPECT_EQ(tcp.pdu_type(), PDU::TCP); +} + +TEST_F(TCPTest, CopyConstructor) { + TCP tcp1(0x6d1f, 0x78f2); + TCP tcp2(tcp1); + test_equals(tcp1, tcp2); +} + +TEST_F(TCPTest, CompleteConstructor) { + TCP tcp(0x6d1f, 0x78f2); + EXPECT_EQ(tcp.dport(), 0x6d1f); + EXPECT_EQ(tcp.sport(), 0x78f2); +} + +TEST_F(TCPTest, DPort) { + TCP tcp; + tcp.dport(0x5fad); + EXPECT_EQ(tcp.dport(), 0x5fad); +} + +TEST_F(TCPTest, SPort) { + TCP tcp; + tcp.sport(0x5fad); + EXPECT_EQ(tcp.sport(), 0x5fad); +} + +TEST_F(TCPTest, Seq) { + TCP tcp; + tcp.seq(0x5fad65fb); + EXPECT_EQ(tcp.seq(), 0x5fad65fb); +} + +TEST_F(TCPTest, AckSeq) { + TCP tcp; + tcp.ack_seq(0x5fad65fb); + EXPECT_EQ(tcp.ack_seq(), 0x5fad65fb); +} + +TEST_F(TCPTest, Window) { + TCP tcp; + tcp.window(0x5fad); + EXPECT_EQ(tcp.window(), 0x5fad); +} + +TEST_F(TCPTest, Check) { + TCP tcp; + tcp.check(0x5fad); + EXPECT_EQ(tcp.check(), 0x5fad); +} + +TEST_F(TCPTest, UrgPtr) { + TCP tcp; + tcp.urg_ptr(0x5fad); + EXPECT_EQ(tcp.urg_ptr(), 0x5fad); +} + +TEST_F(TCPTest, DataOffset) { + TCP tcp; + tcp.data_offset(0xe); + EXPECT_EQ(tcp.data_offset(), 0xe); +} + +TEST_F(TCPTest, SetFlag) { + TCP tcp; + tcp.set_flag(TCP::SYN, 1); + tcp.set_flag(TCP::FIN, 1); + + EXPECT_EQ(tcp.get_flag(TCP::SYN), 1); + EXPECT_EQ(tcp.get_flag(TCP::FIN), 1); + EXPECT_EQ(tcp.get_flag(TCP::RST), 0); + EXPECT_EQ(tcp.get_flag(TCP::PSH), 0); + EXPECT_EQ(tcp.get_flag(TCP::ACK), 0); + EXPECT_EQ(tcp.get_flag(TCP::URG), 0); + EXPECT_EQ(tcp.get_flag(TCP::ECE), 0); + EXPECT_EQ(tcp.get_flag(TCP::CWR), 0); +} + +TEST_F(TCPTest, MSS) { + TCP tcp; + uint16_t mss = 0x456f, found_mss; + tcp.add_mss_option(mss); + ASSERT_TRUE(tcp.search_mss_option(&found_mss)); + EXPECT_EQ(mss, found_mss); +} + +TEST_F(TCPTest, WindowScale) { + TCP tcp; + uint8_t scale = 0x4f, found_scale; + tcp.add_winscale_option(scale); + ASSERT_TRUE(tcp.search_winscale_option(&found_scale)); + EXPECT_EQ(scale, found_scale); +} + +TEST_F(TCPTest, SackPermitted) { + TCP tcp; + tcp.add_sack_permitted_option(); + ASSERT_TRUE(tcp.search_sack_permitted_option()); +} + +TEST_F(TCPTest, Sack) { + TCP tcp; + list edges, edges_found; + edges.push_back(0x13); + edges.push_back(0x63fa1d7a); + edges.push_back(0xff1c); + tcp.add_sack_option(edges); + ASSERT_TRUE(tcp.search_sack_option(&edges_found)); + ASSERT_EQ(edges.size(), edges_found.size()); + while(edges.size()) { + EXPECT_EQ(edges.front(), edges_found.front()); + edges.pop_front(); + edges_found.pop_front(); + } +} + +TEST_F(TCPTest, AlternateChecksum) { + TCP tcp; + uint8_t found; + tcp.add_altchecksum_option(TCP::CHK_16FLETCHER); + ASSERT_TRUE(tcp.search_altchecksum_option(&found)); + EXPECT_EQ(found, (uint8_t)TCP::CHK_16FLETCHER); +} + +TEST_F(TCPTest, Timestamp) { + TCP tcp; + uint32_t value = 0x456fa23d, found_value; + uint32_t reply = 0xfa12d345, found_reply; + tcp.add_timestamp_option(value, reply); + ASSERT_TRUE(tcp.search_timestamp_option(&found_value, &found_reply)); + EXPECT_EQ(value, found_value); + EXPECT_EQ(reply, found_reply); +} + +void TCPTest::test_equals(const TCP &tcp1, const TCP &tcp2) { + EXPECT_EQ(tcp1.dport(), tcp2.dport()); + EXPECT_EQ(tcp2.sport(), tcp2.sport()); + EXPECT_EQ(tcp1.seq(), tcp2.seq()); + EXPECT_EQ(tcp1.ack_seq(), tcp2.ack_seq()); + EXPECT_EQ(tcp1.window(), tcp2.window()); + EXPECT_EQ(tcp1.check(), tcp2.check()); + EXPECT_EQ(tcp1.urg_ptr(), tcp2.urg_ptr()); + EXPECT_EQ(tcp1.data_offset(), tcp2.data_offset()); +} + +TEST_F(TCPTest, ConstructorFromBuffer) { + TCP tcp1(expected_packet, sizeof(expected_packet)); + uint32_t value32, ovalue32; + uint16_t value16; + uint8_t value8; + + EXPECT_EQ(tcp1.dport(), 0x4f1d); + EXPECT_EQ(tcp1.sport(), 0x7f4d); + EXPECT_EQ(tcp1.seq(), 0xf1dae546); + EXPECT_EQ(tcp1.ack_seq(), 0x5faed123); + EXPECT_EQ(tcp1.window(), 0x71da); + EXPECT_EQ(tcp1.urg_ptr(), 0x1fae); + EXPECT_EQ(tcp1.data_offset(), 0xd); + + ASSERT_TRUE(tcp1.search_timestamp_option(&value32, &ovalue32)); + EXPECT_EQ(value32, 0x4fd23acb); + EXPECT_EQ(ovalue32, 0x89fe1234); + + EXPECT_TRUE(tcp1.search_sack_permitted_option()); + + ASSERT_TRUE(tcp1.search_winscale_option(&value8)); + EXPECT_EQ(value8, 0x7a); + + ASSERT_TRUE(tcp1.search_mss_option(&value16)); + EXPECT_EQ(value16, 0x98fa); + + list edges; + ASSERT_TRUE(tcp1.search_sack_option(&edges)); + ASSERT_EQ(edges.size(), 2); + EXPECT_EQ(edges.front(), 0x00010203); edges.pop_front(); + EXPECT_EQ(edges.front(), 0x04050607); + + uint32_t size; + uint8_t *buffer = tcp1.serialize(size); + + TCP tcp2(buffer, size); + test_equals(tcp1, tcp2); + delete[] buffer; +} +