Commit f73fba3c authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3180'

parents e01ba204 36eabc93
......@@ -53,12 +53,14 @@
* - @subpage dhcpv4Session
* - @subpage dhcpv4ConfigParser
* - @subpage dhcpv4ConfigInherit
* - @subpage dhcpv4OptionsParse
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
* - @subpage dhcpv6ConfigParser
* - @subpage dhcpv6ConfigInherit
* - @subpage dhcpv6DDNSIntegration
* - @subpage dhcpv6OptionsParse
* - @subpage dhcpv6Other
* - @subpage libdhcp
* - @subpage libdhcpIntro
......
// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
......@@ -79,6 +79,11 @@ See \ref dhcpv6ConfigParser.
Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
counterpart. See \ref dhcpv6ConfigInherit.
@section dhcpv4OptionsParse Custom functions to parse message options
The DHCPv4 server uses the same logic to supply custom callback function to
parse message option as DHCPv6 server implementation. See \ref dhcpv6OptionsParse.
@section dhcpv4Other Other DHCPv4 topics
For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
......
......@@ -35,6 +35,7 @@
#include <hooks/hooks_manager.h>
#include <boost/algorithm/string/erase.hpp>
#include <boost/bind.hpp>
#include <iomanip>
#include <fstream>
......@@ -197,6 +198,15 @@ Dhcpv4Srv::run() {
continue;
}
// In order to parse the DHCP options, the server needs to use some
// configuration information such as: existing option spaces, option
// definitions etc. This is the kind of information which is not
// available in the libdhcp, so we need to supply our own implementation
// of the option parsing function here, which would rely on the
// configuration data.
query->setCallback(boost::bind(&Dhcpv4Srv::unpackOptions, this,
_1, _2, _3));
bool skip_unpack = false;
// The packet has just been received so contains the uninterpreted wire
......@@ -1161,6 +1171,95 @@ Dhcpv4Srv::openActiveSockets(const uint16_t port,
}
}
size_t
Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options) {
size_t offset = 0;
OptionDefContainer option_defs;
if (option_space == "dhcp4") {
// Get the list of stdandard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V4);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr =
CfgMgr::instance().getOptionDefs(option_space);
if (option_defs_ptr != NULL) {
option_defs = *option_defs_ptr;
}
}
// Get the search index #1. It allows to search for option definitions
// using option code.
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
// The buffer being read comprises a set of options, each starting with
// a one-byte type code and a one-byte length field.
while (offset + 1 <= buf.size()) {
uint8_t opt_type = buf[offset++];
// DHO_END is a special, one octet long option
if (opt_type == DHO_END)
return (offset); // just return. Don't need to add DHO_END option
// DHO_PAD is just a padding after DHO_END. Let's continue parsing
// in case we receive a message without DHO_END.
if (opt_type == DHO_PAD)
continue;
if (offset + 1 >= buf.size()) {
// opt_type must be cast to integer so as it is not treated as
// unsigned char value (a number is presented in error message).
isc_throw(OutOfRange, "Attempt to parse truncated option "
<< static_cast<int>(opt_type));
}
uint8_t opt_len = buf[offset++];
if (offset + opt_len > buf.size()) {
isc_throw(OutOfRange, "Option parse failed. Tried to parse "
<< offset + opt_len << " bytes from " << buf.size()
<< "-byte long buffer.");
}
// Get all definitions with the particular option code. Note that option code
// is non-unique within this container however at this point we expect
// to get one option definition with the particular code. If more are
// returned we report an error.
const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
// Get the number of returned option definitions for the option code.
size_t num_defs = distance(range.first, range.second);
OptionPtr opt;
if (num_defs > 1) {
// Multiple options of the same code are not supported right now!
isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
" for option type " << static_cast<int>(opt_type)
<< " returned. Currently it is not supported to initialize"
<< " multiple option definitions for the same option code."
<< " This will be supported once support for option spaces"
<< " is implemented");
} else if (num_defs == 0) {
opt = OptionPtr(new Option(Option::V4, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
opt->setEncapsulatedSpace("dhcp4");
} else {
// The option definition has been found. Use it to create
// the option instance from the provided buffer chunk.
const OptionDefinitionPtr& def = *(range.first);
assert(def);
opt = def->optionFactory(Option::V4, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len,
boost::bind(&Dhcpv4Srv::unpackOptions,
this, _1, _2, _3));
}
options.insert(std::make_pair(opt_type, opt));
offset += opt_len;
}
return (offset);
}
} // namespace dhcp
} // namespace isc
......@@ -349,6 +349,18 @@ protected:
/// simulates transmission of a packet. For that purpose it is protected.
virtual void sendPacket(const Pkt4Ptr& pkt);
/// @brief Implements a callback function to parse options in the message.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param option_space A name of the option space which holds definitions
/// of to be used to parse options in the packets.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @return An offset to the first byte after last parsed option.
size_t unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options);
private:
/// @brief Constructs netmask option based on subnet4
......
......@@ -22,6 +22,7 @@
#include <dhcp/option.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter_inet.h>
......@@ -147,6 +148,7 @@ public:
using Dhcpv4Srv::writeServerID;
using Dhcpv4Srv::sanityCheck;
using Dhcpv4Srv::srvidToString;
using Dhcpv4Srv::unpackOptions;
};
static const char* SRVID_FILE = "server-id-test.txt";
......@@ -1658,21 +1660,94 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
EXPECT_TRUE(hook_index_buffer4_send > 0);
}
// a dummy MAC address
const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
// This test verifies that the following option structure can be parsed:
// - option (option space 'foobar')
// - sub option (option space 'foo')
// - sub option (option space 'bar')
// @todo Add more thorough unit tests for unpackOptions.
TEST_F(Dhcpv4SrvTest, unpackOptions) {
// Create option definition for each level of encapsulation. Each option
// definition is for the option code 1. Options may have the same
// option code because they belong to different option spaces.
// Top level option encapsulates options which belong to 'space-foo'.
OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
"space-foo"));\
// Middle option encapsulates options which belong to 'space-bar'
OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
"space-bar"));
// Low level option doesn't encapsulate any option space.
OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
"uint8"));
// Add option definitions to the Configuration Manager. Each goes under
// different option space.
CfgMgr& cfgmgr = CfgMgr::instance();
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def, "space-foobar"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def2, "space-foo"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def3, "space-bar"));
// Create the buffer holding the structure of options.
const char raw_data[] = {
// First option starts here.
0x01, // option code = 1
0x0B, // option length = 11
0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
// Sub option starts here.
0x01, // option code = 1
0x05, // option length = 5
0x01, 0x02, // this option carries uint16 value
// Last option starts here.
0x01, // option code = 1
0x01, // option length = 1
0x00 // This option carries a single uint8
// value and has no sub options.
};
OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
// Parse options.
NakedDhcpv4Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options));
// There should be one top level option.
ASSERT_EQ(1, options.size());
boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
second);
ASSERT_TRUE(option_foobar);
EXPECT_EQ(1, option_foobar->getType());
EXPECT_EQ(0x00010203, option_foobar->getValue());
// There should be a middle level option held in option_foobar.
boost::shared_ptr<OptionInt<uint16_t> > option_foo =
boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
getOption(1));
ASSERT_TRUE(option_foo);
EXPECT_EQ(1, option_foo->getType());
EXPECT_EQ(0x0102, option_foo->getValue());
// Finally, there should be a low level option under option_foo.
boost::shared_ptr<OptionInt<uint8_t> > option_bar =
boost::dynamic_pointer_cast<OptionInt<uint8_t> >(option_foo->getOption(1));
ASSERT_TRUE(option_bar);
EXPECT_EQ(1, option_bar->getType());
EXPECT_EQ(0x0, option_bar->getValue());
}
// a dummy MAC address
const uint8_t dummyMacAddr[] = {0, 1, 2, 3, 4, 5};
// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
// A dummy MAC address, padded with 0s
const uint8_t dummyChaddr[16] = {0, 1, 2, 3, 4, 5, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0 };
// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
// Let's use some creative test content here (128 chars + \0)
const uint8_t dummyFile[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit. Proin mollis placerat metus, at "
"lacinia orci ornare vitae. Mauris amet.";
// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
// Yet another type of test content (64 chars + \0)
const uint8_t dummySname[] = "Lorem ipsum dolor sit amet, consectetur "
"adipiscing elit posuere.";
/// @brief a class dedicated to Hooks testing in DHCPv4 server
///
......
......@@ -167,6 +167,29 @@ Once the configuration is implemented, these constants will be removed.
@todo Add section about setting up options and their definitions with bindctl.
@section dhcpv6OptionsParse Custom functions to parse message options
The DHCPv6 server implementation provides a generic support to define option
formats and set option values. A number of options formats have been defined
for standard options in libdhcp++. However, the formats for vendor specific
options are dynamically configured by the server's administrator and thus can't
be stored in libdhcp++. Such option formats are stored in the
@c isc::dhcp::CfgMgr. The libdhcp++ provides functions for recursive parsing
of options which may be encapsulated by other options up to the any level of
encapsulation but these functions are unaware of the option formats defined
in the @c isc::dhcp::CfgMgr because they belong to a different library.
Therefore, the generic functions @c isc::dhcp::LibDHCP::unpackOptions4 and
@c isc::dhcp::LibDHCP::unpackOptions6 are only useful to parse standard
options which definitions are provided in the libdhcp++. In order to overcome
this problem a callback mechanism has been implemented in @c Option and @c Pkt6
classes. By installing a callback function on the instance of the @c Pkt6 the
server may provide a custom implementation of the options parsing algorithm.
This callback function will take precedence over the @c LibDHCP::unpackOptions6
and @c LibDHCP::unpackOptions4 functions. With this approach, the callback is
implemented within the context of the server and it has access to all objects
which define its configuration (including dynamically created option
definitions).
@section dhcpv6Other Other DHCPv6 topics
For hooks API support in DHCPv6, see @ref dhcpv6Hooks.
......
......@@ -229,6 +229,15 @@ bool Dhcpv6Srv::run() {
continue;
}
// In order to parse the DHCP options, the server needs to use some
// configuration information such as: existing option spaces, option
// definitions etc. This is the kind of information which is not
// available in the libdhcp, so we need to supply our own implementation
// of the option parsing function here, which would rely on the
// configuration data.
query->setCallback(boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
_3, _4, _5));
bool skip_unpack = false;
// The packet has just been received so contains the uninterpreted wire
......@@ -703,7 +712,7 @@ Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
void
Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
RequirementLevel serverid) {
Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
switch (clientid) {
case MANDATORY:
if (client_ids.size() != 1) {
......@@ -724,7 +733,7 @@ Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
break;
}
Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
switch (serverid) {
case FORBIDDEN:
if (!server_ids.empty()) {
......@@ -869,7 +878,7 @@ Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer,
//
// @todo: expand this to cover IA_PD and IA_TA once we implement support for
// prefix delegation and temporary addresses.
for (Option::OptionCollection::iterator opt = question->options_.begin();
for (OptionCollection::iterator opt = question->options_.begin();
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
......@@ -1059,8 +1068,8 @@ Dhcpv6Srv::createNameChangeRequests(const Pkt6Ptr& answer,
// Get all IAs from the answer. For each IA, holding an address we will
// create a corresponding NameChangeRequest.
Option::OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
for (Option::OptionCollection::const_iterator answer_ia =
OptionCollection answer_ias = answer->getOptions(D6O_IA_NA);
for (OptionCollection::const_iterator answer_ia =
answer_ias.begin(); answer_ia != answer_ias.end(); ++answer_ia) {
// @todo IA_NA may contain multiple addresses. We should process
// each address individually. Currently we get only one.
......@@ -1702,7 +1711,7 @@ Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply,
}
DuidPtr duid(new DUID(opt_duid->getData()));
for (Option::OptionCollection::iterator opt = renew->options_.begin();
for (OptionCollection::iterator opt = renew->options_.begin();
opt != renew->options_.end(); ++opt) {
switch (opt->second->getType()) {
......@@ -1769,7 +1778,7 @@ Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
// handled properly. Therefore the releaseIA_NA and releaseIA_PD options
// may turn the status code to some error, but can't turn it back to success.
int general_status = STATUS_Success;
for (Option::OptionCollection::iterator opt = release->options_.begin();
for (OptionCollection::iterator opt = release->options_.begin();
opt != release->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
......@@ -2243,5 +2252,99 @@ Dhcpv6Srv::openActiveSockets(const uint16_t port) {
}
}
size_t
Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len) {
size_t offset = 0;
size_t length = buf.size();
OptionDefContainer option_defs;
if (option_space == "dhcp6") {
// Get the list of stdandard option definitions.
option_defs = LibDHCP::getOptionDefs(Option::V6);
} else if (!option_space.empty()) {
OptionDefContainerPtr option_defs_ptr =
CfgMgr::instance().getOptionDefs(option_space);
if (option_defs_ptr != NULL) {
option_defs = *option_defs_ptr;
}
}
// Get the search index #1. It allows to search for option definitions
// using option code.
const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
// The buffer being read comprises a set of options, each starting with
// a two-byte type code and a two-byte length field.
while (offset + 4 <= length) {
uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2;
uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2;
if (offset + opt_len > length) {
// @todo: consider throwing exception here.
return (offset);
}
if (opt_type == D6O_RELAY_MSG && relay_msg_offset && relay_msg_len) {
// remember offset of the beginning of the relay-msg option
*relay_msg_offset = offset;
*relay_msg_len = opt_len;
// do not create that relay-msg option
offset += opt_len;
continue;
}
// Get all definitions with the particular option code. Note that option
// code is non-unique within this container however at this point we
// expect to get one option definition with the particular code. If more
// are returned we report an error.
const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
// Get the number of returned option definitions for the option code.
size_t num_defs = distance(range.first, range.second);
OptionPtr opt;
if (num_defs > 1) {
// Multiple options of the same code are not supported right now!
isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
" for option type " << opt_type << " returned. Currently it is not"
" supported to initialize multiple option definitions"
" for the same option code. This will be supported once"
" support for option spaces is implemented");
} else if (num_defs == 0) {
// @todo Don't crash if definition does not exist because only a few
// option definitions are initialized right now. In the future
// we will initialize definitions for all options and we will
// remove this elseif. For now, return generic option.
opt = OptionPtr(new Option(Option::V6, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
opt->setEncapsulatedSpace("dhcp6");
} else {
// The option definition has been found. Use it to create
// the option instance from the provided buffer chunk.
const OptionDefinitionPtr& def = *(range.first);
assert(def);
opt = def->optionFactory(Option::V6, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len,
boost::bind(&Dhcpv6Srv::unpackOptions, this, _1, _2,
_3, _4, _5));
}
// add option to options
options.insert(std::make_pair(opt_type, opt));
offset += opt_len;
}
return (offset);
}
};
};
......@@ -506,6 +506,24 @@ protected:
/// simulates transmission of a packet. For that purpose it is protected.
virtual void sendPacket(const Pkt6Ptr& pkt);
/// @brief Implements a callback function to parse options in the message.
///
/// @param buf a A buffer holding options in on-wire format.
/// @param option_space A name of the option space which holds definitions
/// of to be used to parse options in the packets.
/// @param [out] options A reference to the collection where parsed options
/// will be stored.
/// @param relay_msg_offset Reference to a size_t structure. If specified,
/// offset to beginning of relay_msg option will be stored in it.
/// @param relay_msg_len reference to a size_t structure. If specified,
/// length of the relay_msg option will be stored in it.
/// @return An offset to the first byte after last parsed option.
size_t unpackOptions(const OptionBuffer& buf,
const std::string& option_space,
isc::dhcp::OptionCollection& options,
size_t* relay_msg_offset,
size_t* relay_msg_len);
private:
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
......
......@@ -24,6 +24,7 @@
#include <dhcp/option6_client_fqdn.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
......@@ -1994,7 +1995,7 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
// @todo Uncomment this test as part of #3180 work.
// Kea code currently fails to handle docsis traffic.
TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
TEST_F(Dhcpv6SrvTest, docsisTraffic) {
NakedDhcpv6Srv srv(0);
......@@ -2018,6 +2019,78 @@ TEST_F(Dhcpv6SrvTest, DISABLED_docsisTraffic) {
/// that is relayed properly, etc.
}
// This test verifies that the following option structure can be parsed:
// - option (option space 'foobar')
// - sub option (option space 'foo')
// - sub option (option space 'bar')
TEST_F(Dhcpv6SrvTest, unpackOptions) {
// Create option definition for each level of encapsulation. Each option
// definition is for the option code 1. Options may have the same
// option code because they belong to different option spaces.
// Top level option encapsulates options which belong to 'space-foo'.
OptionDefinitionPtr opt_def(new OptionDefinition("option-foobar", 1, "uint32",
"space-foo"));\
// Middle option encapsulates options which belong to 'space-bar'
OptionDefinitionPtr opt_def2(new OptionDefinition("option-foo", 1, "uint16",
"space-bar"));
// Low level option doesn't encapsulate any option space.
OptionDefinitionPtr opt_def3(new OptionDefinition("option-bar", 1,
"uint8"));
// Add option definitions to the Configuration Manager. Each goes under
// different option space.
CfgMgr& cfgmgr = CfgMgr::instance();
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def, "space-foobar"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def2, "space-foo"));
ASSERT_NO_THROW(cfgmgr.addOptionDef(opt_def3, "space-bar"));
// Create the buffer holding the structure of options.
const char raw_data[] = {
// First option starts here.
0x00, 0x01, // option code = 1
0x00, 0x0F, // option length = 15
0x00, 0x01, 0x02, 0x03, // This option carries uint32 value
// Sub option starts here.
0x00, 0x01, // option code = 1
0x00, 0x07, // option length = 7
0x01, 0x02, // this option carries uint16 value
// Last option starts here.
0x00, 0x01, // option code = 1
0x00, 0x01, // option length = 1
0x00 // This option carries a single uint8 value and has no sub options.
};
OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
// Parse options.
NakedDhcpv6Srv srv(0);
OptionCollection options;
ASSERT_NO_THROW(srv.unpackOptions(buf, "space-foobar", options, 0, 0));
// There should be one top level option.
ASSERT_EQ(1, options.size());
boost::shared_ptr<OptionInt<uint32_t> > option_foobar =
boost::dynamic_pointer_cast<OptionInt<uint32_t> >(options.begin()->
second);
ASSERT_TRUE(option_foobar);
EXPECT_EQ(1, option_foobar->getType());
EXPECT_EQ(0x00010203, option_foobar->getValue());
// There should be a middle level option held in option_foobar.
boost::shared_ptr<OptionInt<uint16_t> > option_foo =
boost::dynamic_pointer_cast<OptionInt<uint16_t> >(option_foobar->
getOption(1));
ASSERT_TRUE(option_foo);
EXPECT_EQ(1, option_foo->getType());
EXPECT_EQ(0x0102, option_foo->getValue());