From d92767e4f68a670fae1190061c4af34105733277 Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Wed, 23 Mar 2016 15:21:42 +0100 Subject: [PATCH] [4268a] Rebased TokenPkt4 code --- src/lib/eval/tests/context_unittest.cc | 81 ++++++++++++++++++++++++++ src/lib/eval/tests/token_unittest.cc | 79 +++++++++++++++++++++++++ src/lib/eval/token.cc | 64 ++++++++++++++++++++ src/lib/eval/token.h | 55 +++++++++++++++++ 4 files changed, 279 insertions(+) diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc index 7465bb2df1..7e4ef26f2c 100644 --- a/src/lib/eval/tests/context_unittest.cc +++ b/src/lib/eval/tests/context_unittest.cc @@ -132,6 +132,51 @@ public: } } + /// @brief checks if the given token is Pkt4 of specified type + /// @param token token to be checked + /// @param type expected type of the Pkt4 field + void checkTokenPkt4(const TokenPtr& token, TokenPkt4::FieldType type) { + ASSERT_TRUE(token); + boost::shared_ptr pkt = + boost::dynamic_pointer_cast(token); + ASSERT_TRUE(pkt); + + EXPECT_EQ(type, pkt->getType()); + } + + /// @brief Test that verifies access to the DHCPv4 packet fields. + /// + /// This test attempts to parse the expression, will check if the number + /// of tokens is exactly as expected and then will try to verify if the + /// first token represents the expected field in DHCPv4 packet. + /// + /// @param expr expression to be parsed + /// @param exp_type expected field type to be parsed + /// @param exp_tokens expected number of tokens + void testPkt4Field(std::string expr, + TokenPkt4::FieldType exp_type, + int exp_tokens) { + EvalContext eval(Option::V4); + + // Parse the expression. + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() << "Exception thrown: " << ex.what(); + return; + } + + // Parsing should succeed and return a token. + EXPECT_TRUE(parsed_); + + // There should be exactly the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // Check that the first token is TokenPkt4 instance and has correct type. + checkTokenPkt4(eval.expression.at(0), exp_type); + } + /// @brief checks if the given token is a substring operator void checkTokenSubstring(const TokenPtr& token) { ASSERT_TRUE(token); @@ -430,6 +475,41 @@ TEST_F(EvalContextTest, relay4Error) { ":1.1-6: relay4 can only be used in DHCPv4."); } +// Tests whether chaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldChaddr) { + testPkt4Field("pkt4.mac == 0x000102030405", TokenPkt4::CHADDR, 3); +} + +// Tests whether hlen field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldHlen) { + testPkt4Field("pkt4.hlen == 0x6", TokenPkt4::HLEN, 3); +} + +// Tests whether htype field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldHtype) { + testPkt4Field("pkt4.htype == 0x1", TokenPkt4::HTYPE, 3); +} + +// Tests whether ciaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldCiaddr) { + testPkt4Field("pkt4.ciaddr == 192.0.2.1", TokenPkt4::CIADDR, 3); +} + +// Tests whether giaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldGiaddr) { + testPkt4Field("pkt4.giaddr == 192.0.2.1", TokenPkt4::GIADDR, 3); +} + +// Tests whether yiaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldYiaddr) { + testPkt4Field("pkt4.yiaddr == 192.0.2.1", TokenPkt4::YIADDR, 3); +} + +// Tests whether siaddr field in DHCPv4 can be accessed. +TEST_F(EvalContextTest, pkt4FieldSiaddr) { + testPkt4Field("pkt4.siaddr == 192.0.2.1", TokenPkt4::SIADDR, 3); +} + // Test parsing of logical operators TEST_F(EvalContextTest, logicalOps) { // option.exists @@ -588,6 +668,7 @@ TEST_F(EvalContextTest, scanErrors) { checkError("foo", ":1.1: Invalid character: f"); checkError(" bar", ":1.2: Invalid character: b"); checkError("relay[12].hex == 'foo'", ":1.1: Invalid character: r"); + checkError("pkt4.ziaddr", ":1.6: Invalid character: z"); } // Tests some scanner/parser error cases diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc index 3449281dbd..85a3811ba2 100644 --- a/src/lib/eval/tests/token_unittest.cc +++ b/src/lib/eval/tests/token_unittest.cc @@ -20,6 +20,7 @@ using namespace std; using namespace isc::dhcp; +using namespace isc::asiolink; namespace { @@ -603,6 +604,84 @@ TEST_F(TokenTest, relay4RAIOnly) { EXPECT_EQ("false", values_.top()); } +// Verifies if the DHCPv4 packet fields can be extracted. +TEST_F(TokenTest, pkt4Fields) { + pkt4_->setGiaddr(IOAddress("192.0.2.1")); + pkt4_->setCiaddr(IOAddress("192.0.2.2")); + pkt4_->setYiaddr(IOAddress("192.0.2.3")); + pkt4_->setSiaddr(IOAddress("192.0.2.4")); + + // We're setting hardware address to uncommon (7 bytes rather than 6 and + // hardware type 123) HW address. We'll use it in hlen and htype checks. + HWAddrPtr hw(new HWAddr(HWAddr::fromText("01:02:03:04:05:06:07", 123))); + pkt4_->setHWAddr(hw); + + // Check hardware address field. + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CHADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + uint8_t expected_hw[] = { 1, 2, 3, 4, 5, 6, 7 }; + ASSERT_EQ(7, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_hw, &values_.top()[0], 7)); + + // Check hlen value field. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(1, values_.top().size()); + EXPECT_EQ(7, static_cast(values_.top()[0])); + + // Check htype value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HTYPE))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + ASSERT_EQ(1, values_.top().size()); + EXPECT_EQ(123, static_cast(values_.top()[0])); + + // Check giaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::GIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + uint8_t expected_addr[] = { 192, 0, 2, 1 }; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check ciaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::CIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 2; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check yiaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::YIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 3; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check siaddr value. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::SIADDR))); + EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_)); + ASSERT_EQ(1, values_.size()); + expected_addr[3] = 4; + ASSERT_EQ(4, values_.top().size()); + EXPECT_EQ(0, memcmp(expected_addr, &values_.top()[0], 4)); + + // Check a DHCPv6 packet throws. + clearStack(); + ASSERT_NO_THROW(t_.reset(new TokenPkt4(TokenPkt4::HLEN))); + EXPECT_THROW(t_->evaluate(*pkt6_, values_), EvalTypeError); +} + // This test checks if a token representing an == operator is able to // compare two values (with incorrectly built stack). TEST_F(TokenTest, optionEqualInvalid) { diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc index fcbdc91057..095fd52380 100644 --- a/src/lib/eval/token.cc +++ b/src/lib/eval/token.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,69 @@ OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) { return (rai->getOption(option_code_)); } +void +TokenPkt4::evaluate(const Pkt& pkt, ValueStack& values) { + + vector binary; + try { + // Check if it's a Pkt4. If it's not, the dynamic_cast will throw + // std::bad_cast (failed dynamic_cast returns NULL for pointers and + // throws for references). + const Pkt4& pkt4 = dynamic_cast(pkt); + + switch (type_) { + case CHADDR: { + HWAddrPtr hwaddr = pkt4.getHWAddr(); + if (!hwaddr) { + // This should never happen. Every Pkt4 should always have + // a hardware address. + isc_throw(EvalTypeError, + "Packet does not have hardware address"); + } + binary = hwaddr->hwaddr_; + break; + } + case GIADDR: + binary = pkt4.getGiaddr().toBytes(); + break; + + case CIADDR: + binary = pkt4.getCiaddr().toBytes(); + break; + + case YIADDR: + binary = pkt4.getYiaddr().toBytes(); + break; + + case SIADDR: + binary = pkt4.getSiaddr().toBytes(); + break; + + case HLEN: + binary.assign(1, pkt4.getHlen()); + break; + + case HTYPE: + binary.assign(1, pkt4.getHtype()); + break; + + default: + isc_throw(EvalTypeError, "Bad field specified: " + << static_cast(type_) ); + } + + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not a Pkt4"); + } + + string value; + value.resize(binary.size()); + if (!binary.empty()) { + memmove(&value[0], &binary[0], binary.size()); + } + values.push(value); +} + void TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) { diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h index 4d4ab7a41b..b3c4cedd62 100644 --- a/src/lib/eval/token.h +++ b/src/lib/eval/token.h @@ -288,6 +288,61 @@ protected: virtual OptionPtr getOption(const Pkt& pkt); }; +/// @brief Token that represents fields of a DHCPv4 packet. +/// +/// For example in the expression pkt4.chaddr == 0x0102030405 +/// this token represents the pkt4.chaddr expression. +/// +/// Currently supported fields are: +/// - chaddr (client hardware address, hlen [0..16] octets) +/// - giaddr (relay agent IP address, 4 octets) +/// - ciaddr (client IP address, 4 octets) +/// - yiaddr ('your' (client) IP address, 4 octets) +/// - siaddr (next server IP address, 4 octets) +/// - hlen (hardware address length, 1 octet) +/// - htype (hardware address type, 1 octet) +class TokenPkt4 : public Token { +public: + + /// @brief enum value that determines the field. + enum FieldType { + CHADDR, ///< chaddr field (up to 16 bytes link-layer address) + GIADDR, ///< giaddr (IPv4 address) + CIADDR, ///< ciaddr (IPv4 address) + YIADDR, ///< yiaddr (IPv4 address) + SIADDR, ///< siaddr (IPv4 address) + HLEN, ///< hlen (hardware address length) + HTYPE ///< htype (hardware address type) + }; + + /// @brief Constructor (does nothing) + TokenPkt4(const FieldType type) + : type_(type) {} + + /// @brief Gets a value from the specified packet. + /// + /// Evaluation uses fields available in the packet. It does not require + /// any values to be present on the stack. + /// + /// @throw EvalTypeError when called for DHCPv6 packet + /// + /// @param pkt - fields will be extracted from here + /// @param values - stack of values (1 result will be pushed) + void evaluate(const Pkt& pkt, ValueStack& values); + + /// @brief Returns field type + /// + /// This method is used only in tests. + /// @return type of the field. + FieldType getType() { + return (type_); + } + +private: + /// @brief Specifies field of the DHCPv4 packet + FieldType type_; +}; + /// @brief Token that represents equality operator (compares two other tokens) /// /// For example in the expression option[vendor-class].text == "MSFT" -- 2.47.2