BIND 10 pd-ietf-demo, updated. 7b04f97e186aa9e9b3a4cda775e79fafc35e3843 Merge branch 'trac3195' into pd-ietf-demo
BIND 10 source code commits
bind10-changes at lists.isc.org
Wed Oct 16 17:29:41 UTC 2013
The branch, pd-ietf-demo has been updated
via 7b04f97e186aa9e9b3a4cda775e79fafc35e3843 (commit)
via d09b192a99b643b60c8129fa2025ac94201a5226 (commit)
via 07a72f62c2e93585de92d032b5af56c9a2ca562b (commit)
via ebeb291e7a086a17a6fcddb1abcab583dc59b46c (commit)
via a7cbda0d6f5c2395c35ac9e7330b6fe158295262 (commit)
via d843534d3a9b561b1c471e89c12bdf9f41a33e1c (commit)
via 6adc7f8cb6048483fe367a8c37f977804815f14b (commit)
via 46e2bb18c4e734957469a044ab44cb30f4e2abe0 (commit)
via 4a0cded796c63d06b237f42acf30c7d18899224f (commit)
via cfeb9750b04c63bb4387088c8db7ef815a806856 (commit)
via 638c6a3abbc9e00f8609d7ea894219facc5dec71 (commit)
via 12035904417740858ad86de2cbaae044e02c590e (commit)
via 7731dd88bdb90869fe1c463a9bdbfa682317a6b7 (commit)
via fcdbac57aa38264b8fe767178703fbd66df646e9 (commit)
via 73ab9c6c43eb46eb3c093678e561e47265a84fb2 (commit)
via d1a691d898010d4231b9e46aabe90202137500b0 (commit)
via 308eecf9fbe9aba6fedc86c0e44ac787abc42a05 (commit)
via 760d4bda007297da1c5e40deda150ab79960a040 (commit)
via 5224c8549dad60f83428a00a1c8009ffc3ddac16 (commit)
from a7a0ff00601b60ec9ca1db80a3f232c960a6722f (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 7b04f97e186aa9e9b3a4cda775e79fafc35e3843
Merge: d09b192 07a72f6
Author: Tomek Mrugalski <tomasz at isc.org>
Date: Wed Oct 16 19:13:50 2013 +0200
Merge branch 'trac3195' into pd-ietf-demo
commit d09b192a99b643b60c8129fa2025ac94201a5226
Merge: a7a0ff0 a7cbda0
Author: Tomek Mrugalski <tomasz at isc.org>
Date: Wed Oct 16 19:13:42 2013 +0200
Merge branch 'trac3194' into pd-ietf-demo
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 14 ++
src/bin/dhcp4/config_parser.cc | 7 +
src/bin/dhcp4/dhcp4_srv.cc | 80 ++++++
src/bin/dhcp4/dhcp4_srv.h | 12 +
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 89 ++++++-
src/bin/dhcp4/tests/wireshark.cc | 2 +
src/bin/dhcp6/config_parser.cc | 7 +
src/bin/dhcp6/dhcp6_srv.cc | 71 ++++++
src/bin/dhcp6/dhcp6_srv.h | 9 +
src/bin/dhcp6/tests/config_parser_unittest.cc | 112 +++++++++
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 208 +++++++++++++++-
src/lib/dhcp/Makefile.am | 1 +
src/lib/dhcp/docsis3_option_defs.h | 65 +++++
src/lib/dhcp/libdhcp++.cc | 327 +++++++++++++++++++++++--
src/lib/dhcp/libdhcp++.h | 26 ++
src/lib/dhcp/option_definition.h | 4 +
src/lib/dhcp/option_vendor.cc | 68 +++++
src/lib/dhcp/option_vendor.h | 104 ++++++++
src/lib/dhcp/std_option_defs.h | 2 +
src/lib/dhcp/tests/iface_mgr_unittest.cc | 116 +++++++++
src/lib/dhcpsrv/cfgmgr.h | 2 +-
src/lib/dhcpsrv/dhcp_parsers.cc | 69 +++++-
src/lib/dhcpsrv/dhcp_parsers.h | 12 +-
src/lib/dhcpsrv/option_space_container.h | 13 +-
src/lib/dhcpsrv/subnet.cc | 32 +++
src/lib/dhcpsrv/subnet.h | 18 +-
src/lib/dhcpsrv/tests/subnet_unittest.cc | 70 ++++++
27 files changed, 1500 insertions(+), 40 deletions(-)
create mode 100644 src/lib/dhcp/docsis3_option_defs.h
create mode 100644 src/lib/dhcp/option_vendor.cc
create mode 100644 src/lib/dhcp/option_vendor.h
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index a8af862..33a8c63 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+6XX. [func] tomek
+ b10-dhcp6 is now able to listen on global IPv6 unicast addresses.
+ (Trac #3195, git ABCD)
+
+6XX. [bug] tomek
+ b10-dhcp6 now handles exceptions better when processing initial
+ configuration. In particular, errors with socket binding do not
+ prevent b10-dhcp6 from establishing configuration session anymore.
+ (Trac #3195, git ABCD)
+
+6XX. [bug] tomek
+ b10-dhcp6 now handles IPv6 interface enabling correctly.
+ (Trac #3195, git ABCD)
+
690. [bug] tomek
b10-dhcp4: Relay Agent Info option is now echoed back in
DHCPv4 responses.
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 98393c1..ddf5ed4 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -96,6 +96,13 @@ protected:
<< " for DHCPv6 server");
}
+ // Check if this is a vendor-option. If it is, get vendor-specific
+ // definition.
+ uint32_t vendor_id = SubnetConfigParser::optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ def = LibDHCP::getVendorOptionDef(Option::V4, vendor_id, option_code);
+ }
+
return (def);
}
};
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index f2903ba..aeb4729 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -20,7 +20,9 @@
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
#include <dhcp/pkt4.h>
+#include <dhcp/docsis3_option_defs.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcpsrv/addr_utilities.h>
@@ -36,6 +38,7 @@
#include <boost/algorithm/string/erase.hpp>
#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
#include <iomanip>
#include <fstream>
@@ -644,6 +647,62 @@ Dhcpv4Srv::appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
}
void
+Dhcpv4Srv::appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+ // Get the configured subnet suitable for the incoming packet.
+ Subnet4Ptr subnet = selectSubnet(question);
+ // Leave if there is no subnet matching the incoming packet.
+ // There is no need to log the error message here because
+ // it will be logged in the assignLease() when it fails to
+ // pick the suitable subnet. We don't want to duplicate
+ // error messages in such case.
+ if (!subnet) {
+ return;
+ }
+
+ // Try to get the vendor option
+ boost::shared_ptr<OptionVendor> vendor_req =
+ boost::dynamic_pointer_cast<OptionVendor>(question->getOption(DHO_VIVSO_SUBOPTIONS));
+ if (!vendor_req) {
+ return;
+ }
+
+ uint32_t vendor_id = vendor_req->getVendorId();
+
+ // Let's try to get ORO within that vendor-option
+ /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
+ /// may have different policies.
+ OptionPtr oro = vendor_req->getOption(DOCSIS3_V4_ORO);
+
+ /// @todo: see OPT_UINT8_TYPE definition in OptionDefinition::optionFactory().
+ /// I think it should be OptionUint8Array, not OptionGeneric
+
+ // Option ORO not found. Don't do anything then.
+ if (!oro) {
+ return;
+ }
+
+ boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V4, vendor_id));
+
+ // Get the list of options that client requested.
+ bool added = false;
+ const OptionBuffer& requested_opts = oro->getData();
+
+ for (OptionBuffer::const_iterator code = requested_opts.begin();
+ code != requested_opts.end(); ++code) {
+ Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, *code);
+ if (desc.option) {
+ vendor_rsp->addOption(desc.option);
+ added = true;
+ }
+ }
+
+ if (added) {
+ answer->addOption(vendor_rsp);
+ }
+}
+
+
+void
Dhcpv4Srv::appendBasicOptions(const Pkt4Ptr& question, Pkt4Ptr& msg) {
// Identify options that we always want to send to the
// client (if they are configured).
@@ -858,6 +917,7 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
copyDefaultFields(discover, offer);
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(discover, offer);
+ appendRequestedVendorOptions(discover, offer);
assignLease(discover, offer);
@@ -881,6 +941,7 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
copyDefaultFields(request, ack);
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(request, ack);
+ appendRequestedVendorOptions(request, ack);
// Note that we treat REQUEST message uniformly, regardless if this is a
// first request (requesting for new address), renewing existing address
@@ -1226,6 +1287,25 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
<< "-byte long buffer.");
}
+ /// @todo: Not sure if this is needed. Perhaps it would be better to extend
+ /// DHO_VIVSO_SUBOPTIONS definitions in std_option_defs.h to cover
+ /// OptionVendor class?
+ if (opt_type == DHO_VIVSO_SUBOPTIONS) {
+ if (offset + 4 > buf.size()) {
+ // Truncated vendor-option. There is expected at least 4 bytes
+ // long enterprise-id field
+ return (offset);
+ }
+
+ // Parse this as vendor option
+ OptionPtr vendor_opt(new OptionVendor(Option::V4, buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ options.insert(std::make_pair(opt_type, vendor_opt));
+
+ 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
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 5c89c85..66fe3a6 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -227,6 +227,18 @@ protected:
/// @param msg outgoing message (options will be added here)
void appendRequestedOptions(const Pkt4Ptr& question, Pkt4Ptr& msg);
+ /// @brief Appends requested vendor options as requested by client.
+ ///
+ /// This method is similar to \ref appendRequestedOptions(), but uses
+ /// vendor options. The major difference is that vendor-options use
+ /// its own option spaces (there may be more than one distinct set of vendor
+ /// options, each with unique vendor-id). Vendor options are requested
+ /// using separate options within their respective vendor-option spaces.
+ ///
+ /// @param question DISCOVER or REQUEST message from a client.
+ /// @param msg outgoing message (options will be added here)
+ void appendRequestedVendorOptions(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
/// @brief Assigns a lease and appends corresponding options
///
/// This method chooses the most appropriate lease for reqesting
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 3f2da84..4d2ba56 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -25,8 +25,10 @@
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_custom.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_vendor.h>
#include <dhcp/pkt_filter.h>
#include <dhcp/pkt_filter_inet.h>
+#include <dhcp/docsis3_option_defs.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/config_parser.h>
@@ -1138,11 +1140,7 @@ TEST_F(Dhcpv4SrvTest, ServerID) {
EXPECT_EQ(srvid_text, text);
}
-// Checks if callouts installed on pkt4_receive are indeed called and the
-// all necessary parameters are passed.
-//
-// Note that the test name does not follow test naming convention,
-// but the proper hook name is "buffer4_receive".
+// Checks if received relay agent info option is echoed back to the client
TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
NakedDhcpv4Srv srv(0);
@@ -1180,6 +1178,87 @@ TEST_F(Dhcpv4SrvTest, relayAgentInfoEcho) {
EXPECT_TRUE(rai_response->equal(rai_query));
}
+// Checks if vendor options are parsed correctly and requested vendor options
+// are echoed back.
+TEST_F(Dhcpv4SrvTest, vendorOptionsDocsis) {
+
+ NakedDhcpv4Srv srv(0);
+
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-data\": [ {"
+ " \"name\": \"tftp-servers\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 2,"
+ " \"data\": \"10.253.175.16\","
+ " \"csv-format\": True"
+ " }],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"10.254.226.0/25\" ],"
+ " \"subnet\": \"10.254.226.0/24\", "
+ " \"interface\": \"" + valid_iface_ + "\" "
+ " }, {"
+ " \"pool\": [ \"192.0.3.0/25\" ],"
+ " \"subnet\": \"192.0.3.0/24\" "
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+ ConstElementPtr status;
+
+ // Configure the server and make sure the config is accepted
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Let's create a relayed DISCOVER. This particular relayed DISCOVER has
+ // added option 82 (relay agent info) with 3 suboptions. The server
+ // is supposed to echo it back in its response.
+ Pkt4Ptr dis;
+ ASSERT_NO_THROW(dis = captureRelayedDiscover());
+
+ // Simulate that we have received that traffic
+ srv.fakeReceive(dis);
+
+ // Server will now process to run its normal loop, but instead of calling
+ // IfaceMgr::receive4(), it will read all packets from the list set by
+ // fakeReceive()
+ // In particular, it should call registered buffer4_receive callback.
+ srv.run();
+
+ // Check that the server did send a reposonse
+ ASSERT_EQ(1, srv.fake_sent_.size());
+
+ // Make sure that we received a response
+ Pkt4Ptr offer = srv.fake_sent_.front();
+ ASSERT_TRUE(offer);
+
+ // Get Relay Agent Info from query...
+ OptionPtr vendor_opt_response = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+ ASSERT_TRUE(vendor_opt_response);
+
+ // Check if it's of a correct type
+ boost::shared_ptr<OptionVendor> vendor_opt =
+ boost::dynamic_pointer_cast<OptionVendor>(vendor_opt_response);
+ ASSERT_TRUE(vendor_opt);
+
+ // Get Relay Agent Info from response...
+ OptionPtr tftp_servers_generic = vendor_opt->getOption(DOCSIS3_V4_TFTP_SERVERS);
+ ASSERT_TRUE(tftp_servers_generic);
+
+ Option4AddrLstPtr tftp_servers =
+ boost::dynamic_pointer_cast<Option4AddrLst>(tftp_servers_generic);
+
+ ASSERT_TRUE(tftp_servers);
+
+ Option4AddrLst::AddressContainer addrs = tftp_servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.253.175.16", addrs[0].toText());
+}
+
+
/// @todo Implement tests for subnetSelect See tests in dhcp6_srv_unittest.cc:
/// selectSubnetAddr, selectSubnetIface, selectSubnetRelayLinkaddr,
/// selectSubnetRelayInterfaceId. Note that the concept of interface-id is not
diff --git a/src/bin/dhcp4/tests/wireshark.cc b/src/bin/dhcp4/tests/wireshark.cc
index b563354..80b4737 100644
--- a/src/bin/dhcp4/tests/wireshark.cc
+++ b/src/bin/dhcp4/tests/wireshark.cc
@@ -100,6 +100,8 @@ Bootstrap Protocol
Option: (55) Parameter Request List
Option: (60) Vendor class identifier
Option: (125) V-I Vendor-specific Information
+ - suboption 1 (Option Request): requesting option 2
+ - suboption 5 (Modem Caps): 117 bytes
Option: (43) Vendor-Specific Information (CableLabs)
Option: (61) Client identifier
Option: (57) Maximum DHCP Message Size
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index fdf0bae..0656297 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -111,6 +111,13 @@ protected:
<< " for DHCPv4 server");
}
+ // Check if this is a vendor-option. If it is, get vendor-specific
+ // definition.
+ uint32_t vendor_id = SubnetConfigParser::optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ def = LibDHCP::getVendorOptionDef(Option::V6, vendor_id, option_code);
+ }
+
return def;
}
};
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 381d7c0..75654b9 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp_ddns/ncr_msg.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
#include <dhcp/duid.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/libdhcp++.h>
@@ -26,6 +27,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaprefix.h>
#include <dhcp/option_custom.h>
+#include <dhcp/option_vendor.h>
#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
@@ -683,6 +685,57 @@ Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
+void
+Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer) {
+ // Get the configured subnet suitable for the incoming packet.
+ Subnet6Ptr subnet = selectSubnet(question);
+ // Leave if there is no subnet matching the incoming packet.
+ // There is no need to log the error message here because
+ // it will be logged in the assignLease() when it fails to
+ // pick the suitable subnet. We don't want to duplicate
+ // error messages in such case.
+ if (!subnet) {
+ return;
+ }
+
+ // Try to get the vendor option
+ boost::shared_ptr<OptionVendor> vendor_req =
+ boost::dynamic_pointer_cast<OptionVendor>(question->getOption(D6O_VENDOR_OPTS));
+ if (!vendor_req) {
+ return;
+ }
+
+ uint32_t vendor_id = vendor_req->getVendorId();
+
+ // Let's try to get ORO within that vendor-option
+ /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
+ /// may have different policies.
+ boost::shared_ptr<OptionUint16Array> oro =
+ boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO));
+
+ // Option ORO not found. Don't do anything then.
+ if (!oro) {
+ return;
+ }
+
+ boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
+
+ // Get the list of options that client requested.
+ bool added = false;
+ const std::vector<uint16_t>& requested_opts = oro->getValues();
+ BOOST_FOREACH(uint16_t opt, requested_opts) {
+ Subnet::OptionDescriptor desc = subnet->getVendorOptionDescriptor(vendor_id, opt);
+ if (desc.option) {
+ vendor_rsp->addOption(desc.option);
+ added = true;
+ }
+ }
+
+ if (added) {
+ answer->addOption(vendor_rsp);
+ }
+}
+
OptionPtr
Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
// @todo This function uses OptionCustom class to manage contents
@@ -2114,6 +2167,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
copyDefaultOptions(solicit, advertise);
appendDefaultOptions(solicit, advertise);
appendRequestedOptions(solicit, advertise);
+ appendRequestedVendorOptions(solicit, advertise);
Option6ClientFqdnPtr fqdn = processClientFqdn(solicit);
assignLeases(solicit, advertise, fqdn);
@@ -2135,6 +2189,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
copyDefaultOptions(request, reply);
appendDefaultOptions(request, reply);
appendRequestedOptions(request, reply);
+ appendRequestedVendorOptions(request, reply);
Option6ClientFqdnPtr fqdn = processClientFqdn(request);
assignLeases(request, reply, fqdn);
@@ -2310,6 +2365,22 @@ Dhcpv6Srv::unpackOptions(const OptionBuffer& buf,
continue;
}
+ if (opt_type == D6O_VENDOR_OPTS) {
+ if (offset + 4 > length) {
+ // Truncated vendor-option. There is expected at least 4 bytes
+ // long enterprise-id field
+ return (offset);
+ }
+
+ // Parse this as vendor option
+ OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ options.insert(std::make_pair(opt_type, vendor_opt));
+
+ 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
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 65edd77..577c075 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -336,6 +336,15 @@ protected:
/// @param answer server's message (options will be added here)
void appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @brief Appends requested vendor options to server's answer.
+ ///
+ /// This is mostly useful for Cable Labs options for now, but the method
+ /// is easily extensible to other vendors.
+ ///
+ /// @param question client's message
+ /// @param answer server's message (vendor options will be added here)
+ void appendRequestedVendorOptions(const Pkt6Ptr& question, Pkt6Ptr& answer);
+
/// @brief Assigns leases.
///
/// It supports addresses (IA_NA) only. It does NOT support temporary
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index acfc270..c4b96ca 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -2049,6 +2049,118 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
EXPECT_EQ(1516, optionIA->getT2());
}
+// This test checks if vendor options can be specified in the config file
+// (in hex format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
+
+ // This configuration string is to configure two options
+ // sharing the code 1 and belonging to the different vendor spaces.
+ // (different vendor-id values).
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"option-one\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 100,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"option-two\","
+ " \"space\": \"vendor-1234\","
+ " \"code\": 100,"
+ " \"data\": \"1234\","
+ " \"csv-format\": False"
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+
+ // Try to get the option from the vendor space 4491
+ Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(100, desc1.option->getType());
+ // Try to get the option from the vendor space 1234
+ Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(1234, 100);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(100, desc1.option->getType());
+
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getVendorOptionDescriptor(5678, 38);
+ ASSERT_FALSE(desc3.option);
+}
+
+// This test checks if vendor options can be specified in the config file,
+// (in csv format), and later retrieved from configured subnet
+TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
+
+ // This configuration string is to configure two options
+ // sharing the code 1 and belonging to the different vendor spaces.
+ // (different vendor-id values).
+ string config = "{ \"interfaces\": [ \"*\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"foo\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 100,"
+ " \"data\": \"this is a string vendor-opt\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-4491\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now available for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+
+ // Try to get the option from the vendor space 4491
+ Subnet::OptionDescriptor desc1 = subnet->getVendorOptionDescriptor(4491, 100);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(100, desc1.option->getType());
+
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc2 = subnet->getVendorOptionDescriptor(5678, 38);
+ ASSERT_FALSE(desc2.option);
+}
+
// The goal of this test is to verify that the standard option can
// be configured to encapsulate multiple other options.
TEST_F(Dhcp6ParserTest, stdOptionDataEncapsulate) {
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index ba8b8da..26a66c5 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -25,10 +25,13 @@
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_int.h>
+#include <dhcp/option_vendor.h>
#include <dhcp/option_int_array.h>
+#include <dhcp/option_string.h>
#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp/dhcp6.h>
+#include <dhcp/docsis3_option_defs.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
@@ -2014,9 +2017,210 @@ TEST_F(Dhcpv6SrvTest, docsisTraffic) {
ASSERT_FALSE(srv.fake_sent_.empty());
Pkt6Ptr adv = srv.fake_sent_.front();
ASSERT_TRUE(adv);
+}
+
+// Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
+TEST_F(Dhcpv6SrvTest, docsisVendorOptionsParse) {
+
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt6Ptr sol = captureDocsisRelayedSolicit();
+ EXPECT_NO_THROW(sol->unpack());
+
+ // Check if the packet contain
+ OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+
+ EXPECT_TRUE(vendor->getOption(1));
+ EXPECT_TRUE(vendor->getOption(36));
+ EXPECT_TRUE(vendor->getOption(35));
+ EXPECT_TRUE(vendor->getOption(2));
+ EXPECT_TRUE(vendor->getOption(3));
+ EXPECT_TRUE(vendor->getOption(4));
+ EXPECT_TRUE(vendor->getOption(5));
+ EXPECT_TRUE(vendor->getOption(6));
+ EXPECT_TRUE(vendor->getOption(7));
+ EXPECT_TRUE(vendor->getOption(8));
+ EXPECT_TRUE(vendor->getOption(9));
+ EXPECT_TRUE(vendor->getOption(10));
+ EXPECT_TRUE(vendor->getOption(15));
+
+ EXPECT_FALSE(vendor->getOption(20));
+ EXPECT_FALSE(vendor->getOption(11));
+ EXPECT_FALSE(vendor->getOption(17));
+}
+
+// Checks if server is able to parse incoming docsis option and extract suboption 1 (docsis ORO)
+TEST_F(Dhcpv6SrvTest, docsisVendorORO) {
+
+ NakedDhcpv6Srv srv(0);
+
+ // Let's get a traffic capture from DOCSIS3.0 modem
+ Pkt6Ptr sol = captureDocsisRelayedSolicit();
+ EXPECT_NO_THROW(sol->unpack());
+
+ // Check if the packet contain
+ OptionPtr opt = sol->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(opt);
+
+ boost::shared_ptr<OptionVendor> vendor = boost::dynamic_pointer_cast<OptionVendor>(opt);
+ ASSERT_TRUE(vendor);
+
+ opt = vendor->getOption(DOCSIS3_V6_ORO);
+ ASSERT_TRUE(opt);
+
+ OptionUint16ArrayPtr oro = boost::dynamic_pointer_cast<OptionUint16Array>(opt);
+ EXPECT_TRUE(oro);
+}
+
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the requested options are actually assigned.
+TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
+ ConstElementPtr x;
+ string config = "{ \"interfaces\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-def\": [ {"
+ " \"name\": \"config-file\","
+ " \"code\": 33,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"vendor-4491\","
+ " \"encapsulate\": \"\""
+ " } ],"
+ " \"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": 33,"
+ " \"data\": \"normal_erouter_v6.cm\","
+ " \"csv-format\": True"
+ " }],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/64\" ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"renew-timer\": 1000, "
+ " \"rebind-timer\": 1000, "
+ " \"preferred-lifetime\": 3000,"
+ " \"valid-lifetime\": 4000,"
+ " \"interface-id\": \"\","
+ " \"interface\": \"\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ NakedDhcpv6Srv srv(0);
+
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+
+ ASSERT_EQ(0, rcode_);
+
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("fe80::abcd"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
+
+ // Pass it to the server and get an advertise
+ Pkt6Ptr adv = srv.processSolicit(sol);
+
+ // check if we get response at all
+ ASSERT_TRUE(adv);
+
+ // We did not include any vendor opts in SOLCIT, so there should be none
+ // in ADVERTISE.
+ ASSERT_FALSE(adv->getOption(D6O_VENDOR_OPTS));
+
+ // Let's add a vendor-option (vendor-id=4491) with a single sub-option.
+ // That suboption has code 1 and is a docsis ORO option.
+ boost::shared_ptr<OptionUint16Array> vendor_oro(new OptionUint16Array(Option::V6,
+ DOCSIS3_V6_ORO));
+ vendor_oro->addValue(DOCSIS3_V6_CONFIG_FILE); // Request option 33
+ OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+ vendor->addOption(vendor_oro);
+ sol->addOption(vendor);
- /// @todo Check that the ADVERTISE is ok, that it includes all options,
- /// that is relayed properly, etc.
+ // Need to process SOLICIT again after requesting new option.
+ adv = srv.processSolicit(sol);
+ ASSERT_TRUE(adv);
+
+ // Check if thre is vendor option response
+ OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
+ ASSERT_TRUE(tmp);
+
+ // The response should be OptionVendor object
+ boost::shared_ptr<OptionVendor> vendor_resp =
+ boost::dynamic_pointer_cast<OptionVendor>(tmp);
+ ASSERT_TRUE(vendor_resp);
+
+ OptionPtr docsis33 = vendor_resp->getOption(33);
+ ASSERT_TRUE(docsis33);
+
+ OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+ ASSERT_TRUE(config_file);
+ EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
+
+// Test checks whether it is possible to use option definitions defined in
+// src/lib/dhcp/docsis3_option_defs.h.
+TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
+ ConstElementPtr x;
+ string config_prefix = "{ \"interfaces\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ " \"option-data\": [ {"
+ " \"name\": \"config-file\","
+ " \"space\": \"vendor-4491\","
+ " \"code\": ";
+ string config_postfix = ","
+ " \"data\": \"normal_erouter_v6.cm\","
+ " \"csv-format\": True"
+ " }],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/64\" ],"
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"renew-timer\": 1000, "
+ " \"rebind-timer\": 1000, "
+ " \"preferred-lifetime\": 3000,"
+ " \"valid-lifetime\": 4000,"
+ " \"interface-id\": \"\","
+ " \"interface\": \"\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ // There is docsis3 (vendor-id=4491) vendor option 33, which is a
+ // config-file. Its format is a single string.
+ string config_valid = config_prefix + "33" + config_postfix;
+
+ // There is no option 99 defined in vendor-id=4491. As there is no
+ // definition, the config should fail.
+ string config_bogus = config_prefix + "99" + config_postfix;
+
+ ElementPtr json_bogus = Element::fromJSON(config_bogus);
+ ElementPtr json_valid = Element::fromJSON(config_valid);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This should fail (missing option definition)
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_bogus));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+
+ // This should work (option definition present)
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json_valid));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
}
// This test verifies that the following option structure can be parsed:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 7402dbe..316039b 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -29,6 +29,7 @@ libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_iaprefix.cc option6_iaprefix.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
libb10_dhcp___la_SOURCES += option6_client_fqdn.cc option6_client_fqdn.h
+libb10_dhcp___la_SOURCES += option_vendor.cc option_vendor.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
libb10_dhcp___la_SOURCES += option.cc option.h
diff --git a/src/lib/dhcp/docsis3_option_defs.h b/src/lib/dhcp/docsis3_option_defs.h
new file mode 100644
index 0000000..c8cb606
--- /dev/null
+++ b/src/lib/dhcp/docsis3_option_defs.h
@@ -0,0 +1,65 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DOCSIS3_OPTION_DEFS_H
+#define DOCSIS3_OPTION_DEFS_H
+
+#include <dhcp/std_option_defs.h>
+#include <dhcp/option_data_types.h>
+
+
+namespace {
+
+#define VENDOR_ID_CABLE_LABS 4491
+
+#define DOCSIS3_V4_ORO 1
+#define DOCSIS3_V4_TFTP_SERVERS 2
+
+/// @brief Definitions of standard DHCPv4 options.
+const OptionDefParams DOCSIS3_V4_DEFS[] = {
+ { "oro", DOCSIS3_V4_ORO, OPT_UINT8_TYPE, true, NO_RECORD_DEF, "" },
+ { "tftp-servers", DOCSIS3_V4_TFTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF, "" }
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V4_DEFS_SIZE = sizeof(DOCSIS3_V4_DEFS) / sizeof(OptionDefParams);
+
+#define DOCSIS3_V6_ORO 1
+#define DOCSIS3_V6_DEVICE_TYPE 2
+#define DOCSIS3_V6_VENDOR_NAME 10
+#define DOCSIS3_V6_TFTP_SERVERS 32
+#define DOCSIS3_V6_CONFIG_FILE 33
+#define DOCSIS3_V6_SYSLOG_SERVERS 34
+#define DOCSIS3_V6_TIME_SERVERS 37
+#define DOCSIS3_V6_TIME_OFFSET 38
+
+/// @brief Definitions of standard DHCPv6 options.
+const OptionDefParams DOCSIS3_V6_DEFS[] = {
+ { "oro", DOCSIS3_V6_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF, "" },
+ { "device-type", DOCSIS3_V6_DEVICE_TYPE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "vendor-type", DOCSIS3_V6_VENDOR_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "tftp-servers", DOCSIS3_V6_TFTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "time-servers", DOCSIS3_V6_TFTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "config-file", DOCSIS3_V6_CONFIG_FILE, OPT_STRING_TYPE, false, NO_RECORD_DEF, "" },
+ { "syslog-servers", DOCSIS3_V6_SYSLOG_SERVERS, OPT_IPV6_ADDRESS_TYPE, true, NO_RECORD_DEF, "" },
+ { "time-offset", DOCSIS3_V6_TIME_OFFSET, OPT_INT32_TYPE, false, NO_RECORD_DEF, "" }
+ // @todo add definitions for all remaning options.
+};
+
+/// Number of option definitions defined.
+const int DOCSIS3_V6_DEFS_SIZE = sizeof(DOCSIS3_V6_DEFS) / sizeof(OptionDefParams);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 351fe8c..524d56a 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -18,13 +18,16 @@
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option.h>
+#include <dhcp/option_vendor.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option_definition.h>
#include <dhcp/option_int_array.h>
#include <dhcp/std_option_defs.h>
+#include <dhcp/docsis3_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
+#include <dhcp/option_definition.h>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
@@ -45,17 +48,29 @@ OptionDefContainer LibDHCP::v4option_defs_;
// Static container with DHCPv6 option definitions.
OptionDefContainer LibDHCP::v6option_defs_;
+VendorOptionDefContainers LibDHCP::vendor4_defs_;
+
+VendorOptionDefContainers LibDHCP::vendor6_defs_;
+
+// Let's keep it in .cc file. Moving it to .h would require including optionDefParams
+// definitions there
+void initOptionSpace(OptionDefContainer& defs,
+ const OptionDefParams* params,
+ size_t params_size);
+
const OptionDefContainer&
LibDHCP::getOptionDefs(const Option::Universe u) {
switch (u) {
case Option::V4:
if (v4option_defs_.empty()) {
initStdOptionDefs4();
+ initVendorOptsDocsis4();
}
return (v4option_defs_);
case Option::V6:
if (v6option_defs_.empty()) {
initStdOptionDefs6();
+ initVendorOptsDocsis6();
}
return (v6option_defs_);
default:
@@ -63,6 +78,38 @@ LibDHCP::getOptionDefs(const Option::Universe u) {
}
}
+const OptionDefContainer*
+LibDHCP::getVendorOption4Defs(uint32_t vendor_id) {
+
+ if (vendor_id == VENDOR_ID_CABLE_LABS &&
+ vendor4_defs_.find(VENDOR_ID_CABLE_LABS) == vendor4_defs_.end()) {
+ initVendorOptsDocsis4();
+ }
+
+ VendorOptionDefContainers::const_iterator def = vendor4_defs_.find(vendor_id);
+ if (def == vendor4_defs_.end()) {
+ // No such vendor-id space
+ return (NULL);
+ }
+ return (&(def->second));
+}
+
+const OptionDefContainer*
+LibDHCP::getVendorOption6Defs(uint32_t vendor_id) {
+
+ if (vendor_id == VENDOR_ID_CABLE_LABS &&
+ vendor6_defs_.find(VENDOR_ID_CABLE_LABS) == vendor6_defs_.end()) {
+ initVendorOptsDocsis6();
+ }
+
+ VendorOptionDefContainers::const_iterator def = vendor6_defs_.find(vendor_id);
+ if (def == vendor6_defs_.end()) {
+ // No such vendor-id space
+ return (NULL);
+ }
+ return (&(def->second));
+}
+
OptionDefinitionPtr
LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
const OptionDefContainer& defs = getOptionDefs(u);
@@ -74,6 +121,31 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
return (OptionDefinitionPtr());
}
+OptionDefinitionPtr
+LibDHCP::getVendorOptionDef(const Option::Universe u, const uint32_t vendor_id,
+ const uint16_t code) {
+ const OptionDefContainer* defs = NULL;
+ if (u == Option::V4) {
+ defs = getVendorOption4Defs(vendor_id);
+ } else if (u == Option::V6) {
+ defs = getVendorOption6Defs(vendor_id);
+ }
+
+ if (!defs) {
+ // Weird universe or unknown vendor_id. We don't care. No definitions
+ // one way or another
+ // What is it anyway?
+ return (OptionDefinitionPtr());
+ }
+
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+ return (OptionDefinitionPtr());
+}
+
bool
LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
if (u == Option::V6) {
@@ -164,6 +236,23 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
continue;
}
+ if (opt_type == D6O_VENDOR_OPTS) {
+ if (offset + 4 > length) {
+ // Truncated vendor-option. There is expected at least 4 bytes
+ // long enterprise-id field
+ return (offset);
+ }
+
+ // Parse this as vendor option
+ OptionPtr vendor_opt(new OptionVendor(Option::V6, buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ options.insert(std::make_pair(opt_type, vendor_opt));
+
+ 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
@@ -280,6 +369,191 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
return (offset);
}
+size_t LibDHCP::unpackVendorOptions6(uint32_t vendor_id,
+ const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options) {
+ size_t offset = 0;
+ size_t length = buf.size();
+
+ // Get the list of option definitions for this particular vendor-id
+ const OptionDefContainer* option_defs = LibDHCP::getVendorOption6Defs(vendor_id);
+
+ // Get the search index #1. It allows to search for option definitions
+ // using option code. If there's no such vendor-id space, we're out of luck
+ // anyway.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ 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);
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ // If there is a definition for such a vendor option...
+ if (idx) {
+ // 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);
+
+ 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 == 1) {
+ // 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);
+ }
+ }
+
+ // This can happen in one of 2 cases:
+ // 1. we do not have definitions for that vendor-space
+ // 2. we do have definitions, but that particular option was not defined
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ // add option to options
+ if (opt) {
+ options.insert(std::make_pair(opt_type, opt));
+ }
+ offset += opt_len;
+ }
+
+ return (offset);
+}
+
+size_t LibDHCP::unpackVendorOptions4(uint32_t vendor_id, const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options) {
+ size_t offset = 0;
+
+ // Get the list of stdandard option definitions.
+ const OptionDefContainer* option_defs = LibDHCP::getVendorOption4Defs(vendor_id);
+ // Get the search index #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex* idx = NULL;
+ if (option_defs) {
+ 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()) {
+
+ // Note that Vendor-Specific info option (RFC3925) has a different option
+ // format than Vendor-Spec info for DHCPv6. (there's additional layer of
+ // data-length
+ uint8_t data_len = buf[offset++];
+
+ if (offset + data_len > buf.size()) {
+ // Truncated data-option
+ return (offset);
+ }
+
+ uint8_t offset_end = offset + data_len;
+
+ // beginning of data-chunk parser
+ while (offset + 1 <= offset_end) {
+ 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.");
+ }
+
+ OptionPtr opt;
+ opt.reset();
+
+ if (idx) {
+ // 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);
+
+ 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 == 1) {
+ // 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);
+ }
+ }
+
+ if (!opt) {
+ opt = OptionPtr(new Option(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ }
+
+ options.insert(std::make_pair(opt_type, opt));
+ offset += opt_len;
+
+ } // end of data-chunk
+
+ }
+ return (offset);
+}
+
+
+
void
LibDHCP::packOptions(isc::util::OutputBuffer& buf,
const OptionCollection& options) {
@@ -385,13 +659,30 @@ LibDHCP::initStdOptionDefs4() {
void
LibDHCP::initStdOptionDefs6() {
- v6option_defs_.clear();
+ initOptionSpace(v6option_defs_, OPTION_DEF_PARAMS6, OPTION_DEF_PARAMS_SIZE6);
+}
- for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
- std::string encapsulates(OPTION_DEF_PARAMS6[i].encapsulates);
- if (!encapsulates.empty() && OPTION_DEF_PARAMS6[i].array) {
+void
+LibDHCP::initVendorOptsDocsis4() {
+ initOptionSpace(vendor4_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE);
+}
+
+void
+LibDHCP::initVendorOptsDocsis6() {
+ vendor6_defs_[VENDOR_ID_CABLE_LABS] = OptionDefContainer();
+ initOptionSpace(vendor6_defs_[VENDOR_ID_CABLE_LABS], DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE);
+}
+
+void initOptionSpace(OptionDefContainer& defs,
+ const OptionDefParams* params,
+ size_t params_size) {
+ defs.clear();
+
+ for (int i = 0; i < params_size; ++i) {
+ std::string encapsulates(params[i].encapsulates);
+ if (!encapsulates.empty() && params[i].array) {
isc_throw(isc::BadValue, "invalid standard option definition: "
- << "option with code '" << OPTION_DEF_PARAMS6[i].code
+ << "option with code '" << params[i].code
<< "' may not encapsulate option space '"
<< encapsulates << "' because the definition"
<< " indicates that this option comprises an array"
@@ -404,33 +695,33 @@ LibDHCP::initStdOptionDefs6() {
OptionDefinitionPtr definition;
if (encapsulates.empty()) {
// Option does not encapsulate any option space.
- definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
- OPTION_DEF_PARAMS6[i].code,
- OPTION_DEF_PARAMS6[i].type,
- OPTION_DEF_PARAMS6[i].array));
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].type,
+ params[i].array));
} else {
// Option does encapsulate an option space.
- definition.reset(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
- OPTION_DEF_PARAMS6[i].code,
- OPTION_DEF_PARAMS6[i].type,
- OPTION_DEF_PARAMS6[i].encapsulates));
+ definition.reset(new OptionDefinition(params[i].name,
+ params[i].code,
+ params[i].type,
+ params[i].encapsulates));
}
- for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
- definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
+ for (int rec = 0; rec < params[i].records_size; ++rec) {
+ definition->addRecordField(params[i].records[rec]);
}
try {
definition->validate();
- } catch (const Exception& ex) {
+ } catch (const isc::Exception& ex) {
// This is unlikely event that validation fails and may
// be only caused by programming error. To guarantee the
// data consistency we clear all option definitions that
// have been added so far and pass the exception forward.
- v6option_defs_.clear();
+ defs.clear();
throw;
}
- v6option_defs_.push_back(definition);
+ defs.push_back(definition);
}
}
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index 71cf3f4..4931746 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -55,6 +55,11 @@ public:
static OptionDefinitionPtr getOptionDef(const Option::Universe u,
const uint16_t code);
+
+ static OptionDefinitionPtr getVendorOptionDef(const Option::Universe u,
+ const uint32_t vendor_id,
+ const uint16_t code);
+
/// @brief Check if the specified option is a standard option.
///
/// @param u universe (V4 or V6)
@@ -149,6 +154,19 @@ public:
uint16_t type,
Option::Factory * factory);
+ static const OptionDefContainer*
+ getVendorOption4Defs(uint32_t vendor_id);
+
+ static const OptionDefContainer*
+ getVendorOption6Defs(uint32_t vendor_id);
+
+ static size_t unpackVendorOptions6(uint32_t vendor_id,
+ const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options);
+
+ static size_t unpackVendorOptions4(uint32_t vendor_id, const OptionBuffer& buf,
+ isc::dhcp::OptionCollection& options);
+
private:
/// Initialize standard DHCPv4 option definitions.
@@ -170,6 +188,10 @@ private:
/// is incorrect. This is a programming error.
static void initStdOptionDefs6();
+ static void initVendorOptsDocsis4();
+
+ static void initVendorOptsDocsis6();
+
/// pointers to factories that produce DHCPv6 options
static FactoryMap v4factories_;
@@ -181,6 +203,10 @@ private:
/// Container with DHCPv6 option definitions.
static OptionDefContainer v6option_defs_;
+
+ static VendorOptionDefContainers vendor4_defs_;
+
+ static VendorOptionDefContainers vendor6_defs_;
};
}
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 878abeb..29fca63 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -23,6 +23,7 @@
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/shared_ptr.hpp>
+#include <map>
namespace isc {
namespace dhcp {
@@ -601,6 +602,9 @@ typedef boost::multi_index_container<
/// Pointer to an option definition container.
typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+/// Container that holds various vendor option containers
+typedef std::map<uint32_t, OptionDefContainer> VendorOptionDefContainers;
+
/// Type of the index #1 - option type.
typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
/// Pair of iterators to represent the range of options definitions
diff --git a/src/lib/dhcp/option_vendor.cc b/src/lib/dhcp/option_vendor.cc
new file mode 100644
index 0000000..22db1af
--- /dev/null
+++ b/src/lib/dhcp/option_vendor.cc
@@ -0,0 +1,68 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/option_vendor.h>
+
+using namespace isc::dhcp;
+
+OptionVendor::OptionVendor(Option::Universe u, uint32_t vendor_id)
+ :Option(u, u==Option::V4?DHO_VIVSO_SUBOPTIONS:D6O_VENDOR_OPTS), vendor_id_(vendor_id) {
+}
+
+OptionVendor::OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ :Option(u, u==Option::V4?DHO_VIVSO_SUBOPTIONS:D6O_VENDOR_OPTS), vendor_id_(0) {
+ unpack(begin, end);
+}
+
+
+void OptionVendor::pack(isc::util::OutputBuffer& buf) {
+ packHeader(buf);
+
+ buf.writeUint32(vendor_id_);
+
+ packOptions(buf);
+}
+
+void OptionVendor::unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (distance(begin, end) < sizeof(uint32_t)) {
+ isc_throw(OutOfRange, "Truncated vendor-specific information option");
+ }
+
+ vendor_id_ = isc::util::readUint32(&(*begin));
+
+ OptionBuffer vendor_buffer(begin +4, end);
+
+ if (universe_ == Option::V6) {
+ LibDHCP::unpackVendorOptions6(vendor_id_, vendor_buffer, options_);
+ } else {
+ LibDHCP::unpackVendorOptions4(vendor_id_, vendor_buffer, options_);
+ }
+}
+
+uint16_t OptionVendor::len() {
+ uint16_t length = getHeaderLen();
+
+ length += sizeof(uint32_t); // Vendor-id field
+
+ // length of all suboptions
+ for (OptionCollection::iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+}
diff --git a/src/lib/dhcp/option_vendor.h b/src/lib/dhcp/option_vendor.h
new file mode 100644
index 0000000..15ac2d2
--- /dev/null
+++ b/src/lib/dhcp/option_vendor.h
@@ -0,0 +1,104 @@
+// Copyright (C) 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_VENDOR_H
+#define OPTION_VENDOR_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// This class represents vendor-specific information option.
+class OptionVendor: public Option {
+
+public:
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id vendor enterprise-id (unique 32 bit integer)
+ OptionVendor(Option::Universe u, uint32_t vendor_id);
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This constructor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ /// @todo Extend constructor to set encapsulated option space name.
+ OptionVendor(Option::Universe u, OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ void pack(isc::util::OutputBuffer& buf);
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
+
+ /// @brief Set option value.
+ ///
+ /// @param value new option value.
+ void setVendorId(uint32_t vendor_id) { vendor_id_ = vendor_id; }
+
+ /// @brief Return option value.
+ ///
+ /// @return option value.
+ uint32_t getVendorId() const { return vendor_id_; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len();
+
+private:
+
+ uint32_t vendor_id_; ///< Enterprise-id
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_H
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
index 9a94266..3e0ec1f 100644
--- a/src/lib/dhcp/std_option_defs.h
+++ b/src/lib/dhcp/std_option_defs.h
@@ -16,6 +16,8 @@
#define STD_OPTION_DEFS_H
#include <dhcp/option_data_types.h>
+#include <dhcp/dhcp4.h>
+#include <dhcp/dhcp6.h>
namespace {
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 488ecb3..2518f46 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -145,6 +145,27 @@ public:
return (sockets_count);
}
+ /// @brief returns socket bound to a specific address (or NULL)
+ ///
+ /// A helper function, used to pick a socketinfo that is bound to a given
+ /// address.
+ ///
+ /// @param sockets sockets collection
+ /// @param addr address the socket is bound to
+ ///
+ /// @return socket info structure (or NULL)
+ const isc::dhcp::SocketInfo*
+ getSocketByAddr(const isc::dhcp::Iface::SocketCollection& sockets,
+ const IOAddress& addr) {
+ for (isc::dhcp::Iface::SocketCollection::const_iterator s =
+ sockets.begin(); s != sockets.end(); ++s) {
+ if (s->addr_ == addr) {
+ return (&(*s));
+ }
+ }
+ return (NULL);
+ }
+
};
// We need some known interface to work reliably. Loopback interface is named
@@ -1520,4 +1541,99 @@ TEST_F(IfaceMgrTest, controlSession) {
close(pipefd[0]);
}
+// Test checks if the unicast sockets can be opened.
+// This test is now disabled, because there is no reliable way to test it. We
+// can't even use loopback, beacuse openSockets() skips loopback interface
+// (as it should be, because DHCP server is not supposed to listen on loopback).
+TEST_F(IfaceMgrTest, DISABLED_openUnicastSockets) {
+ /// @todo Need to implement a test that is able to check whether we can open
+ /// unicast sockets. There are 2 problems with it:
+ /// 1. We need to have a non-link-local address on an interface that is
+ /// up, running, IPv6 and multicast capable
+ /// 2. We need that information on every OS that we run tests on. So far
+ /// we are only supporting interface detection in Linux.
+ ///
+ /// To achieve this, we will probably need a pre-test setup, similar to what
+ /// BIND9 is doing (i.e. configuring well known addresses on loopback).
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ // Get the interface (todo: which interface)
+ Iface* iface = ifacemgr->getIface("eth0");
+ ASSERT_TRUE(iface);
+ iface->inactive6_ = false;
+
+ // Tell the interface that it should bind to this global interface
+ EXPECT_NO_THROW(iface->addUnicast(IOAddress("2001:db8::1")));
+
+ // Tell IfaceMgr to open sockets. This should trigger at least 2 sockets
+ // to open on eth0: link-local and global. On some systems (Linux), an
+ // additional socket for multicast may be opened.
+ EXPECT_TRUE(ifacemgr->openSockets6(PORT1));
+
+ const Iface::SocketCollection& sockets = iface->getSockets();
+ ASSERT_GE(2, sockets.size());
+
+ // Global unicast should be first
+ EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("2001:db8::1")));
+ EXPECT_TRUE(getSocketByAddr(sockets, IOAddress("figure-out-link-local-addr")));
+}
+
+// This test requires addresses 2001:db8:15c::1/128 and fe80::1/64 to be
+// configured on loopback interface
+//
+// Useful commands:
+// ip a a 2001:db8:15c::1/128 dev lo
+// ip a a fe80::1/64 dev lo
+//
+// If you do not issue those commands before running this test, it will fail.
+TEST_F(IfaceMgrTest, DISABLED_getSocket) {
+ // Testing socket operation in a portable way is tricky
+ // without interface detection implemented.
+
+ scoped_ptr<NakedIfaceMgr> ifacemgr(new NakedIfaceMgr());
+
+ IOAddress lo_addr("::1");
+ IOAddress link_local("fe80::1");
+ IOAddress global("2001:db8:15c::1");
+
+ IOAddress dst_link_local("fe80::dead:beef");
+ IOAddress dst_global("2001:db8:15c::dead:beef");
+
+ // Bind loopback address
+ int socket1 = ifacemgr->openSocket(LOOPBACK, lo_addr, 10547);
+ EXPECT_GE(socket1, 0); // socket >= 0
+
+ // Bind link-local address
+ int socket2 = ifacemgr->openSocket(LOOPBACK, link_local, 10547);
+ EXPECT_GE(socket2, 0);
+
+ int socket3 = ifacemgr->openSocket(LOOPBACK, global, 10547);
+ EXPECT_GE(socket3, 0);
+
+ // Let's make sure those sockets are unique
+ EXPECT_NE(socket1, socket2);
+ EXPECT_NE(socket2, socket3);
+ EXPECT_NE(socket3, socket1);
+
+ // Create a packet
+ Pkt6 pkt6(DHCPV6_SOLICIT, 123);
+ pkt6.setIface(LOOPBACK);
+
+ // Check that packets sent to link-local will get socket bound to link local
+ pkt6.setLocalAddr(global);
+ pkt6.setRemoteAddr(dst_global);
+ EXPECT_EQ(socket3, ifacemgr->getSocket(pkt6));
+
+ // Check that packets sent to link-local will get socket bound to link local
+ pkt6.setLocalAddr(link_local);
+ pkt6.setRemoteAddr(dst_link_local);
+ EXPECT_EQ(socket2, ifacemgr->getSocket(pkt6));
+
+ // Close sockets here because the following tests will want to
+ // open sockets on the same ports.
+ ifacemgr->closeSockets();
+}
+
+
}
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index fc8bf9d..594490e 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -364,7 +364,7 @@ private:
/// A collection of option definitions that can be accessed
/// using option space name they belong to.
OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> option_def_spaces_;
+ OptionDefinitionPtr, std::string> option_def_spaces_;
/// @brief Container for defined DHCPv6 option spaces.
OptionSpaceCollection spaces6_;
diff --git a/src/lib/dhcpsrv/dhcp_parsers.cc b/src/lib/dhcpsrv/dhcp_parsers.cc
index 2684e2f..87425b1 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/dhcp_parsers.cc
@@ -430,6 +430,8 @@ OptionDataParser::createOption() {
<< "')");
}
+ const bool csv_format = boolean_values_->getParam("csv-format");
+
// Find the Option Definition for the option by its option code.
// findOptionDefinition will throw if not found, no need to test.
OptionDefinitionPtr def;
@@ -450,7 +452,10 @@ OptionDataParser::createOption() {
if (std::distance(range.first, range.second) > 0) {
def = *range.first;
}
- if (!def) {
+
+ // It's ok if we don't have option format if the option is
+ // specified as hex
+ if (!def && csv_format) {
isc_throw(DhcpConfigError, "definition for the option '"
<< option_space << "." << option_name
<< "' having code '" << option_code
@@ -460,7 +465,6 @@ OptionDataParser::createOption() {
// Get option data from the configuration database ('data' field).
const std::string option_data = string_values_->getParam("data");
- const bool csv_format = boolean_values_->getParam("csv-format");
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
@@ -1048,8 +1052,16 @@ SubnetConfigParser::createSubnet() {
}
// Add sub-options (if any).
appendSubOptions(option_space, desc.option);
- // In any case, we add the option to the subnet.
- subnet_->addOption(desc.option, false, option_space);
+
+ // thomson
+ uint32_t vendor_id = optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ // This is a vendor option
+ subnet_->addVendorOption(desc.option, false, vendor_id);
+ } else {
+ // This is a normal option
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
@@ -1078,12 +1090,59 @@ SubnetConfigParser::createSubnet() {
if (!existing_desc.option) {
// Add sub-options (if any).
appendSubOptions(option_space, desc.option);
- subnet_->addOption(desc.option, false, option_space);
+
+ uint32_t vendor_id = optionSpaceToVendorId(option_space);
+ if (vendor_id) {
+ // This is a vendor option
+ subnet_->addVendorOption(desc.option, false, vendor_id);
+ } else {
+ // This is a normal option
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
}
}
+uint32_t
+SubnetConfigParser::optionSpaceToVendorId(const std::string& option_space) {
+ if (option_space.size() < 8) {
+ // 8 is a minimal length of "vendor-X" format
+ return (0);
+ }
+ if (option_space.substr(0,7) != "vendor-") {
+ return (0);
+ }
+
+ // text after "vendor-", supposedly numbers only
+ string x = option_space.substr(7);
+
+ int64_t check;
+ try {
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ /// @todo: Should we throw here?
+ // isc_throw(BadValue, "Failed to parse vendor-X value (" << x
+ // << ") as unsigned 32-bit integer.");
+ return (0);
+ }
+ if (check > std::numeric_limits<uint32_t>::max()) {
+ /// @todo: Should we throw here?
+ //isc_throw(BadValue, "Value " << x << "is too large"
+ // << " for unsigned 32-bit integer.");
+ return (0);
+ }
+ if (check < 0) {
+ /// @todo: Should we throw here?
+ // isc_throw(BadValue, "Value " << x << "is negative."
+ // << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+ return (0);
+ }
+
+ // value is small enough to fit
+ return (static_cast<uint32_t>(check));
+}
+
isc::dhcp::Triplet<uint32_t>
SubnetConfigParser::getParam(const std::string& name) {
uint32_t value = 0;
diff --git a/src/lib/dhcpsrv/dhcp_parsers.h b/src/lib/dhcpsrv/dhcp_parsers.h
index 0829b21..738c556 100644
--- a/src/lib/dhcpsrv/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/dhcp_parsers.h
@@ -34,14 +34,14 @@ namespace dhcp {
/// @brief Storage for option definitions.
typedef OptionSpaceContainer<OptionDefContainer,
- OptionDefinitionPtr> OptionDefStorage;
+ OptionDefinitionPtr, std::string> OptionDefStorage;
/// @brief Shared pointer to option definitions storage.
typedef boost::shared_ptr<OptionDefStorage> OptionDefStoragePtr;
/// Collection of containers holding option spaces. Each container within
/// a particular option space holds so-called option descriptors.
typedef OptionSpaceContainer<Subnet::OptionContainer,
- Subnet::OptionDescriptor> OptionStorage;
+ Subnet::OptionDescriptor, std::string> OptionStorage;
/// @brief Shared pointer to option storage.
typedef boost::shared_ptr<OptionStorage> OptionStoragePtr;
@@ -773,6 +773,14 @@ public:
/// @brief Adds the created subnet to a server's configuration.
virtual void commit() = 0;
+ /// @brief tries to convert option_space string to numeric vendor_id
+ ///
+ /// This will work if the option_space has format "vendor-1234".
+ /// This is used to detect whether a given option-space is a vendor
+ /// space or not. Returns 0 if the format is different.
+ /// @return numeric vendor-id (or 0 if the format does not match)
+ static uint32_t optionSpaceToVendorId(const std::string& option_space);
+
protected:
/// @brief creates parsers for entries in subnet definition
///
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
index ba16fbb..6417207 100644
--- a/src/lib/dhcpsrv/option_space_container.h
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -31,7 +31,8 @@ namespace dhcp {
/// @tparam ContainerType of the container holding items within
/// option space.
/// @tparam ItemType type of the item being held by the container.
-template<typename ContainerType, typename ItemType>
+/// @tparam Selector a string (for option spaces) or uint32_t (for vendor options)
+template<typename ContainerType, typename ItemType, typename Selector>
class OptionSpaceContainer {
public:
@@ -42,7 +43,7 @@ public:
///
/// @param item reference to the item being added.
/// @param option_space name of the option space.
- void addItem(const ItemType& item, const std::string& option_space) {
+ void addItem(const ItemType& item, const Selector& option_space) {
ItemsContainerPtr items = getItems(option_space);
items->push_back(item);
option_space_map_[option_space] = items;
@@ -57,7 +58,7 @@ public:
/// @param option_space name of the option space.
///
/// @return pointer to the container holding items.
- ItemsContainerPtr getItems(const std::string& option_space) const {
+ ItemsContainerPtr getItems(const Selector& option_space) const {
const typename OptionSpaceMap::const_iterator& items =
option_space_map_.find(option_space);
if (items == option_space_map_.end()) {
@@ -73,8 +74,8 @@ public:
/// @todo This function is likely to be removed once
/// we create a structore of OptionSpaces defined
/// through the configuration manager.
- std::list<std::string> getOptionSpaceNames() {
- std::list<std::string> names;
+ std::list<Selector> getOptionSpaceNames() {
+ std::list<Selector> names;
for (typename OptionSpaceMap::const_iterator space =
option_space_map_.begin();
space != option_space_map_.end(); ++space) {
@@ -91,7 +92,7 @@ public:
private:
/// A map holding container (option space name is the key).
- typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+ typedef std::map<Selector, ItemsContainerPtr> OptionSpaceMap;
OptionSpaceMap option_space_map_;
};
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 4320733..d01fb3d 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -88,6 +88,38 @@ Subnet::getOptionDescriptor(const std::string& option_space,
return (*range.first);
}
+void Subnet::addVendorOption(const OptionPtr& option, bool persistent,
+ uint32_t vendor_id){
+
+ validateOption(option);
+
+ vendor_option_spaces_.addItem(OptionDescriptor(option, persistent), vendor_id);
+}
+
+Subnet::OptionContainerPtr
+Subnet::getVendorOptionDescriptors(uint32_t vendor_id) const {
+ return (vendor_option_spaces_.getItems(vendor_id));
+}
+
+Subnet::OptionDescriptor
+Subnet::getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code) {
+ OptionContainerPtr options = getVendorOptionDescriptors(vendor_id);
+ if (!options || options->empty()) {
+ return (OptionDescriptor(false));
+ }
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ const OptionContainerTypeRange& range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDescriptor(false));
+ }
+
+ return (*range.first);
+}
+
+void Subnet::delVendorOptions() {
+ vendor_option_spaces_.clearItems();
+}
+
isc::asiolink::IOAddress Subnet::getLastAllocated(Lease::Type type) const {
// check if the type is valid (and throw if it isn't)
checkType(type);
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index ac6de03..f6714d1 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -180,9 +180,14 @@ public:
void addOption(const OptionPtr& option, bool persistent,
const std::string& option_space);
+ void addVendorOption(const OptionPtr& option, bool persistent,
+ uint32_t vendor_id);
+
/// @brief Delete all options configured for the subnet.
void delOptions();
+ void delVendorOptions();
+
/// @brief checks if the specified address is in pools
///
/// Note the difference between inSubnet() and inPool(). For a given
@@ -221,6 +226,9 @@ public:
OptionContainerPtr
getOptionDescriptors(const std::string& option_space) const;
+ OptionContainerPtr
+ getVendorOptionDescriptors(uint32_t vendor_id) const;
+
/// @brief Return single option descriptor.
///
/// @param option_space name of the option space.
@@ -232,6 +240,9 @@ public:
getOptionDescriptor(const std::string& option_space,
const uint16_t option_code);
+ OptionDescriptor
+ getVendorOptionDescriptor(uint32_t vendor_id, uint16_t option_code);
+
/// @brief returns the last address that was tried from this pool
///
/// This method returns the last address that was attempted to be allocated
@@ -440,9 +451,14 @@ private:
/// A collection of option spaces grouping option descriptors.
typedef OptionSpaceContainer<OptionContainer,
- OptionDescriptor> OptionSpaceCollection;
+ OptionDescriptor, std::string> OptionSpaceCollection;
+
+ typedef OptionSpaceContainer<OptionContainer,
+ OptionDescriptor, uint32_t> VendorOptionSpaceCollection;
+
OptionSpaceCollection option_spaces_;
+ VendorOptionSpaceCollection vendor_option_spaces_;
};
/// @brief A generic pointer to either Subnet4 or Subnet6 object
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 0af8191..f872ed9 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -597,6 +597,76 @@ TEST(Subnet6Test, getOptionDescriptor) {
}
}
+
+TEST(Subnet6Test, addVendorOptions) {
+
+ uint32_t vendor_id1 = 12345678;
+ uint32_t vendor_id2 = 87654321;
+ uint32_t vendor_id_bogus = 1111111;
+
+ // Create as subnet to add options to it.
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+
+ // Differentiate options by their codes (100-109)
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id1));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addVendorOption(option, false, vendor_id2));
+ }
+
+ // Get options from the Subnet and check if all 10 are there.
+ Subnet::OptionContainerPtr options = subnet->getVendorOptionDescriptors(vendor_id1);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
+
+ // Validate codes of options added to dhcp6 option space.
+ uint16_t expected_code = 100;
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ options = subnet->getVendorOptionDescriptors(vendor_id2);
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getVendorOptionDescriptors(vendor_id_bogus);
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ // Delete options from all spaces.
+ subnet->delVendorOptions();
+
+ // Make sure that all options have been removed.
+ options = subnet->getVendorOptionDescriptors(vendor_id1);
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ options = subnet->getVendorOptionDescriptors(vendor_id2);
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+}
+
+
+
// This test verifies that inRange() and inPool() methods work properly.
TEST(Subnet6Test, inRangeinPool) {
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4));
More information about the bind10-changes
mailing list