Browse Source

[master] Finishing merge of trac5277 (bootp parameters)

Francis Dupont 7 years ago
parent
commit
d0d611d0e7

+ 4 - 0
doc/examples/kea4/shared-network.json

@@ -72,6 +72,8 @@
                     "id": 1,
                     "match-client-id": true,
                     "next-server": "0.0.0.0",
+                    "server-hostname": "",
+                    "boot-file-name": "",
                     "option-data": [ ],
                     "pools": [ ],
                     "rebind-timer": 20,
@@ -91,6 +93,8 @@
                     "id": 2,
                     "match-client-id": true,
                     "next-server": "0.0.0.0",
+                    "server-hostname": "",
+                    "boot-file-name": "",
                     "option-data": [ ],
                     "pools": [ ],
                     "rebind-timer": 20,

+ 12 - 2
doc/guide/dhcp4-srv.xml

@@ -2645,14 +2645,24 @@ It is merely echoed by the server
       not be sent. It may also be set to an empty string, which means the same as if
       it was not defined at all, i.e. use the global value.
       </para>
+      <para>
+      The <command>server-hostname</command> (that conveys a server hostname,
+      can be up to 64 bytes long and will be sent in the sname field) and
+      <command>boot-file-name</command> (that conveys the configuration file,
+      can be up to 128 bytes long and will be sent using file field)
+      directives are handled the same way as <command>next-server</command>.
+      </para>
 
 <screen>
 "Dhcp4": {
-    <userinput>"next-server": "192.0.2.123"</userinput>,
+    <userinput>"next-server": "192.0.2.123",
+    "boot-file-name": "/dev/null"</userinput>,
     ...,
     "subnet4": [
         {
-            <userinput>"next-server": "192.0.2.234"</userinput>,
+            <userinput>"next-server": "192.0.2.234",
+            "server-hostname": "some-name.example.org",
+            "boot-file-name": "bootfile.efi"</userinput>,
             ...
         }
     ]

+ 24 - 23
src/bin/dhcp4/dhcp4_lexer.cc

@@ -1592,8 +1592,8 @@ static const flex_int16_t yy_rule_linenum[158] =
      1128, 1137, 1146, 1155, 1164, 1173, 1182, 1191, 1200, 1209,
      1219, 1229, 1239, 1249, 1259, 1269, 1279, 1289, 1299, 1308,
      1317, 1326, 1335, 1344, 1353, 1364, 1375, 1388, 1399, 1412,
-     1510, 1515, 1520, 1525, 1526, 1527, 1528, 1529, 1530, 1532,
-     1550, 1563, 1568, 1572, 1574, 1576, 1578
+     1511, 1516, 1521, 1526, 1527, 1528, 1529, 1530, 1531, 1533,
+     1551, 1564, 1569, 1573, 1575, 1577, 1579
     } ;
 
 /* The intent behind this definition is that it'll catch
@@ -3788,6 +3788,7 @@ YY_RULE_SETUP
         case '"':
             /* impossible condition */
             driver.error(driver.loc_, "Bad quote in \"" + raw + "\"");
+            break;
         case '\\':
             ++pos;
             if (pos >= len) {
@@ -3873,7 +3874,7 @@ YY_RULE_SETUP
 case 141:
 /* rule 141 can match eol */
 YY_RULE_SETUP
-#line 1510 "dhcp4_lexer.ll"
+#line 1511 "dhcp4_lexer.ll"
 {
     /* Bad string with a forbidden control character inside */
     driver.error(driver.loc_, "Invalid control in " + std::string(yytext));
@@ -3882,7 +3883,7 @@ YY_RULE_SETUP
 case 142:
 /* rule 142 can match eol */
 YY_RULE_SETUP
-#line 1515 "dhcp4_lexer.ll"
+#line 1516 "dhcp4_lexer.ll"
 {
     /* Bad string with a bad escape inside */
     driver.error(driver.loc_, "Bad escape in " + std::string(yytext));
@@ -3890,7 +3891,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 143:
 YY_RULE_SETUP
-#line 1520 "dhcp4_lexer.ll"
+#line 1521 "dhcp4_lexer.ll"
 {
     /* Bad string with an open escape at the end */
     driver.error(driver.loc_, "Overflow escape in " + std::string(yytext));
@@ -3898,37 +3899,37 @@ YY_RULE_SETUP
 	YY_BREAK
 case 144:
 YY_RULE_SETUP
-#line 1525 "dhcp4_lexer.ll"
+#line 1526 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_LSQUARE_BRACKET(driver.loc_); }
 	YY_BREAK
 case 145:
 YY_RULE_SETUP
-#line 1526 "dhcp4_lexer.ll"
+#line 1527 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_RSQUARE_BRACKET(driver.loc_); }
 	YY_BREAK
 case 146:
 YY_RULE_SETUP
-#line 1527 "dhcp4_lexer.ll"
+#line 1528 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_LCURLY_BRACKET(driver.loc_); }
 	YY_BREAK
 case 147:
 YY_RULE_SETUP
-#line 1528 "dhcp4_lexer.ll"
+#line 1529 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_RCURLY_BRACKET(driver.loc_); }
 	YY_BREAK
 case 148:
 YY_RULE_SETUP
-#line 1529 "dhcp4_lexer.ll"
+#line 1530 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_COMMA(driver.loc_); }
 	YY_BREAK
 case 149:
 YY_RULE_SETUP
-#line 1530 "dhcp4_lexer.ll"
+#line 1531 "dhcp4_lexer.ll"
 { return isc::dhcp::Dhcp4Parser::make_COLON(driver.loc_); }
 	YY_BREAK
 case 150:
 YY_RULE_SETUP
-#line 1532 "dhcp4_lexer.ll"
+#line 1533 "dhcp4_lexer.ll"
 {
     /* An integer was found. */
     std::string tmp(yytext);
@@ -3949,7 +3950,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 151:
 YY_RULE_SETUP
-#line 1550 "dhcp4_lexer.ll"
+#line 1551 "dhcp4_lexer.ll"
 {
     /* A floating point was found. */
     std::string tmp(yytext);
@@ -3965,7 +3966,7 @@ YY_RULE_SETUP
 	YY_BREAK
 case 152:
 YY_RULE_SETUP
-#line 1563 "dhcp4_lexer.ll"
+#line 1564 "dhcp4_lexer.ll"
 {
     string tmp(yytext);
     return isc::dhcp::Dhcp4Parser::make_BOOLEAN(tmp == "true", driver.loc_);
@@ -3973,33 +3974,33 @@ YY_RULE_SETUP
 	YY_BREAK
 case 153:
 YY_RULE_SETUP
-#line 1568 "dhcp4_lexer.ll"
+#line 1569 "dhcp4_lexer.ll"
 {
    return isc::dhcp::Dhcp4Parser::make_NULL_TYPE(driver.loc_);
 }
 	YY_BREAK
 case 154:
 YY_RULE_SETUP
-#line 1572 "dhcp4_lexer.ll"
+#line 1573 "dhcp4_lexer.ll"
 driver.error (driver.loc_, "JSON true reserved keyword is lower case only");
 	YY_BREAK
 case 155:
 YY_RULE_SETUP
-#line 1574 "dhcp4_lexer.ll"
+#line 1575 "dhcp4_lexer.ll"
 driver.error (driver.loc_, "JSON false reserved keyword is lower case only");
 	YY_BREAK
 case 156:
 YY_RULE_SETUP
-#line 1576 "dhcp4_lexer.ll"
+#line 1577 "dhcp4_lexer.ll"
 driver.error (driver.loc_, "JSON null reserved keyword is lower case only");
 	YY_BREAK
 case 157:
 YY_RULE_SETUP
-#line 1578 "dhcp4_lexer.ll"
+#line 1579 "dhcp4_lexer.ll"
 driver.error (driver.loc_, "Invalid character: " + std::string(yytext));
 	YY_BREAK
 case YY_STATE_EOF(INITIAL):
-#line 1580 "dhcp4_lexer.ll"
+#line 1581 "dhcp4_lexer.ll"
 {
     if (driver.states_.empty()) {
         return isc::dhcp::Dhcp4Parser::make_END(driver.loc_);
@@ -4025,10 +4026,10 @@ case YY_STATE_EOF(INITIAL):
 	YY_BREAK
 case 158:
 YY_RULE_SETUP
-#line 1603 "dhcp4_lexer.ll"
+#line 1604 "dhcp4_lexer.ll"
 ECHO;
 	YY_BREAK
-#line 4031 "dhcp4_lexer.cc"
+#line 4032 "dhcp4_lexer.cc"
 
 	case YY_END_OF_BUFFER:
 		{
@@ -5133,7 +5134,7 @@ void yyfree (void * ptr )
 
 /* %ok-for-header */
 
-#line 1603 "dhcp4_lexer.ll"
+#line 1604 "dhcp4_lexer.ll"
 
 
 using namespace isc::dhcp;

+ 17 - 15
src/bin/dhcp4/dhcp4_lexer.ll

@@ -442,7 +442,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_);
@@ -453,7 +453,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_);
@@ -464,7 +464,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_);
@@ -492,10 +492,10 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 
 \"shared-networks\" {
     switch (driver.ctx_) {
-    case Parser4Context::DHCP4:
-        return Dhcp4Parser::make_SHARED_NETWORKS(driver.loc_);
+    case isc::dhcp::Parser4Context::DHCP4:
+        return isc::dhcp::Dhcp4Parser::make_SHARED_NETWORKS(driver.loc_);
     default:
-        return Dhcp4Parser::make_STRING("shared-networks", driver.loc_);
+        return isc::dhcp::Dhcp4Parser::make_STRING("shared-networks", driver.loc_);
     }
 }
 
@@ -513,11 +513,11 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::POOLS:
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
     case isc::dhcp::Parser4Context::CLIENT_CLASS:
-    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
@@ -532,7 +532,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser4Context::OPTION_DATA:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
     case isc::dhcp::Parser4Context::CLIENT_CLASS:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::LOGGERS:
         return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
     default:
@@ -598,7 +598,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_);
@@ -791,8 +791,8 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"client-class\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
-    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("client-class", driver.loc_);
@@ -926,7 +926,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"relay\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_);
@@ -1352,8 +1352,6 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"echo-client-id\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
-    case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_ECHO_CLIENT_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("echo-client-id", driver.loc_);
@@ -1364,7 +1362,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
-    case Parser4Context::SHARED_NETWORK:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_MATCH_CLIENT_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("match-client-id", driver.loc_);
@@ -1375,9 +1373,9 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
-    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_NEXT_SERVER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("next-server", driver.loc_);
@@ -1386,7 +1384,9 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 
 \"server-hostname\" {
     switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
         return isc::dhcp::Dhcp4Parser::make_SERVER_HOSTNAME(driver.loc_);
@@ -1397,7 +1397,9 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 
 \"boot-file-name\" {
     switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
         return isc::dhcp::Dhcp4Parser::make_BOOT_FILE_NAME(driver.loc_);

+ 6 - 0
src/bin/dhcp4/dhcp4_parser.yy

@@ -430,6 +430,8 @@ global_param: valid_lifetime
             | echo_client_id
             | match_client_id
             | next_server
+            | server_hostname
+            | boot_file_name
             | unknown_map_entry
             ;
 
@@ -912,6 +914,8 @@ subnet4_param: valid_lifetime
              | relay
              | match_client_id
              | next_server
+             | server_hostname
+             | boot_file_name
              | subnet_4o6_interface
              | subnet_4o6_interface_id
              | subnet_4o6_subnet
@@ -1039,6 +1043,8 @@ shared_network_param: name
                     | option_data_list
                     | match_client_id
                     | next_server
+                    | server_hostname
+                    | boot_file_name
                     | relay
                     | reservation_mode
                     | client_class

+ 22 - 0
src/bin/dhcp4/dhcp4_srv.cc

@@ -2214,6 +2214,28 @@ Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
         if (!subnet_next_server.isV4Zero()) {
             response->setSiaddr(subnet_next_server);
         }
+
+        const string& sname = subnet->getSname();
+        if (!sname.empty()) {
+            // Converting string to (const uint8_t*, size_t len) format is
+            // tricky. reinterpret_cast is not the most elegant solution,
+            // but it does avoid us making unnecessary copy. We will convert
+            // sname and file fields in Pkt4 to string one day and life
+            // will be easier.
+            response->setSname(reinterpret_cast<const uint8_t*>(sname.c_str()),
+                               sname.size());
+        }
+
+        const string& filename = subnet->getFilename();
+        if (!filename.empty()) {
+            // Converting string to (const uint8_t*, size_t len) format is
+            // tricky. reinterpret_cast is not the most elegant solution,
+            // but it does avoid us making unnecessary copy. We will convert
+            // sname and file fields in Pkt4 to string one day and life
+            // will be easier.
+            response->setFile(reinterpret_cast<const uint8_t*>(filename.c_str()),
+                              filename.size());
+        }
     }
 
     // Step 2: Try to set the values based on classes.

+ 3 - 1
src/bin/dhcp4/json_config_parser.cc

@@ -455,7 +455,9 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
                  (config_pair.first == "dhcp4o6-port") ||
                  (config_pair.first == "echo-client-id") ||
                  (config_pair.first == "match-client-id") ||
-                 (config_pair.first == "next-server")) {
+                 (config_pair.first == "next-server") ||
+                 (config_pair.first == "server-hostname") ||
+                 (config_pair.first == "boot-file-name")) {
                 continue;
             }
 

+ 1 - 1
src/bin/dhcp4/location.hh

@@ -1,4 +1,4 @@
-// Generated 201709252348
+// Generated 201710011747
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Locations for Bison parsers in C++

+ 1 - 1
src/bin/dhcp4/position.hh

@@ -1,4 +1,4 @@
-// Generated 201709252348
+// Generated 201710011747
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 1 - 1
src/bin/dhcp4/stack.hh

@@ -1,4 +1,4 @@
-// Generated 201709252348
+// Generated 201710011747
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Stack handling for Bison parsers in C++

+ 71 - 4
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -1106,14 +1106,16 @@ TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
 
 /// @todo: implement subnet removal test as part of #3281.
 
-// Checks if the next-server defined as global parameter is taken into
-// consideration.
+// Checks if the next-server and other fixed BOOTP fields defined as
+// global parameter are taken into consideration.
 TEST_F(Dhcp4ParserTest, nextServerGlobal) {
 
     string config = "{ " + genIfaceConfig() + "," +
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"next-server\": \"1.2.3.4\", "
+        "\"server-hostname\": \"foo\", "
+        "\"boot-file-name\": \"bar\", "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
@@ -1135,10 +1137,12 @@ TEST_F(Dhcp4ParserTest, nextServerGlobal) {
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+    EXPECT_EQ("foo", subnet->getSname());
+    EXPECT_EQ("bar", subnet->getFilename());
 }
 
-// Checks if the next-server defined as subnet parameter is taken into
-// consideration.
+// Checks if the next-server and other fixed BOOTP fields defined as
+// subnet parameter are taken into consideration.
 TEST_F(Dhcp4ParserTest, nextServerSubnet) {
 
     string config = "{ " + genIfaceConfig() + "," +
@@ -1147,6 +1151,8 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"next-server\": \"1.2.3.4\", "
+        "    \"server-hostname\": \"foo\", "
+        "    \"boot-file-name\": \"bar\", "
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
@@ -1166,6 +1172,8 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+    EXPECT_EQ("foo", subnet->getSname());
+    EXPECT_EQ("bar", subnet->getFilename());
 }
 
 // Test checks several negative scenarios for next-server configuration: bogus
@@ -1209,12 +1217,43 @@ TEST_F(Dhcp4ParserTest, nextServerNegative) {
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
+    // Config with too large server-hostname
+    string bigsname(Pkt4::MAX_SNAME_LEN + 1, ' ');
+    string config_bogus4 = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"server-hostname\": \"" + bigsname + "\", " +
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    // Config with too large boot-file-hostname
+    string bigfilename(Pkt4::MAX_FILE_LEN + 1, ' ');
+    string config_bogus5 = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"boot-file-name\": \"" + bigfilename + "\", " +
+        "    \"subnet\": \"192.0.2.0/24\" } ],"
+        "\"valid-lifetime\": 4000 }";
+
+
     ConstElementPtr json1;
     ASSERT_NO_THROW(json1 = parseDHCP4(config_bogus1));
     ConstElementPtr json2;
     ASSERT_NO_THROW(json2 = parseDHCP4(config_bogus2));
     ConstElementPtr json3;
     ASSERT_NO_THROW(json3 = parseDHCP4(config_bogus3));
+    ConstElementPtr json4;
+    ASSERT_NO_THROW(json4 = parseDHCP4(config_bogus4));
+    ConstElementPtr json5;
+    ASSERT_NO_THROW(json5 = parseDHCP4(config_bogus5));
 
     // check if returned status is always a failure
     ConstElementPtr status;
@@ -1233,6 +1272,18 @@ TEST_F(Dhcp4ParserTest, nextServerNegative) {
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json3));
     checkResult(status, 0);
     EXPECT_FALSE(errorContainsPosition(status, "<string>"));
+
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json4));
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
+
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json5));
+    checkResult(status, 1);
+    EXPECT_TRUE(errorContainsPosition(status, "<string>"));
 }
 
 // Checks if the next-server defined as global value is overridden by subnet
