Commit 7e468146 authored by Shawn Routhier's avatar Shawn Routhier

[trac4265] Add test code, tidy up main code

parent ce3d4e3c
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)
......
......@@ -170,6 +170,24 @@
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>
......@@ -211,10 +229,24 @@ sub-option with code "code" from the DHCPv4 Relay Agent Information option
</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>
......
......@@ -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
......
......@@ -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
......
......@@ -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());
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));
}
......
......@@ -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<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) {
......@@ -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("'", "<string>:1.1: Invalid character: '");
......
......@@ -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, "");
}
......@@ -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<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);
}
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment