Browse Source

fixed empty [sub]option parsing (#3661)

Francis Dupont 10 years ago
parent
commit
42a4854208
4 changed files with 118 additions and 7 deletions
  1. 5 0
      ChangeLog
  2. 1 1
      src/bin/dhcp4/dhcp4_srv.cc
  3. 109 3
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  4. 3 3
      src/lib/dhcp/libdhcp++.cc

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+894.	[bug]		fdupont
+	Accept empty options or sub options in DHCPv4 messages unpacking
+	routines.
+	(Trac #3661, git )
+
 893.	[func,bug]	fdupont
 893.	[func,bug]	fdupont
 	Changed the qualifying-suffix parameter in the dhcp-ddns
 	Changed the qualifying-suffix parameter in the dhcp-ddns
 	configuration element to be mandatory with no default value when
 	configuration element to be mandatory with no default value when

+ 1 - 1
src/bin/dhcp4/dhcp4_srv.cc

@@ -1892,7 +1892,7 @@ Dhcpv4Srv::unpackOptions(const OptionBuffer& buf,
         if (opt_type == DHO_PAD)
         if (opt_type == DHO_PAD)
             continue;
             continue;
 
 
-        if (offset + 1 >= buf.size()) {
+        if (offset + 1 > buf.size()) {
             // opt_type must be cast to integer so as it is not treated as
             // opt_type must be cast to integer so as it is not treated as
             // unsigned char value (a number is presented in error message).
             // unsigned char value (a number is presented in error message).
             isc_throw(OutOfRange, "Attempt to parse truncated option "
             isc_throw(OutOfRange, "Attempt to parse truncated option "

+ 109 - 3
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1328,7 +1328,7 @@ TEST_F(Dhcpv4SrvTest, Hooks) {
 //   - sub option (option space 'foo')
 //   - sub option (option space 'foo')
 //      - sub option (option space 'bar')
 //      - sub option (option space 'bar')
 // @todo Add more thorough unit tests for unpackOptions.
 // @todo Add more thorough unit tests for unpackOptions.
-TEST_F(Dhcpv4SrvTest, unpackOptions) {
+TEST_F(Dhcpv4SrvTest, unpackSubOptions) {
     // Create option definition for each level of encapsulation. Each option
     // Create option definition for each level of encapsulation. Each option
     // definition is for the option code 1. Options may have the same
     // definition is for the option code 1. Options may have the same
     // option code because they belong to different option spaces.
     // option code because they belong to different option spaces.
@@ -1353,7 +1353,7 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
     CfgMgr::instance().commit();
     CfgMgr::instance().commit();
 
 
     // Create the buffer holding the structure of options.
     // Create the buffer holding the structure of options.
-    const char raw_data[] = {
+    const uint8_t raw_data[] = {
         // First option starts here.
         // First option starts here.
         0x01,                   // option code = 1
         0x01,                   // option code = 1
         0x0B,                   // option length = 11
         0x0B,                   // option length = 11
@@ -1368,7 +1368,8 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
         0x00                    // This option carries a single uint8
         0x00                    // This option carries a single uint8
                                 // value and has no sub options.
                                 // value and has no sub options.
     };
     };
-    OptionBuffer buf(raw_data, raw_data + sizeof(raw_data));
+    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
+    OptionBuffer buf(raw_data, raw_data + raw_data_len);
 
 
     // Parse options.
     // Parse options.
     NakedDhcpv4Srv srv(0);
     NakedDhcpv4Srv srv(0);
@@ -1398,6 +1399,111 @@ TEST_F(Dhcpv4SrvTest, unpackOptions) {
     EXPECT_EQ(0x0, option_bar->getValue());
     EXPECT_EQ(0x0, option_bar->getValue());
 }
 }
 
 
+// Check parsing of an empty option
+TEST_F(Dhcpv4SrvTest, unpackEmptyOption) {
+    // Create option definition for the option code 1 without fields.
+    OptionDefinitionPtr opt_def(new OptionDefinition("option-empty", 1,
+                                                     "empty", false));
+
+    // Add it to the Configuration Manager.
+    CfgOptionDefPtr cfg_option_def =
+        CfgMgr::instance().getStagingCfg()->getCfgOptionDef();
+    ASSERT_NO_THROW(cfg_option_def->add(opt_def, "space-empty"));
+    CfgMgr::instance().commit();
+
+    // Create the buffer holding the structure of the empty option.
+    const uint8_t raw_data[] = {
+      0x01,                     // option code = 1
+      0x00                      // option length = 0
+    };
+    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
+    OptionBuffer buf(raw_data, raw_data + raw_data_len);
+
+    // Parse options.
+    NakedDhcpv4Srv srv(0);
+    OptionCollection options;
+    ASSERT_NO_THROW(srv.unpackOptions(buf, "space-empty", options));
+
+    // There should be one option.
+    ASSERT_EQ(1, options.size());
+    OptionPtr option_empty = options.begin()->second;
+    ASSERT_TRUE(option_empty);
+    EXPECT_EQ(1, option_empty->getType());
+    EXPECT_EQ(2, option_empty->len());
+}
+
+// Check parsing of an empty VSI sub option
+TEST_F(Dhcpv4SrvTest, unpackVSIOption) {
+    // Create the buffer holding the structure of the Vendor-Specific Info
+    const uint8_t raw_data[] = {
+      43,     // option code = DHO_VENDOR_ENCAPSULATED_OPTIONS
+      2,      // option length
+      0xdc,   // suboption code
+      0       // suboption length
+    };
+    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
+    OptionBuffer buf(raw_data, raw_data + raw_data_len);
+
+    // Parse options.
+    NakedDhcpv4Srv srv(0);
+    OptionCollection options;
+    ASSERT_NO_THROW(srv.unpackOptions(buf, "dhcp4", options));
+
+    // There should be one option: the VSI
+    ASSERT_EQ(1, options.size());
+    OptionPtr vsi = options.begin()->second;
+    ASSERT_TRUE(vsi);
+    EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, vsi->getType());
+    OptionCollection suboptions = vsi->getOptions();
+
+    // There should be one suboption
+    ASSERT_EQ(1, suboptions.size());
+    OptionPtr eso = suboptions.begin()->second;
+    ASSERT_TRUE(eso);
+    EXPECT_EQ(0xdc, eso->getType());
+    EXPECT_EQ(2, eso->len());
+}
+
+// Check parsing of an empty VIVSI sub option
+TEST_F(Dhcpv4SrvTest, unpackVIVSIOption) {
+    // Create the buffer holding the structure of the Vendor-Identifying
+    // Vendor-Specific Info
+    const uint8_t raw_data[] = {
+      125,            // option code = DHO_VIVSO_SUBOPTIONS
+      7,              // option length
+      0, 0, 9, 0xbf,  // ISC enterprise number (2495)
+      2,              // option data length
+      0xdc,           // suboption code
+      0               // suboption length
+    };
+    size_t raw_data_len = sizeof(raw_data) / sizeof(uint8_t);
+    OptionBuffer buf(raw_data, raw_data + raw_data_len);
+
+    // Parse options.
+    NakedDhcpv4Srv srv(0);
+    OptionCollection options;
+    ASSERT_NO_THROW(srv.unpackOptions(buf, "dhcp4", options));
+
+    // There should be one option: the VIVSI
+    ASSERT_EQ(1, options.size());
+    OptionPtr vivsi = options.begin()->second;
+    ASSERT_TRUE(vivsi);
+    EXPECT_EQ(DHO_VIVSO_SUBOPTIONS, vivsi->getType());
+
+    // Cast to OptionVendor
+    OptionVendorPtr vsi = boost::dynamic_pointer_cast<OptionVendor>(vivsi);
+    ASSERT_TRUE(vsi);
+    EXPECT_EQ(2495, vsi->getVendorId());
+    OptionCollection suboptions = vsi->getOptions();
+
+    // There should be one suboption
+    ASSERT_EQ(1, suboptions.size());
+    OptionPtr eso = suboptions.begin()->second;
+    ASSERT_TRUE(eso);
+    EXPECT_EQ(0xdc, eso->getType());
+    EXPECT_EQ(2, eso->len());
+}
+
 // Checks whether the server uses default (0.0.0.0) siaddr value, unless
 // Checks whether the server uses default (0.0.0.0) siaddr value, unless
 // explicitly specified
 // explicitly specified
 TEST_F(Dhcpv4SrvTest, siaddrDefault) {
 TEST_F(Dhcpv4SrvTest, siaddrDefault) {

+ 3 - 3
src/lib/dhcp/libdhcp++.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
 // purpose with or without fee is hereby granted, provided that the above
@@ -383,7 +383,7 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
         if (opt_type == DHO_PAD)
         if (opt_type == DHO_PAD)
             continue;
             continue;
 
 
-        if (offset + 1 >= buf.size()) {
+        if (offset + 1 > buf.size()) {
             // opt_type must be cast to integer so as it is not treated as
             // opt_type must be cast to integer so as it is not treated as
             // unsigned char value (a number is presented in error message).
             // unsigned char value (a number is presented in error message).
             isc_throw(OutOfRange, "Attempt to parse truncated option "
             isc_throw(OutOfRange, "Attempt to parse truncated option "
@@ -574,7 +574,7 @@ size_t LibDHCP::unpackVendorOptions4(const uint32_t vendor_id, const OptionBuffe
             if (opt_type == DHO_PAD)
             if (opt_type == DHO_PAD)
                 continue;
                 continue;
 
 
-            if (offset + 1 >= buf.size()) {
+            if (offset + 1 > buf.size()) {
                 // opt_type must be cast to integer so as it is not treated as
                 // opt_type must be cast to integer so as it is not treated as
                 // unsigned char value (a number is presented in error message).
                 // unsigned char value (a number is presented in error message).
                 isc_throw(OutOfRange, "Attempt to parse truncated vendor option "
                 isc_throw(OutOfRange, "Attempt to parse truncated vendor option "