@@ -1243,9 +1294,13 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"next-server\": \"192.0.0.1\", "
+        "\"server-hostname\": \"nohost\","
+        "\"boot-file-name\": \"nofile\","        
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"next-server\": \"1.2.3.4\", "
+        "    \"server-hostname\": \"some-name.example.org\","
+        "    \"boot-file-name\": \"bootfile.efi\","
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
@@ -1265,6 +1320,8 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
         getCfgSubnets4()->selectSubnet(IOAddress("192.0.2.200"));
     ASSERT_TRUE(subnet);
     EXPECT_EQ("1.2.3.4", subnet->getSiaddr().toText());
+    EXPECT_EQ("some-name.example.org", subnet->getSname());
+    EXPECT_EQ("bootfile.efi", subnet->getFilename());
 }
 
 // Check whether it is possible to configure echo-client-id
@@ -5289,6 +5346,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
         "    \"interface\": \"eth0\",\n"
         "    \"match-client-id\": false,\n"
         "    \"next-server\": \"1.2.3.4\",\n"
+        "    \"server-hostname\": \"foo\",\n"
+        "    \"boot-file-name\": \"bar\",\n"
         "    \"relay\": {\n"
         "        \"ip-address\": \"5.6.7.8\"\n"
         "    },\n"
@@ -5309,6 +5368,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
         "        \"valid-lifetime\": 400,\n"
         "        \"match-client-id\": true,\n"
         "        \"next-server\": \"11.22.33.44\",\n"
