Browse Source

[4268a] Rebased TokenPkt4 code

Francis Dupont 9 years ago
parent
commit
d92767e4f6

+ 81 - 0
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<TokenPkt4> pkt =
+            boost::dynamic_pointer_cast<TokenPkt4>(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
     /// @brief checks if the given token is a substring operator
     void checkTokenSubstring(const TokenPtr& token) {
     void checkTokenSubstring(const TokenPtr& token) {
         ASSERT_TRUE(token);
         ASSERT_TRUE(token);
@@ -430,6 +475,41 @@ TEST_F(EvalContextTest, relay4Error) {
                "<string>:1.1-6: relay4 can only be used in DHCPv4.");
                "<string>: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 parsing of logical operators
 TEST_F(EvalContextTest, logicalOps) {
 TEST_F(EvalContextTest, logicalOps) {
     // option.exists
     // option.exists
@@ -588,6 +668,7 @@ TEST_F(EvalContextTest, scanErrors) {
     checkError("foo", "<string>:1.1: Invalid character: f");
     checkError("foo", "<string>:1.1: Invalid character: f");
     checkError(" bar", "<string>:1.2: Invalid character: b");
     checkError(" bar", "<string>:1.2: Invalid character: b");
     checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r");
     checkError("relay[12].hex == 'foo'", "<string>:1.1: Invalid character: r");
+    checkError("pkt4.ziaddr", "<string>:1.6: Invalid character: z");
 }
 }
 
 
 // Tests some scanner/parser error cases
 // Tests some scanner/parser error cases

+ 79 - 0
src/lib/eval/tests/token_unittest.cc

@@ -20,6 +20,7 @@
 
 
 using namespace std;
 using namespace std;
 using namespace isc::dhcp;
 using namespace isc::dhcp;
+using namespace isc::asiolink;
 
 
 namespace {
 namespace {
 
 
@@ -603,6 +604,84 @@ TEST_F(TokenTest, relay4RAIOnly) {
     EXPECT_EQ("false", values_.top());
     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<uint8_t>(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<uint8_t>(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
 // This test checks if a token representing an == operator is able to
 // compare two values (with incorrectly built stack).
 // compare two values (with incorrectly built stack).
 TEST_F(TokenTest, optionEqualInvalid) {
 TEST_F(TokenTest, optionEqualInvalid) {

+ 64 - 0
src/lib/eval/token.cc

@@ -8,6 +8,7 @@
 #include <eval/eval_log.h>
 #include <eval/eval_log.h>
 #include <util/encode/hex.h>
 #include <util/encode/hex.h>
 #include <asiolink/io_address.h>
 #include <asiolink/io_address.h>
+#include <dhcp/pkt4.h>
 #include <boost/lexical_cast.hpp>
 #include <boost/lexical_cast.hpp>
 #include <cstring>
 #include <cstring>
 #include <string>
 #include <string>
@@ -124,6 +125,69 @@ OptionPtr TokenRelay4Option::getOption(const Pkt& pkt) {
 }
 }
 
 
 void
 void
+TokenPkt4::evaluate(const Pkt& pkt, ValueStack& values) {
+
+    vector<uint8_t> 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<const Pkt4&>(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<int>(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) {
 TokenEqual::evaluate(const Pkt& /*pkt*/, ValueStack& values) {
 
 
     if (values.size() < 2) {
     if (values.size() < 2) {

+ 55 - 0
src/lib/eval/token.h

@@ -288,6 +288,61 @@ protected:
     virtual OptionPtr getOption(const Pkt& pkt);
     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)
 /// @brief Token that represents equality operator (compares two other tokens)
 ///
 ///
 /// For example in the expression option[vendor-class].text == "MSFT"
 /// For example in the expression option[vendor-class].text == "MSFT"