From: Shawn Routhier Date: Mon, 4 Apr 2016 17:30:10 +0000 (-0700) Subject: [trac4265] Add test code, tidy up main code X-Git-Tag: trac4106_update_base~51^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7e468146f14c2e2b97f14a7bd4385a4bc905830f;p=thirdparty%2Fkea.git [trac4265] Add test code, tidy up main code --- diff --git a/ChangeLog b/ChangeLog index 0939acb2b7..b7e0064b7f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +1xxx. [func] sar + Added access to the peer address, link address and option + information added by relays in a DHCPv6 message. + (Trac $4269, git tbd) + 1098. [func] kalmus, marcin Implemented IPv6 address/prefix reservations in MySQL. (Trac #4212, git 79481043935789fc6898d4743bede1606f82eb75) diff --git a/doc/guide/classify.xml b/doc/guide/classify.xml index fef363ed9b..77c4981e32 100644 --- a/doc/guide/classify.xml +++ b/doc/guide/classify.xml @@ -170,6 +170,24 @@ sub-optionrelay4[code].hexThe value of sub-option with code "code" from the DHCPv4 Relay Agent Information option (option 82) + + DHCPv6 Relay Options + relay6[nest].option[code].hex + + The value of the option with code "code" from the relay encapsulation "nest" + + + DHCPv6 Relay Peer Address + relay6[nest].peeraddr +n + The value of the peer address field from the relay encapsulation "nest" + + + DHCPv6 Relay Link Address + relay6[nest].linkaddr +n + The value of the link address field from the relay encapsulation "nest" + @@ -211,10 +229,24 @@ sub-option with code "code" from the DHCPv4 Relay Agent Information option - "relay4" shares the same representation types than "option", for + "relay4" shares the same representation types as "option", for instance "relay4[code].exists" is supported. + + "relay6[nest]" allows access to the encapsulations used by any DHCPv6 + relays that forwarded the packet. The "nest" level specifies the relay + from which to extract the information, with a value of 0 indicating + the relay closest to the DHCPv6 server. If the requested encapsulation + doesn't exist an empty string "" is returned. This expression is + allowed in DHCPv6 only. + + + + "relay6[nest].option[code]" shares the same representation types as + "option", for instance "relay6[nest].option[code].exists" is supported. + + List of Classification Expressions diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc index 432dd95e9a..cc45d8d3b1 100644 --- a/src/lib/dhcp/pkt6.cc +++ b/src/lib/dhcp/pkt6.cc @@ -119,13 +119,33 @@ OptionPtr Pkt6::getRelayOption(uint16_t opt_type, uint8_t relay_level) const { } OptionCollection::const_iterator x = relay_info_[relay_level].options_.find(opt_type); - if (x != options_.end()) { + if (x != relay_info_[relay_level].options_.end()) { return (*x).second; } return (OptionPtr()); } +const isc::asiolink::IOAddress& +Pkt6::getRelay6LinkAddress(uint8_t relay_level) const { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)." + << " There is no info about " << relay_level + 1 << " relay."); + } + + return (relay_info_[relay_level].linkaddr_); +} + +const isc::asiolink::IOAddress& +Pkt6::getRelay6PeerAddress(uint8_t relay_level) const { + if (relay_level >= relay_info_.size()) { + isc_throw(OutOfRange, "This message was relayed " << relay_info_.size() << " time(s)." + << " There is no info about " << relay_level + 1 << " relay."); + } + + return (relay_info_[relay_level].peeraddr_); +} + uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) const { uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header + Option::OPTION6_HDR_LEN; // header of the relay-msg option diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h index 83704c9c16..562637af0b 100644 --- a/src/lib/dhcp/pkt6.h +++ b/src/lib/dhcp/pkt6.h @@ -253,6 +253,39 @@ public: /// @return option pointer (or NULL if no option matches specified criteria) OptionPtr getAnyRelayOption(uint16_t option_code, RelaySearchOrder order); + /// @brief return the link address field from a relay option + /// + /// As with @c Pkt6::getRelayOption this returns information from the + /// specified relay scope. The relay_level specifies which relay + /// scope is to be used. 0 is the outermost encapsulation (relay closest + /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation + /// (relay closest to the client). + /// + /// @throw isc::OutOfRange if relay level has an invalid value. + /// + /// @param relay_level see description above + /// + /// @return pointer to the link address field + const isc::asiolink::IOAddress& + getRelay6LinkAddress(uint8_t relay_level) const; + + /// @brief return the peer address field from a relay option + /// + /// As with @c Pkt6::getRelayOption this returns information from the + /// specified relay scope. The relay_level specifies which relay + /// scope is to be used. 0 is the outermost encapsulation (relay closest + /// to the server). pkt->relay_info_.size() -1 is the innermost encapsulation + /// (relay closest to the client). + /// + /// @throw isc::OutOfRange if relay level has an invalid value. + /// + /// @param relay_level see description above + /// + /// @return pointer to the peer address field + const isc::asiolink::IOAddress& + getRelay6PeerAddress(uint8_t relay_level) const; + + /// /// @brief Returns all instances of specified type. /// /// Returns all instances of options of the specified type. DHCPv6 protocol @@ -279,13 +312,13 @@ public: /// @param type DHCPv6 message type which name should be returned. /// /// @return Pointer to "const" string containing the message name. If - /// the message type is unknnown the "UNKNOWN" is returned. The caller + /// the message type is unknown the "UNKNOWN" is returned. The caller /// must not release the returned pointer. static const char* getName(const uint8_t type); /// @brief Returns name of the DHCPv6 message. /// - /// This method requires an object. There is also static version, which + /// This method requires an object. There is also a static version, which /// requires one parameter (type). /// /// @return Pointer to "const" string containing the message name. If diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc index 4c294689a7..0859575be5 100644 --- a/src/lib/dhcp/tests/pkt6_unittest.cc +++ b/src/lib/dhcp/tests/pkt6_unittest.cc @@ -763,7 +763,7 @@ TEST_F(Pkt6Test, relayPack) { Pkt6::RelayInfo relay1; relay1.msg_type_ = DHCPV6_RELAY_REPL; - relay1.hop_count_ = 17; // not very miningful, but useful for testing + relay1.hop_count_ = 17; // not very meaningful, but useful for testing relay1.linkaddr_ = IOAddress("2001:db8::1"); relay1.peeraddr_ = IOAddress("fe80::abcd"); @@ -830,6 +830,16 @@ TEST_F(Pkt6Test, relayPack) { OptionBuffer data = opt->getData(); ASSERT_EQ(data.size(), sizeof(relay_opt_data)); EXPECT_EQ(0, memcmp(&data[0], relay_opt_data, sizeof(relay_opt_data))); + + // As we have a nicely built relay packet we can check + // that the functions to get the peer and link addreses work + EXPECT_EQ("2001:db8::1", clone->getRelay6LinkAddress(0).toText()); + EXPECT_EQ("fe80::abcd", clone->getRelay6PeerAddress(0).toText()); + + vectorbinary = clone->getRelay6LinkAddress(0).toBytes(); + uint8_t expected0[] = {0x20, 1, 0x0d, 0xb8, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1}; + EXPECT_EQ(0, memcmp(expected0, &binary[0], 16)); } diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc index 7465bb2df1..76939d0750 100644 --- a/src/lib/eval/tests/context_unittest.cc +++ b/src/lib/eval/tests/context_unittest.cc @@ -148,6 +148,114 @@ public: EXPECT_TRUE(conc); } + /// @brief checks if the given token is a TokenRelay6Option with + /// the correct nesting level, option code and representation. + /// @param token token to be checked + /// @param expected_level expected nesting level + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenRelay6Option(const TokenPtr& token, + uint8_t expected_level, + uint16_t expected_code, + TokenOption::RepresentationType expected_repr) { + ASSERT_TRUE(token); + boost::shared_ptr opt = + boost::dynamic_pointer_cast(token); + ASSERT_TRUE(opt); + + EXPECT_EQ(expected_level, opt->getNest()); + EXPECT_EQ(expected_code, opt->getCode()); + EXPECT_EQ(expected_repr, opt->getRepresentation()); + } + + /// @brief This tests attempts to parse the expression then checks + /// if the number of tokens is correct and the TokenRelay6Option + /// is as expected. + /// + /// @param expr expression to be parsed + /// @param exp_level expected level to be parsed + /// @param exp_code expected option code to be parsed + /// @param exp_repr expected representation to be parsed + /// @param exp_tokens expected number of tokens + void testRelay6Option(std::string expr, + uint8_t exp_level, + uint16_t exp_code, + TokenOption::RepresentationType exp_repr, + int exp_tokens) { + EvalContext eval(Option::V6); + + // parse the expression + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() <<"Exception thrown: " << ex.what(); + return; + } + + // Parsing should succed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // checkt that the first token is TokenRelay6Option and that + // is has the correct attributes + checkTokenRelay6Option(eval.expression.at(0), exp_level, exp_code, exp_repr); + } + + /// @brief checks if the given token is a TokenRelay with the + /// correct nesting level and field type. + /// @param token token to be checked + /// @param expected_level expected nesting level + /// @param expected_code expected option code + /// @param expected_repr expected representation (text, hex, exists) + void checkTokenRelay6(const TokenPtr& token, + uint8_t expected_level, + TokenRelay6::FieldType expected_type) { + ASSERT_TRUE(token); + boost::shared_ptr opt = + boost::dynamic_pointer_cast(token); + ASSERT_TRUE(opt); + + EXPECT_EQ(expected_level, opt->getNest()); + EXPECT_EQ(expected_type, opt->getType()); + } + + /// @brief This tests attempts to parse the expression then checks + /// if the number of tokens is correct and the TokenRelay6 is as + /// expected. + /// + /// @param expr expression to be parsed + /// @param exp_level expected level to be parsed + /// @param exp_type expected field type to be parsed + /// @param exp_tokens expected number of tokens + void testRelay6Field(std::string expr, + uint8_t exp_level, + TokenRelay6::FieldType exp_type, + int exp_tokens) { + EvalContext eval(Option::V6); + + // parse the expression + try { + parsed_ = eval.parseString(expr); + } + catch (const EvalParseError& ex) { + FAIL() <<"Exception thrown: " << ex.what(); + return; + } + + // Parsing should succed and return a token. + EXPECT_TRUE(parsed_); + + // There should be the expected number of tokens. + ASSERT_EQ(exp_tokens, eval.expression.size()); + + // checkt that the first token is TokenRelay6 and that + // is has the correct attributes + checkTokenRelay6(eval.expression.at(0), exp_level, exp_type); + } + /// @brief checks if the given expression raises the expected message /// when it is parsed. void checkError(const string& expr, const string& msg) { @@ -576,6 +684,43 @@ TEST_F(EvalContextTest, concat) { checkTokenConcat(tmp3); } +// Test the parsing of a relay6 option +TEST_F(EvalContextTest, relay6Option) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[0].option[123].text == 'foo'", + 0, 123, TokenOption::TEXTUAL, 3); +} + +// Test the parsing of existence for a relay6 option +TEST_F(EvalContextTest, relay6OptionExists) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[1].option[75].exists", + 1, 75, TokenOption::EXISTS, 1); +} + +// Test the parsing of hex for a relay6 option +TEST_F(EvalContextTest, relay6OptionHex) { + EvalContext eval(Option::V6); + + testRelay6Option("relay6[2].option[85].hex == 'foo'", + 2, 85, TokenOption::HEXADECIMAL, 3); +} + +// Tests if the linkaddr field in a Relay6 encapsulation can be accessed. +TEST_F(EvalContextTest, relay6FieldLinkAddr) { + testRelay6Field("relay6[0].linkaddr == ::", + 0, TokenRelay6::LINKADDR, 3); +} + +// Tests if the peeraddr field in a Relay6 encapsulation can be accessed. +TEST_F(EvalContextTest, relay6FieldPeerAddr) { + testRelay6Field("relay6[1].peeraddr == ::", + 1, TokenRelay6::PEERADDR, 3); +} + +// // Test some scanner error cases TEST_F(EvalContextTest, scanErrors) { checkError("'", ":1.1: Invalid character: '"); diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc index 3449281dbd..60f6a8eccd 100644 --- a/src/lib/eval/tests/token_unittest.cc +++ b/src/lib/eval/tests/token_unittest.cc @@ -62,6 +62,111 @@ public: pkt4_->addOption(rai); } + /// @brief Adds relay encapsulations with some suboptions + /// + /// This will add 2 relay encapsulations all will have + /// msg_type of RELAY_FORW + /// Relay 0 (closest to server) will have + /// linkaddr = peeraddr = 0, hop-count = 1 + /// option 100 "hundred.zero", option 101 "hundredone.zero" + /// Relay 1 (closest to client) will have + /// linkaddr 1::1= peeraddr = 1::2, hop-count = 0 + /// option 100 "hundred.one", option 102 "hundredtwo.one" + void addRelay6Encapsulations() { + // First relay + Pkt6::RelayInfo relay0; + relay0.msg_type_ = DHCPV6_RELAY_FORW; + relay0.hop_count_ = 1; + relay0.linkaddr_ = isc::asiolink::IOAddress("::"); + relay0.peeraddr_ = isc::asiolink::IOAddress("::"); + OptionPtr optRelay01(new OptionString(Option::V6, 100, + "hundred.zero")); + OptionPtr optRelay02(new OptionString(Option::V6, 101, + "hundredone.zero")); + + relay0.options_.insert(make_pair(optRelay01->getType(), optRelay01)); + relay0.options_.insert(make_pair(optRelay02->getType(), optRelay02)); + + pkt6_->addRelayInfo(relay0); + // Second relay + Pkt6::RelayInfo relay1; + relay1.msg_type_ = DHCPV6_RELAY_FORW; + relay1.hop_count_ = 0; + relay1.linkaddr_ = isc::asiolink::IOAddress("1::1"); + relay1.peeraddr_ = isc::asiolink::IOAddress("1::2"); + OptionPtr optRelay11(new OptionString(Option::V6, 100, + "hundred.one")); + OptionPtr optRelay12(new OptionString(Option::V6, 102, + "hundredtwo.one")); + + relay1.options_.insert(make_pair(optRelay11->getType(), optRelay11)); + relay1.options_.insert(make_pair(optRelay12->getType(), optRelay12)); + pkt6_->addRelayInfo(relay1); + } + + /// @brief Verify that the relay6 option evaluatiosn work properly + /// + /// Given the nesting level and option code extract the option + /// and compare it to the expected string. + /// + /// @param test_level The nesting level + /// @param test_code The code of the option to extract + /// @param result_addr The expected result of the address as a string + void verifyRelay6Option(const uint8_t test_level, + const uint16_t test_code, + const TokenOption::RepresentationType& test_rep, + const std::string& result_string) { + // Create the token + ASSERT_NO_THROW(t_.reset(new TokenRelay6Option(test_level, + test_code, + test_rep))); + + // We should be able to evaluate it + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + + // We should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // And it should match the expected result + // Invalid nesting levels result in a 0 length string + EXPECT_EQ(result_string, values_.top()); + + // Then we clear the stack + clearStack(); + } + + /// @brief Verify that the relay6 field evaluations work properly + /// + /// Given the nesting level, the field to extract and the expected + /// address create a token and evaluate it then compare the addresses + /// + /// @param test_level The nesting level + /// @param test_field The type of the field to extract + /// @param result_addr The expected result of the address as a string + void verifyRelay6Eval(const uint8_t test_level, + const TokenRelay6::FieldType test_field, + const int result_len, + const uint8_t result_addr[]) { + // Create the token + ASSERT_NO_THROW(t_.reset(new TokenRelay6(test_level, test_field))); + + // We should be able to evaluate it + EXPECT_NO_THROW(t_->evaluate(*pkt6_, values_)); + + // We should have one value on the stack + ASSERT_EQ(1, values_.size()); + + // And it should match the expected result + // Invalid nesting levels result in a 0 length string + EXPECT_EQ(result_len, values_.top().size()); + if (result_len != 0) { + EXPECT_EQ(0, memcmp(result_addr, &values_.top()[0], result_len)); + } + + // Then we clear the stack + clearStack(); + } + /// @brief Convenience function. Removes token and values stacks. void clearStack() { while (!values_.empty()) { @@ -1012,3 +1117,89 @@ TEST_F(TokenTest, concat) { ASSERT_EQ(1, values_.size()); EXPECT_EQ("foobar", values_.top()); } + +// This test checks if we can properly extract the link and peer +// address fields from relay encapsulations. Our packet has +// two relay encapsulations. We attempt to extract the two +// fields from both of the encapsulations and compare them. +// We also try to extract one of the fields from an encapsulation +// that doesn't exist (level 2), this should result in an empty +// string. +TEST_F(TokenTest, relay6Field) { + // Values for the address results + uint8_t zeroaddr[] = { 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0 }; + uint8_t linkaddr[] = { 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1 }; + uint8_t peeraddr[] = { 0, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 2 }; + + // We start by adding a set of relay encapsulations to the + // basic v6 packet. + addRelay6Encapsulations(); + + // Then we work our way through the set of choices + // Level 0 both link and peer address should be 0::0 + verifyRelay6Eval(0, TokenRelay6::LINKADDR, 16, zeroaddr); + verifyRelay6Eval(0, TokenRelay6::PEERADDR, 16, zeroaddr); + + // Level 1 link and peer should have different non-zero addresses + verifyRelay6Eval(1, TokenRelay6::LINKADDR, 16, linkaddr); + verifyRelay6Eval(1, TokenRelay6::PEERADDR, 16, peeraddr); + + // Level 2 has no encapsulation so the address should be zero length + verifyRelay6Eval(2, TokenRelay6::LINKADDR, 0, zeroaddr); + + // Lets check that the layout of the address returned by the + // token matches that of the TokenIpAddress + TokenPtr trelay; + TokenPtr taddr; + TokenPtr tequal; + ASSERT_NO_THROW(trelay.reset(new TokenRelay6(1, TokenRelay6::LINKADDR))); + ASSERT_NO_THROW(taddr.reset(new TokenIpAddress("1::1"))); + ASSERT_NO_THROW(tequal.reset(new TokenEqual())); + + EXPECT_NO_THROW(trelay->evaluate(*pkt6_, values_)); + EXPECT_NO_THROW(taddr->evaluate(*pkt6_, values_)); + EXPECT_NO_THROW(tequal->evaluate(*pkt6_, values_)); + + // We should have a single value on the stack and it should be "true" + ASSERT_EQ(1, values_.size()); + EXPECT_EQ("true", values_.top()); + + // be tidy + clearStack(); +} + +// This test checks if we can properly extract an option +// from relay encapsulations. Our packet has two relay +// encapsulations. Both include a common option with the +// original message (option 100) and both include their +// own option (101 and 102). We attempt to extract the +// options and compare them to expected values. We also +// try to extract an option from an encapsulation +// that doesn't exist (level 2), this should result in an empty +// string. +TEST_F(TokenTest, relay6Option) { + // We start by adding a set of relay encapsulations to the + // basic v6 packet. + addRelay6Encapsulations(); + + // Then we work our way through the set of choices + // Level 0 both options it has and the check that + // the checking for an option it doesn't have results + // in an empty string. + verifyRelay6Option(0, 100, TokenOption::TEXTUAL, "hundred.zero"); + verifyRelay6Option(0, 100, TokenOption::EXISTS, "true"); + verifyRelay6Option(0, 101, TokenOption::TEXTUAL, "hundredone.zero"); + verifyRelay6Option(0, 102, TokenOption::TEXTUAL, ""); + verifyRelay6Option(0, 102, TokenOption::EXISTS, "false"); + + // Level 1, again both options it has and the one for level 0 + verifyRelay6Option(1, 100, TokenOption::TEXTUAL, "hundred.one"); + verifyRelay6Option(1, 101, TokenOption::TEXTUAL, ""); + verifyRelay6Option(1, 102, TokenOption::TEXTUAL, "hundredtwo.one"); + + // Level 2, no encapsulation so no options + verifyRelay6Option(2, 100, TokenOption::TEXTUAL, ""); +} diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc index 490c4ceb31..9647bd6ff9 100644 --- a/src/lib/eval/token.cc +++ b/src/lib/eval/token.cc @@ -322,7 +322,39 @@ OptionPtr TokenRelay6Option::getOption(const Pkt& pkt) { } void -TokenRelay6::evaluate(const Pkt& /*pkt*/, ValueStack& values) { - // test routine, need to add code in pkt6 to get the proper fields - values.push(""); +TokenRelay6::evaluate(const Pkt& pkt, ValueStack& values) { + + vector binary; + try { + // Check if it's a Pkt6. If it's not the dynamic_cast will + // throw std::bad_cast. + const Pkt6& pkt6 = dynamic_cast(pkt); + + try { + switch (type_) { + // Now that we have the right type of packet we can + // get the option and return it. + case LINKADDR: + binary = pkt6.getRelay6LinkAddress(nest_level_).toBytes(); + break; + case PEERADDR: + binary = pkt6.getRelay6PeerAddress(nest_level_).toBytes(); + break; + } + } catch (const isc::OutOfRange&) { + // The only exception we expect is OutOfRange if the nest + // level is invalid. We push "" in that case. + values.push(""); + return; + } + } catch (const std::bad_cast&) { + isc_throw(EvalTypeError, "Specified packet is not Pkt6"); + } + + string value; + value.resize(binary.size()); + if (!binary.empty()) { + memmove(&value[0], &binary[0], binary.size()); + } + values.push(value); } diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h index b4a06b8af2..7512ac499b 100644 --- a/src/lib/eval/token.h +++ b/src/lib/eval/token.h @@ -490,7 +490,7 @@ public: /// @param option_code code of the option. /// @param rep_type Token representation type. TokenRelay6Option(const uint8_t nest_level, const uint16_t option_code, - const RepresentationType& rep_type) + const RepresentationType& rep_type) :TokenOption(option_code, rep_type), nest_level_(nest_level) {} /// @brief Returns nest-level @@ -529,7 +529,7 @@ protected: /// The nesting level can go from 0 (closest to the server) to 31. class TokenRelay6 : public Token { public: - + /// @brief enum value that determines the field. enum FieldType { PEERADDR, ///< Peer address field (IPv6 address) @@ -553,6 +553,17 @@ public: /// @param values - stack of values (1 result will be pushed) void evaluate(const Pkt& pkt, ValueStack& values); + /// @brief Returns nest-level + /// + /// This method is used in testing to determine if the parser has + /// instantiated TokenRelay6 with correct parameters. + /// + /// @return nest-level of the relay block this token expects to use + /// for extraction. + uint8_t getNest() const { + return (nest_level_); + } + /// @brief Returns field type /// /// This method is used only in testing to determine if the parser has