+        "        \"server-hostname\": \"some-name.example.org\",\n"
+        "        \"boot-file-name\": \"bootfile.efi\",\n"
         "        \"relay\": {\n"
         "            \"ip-address\": \"55.66.77.88\"\n"
         "        },\n"
@@ -5359,6 +5420,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
     EXPECT_EQ("eth0", s->getIface());
     EXPECT_FALSE(s->getMatchClientId());
     EXPECT_EQ(IOAddress("1.2.3.4"), s->getSiaddr());
+    EXPECT_EQ("foo", s->getSname());
+    EXPECT_EQ("bar", s->getFilename());
     EXPECT_EQ(IOAddress("5.6.7.8"), s->getRelayInfo().addr_);
     EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
 
@@ -5372,6 +5435,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
     EXPECT_EQ("eth0", s->getIface());
     EXPECT_TRUE(s->getMatchClientId());
     EXPECT_EQ(IOAddress("11.22.33.44"), s->getSiaddr());
+    EXPECT_EQ("some-name.example.org", s->getSname());
+    EXPECT_EQ("bootfile.efi", s->getFilename());
     EXPECT_EQ(IOAddress("55.66.77.88"), s->getRelayInfo().addr_);
     EXPECT_EQ(Network::HR_DISABLED, s->getHostReservationMode());
 
@@ -5389,6 +5454,8 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
     EXPECT_EQ("", s->getIface());
     EXPECT_TRUE(s->getMatchClientId());
     EXPECT_EQ(IOAddress("0.0.0.0"), s->getSiaddr());
+    EXPECT_TRUE(s->getSname().empty());
+    EXPECT_TRUE(s->getFilename().empty());
     EXPECT_EQ(IOAddress("0.0.0.0"), s->getRelayInfo().addr_);
     EXPECT_EQ(Network::HR_ALL, s->getHostReservationMode());
 }

+ 27 - 1
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc

