+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)
sub-option</entry><entry>relay4[code].hex</entry><entry>The value of
sub-option with code "code" from the DHCPv4 Relay Agent Information option
(option 82)</entry></row>
+<row>
+ <entry>DHCPv6 Relay Options</entry>
+ <entry>relay6[nest].option[code].hex</entry>
+<!-- <entry>Value of the option</entry> -->
+ <entry>The value of the option with code "code" from the relay encapsulation "nest"</entry>
+</row>
+<row>
+ <entry>DHCPv6 Relay Peer Address</entry>
+ <entry>relay6[nest].peeraddr</entry>
+<!-- <entry>2001:DB8::1</entry> -->n
+ <entry>The value of the peer address field from the relay encapsulation "nest"</entry>
+</row>
+<row>
+ <entry>DHCPv6 Relay Link Address</entry>
+ <entry>relay6[nest].linkaddr</entry>
+<!-- <entry>2001:DB8::1</entry> -->n
+ <entry>The value of the link address field from the relay encapsulation "nest"</entry>
+</row>
</tbody>
</tgroup>
</table>
</para>
<para>
- "relay4" shares the same representation types than "option", for
+ "relay4" shares the same representation types as "option", for
instance "relay4[code].exists" is supported.
</para>
+ <para>
+ "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.
+ </para>
+
+ <para>
+ "relay6[nest].option[code]" shares the same representation types as
+ "option", for instance "relay6[nest].option[code].exists" is supported.
+ </para>
+
<para>
<table frame="all" id="classification-expressions-list">
<title>List of Classification Expressions</title>
}
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
/// @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
/// @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
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");
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());
+
+ vector<uint8_t>binary = 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));
}
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<TokenRelay6Option> opt =
+ boost::dynamic_pointer_cast<TokenRelay6Option>(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<TokenRelay6> opt =
+ boost::dynamic_pointer_cast<TokenRelay6>(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) {
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("'", "<string>:1.1: Invalid character: '");
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()) {
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, "");
+}
}
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<uint8_t> 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<const Pkt6&>(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);
}
/// @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
/// 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)
/// @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