@@ -1337,7 +1337,7 @@ TEST_F(Dhcpv4SrvTest, siaddr) {
 // Checks if the next-server defined as global value is overridden by subnet
 // specific value and returned in server messages. There's also similar test for
 // checking parser only configuration, see Dhcp4ParserTest.nextServerOverride in
-// config_parser_unittest.cc.
+// config_parser_unittest.cc. This test was extended to other BOOTP fixed fields.
 TEST_F(Dhcpv4SrvTest, nextServerOverride) {
     IfaceMgrTestConfig test_config(true);
     IfaceMgr::instance().openSockets4();
@@ -1352,9 +1352,13 @@ TEST_F(Dhcpv4SrvTest, nextServerOverride) {
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"next-server\": \"192.0.0.1\", "
+        "\"server-hostname\": \"nohost\", "
+        "\"boot-file-name\": \"nofile\", "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"next-server\": \"1.2.3.4\", "
+        "    \"server-hostname\": \"some-name.example.org\", "
+        "    \"boot-file-name\": \"bootfile.efi\", "
         "    \"subnet\": \"192.0.2.0/24\" } ],"
         "\"valid-lifetime\": 4000 }";
 
@@ -1382,6 +1386,16 @@ TEST_F(Dhcpv4SrvTest, nextServerOverride) {
     EXPECT_EQ(DHCPOFFER, offer->getType());
 
     EXPECT_EQ("1.2.3.4", offer->getSiaddr().toText());
+    std::string sname("some-name.example.org");
+    uint8_t sname_buf[Pkt4::MAX_SNAME_LEN];
+    std::memset(sname_buf, 0, Pkt4::MAX_SNAME_LEN);
+    std::memcpy(sname_buf, sname.c_str(), sname.size());
+    EXPECT_EQ(0, std::memcmp(sname_buf, &offer->getSname()[0], Pkt4::MAX_SNAME_LEN));
+    std::string filename("bootfile.efi");
+    uint8_t filename_buf[Pkt4::MAX_FILE_LEN];
+    std::memset(filename_buf, 0, Pkt4::MAX_FILE_LEN);
+    std::memcpy(filename_buf, filename.c_str(), filename.size());
+    EXPECT_EQ(0, std::memcmp(filename_buf, &offer->getFile()[0], Pkt4::MAX_FILE_LEN));
 }
 
 // Checks if the next-server defined as global value is used in responses
@@ -1402,6 +1416,8 @@ TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
         "\"rebind-timer\": 2000, "
         "\"renew-timer\": 1000, "
         "\"next-server\": \"192.0.0.1\", "
+        "\"server-hostname\": \"some-name.example.org\", "
+        "\"boot-file-name\": \"bootfile.efi\", "
         "\"subnet4\": [ { "
         "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
         "    \"subnet\": \"192.0.2.0/24\" } ],"
@@ -1431,6 +1447,16 @@ TEST_F(Dhcpv4SrvTest, nextServerGlobal) {
     EXPECT_EQ(DHCPOFFER, offer->getType());
 
     EXPECT_EQ("192.0.0.1", offer->getSiaddr().toText());
+    std::string sname("some-name.example.org");
+    uint8_t sname_buf[Pkt4::MAX_SNAME_LEN];
+    std::memset(sname_buf, 0, Pkt4::MAX_SNAME_LEN);
+    std::memcpy(sname_buf, sname.c_str(), sname.size());
+    EXPECT_EQ(0, std::memcmp(sname_buf, &offer->getSname()[0], Pkt4::MAX_SNAME_LEN));
+    std::string filename("bootfile.efi");
+    uint8_t filename_buf[Pkt4::MAX_FILE_LEN];
+    std::memset(filename_buf, 0, Pkt4::MAX_FILE_LEN);
+    std::memcpy(filename_buf, filename.c_str(), filename.size());
+    EXPECT_EQ(0, std::memcmp(filename_buf, &offer->getFile()[0], Pkt4::MAX_FILE_LEN));
 }
 
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems

+ 2 - 0
src/bin/dhcp4/tests/dora_unittest.cc

@@ -278,6 +278,8 @@ const char* DORA_CONFIGS[] = {
         "},"
         "\"valid-lifetime\": 600,"
         "\"next-server\": \"10.0.0.1\","
+        "\"server-hostname\": \"nohost\","
+        "\"boot-file-name\": \"nofile\","
         "\"subnet4\": [ { "
         "    \"subnet\": \"10.0.0.0/24\", "
         "    \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"

+ 120 - 0
src/bin/dhcp4/tests/get_config_unittest.cc

@@ -223,12 +223,14 @@ const char* EXTRACTED_CONFIGS[] = {
 "    }\n",
     // CONFIGURATION 6
 "{\n"
+"        \"boot-file-name\": \"bar\",\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
 "        },\n"
 "        \"next-server\": \"1.2.3.4\",\n"
 "        \"rebind-timer\": 2000,\n"
 "        \"renew-timer\": 1000,\n"
+"        \"server-hostname\": \"foo\",\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"pools\": [\n"
@@ -250,12 +252,14 @@ const char* EXTRACTED_CONFIGS[] = {
 "        \"renew-timer\": 1000,\n"
 "        \"subnet4\": [\n"
 "            {\n"
+"                \"boot-file-name\": \"bar\",\n"
 "                \"next-server\": \"1.2.3.4\",\n"
 "                \"pools\": [\n"
 "                    {\n"
 "                        \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
 "                    }\n"
 "                ],\n"
+"                \"server-hostname\": \"foo\",\n"
 "                \"subnet\": \"192.0.2.0/24\"\n"
 "            }\n"
 "        ],\n"
@@ -263,20 +267,24 @@ const char* EXTRACTED_CONFIGS[] = {
 "    }\n",
     // CONFIGURATION 8
 "{\n"
+"        \"boot-file-name\": \"nofile\",\n"
 "        \"interfaces-config\": {\n"
 "            \"interfaces\": [ \"*\" ]\n"
 "        },\n"
 "        \"next-server\": \"192.0.0.1\",\n"
 "        \"rebind-timer\": 2000,\n"
 "        \"renew-timer\": 1000,\n"
+"        \"server-hostname\": \"nohost\",\n"
 "        \"subnet4\": [\n"
 "            {\n"
+"                \"boot-file-name\": \"bootfile.efi\",\n"
 "                \"next-server\": \"1.2.3.4\",\n"
 "                \"pools\": [\n"
 "                    {\n"
 "                        \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
 "                    }\n"
 "                ],\n"
+"                \"server-hostname\": \"some-name.example.org\",\n"
 "                \"subnet\": \"192.0.2.0/24\"\n"
 "            }\n"
 "        ],\n"
@@ -1670,6 +1678,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1687,6 +1696,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 900,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -1738,6 +1748,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1755,6 +1766,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -1806,6 +1818,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1823,6 +1836,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -1874,6 +1888,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1891,6 +1906,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -1898,6 +1914,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1915,6 +1932,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -1922,6 +1940,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 3,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1939,6 +1958,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.4.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -1946,6 +1966,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 4,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -1963,6 +1984,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.5.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2014,6 +2036,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1024,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2031,6 +2054,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2038,6 +2062,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 100,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2055,6 +2080,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2062,6 +2088,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2079,6 +2106,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.4.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2086,6 +2114,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 34,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2103,6 +2132,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.5.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2154,6 +2184,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"bar\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"1.2.3.4\",\n"
@@ -2171,6 +2202,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"foo\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2222,6 +2254,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"bar\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"1.2.3.4\",\n"
@@ -2239,6 +2272,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"foo\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2290,6 +2324,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"bootfile.efi\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"1.2.3.4\",\n"
@@ -2307,6 +2342,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"some-name.example.org\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2358,6 +2394,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2375,6 +2412,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2426,6 +2464,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2443,6 +2482,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2494,6 +2534,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2511,6 +2552,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2518,6 +2560,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": false,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2535,6 +2578,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2586,6 +2630,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": false,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2603,6 +2648,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2610,6 +2656,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2627,6 +2674,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2678,6 +2726,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2695,6 +2744,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4\n"
 "            }\n"
@@ -2746,6 +2796,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2767,6 +2818,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -2774,6 +2826,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2795,6 +2848,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -2846,6 +2900,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -2863,6 +2918,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3311,6 +3367,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3328,6 +3385,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3379,6 +3437,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3413,6 +3472,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3491,6 +3551,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3508,6 +3569,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3691,6 +3753,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3708,6 +3771,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 3000\n"
 "            }\n"
@@ -3768,6 +3832,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3802,6 +3867,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3853,6 +3919,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3879,6 +3946,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -3886,6 +3954,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3912,6 +3981,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -3963,6 +4033,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -3997,6 +4068,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -4048,6 +4120,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4087,6 +4160,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -4346,6 +4420,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4363,6 +4438,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 3000\n"
 "            }\n"
@@ -4429,6 +4505,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4446,6 +4523,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -4516,6 +4594,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4533,6 +4612,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -4670,6 +4750,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4687,6 +4768,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -4738,6 +4820,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4755,6 +4838,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4\n"
 "            }\n"
@@ -4806,6 +4890,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 123,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4823,6 +4908,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -4830,6 +4916,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 234,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4902,6 +4989,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"server-hostname\": \"\"\n"
 "                    }\n"
 "                ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -4909,6 +4997,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 542,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -4974,6 +5063,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"server-hostname\": \"\"\n"
 "                    }\n"
 "                ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.4.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5035,6 +5125,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 234,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5072,6 +5163,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"server-hostname\": \"\"\n"
 "                    }\n"
 "                ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5123,6 +5215,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5140,6 +5233,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -5147,6 +5241,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 2,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5164,6 +5259,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"out-of-pool\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.3.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -5171,6 +5267,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 3,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5188,6 +5285,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"disabled\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.4.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            },\n"
@@ -5195,6 +5293,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 4,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5212,6 +5311,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.5.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5435,6 +5535,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5452,6 +5553,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5503,6 +5605,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"2001:db8::123/45\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5520,6 +5623,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5571,6 +5675,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"ethX\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5588,6 +5693,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5639,6 +5745,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"ethX\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"2001:db8::543/21\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5656,6 +5763,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5707,6 +5815,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"vlan123\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5724,6 +5833,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5801,6 +5911,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5818,6 +5929,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5869,6 +5981,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5886,6 +5999,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -5937,6 +6051,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -5955,6 +6070,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -6006,6 +6122,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -6028,6 +6145,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"
@@ -6079,6 +6197,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"4o6-interface\": \"\",\n"
 "                \"4o6-interface-id\": \"\",\n"
 "                \"4o6-subnet\": \"\",\n"
+"                \"boot-file-name\": \"\",\n"
 "                \"id\": 1,\n"
 "                \"match-client-id\": true,\n"
 "                \"next-server\": \"0.0.0.0\",\n"
@@ -6101,6 +6220,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"renew-timer\": 1000,\n"
 "                \"reservation-mode\": \"all\",\n"
 "                \"reservations\": [ ],\n"
+"                \"server-hostname\": \"\",\n"
 "                \"subnet\": \"192.0.2.0/24\",\n"
 "                \"valid-lifetime\": 4000\n"
 "            }\n"

+ 2 - 0
src/bin/dhcp4/tests/inform_unittest.cc

@@ -109,6 +109,8 @@ const char* INFORM_CONFIGS[] = {
         "},"
         "\"valid-lifetime\": 600,"
         "\"next-server\": \"10.0.0.1\","
+        "\"server-hostname\": \"nohost\","
+        "\"server-hostname\": \"nofile\","
         "\"subnet4\": [ { "
         "    \"subnet\": \"192.0.2.0/24\", "
         "    \"reservations\": [ "

+ 26 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -622,6 +622,32 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
                   << next_server << "(" << pos << ")");
     }
 
+    // Set server-hostname.
+    std::string sname = getString(params, "server-hostname");
+    if (!sname.empty()) {
+        if (sname.length() >= Pkt4::MAX_SNAME_LEN) {
+            ConstElementPtr error = params->get("server-hostname");
+            isc_throw(DhcpConfigError, "server-hostname must be at most "
+                      << Pkt4::MAX_SNAME_LEN - 1 << " bytes long, it is "
+                      << sname.length() << " ("
+                      << error->getPosition() << ")");
+        }
+        subnet4->setSname(sname);
+    }
+
+    // Set boot-file-name.
+    std::string filename =getString(params, "boot-file-name");
+    if (!filename.empty()) {
+        if (filename.length() > Pkt4::MAX_FILE_LEN) {
+            ConstElementPtr error = params->get("boot-file-name");
+            isc_throw(DhcpConfigError, "boot-file-name must be at most "
+                      << Pkt4::MAX_FILE_LEN - 1 << " bytes long, it is "
+                      << filename.length() << " ("
+                      << error->getPosition() << ")");
+        }
+        subnet4->setFilename(filename);
+    }
+
     // Get interface name. If it is defined, then the subnet is available
     // directly over specified network interface.
     std::string iface = getString(params, "interface");

+ 5 - 1
src/lib/dhcpsrv/parsers/simple_parser4.cc

@@ -63,7 +63,9 @@ const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
     { "dhcp4o6-port",             Element::integer, "0" },
     { "echo-client-id",           Element::boolean, "true" },
     { "match-client-id",          Element::boolean, "true" },
-    { "next-server",              Element::string,  "0.0.0.0" }
+    { "next-server",              Element::string,  "0.0.0.0" },
+    { "server-hostname",          Element::string,  "" },
+    { "boot-file-name",           Element::string,  "" }
 };
 
 /// @brief This table defines default values for each IPv4 subnet.
@@ -118,6 +120,7 @@ const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = {
 /// This list is also used for inheriting from global to shared networks
 /// and from shared networks to subnets within it.
 const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
+    "boot-file-name",
     "client-class",
     "interface",
     "match-client-id",
@@ -126,6 +129,7 @@ const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
     "relay",
     "renew-timer",
     "reservation-mode",
+    "server-hostname",
     "valid-lifetime"
 };
 

+ 21 - 0
src/lib/dhcpsrv/subnet.cc

@@ -239,6 +239,21 @@ isc::asiolink::IOAddress Subnet4::getSiaddr() const {
     return (siaddr_);
 }
 
+void Subnet4::setSname(const std::string& sname) {
+    sname_ = sname;
+}
+
+const std::string& Subnet4::getSname() const {
+    return (sname_);
+}
+void Subnet4::setFilename(const std::string& filename) {
+    filename_ = filename;
+}
+
+const std::string& Subnet4::getFilename() const {
+    return (filename_);
+}
+
 const PoolCollection& Subnet::getPools(Lease::Type type) const {
     // check if the type is valid (and throw if it isn't)
     checkType(type);
@@ -574,6 +589,12 @@ Subnet4::toElement() const {
     // Set next-server
     map->set("next-server", Element::create(getSiaddr().toText()));
 
+    // Set server-hostname
+    map->set("server-hostname", Element::create(getSname()));
+
+    // Set boot-file-name
+    map->set("boot-file-name",Element::create(getFilename()));
+
     // Set pools
     const PoolCollection& pools = getPools(Lease::TYPE_V4);
     ElementPtr pool_list = Element::createList();

+ 26 - 0
src/lib/dhcpsrv/subnet.h

@@ -457,6 +457,26 @@ public:
     /// @return siaddr value
     isc::asiolink::IOAddress getSiaddr() const;
 
+    /// @brief Sets server hostname for the Subnet4 
+    ///
+    /// Will be used for server hostname field (may be empty if not defined)
+    void setSname(const std::string& sname);
+
+    /// @brief Returns server hostname for this subnet
+    ///
+    /// @return server hostname value
+    const std::string& getSname() const;
+
+    /// @brief Sets boot file name for the Subnet4 
+    ///
+    /// Will be used for boot file name (may be empty if not defined)
+    void setFilename(const std::string& filename);
+
+    /// @brief Returns boot file name for this subnet
+    ///
+    /// @return boot file name value
+    const std::string& getFilename() const;
+
     /// @brief Returns DHCP4o6 configuration parameters.
     ///
     /// This structure is always available. If the 4o6 is not enabled, its
@@ -497,6 +517,12 @@ private:
     /// @brief siaddr value for this subnet
     isc::asiolink::IOAddress siaddr_;
 
+    /// @brief server hostname for this subnet
+    std::string sname_;
+
+    /// @brief boot file name for this subnet
+    std::string filename_;
+
     /// @brief All the information related to DHCP4o6
     Cfg4o6 dhcp4o6_;
 };

+ 8 - 0
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -752,6 +752,8 @@ TEST(CfgSubnets4Test, unparseSubnet) {
         "    \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
         "    \"match-client-id\": true,\n"
         "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"server-hostname\": \"\",\n"
+        "    \"boot-file-name\": \"\",\n"
         "    \"renew-timer\": 1,\n"
         "    \"rebind-timer\": 2,\n"
         "    \"valid-lifetime\": 3,\n"
@@ -769,6 +771,8 @@ TEST(CfgSubnets4Test, unparseSubnet) {
         "    \"interface\": \"lo\",\n"
         "    \"match-client-id\": true,\n"
         "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"server-hostname\": \"\",\n"
+        "    \"boot-file-name\": \"\",\n"
         "    \"renew-timer\": 1,\n"
         "    \"rebind-timer\": 2,\n"
         "    \"valid-lifetime\": 3,\n"
@@ -785,6 +789,8 @@ TEST(CfgSubnets4Test, unparseSubnet) {
         "    \"interface\": \"eth1\",\n"
         "    \"match-client-id\": true,\n"
         "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"server-hostname\": \"\",\n"
+        "    \"boot-file-name\": \"\",\n"
         "    \"renew-timer\": 1,\n"
         "    \"rebind-timer\": 2,\n"
         "    \"valid-lifetime\": 3,\n"
@@ -819,6 +825,8 @@ TEST(CfgSubnets4Test, unparsePool) {
         "    \"relay\": { \"ip-address\": \"0.0.0.0\" },\n"
         "    \"match-client-id\": true,\n"
         "    \"next-server\": \"0.0.0.0\",\n"
+        "    \"server-hostname\": \"\",\n"
+        "    \"boot-file-name\": \"\",\n"
         "    \"renew-timer\": 1,\n"
         "    \"rebind-timer\": 2,\n"
         "    \"valid-lifetime\": 3,\n"

+ 4 - 0
src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc

@@ -49,6 +49,8 @@ public:
                 "            \"valid-lifetime\": 300,"
                 "            \"match-client-id\": false,"
                 "            \"next-server\": \"\","
+                "            \"server-hostname\": \"\","
+                "            \"boot-file-name\": \"\","
                 "            \"client-class\": \"\","
                 "            \"reservation-mode\": \"all\","
                 "            \"4o6-interface\": \"\","
@@ -67,6 +69,8 @@ public:
                 "            \"valid-lifetime\": 30,"
                 "            \"match-client-id\": false,"
                 "            \"next-server\": \"\","
+                "            \"server-hostname\": \"\","
+                "            \"boot-file-name\": \"\","
                 "            \"client-class\": \"\","
                 "            \"reservation-mode\": \"all\","
                 "            \"4o6-interface\": \"\","

+ 4 - 0
src/lib/dhcpsrv/tests/shared_network_unittest.cc

@@ -222,6 +222,8 @@ TEST(SharedNetwork4Test, unparse) {
         "        \"id\": 1,\n"
         "        \"match-client-id\": true,\n"
         "        \"next-server\": \"0.0.0.0\",\n"
+        "        \"server-hostname\": \"\",\n"
+        "        \"boot-file-name\": \"\",\n"
         "        \"option-data\": [ ],\n"
         "        \"pools\": [ ],\n"
         "        \"rebind-timer\": 20,\n"
@@ -240,6 +242,8 @@ TEST(SharedNetwork4Test, unparse) {
         "        \"id\": 2,\n"
         "        \"match-client-id\": true,\n"
         "        \"next-server\": \"0.0.0.0\",\n"
+        "        \"server-hostname\": \"\",\n"
+        "        \"boot-file-name\": \"\",\n"
         "        \"option-data\": [ ],\n"
         "        \"pools\": [ ],\n"
         "        \"rebind-timer\": 20,\n"

+ 28 - 0
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -116,6 +116,34 @@ TEST(Subnet4Test, siaddr) {
         BadValue);
 }
 
+// Checks whether server-hostname field can be set and retrieved correctly.
+TEST(Subnet4Test, serverHostname) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    // Check if the default is empty
+    EXPECT_TRUE(subnet.getSname().empty());
+
+    // Check that we can set it up
+    EXPECT_NO_THROW(subnet.setSname("foobar"));
+
+    // Check that we can get it back
+    EXPECT_EQ("foobar", subnet.getSname());
+}
+
+// Checks whether boot-file-name field can be set and retrieved correctly.
+TEST(Subnet4Test, bootFileName) {
+    Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);
+
+    // Check if the default is empty
+    EXPECT_TRUE(subnet.getFilename().empty());
+
+    // Check that we can set it up
+    EXPECT_NO_THROW(subnet.setFilename("foobar"));
+
+    // Check that we can get it back
+    EXPECT_EQ("foobar", subnet.getFilename());
+}
+
 // Checks if the match-client-id flag can be set and retrieved.
 TEST(Subnet4Test, matchClientId) {
     Subnet4 subnet(IOAddress("192.0.2.1"), 24, 1000, 2000, 3000);