Browse Source

[master] Finished merge of trac5241 (always-send)

Francis Dupont 7 years ago
parent
commit
3e99671764
39 changed files with 5192 additions and 4445 deletions
  1. 6 0
      ChangeLog
  2. 8 0
      doc/examples/kea4/multiple-options.json
  3. 9 0
      doc/examples/kea6/multiple-options.json
  4. 50 2
      doc/guide/dhcp4-srv.xml
  5. 50 4
      doc/guide/dhcp6-srv.xml
  6. 813 816
      src/bin/dhcp4/dhcp4_lexer.cc
  7. 9 0
      src/bin/dhcp4/dhcp4_lexer.ll
  8. 1203 1210
      src/bin/dhcp4/dhcp4_parser.cc
  9. 96 96
      src/bin/dhcp4/dhcp4_parser.h
  10. 7 0
      src/bin/dhcp4/dhcp4_parser.yy
  11. 50 12
      src/bin/dhcp4/dhcp4_srv.cc
  12. 1 1
      src/bin/dhcp4/location.hh
  13. 1 1
      src/bin/dhcp4/position.hh
  14. 2 1
      src/bin/dhcp4/simple_parser4.cc
  15. 1 1
      src/bin/dhcp4/stack.hh
  16. 4 4
      src/bin/dhcp4/tests/config_parser_unittest.cc
  17. 243 3
      src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
  18. 35 0
      src/bin/dhcp4/tests/get_config_unittest.cc
  19. 856 859
      src/bin/dhcp6/dhcp6_lexer.cc
  20. 9 0
      src/bin/dhcp6/dhcp6_lexer.ll
  21. 1250 1260
      src/bin/dhcp6/dhcp6_parser.cc
  22. 117 117
      src/bin/dhcp6/dhcp6_parser.h
  23. 7 0
      src/bin/dhcp6/dhcp6_parser.yy
  24. 51 12
      src/bin/dhcp6/dhcp6_srv.cc
  25. 1 1
      src/bin/dhcp6/location.hh
  26. 1 1
      src/bin/dhcp6/position.hh
  27. 2 1
      src/bin/dhcp6/simple_parser6.cc
  28. 1 1
      src/bin/dhcp6/stack.hh
  29. 69 1
      src/bin/dhcp6/tests/classify_unittests.cc
  30. 147 1
      src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
  31. 28 0
      src/bin/dhcp6/tests/get_config_unittest.cc
  32. 4 0
      src/lib/dhcpsrv/cfg_option.cc
  33. 5 0
      src/lib/dhcpsrv/cfg_option.h
  34. 16 2
      src/lib/dhcpsrv/parsers/dhcp_parsers.cc
  35. 5 0
      src/lib/dhcpsrv/parsers/dhcp_parsers.h
  36. 15 19
      src/lib/dhcpsrv/tests/cfg_option_unittest.cc
  37. 6 3
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  38. 8 2
      src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
  39. 6 14
      src/lib/dhcpsrv/tests/subnet_unittest.cc

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+1283.	[func]		fdupont
+	An 'always-send' parameter has been added to options
+	configuration.  It allows an option to be always sent, even if
+	a client didn't request it.
+	(Trac #5241, git xxx)
+
 1282.	[bug]		fdupont
 	Now all interface service sockets are closed before interface
 	re-detection. Note if the re-configuration fails they remain

+ 8 - 0
doc/examples/kea4/multiple-options.json

@@ -125,6 +125,14 @@
          {
             "name": "default-ip-ttl",
             "data": "0xf0"
+         },
+            // At a few exceptions options are added to response only when
+            // the client requests them. The always-send flag should be used
+            // to enforce a particular option.
+         {
+            "name": "vendor-class-identifier",
+            "data": "isc",
+            "always-send": true
          }
        ],
 

+ 9 - 0
doc/examples/kea6/multiple-options.json

@@ -111,6 +111,15 @@
             {
                 "name": "bootfile-param",
                 "data": "root=/dev/sda2, quiet, splash"
+            },
+
+            // At a few exceptions options are added to response only when
+            // the client requests them. The always-send flag should be used
+            // to enforce a particular option.
+            {
+                "name": "pana-agent",
+                "data": "2001:db8:2::123",
+                "always-send": true
             }
       ],
       "pools": [

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

@@ -990,8 +990,56 @@ temporarily override a list of interface names and listen on all interfaces.
         },
         ...
     ]
-} </screen>
-
+}
+        </screen>
+        Defined options are added to response when the client requests them
+        at a few exceptions which are always added. To enforce the addition
+        of a particular option set the always-send flag to true as in:
+        <screen>
+"Dhcp4": {
+    "option-data": [
+        {
+           <userinput>"name": "domain-name-servers",
+           "data": "192.0.2.1, 192.0.2.2",
+           "always-send": true</userinput>
+        },
+        ...
+    ]
+}
+        </screen>
+        The effect is the same as if the client added the option code in the
+        Parameter Request List option (or its equivalent for vendor
+        options) so in:
+        <screen>
+"Dhcp4": {
+    "option-data": [
+        {
+           <userinput>"name": "domain-name-servers",
+           "data": "192.0.2.1, 192.0.2.2",
+           "always-send": true</userinput>
+        },
+        ...
+    ],
+    "subnet4": [
+        {
+           "subnet": "192.0.3.0/24",
+           "option-data": [
+               {
+                   <userinput>"name": "domain-name-servers",
+                   "data": "192.0.3.1, 192.0.3.2"</userinput>
+               },
+               ...
+           ],
+           ...
+        },
+        ...
+    ],
+    ...
+}
+        </screen>
+        The Domain Name Servers option is always added to responses
+        (the always-send is "sticky") but the value is the subnet one
+        when the client is localized in the subnet.
       </para>
     <para>
       The <command>name</command> parameter specifies the option name. For a

+ 50 - 4
doc/guide/dhcp6-srv.xml

@@ -1010,7 +1010,7 @@ temporarily override a list of interface names and listen on all interfaces.
         be skipped, unless you want to specify the option value as
         hexstring. Therefore the above example can be simplified to:
         <screen>
-"Dhcp4": {
+"Dhcp6": {
     "option-data": [
         {
            <userinput>"name": "dns-servers",
@@ -1018,11 +1018,57 @@ temporarily override a list of interface names and listen on all interfaces.
         },
         ...
     ]
-} </screen>
-
+}
+        </screen>
+        Defined options are added to response when the client requests them
+        at a few exceptions which are always added. To enforce the addition
+        of a particular option set the always-send flag to true as in:
+        <screen>
+"Dhcp6": {
+    "option-data": [
+        {
+           <userinput>"name": "dns-servers",
+           "data": "2001:db8::cafe, 2001:db8::babe",
+           "always-send": true</userinput>
+        },
+        ...
+    ]
+}
+        </screen>
+        The effect is the same as if the client added the option code in the
+        Option Request Option (or its equivalent for vendor options) so in:
+        <screen>
+"Dhcp6": {
+    "option-data": [
+        {
+           <userinput>"name": "dns-servers",
+           "data": "2001:db8::cafe, 2001:db8::babe",
+           "always-send": true</userinput>
+        },
+        ...
+    ],
+    "subnet6": [
+        {
+           "subnet": "2001:db8:1::/64",
+           "option-data": [
+               {
+                   <userinput>"name": "dns-servers",
+                   "data": "2001:db8:1::cafe, 2001:db8:1::babe"</userinput>
+               },
+               ...
+           ],
+           ...
+        },
+        ...
+    ],
+    ...
+}
+        </screen>
+        The DNS Servers option is always added to responses
+        (the always-send is "sticky") but the value is the subnet one
+        when the client is localized in the subnet.
       </para>
 
-
     <para>
       It is possible to override options on a per-subnet basis.  If
       clients connected to most of your subnets are expected to get the

File diff suppressed because it is too large
+ 813 - 816
src/bin/dhcp4/dhcp4_lexer.cc


+ 9 - 0
src/bin/dhcp4/dhcp4_lexer.ll

@@ -496,6 +496,15 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"always-send\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser4Context::OPTION_DATA:
+        return isc::dhcp::Dhcp4Parser::make_ALWAYS_SEND(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp4Parser::make_STRING("always-send", driver.loc_);
+    }
+}
+
 \"pools\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:

File diff suppressed because it is too large
+ 1203 - 1210
src/bin/dhcp4/dhcp4_parser.cc


+ 96 - 96
src/bin/dhcp4/dhcp4_parser.h

@@ -219,7 +219,7 @@ namespace isc { namespace dhcp {
     /// Both variants must be built beforehand, because swapping the actual
     /// data requires reading it (with as()), and this is not possible on
     /// unconstructed variants: it would require some dynamic testing, which
-    /// should not be the variant's responsibility.
+    /// should not be the variant's responsability.
     /// Swapping between built and (possibly) non-built is done with
     /// variant::move ().
     template <typename T>
@@ -398,81 +398,81 @@ namespace isc { namespace dhcp {
         TOKEN_CODE = 306,
         TOKEN_SPACE = 307,
         TOKEN_CSV_FORMAT = 308,
-        TOKEN_RECORD_TYPES = 309,
-        TOKEN_ENCAPSULATE = 310,
-        TOKEN_ARRAY = 311,
-        TOKEN_POOLS = 312,
-        TOKEN_POOL = 313,
-        TOKEN_USER_CONTEXT = 314,
-        TOKEN_SUBNET = 315,
-        TOKEN_INTERFACE = 316,
-        TOKEN_INTERFACE_ID = 317,
-        TOKEN_ID = 318,
-        TOKEN_RAPID_COMMIT = 319,
-        TOKEN_RESERVATION_MODE = 320,
-        TOKEN_DISABLED = 321,
-        TOKEN_OUT_OF_POOL = 322,
-        TOKEN_ALL = 323,
-        TOKEN_HOST_RESERVATION_IDENTIFIERS = 324,
-        TOKEN_CLIENT_CLASSES = 325,
-        TOKEN_TEST = 326,
-        TOKEN_CLIENT_CLASS = 327,
-        TOKEN_RESERVATIONS = 328,
-        TOKEN_DUID = 329,
-        TOKEN_HW_ADDRESS = 330,
-        TOKEN_CIRCUIT_ID = 331,
-        TOKEN_CLIENT_ID = 332,
-        TOKEN_HOSTNAME = 333,
-        TOKEN_FLEX_ID = 334,
-        TOKEN_RELAY = 335,
-        TOKEN_IP_ADDRESS = 336,
-        TOKEN_HOOKS_LIBRARIES = 337,
-        TOKEN_LIBRARY = 338,
-        TOKEN_PARAMETERS = 339,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 340,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 341,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 342,
-        TOKEN_HOLD_RECLAIMED_TIME = 343,
-        TOKEN_MAX_RECLAIM_LEASES = 344,
-        TOKEN_MAX_RECLAIM_TIME = 345,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 346,
-        TOKEN_DHCP4O6_PORT = 347,
-        TOKEN_CONTROL_SOCKET = 348,
-        TOKEN_SOCKET_TYPE = 349,
-        TOKEN_SOCKET_NAME = 350,
-        TOKEN_DHCP_DDNS = 351,
-        TOKEN_ENABLE_UPDATES = 352,
-        TOKEN_QUALIFYING_SUFFIX = 353,
-        TOKEN_SERVER_IP = 354,
-        TOKEN_SERVER_PORT = 355,
-        TOKEN_SENDER_IP = 356,
-        TOKEN_SENDER_PORT = 357,
-        TOKEN_MAX_QUEUE_SIZE = 358,
-        TOKEN_NCR_PROTOCOL = 359,
-        TOKEN_NCR_FORMAT = 360,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 361,
-        TOKEN_OVERRIDE_NO_UPDATE = 362,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 363,
-        TOKEN_REPLACE_CLIENT_NAME = 364,
-        TOKEN_GENERATED_PREFIX = 365,
-        TOKEN_TCP = 366,
-        TOKEN_JSON = 367,
-        TOKEN_WHEN_PRESENT = 368,
-        TOKEN_NEVER = 369,
-        TOKEN_ALWAYS = 370,
-        TOKEN_WHEN_NOT_PRESENT = 371,
-        TOKEN_LOGGING = 372,
-        TOKEN_LOGGERS = 373,
-        TOKEN_OUTPUT_OPTIONS = 374,
-        TOKEN_OUTPUT = 375,
-        TOKEN_DEBUGLEVEL = 376,
-        TOKEN_SEVERITY = 377,
-        TOKEN_FLUSH = 378,
-        TOKEN_MAXSIZE = 379,
-        TOKEN_MAXVER = 380,
-        TOKEN_DHCP6 = 381,
-        TOKEN_DHCPDDNS = 382,
-        TOKEN_CONTROL_AGENT = 383,
+        TOKEN_ALWAYS_SEND = 309,
+        TOKEN_RECORD_TYPES = 310,
+        TOKEN_ENCAPSULATE = 311,
+        TOKEN_ARRAY = 312,
+        TOKEN_POOLS = 313,
+        TOKEN_POOL = 314,
+        TOKEN_USER_CONTEXT = 315,
+        TOKEN_SUBNET = 316,
+        TOKEN_INTERFACE = 317,
+        TOKEN_INTERFACE_ID = 318,
+        TOKEN_ID = 319,
+        TOKEN_RAPID_COMMIT = 320,
+        TOKEN_RESERVATION_MODE = 321,
+        TOKEN_DISABLED = 322,
+        TOKEN_OUT_OF_POOL = 323,
+        TOKEN_ALL = 324,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 325,
+        TOKEN_CLIENT_CLASSES = 326,
+        TOKEN_TEST = 327,
+        TOKEN_CLIENT_CLASS = 328,
+        TOKEN_RESERVATIONS = 329,
+        TOKEN_DUID = 330,
+        TOKEN_HW_ADDRESS = 331,
+        TOKEN_CIRCUIT_ID = 332,
+        TOKEN_CLIENT_ID = 333,
+        TOKEN_HOSTNAME = 334,
+        TOKEN_FLEX_ID = 335,
+        TOKEN_RELAY = 336,
+        TOKEN_IP_ADDRESS = 337,
+        TOKEN_HOOKS_LIBRARIES = 338,
+        TOKEN_LIBRARY = 339,
+        TOKEN_PARAMETERS = 340,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 341,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 342,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 343,
+        TOKEN_HOLD_RECLAIMED_TIME = 344,
+        TOKEN_MAX_RECLAIM_LEASES = 345,
+        TOKEN_MAX_RECLAIM_TIME = 346,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 347,
+        TOKEN_DHCP4O6_PORT = 348,
+        TOKEN_CONTROL_SOCKET = 349,
+        TOKEN_SOCKET_TYPE = 350,
+        TOKEN_SOCKET_NAME = 351,
+        TOKEN_DHCP_DDNS = 352,
+        TOKEN_ENABLE_UPDATES = 353,
+        TOKEN_QUALIFYING_SUFFIX = 354,
+        TOKEN_SERVER_IP = 355,
+        TOKEN_SERVER_PORT = 356,
+        TOKEN_SENDER_IP = 357,
+        TOKEN_SENDER_PORT = 358,
+        TOKEN_MAX_QUEUE_SIZE = 359,
+        TOKEN_NCR_PROTOCOL = 360,
+        TOKEN_NCR_FORMAT = 361,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 362,
+        TOKEN_OVERRIDE_NO_UPDATE = 363,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 364,
+        TOKEN_REPLACE_CLIENT_NAME = 365,
+        TOKEN_GENERATED_PREFIX = 366,
+        TOKEN_TCP = 367,
+        TOKEN_JSON = 368,
+        TOKEN_WHEN_PRESENT = 369,
+        TOKEN_NEVER = 370,
+        TOKEN_ALWAYS = 371,
+        TOKEN_WHEN_NOT_PRESENT = 372,
+        TOKEN_LOGGING = 373,
+        TOKEN_LOGGERS = 374,
+        TOKEN_OUTPUT_OPTIONS = 375,
+        TOKEN_OUTPUT = 376,
+        TOKEN_DEBUGLEVEL = 377,
+        TOKEN_SEVERITY = 378,
+        TOKEN_FLUSH = 379,
+        TOKEN_MAXSIZE = 380,
+        TOKEN_MAXVER = 381,
+        TOKEN_DHCP6 = 382,
+        TOKEN_DHCPDDNS = 383,
         TOKEN_TOPLEVEL_JSON = 384,
         TOKEN_TOPLEVEL_DHCP4 = 385,
         TOKEN_SUB_DHCP4 = 386,
@@ -812,6 +812,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_ALWAYS_SEND (const location_type& l);
+
+    static inline
+    symbol_type
     make_RECORD_TYPES (const location_type& l);
 
     static inline
@@ -1108,10 +1112,6 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
-    make_CONTROL_AGENT (const location_type& l);
-
-    static inline
-    symbol_type
     make_TOPLEVEL_JSON (const location_type& l);
 
     static inline
@@ -1375,8 +1375,8 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 748,     ///< Last index in yytable_.
-      yynnts_ = 317,  ///< Number of nonterminal symbols.
+      yylast_ = 751,     ///< Last index in yytable_.
+      yynnts_ = 316,  ///< Number of nonterminal symbols.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
@@ -1476,8 +1476,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 209: // db_type
       case 286: // hr_mode
-      case 418: // ncr_protocol_value
-      case 426: // replace_client_name_value
+      case 419: // ncr_protocol_value
+      case 427: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
@@ -1519,8 +1519,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 209: // db_type
       case 286: // hr_mode
-      case 418: // ncr_protocol_value
-      case 426: // replace_client_name_value
+      case 419: // ncr_protocol_value
+      case 427: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
@@ -1621,8 +1621,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 209: // db_type
       case 286: // hr_mode
-      case 418: // ncr_protocol_value
-      case 426: // replace_client_name_value
+      case 419: // ncr_protocol_value
+      case 427: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
@@ -1670,8 +1670,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 209: // db_type
       case 286: // hr_mode
-      case 418: // ncr_protocol_value
-      case 426: // replace_client_name_value
+      case 419: // ncr_protocol_value
+      case 427: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
@@ -2078,6 +2078,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_ALWAYS_SEND (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_ALWAYS_SEND, l);
+  }
+
+  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_RECORD_TYPES (const location_type& l)
   {
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
@@ -2522,12 +2528,6 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
-  Dhcp4Parser::make_CONTROL_AGENT (const location_type& l)
-  {
-    return symbol_type (token::TOKEN_CONTROL_AGENT, l);
-  }
-
-  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_TOPLEVEL_JSON (const location_type& l)
   {
     return symbol_type (token::TOKEN_TOPLEVEL_JSON, l);

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

@@ -96,6 +96,7 @@ using namespace std;
   CODE "code"
   SPACE "space"
   CSV_FORMAT "csv-format"
+  ALWAYS_SEND "always-send"
   RECORD_TYPES "record-types"
   ENCAPSULATE "encapsulate"
   ARRAY "array"
@@ -1134,6 +1135,7 @@ option_data_param: option_data_name
                  | option_data_code
                  | option_data_space
                  | option_data_csv_format
+                 | option_data_always_send
                  | unknown_map_entry
                  ;
 
@@ -1156,6 +1158,11 @@ option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
     ctx.stack_.back()->set("csv-format", space);
 };
 
+option_data_always_send: ALWAYS_SEND COLON BOOLEAN {
+    ElementPtr persist(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("always-send", persist);
+};
+
 // ---- pools ------------------------------------
 
 // This defines the "pools": [ ... ] entry that may appear in subnet4.

+ 50 - 12
src/bin/dhcp4/dhcp4_srv.cc

@@ -1226,21 +1226,37 @@ Dhcpv4Srv::appendRequestedOptions(Dhcpv4Exchange& ex) {
     }
 
     Pkt4Ptr query = ex.getQuery();
+    Pkt4Ptr resp = ex.getResponse();
+    std::vector<uint8_t> requested_opts;
 
     // try to get the 'Parameter Request List' option which holds the
     // codes of requested options.
     OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
         OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
-    // If there is no PRL option in the message from the client then
-    // there is nothing to do.
-    if (!option_prl) {
-        return;
+    // Get the codes of requested options.
+    if (option_prl) {
+        requested_opts = option_prl->getValues();
+    }
+    // Iterate on the configured option list to add persistent options
+    for (CfgOptionList::const_iterator copts = co_list.begin();
+         copts != co_list.end(); ++copts) {
+        const OptionContainerPtr& opts = (*copts)->getAll(DHCP4_OPTION_SPACE);
+        if (!opts) {
+            continue;
+        }
+        // Get persistent options
+        const OptionContainerPersistIndex& idx = opts->get<2>();
+        const OptionContainerPersistRange& range = idx.equal_range(true);
+        for (OptionContainerPersistIndex::const_iterator desc = range.first;
+             desc != range.second; ++desc) {
+            // Add the persistent option code to requested options
+            if (desc->option_) {
+                uint8_t code = static_cast<uint8_t>(desc->option_->getType());
+                requested_opts.push_back(code);
+            }
+        }
     }
 
-    Pkt4Ptr resp = ex.getResponse();
-
-    // Get the codes of requested options.
-    const std::vector<uint8_t>& requested_opts = option_prl->getValues();
     // For each requested option code get the instance of the option
     // to be returned to the client.
     for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
@@ -1288,15 +1304,39 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
     }
 
     uint32_t vendor_id = vendor_req->getVendorId();
+    std::vector<uint8_t> requested_opts;
 
     // Let's try to get ORO within that vendor-option
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
     /// vendors may have different policies.
     OptionUint8ArrayPtr oro =
         boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_ORO));
+    // Get the list of options that client requested.
+    if (oro) {
+        requested_opts = oro->getValues();
+    }
+    // Iterate on the configured option list to add persistent options
+    for (CfgOptionList::const_iterator copts = co_list.begin();
+         copts != co_list.end(); ++copts) {
+        const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
+        if (!opts) {
+            continue;
+        }
+        // Get persistent options
+        const OptionContainerPersistIndex& idx = opts->get<2>();
+        const OptionContainerPersistRange& range = idx.equal_range(true);
+        for (OptionContainerPersistIndex::const_iterator desc = range.first;
+             desc != range.second; ++desc) {
+            // Add the persistent option code to requested options
+            if (desc->option_) {
+                uint8_t code = static_cast<uint8_t>(desc->option_->getType());
+                requested_opts.push_back(code);
+            }
+        }
+    }
 
-    // Option ORO not found. Don't do anything then.
-    if (!oro) {
+    // If there is nothing to add don't do anything then.
+    if (requested_opts.empty()) {
         return;
     }
 
@@ -1304,8 +1344,6 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
 
     // Get the list of options that client requested.
     bool added = false;
-    const std::vector<uint8_t>& requested_opts = oro->getValues();
-
     for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
          code != requested_opts.end(); ++code) {
         if  (!vendor_rsp->getOption(*code)) {

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

@@ -1,4 +1,4 @@
-// Generated 201707141116
+// Generated 201707281415
 // 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 201707141116
+// Generated 201707281415
 // A Bison parser, made by GNU Bison 3.0.4.
 
 // Positions for Bison parsers in C++

+ 2 - 1
src/bin/dhcp4/simple_parser4.cc

@@ -45,7 +45,8 @@ const SimpleDefaults SimpleParser4::OPTION4_DEF_DEFAULTS = {
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
     { "space",        Element::string,  "dhcp4"},
-    { "csv-format",   Element::boolean, "true"}
+    { "csv-format",   Element::boolean, "true"},
+    { "always-send",  Element::boolean, "false"}
 };
 
 /// @brief This table defines default global values for DHCPv4

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

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

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

@@ -2820,7 +2820,7 @@ TEST_F(Dhcp4ParserTest, optionDataSinglePool) {
     // Expect a single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range.first, range.second));
     const uint8_t foo_expected[] = {
-	0xAB, 0xCD, 0xEF, 0x01, 0x05
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
     };
     // Check if option is valid in terms of code and carried data.
     testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
@@ -2893,7 +2893,7 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
     // Expect a single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
     const uint8_t foo_expected[] = {
-	0xAB, 0xCD, 0xEF, 0x01, 0x05
+        0xAB, 0xCD, 0xEF, 0x01, 0x05
     };
     // Check if option is valid in terms of code and carried data.
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
@@ -2909,8 +2909,8 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
 
     const OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<OptionContainerTypeIndex::const_iterator,
-	      OptionContainerTypeIndex::const_iterator> range2 =
-	idx2.equal_range(23);
+              OptionContainerTypeIndex::const_iterator> range2 =
+        idx2.equal_range(23);
     ASSERT_EQ(1, std::distance(range2.first, range2.second));
     const uint8_t foo2_expected[] = {
         0x01

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

@@ -93,8 +93,33 @@ const char* CONFIGS[] = {
         "    \"valid-lifetime\": 4000,"
         "    \"interface\": \"eth0\" "
         " } ],"
-    "\"valid-lifetime\": 4000 }"
-};
+    "\"valid-lifetime\": 4000 }",
+
+    // Configuration 2:
+    // - 1 subnet, 2 global options (one forced with always-send)
+    "{"
+    "    \"interfaces-config\": {"
+    "    \"interfaces\": [ \"*\" ] }, "
+    "    \"rebind-timer\": 2000, "
+    "    \"renew-timer\": 1000, "
+    "    \"valid-lifetime\": 4000, "
+    "    \"subnet4\": [ {"
+    "        \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+    "        \"subnet\": \"192.0.2.0/24\""
+    "    } ], "
+    "    \"option-data\": ["
+    "        {"
+    "            \"name\": \"default-ip-ttl\", "
+    "            \"data\": \"FF\", "
+    "            \"csv-format\": false"
+    "        }, "
+    "        {"
+    "            \"name\": \"ip-forwarding\", "
+    "            \"data\": \"false\", "
+    "            \"always-send\": true"
+    "        }"
+    "    ]"
+    "}" };
 
 // This test verifies that the destination address of the response
 // message is set to giaddr, when giaddr is set to non-zero address
@@ -751,7 +776,7 @@ TEST_F(Dhcpv4SrvTest, discoverEchoClientId) {
     CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnets->at(0));
     CfgMgr::instance().getStagingCfg()->setEchoClientId(false);
     CfgMgr::instance().commit();
-    
+
     offer = srv.processDiscover(dis);
 
     // Check if we get response at all
@@ -1451,6 +1476,89 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
     EXPECT_EQ("192.0.2.2", addrs[1].toText());
 }
 
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and persistent options are actually assigned.
+TEST_F(Dhcpv4SrvTest, vendorPersistentOptions) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    ConstElementPtr x;
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-data\": [ {"
+        "          \"name\": \"tftp-servers\","
+        "          \"space\": \"vendor-4491\","
+        "          \"code\": 2,"
+        "          \"data\": \"192.0.2.1, 192.0.2.2\","
+        "          \"csv-format\": true,"
+        "          \"always-send\": true"
+        "        }],"
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.0/25\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"rebind-timer\": 2000, "
+        "    \"renew-timer\": 1000, "
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface\": \"eth0\" "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(srv, json));
+    ASSERT_TRUE(x);
+    comment_ = isc::config::parseAnswer(rcode_, x);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    boost::shared_ptr<Pkt4> dis(new Pkt4(DHCPDISCOVER, 1234));
+    // Set the giaddr and hops to non-zero address as if it was relayed.
+    dis->setGiaddr(IOAddress("192.0.2.1"));
+    dis->setHops(1);
+
+    OptionPtr clientid = generateClientId();
+    dis->addOption(clientid);
+    // Set interface. It is required by the server to generate server id.
+    dis->setIface("eth0");
+
+    // Let's add a vendor-option (vendor-id=4491).
+    OptionPtr vendor(new OptionVendor(Option::V4, 4491));
+    dis->addOption(vendor);
+
+    // Pass it to the server and get an advertise
+    Pkt4Ptr offer = srv.processDiscover(dis);
+
+    // check if we get response at all
+    ASSERT_TRUE(offer);
+
+    // Check if there is a vendor option response
+    OptionPtr tmp = offer->getOption(DHO_VIVSO_SUBOPTIONS);
+    ASSERT_TRUE(tmp);
+
+    // The response should be OptionVendor object
+    boost::shared_ptr<OptionVendor> vendor_resp =
+        boost::dynamic_pointer_cast<OptionVendor>(tmp);
+    ASSERT_TRUE(vendor_resp);
+
+    OptionPtr docsis2 = vendor_resp->getOption(DOCSIS3_V4_TFTP_SERVERS);
+    ASSERT_TRUE(docsis2);
+
+    Option4AddrLstPtr tftp_srvs = boost::dynamic_pointer_cast<Option4AddrLst>(docsis2);
+    ASSERT_TRUE(tftp_srvs);
+
+    Option4AddrLst::AddressContainer addrs = tftp_srvs->getAddresses();
+    ASSERT_EQ(2, addrs.size());
+    EXPECT_EQ("192.0.2.1", addrs[0].toText());
+    EXPECT_EQ("192.0.2.2", addrs[1].toText());
+}
+
 // Test checks whether it is possible to use option definitions defined in
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
@@ -1952,6 +2060,85 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) {
     EXPECT_NE(0, opt->getUint8());
 }
 
+// Checks class options have the priority over global persistent options
+TEST_F(Dhcpv4SrvTest, classGlobalPersistency) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    NakedDhcpv4Srv srv(0);
+
+    // A global ip-forwarding option is set in the response.
+    // The router class matches incoming packets with foo in a host-name
+    // option (code 12) and sets an ip-forwarding option in the response.
+    // Note the persistency flag follows a "OR" semantic so to set
+    // it to false (or to leave the default) has no effect.
+    string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ] }, "
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000, "
+        "\"subnet4\": [ "
+        "{   \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ], "
+        "    \"subnet\": \"192.0.2.0/24\" } ], "
+        "\"option-data\": ["
+        "    {    \"name\": \"ip-forwarding\", "
+        "         \"data\": \"false\", "
+        "         \"always-send\": true } ], "
+        "\"client-classes\": [ "
+        "{   \"name\": \"router\","
+        "    \"option-data\": ["
+        "        {    \"name\": \"ip-forwarding\", "
+        "             \"data\": \"true\", "
+        "             \"always-send\": false } ], "
+        "    \"test\": \"option[12].text == 'foo'\" } ] }";
+
+    ConstElementPtr json;
+    ASSERT_NO_THROW(json = parseDHCP4(config));
+    ConstElementPtr status;
+
+    // Configure the server and make sure the config is accepted
+    EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+    ASSERT_TRUE(status);
+    comment_ = config::parseAnswer(rcode_, status);
+    ASSERT_EQ(0, rcode_);
+
+    CfgMgr::instance().commit();
+
+    // Create a packet with enough to select the subnet and go through
+    // the DISCOVER processing
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    query->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    query->addOption(clientid);
+    query->setIface("eth1");
+
+    // Do not add a PRL
+    OptionPtr prl = query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST);
+    EXPECT_FALSE(prl);
+
+    // Create and add a host-name option to the query
+    OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+    ASSERT_TRUE(hostname);
+    query->addOption(hostname);
+
+    // Classify the packet
+    srv.classifyPacket(query);
+
+    // The packet should be in the router class
+    EXPECT_TRUE(query->inClass("router"));
+
+    // Process the query
+    Pkt4Ptr response = srv.processDiscover(query);
+
+    // Processing should add an ip-forwarding option
+    OptionPtr opt = response->getOption(DHO_IP_FORWARDING);
+    ASSERT_TRUE(opt);
+    ASSERT_GT(opt->len(), opt->getHeaderLen());
+    // Classification sets the value to true/1, global to false/0
+    // Here class has the priority
+    EXPECT_NE(0, opt->getUint8());
+}
+
 // Checks if the client-class field is indeed used for subnet selection.
 // Note that packet classification is already checked in Dhcpv4SrvTest
 // .*Classification above.
@@ -2008,6 +2195,59 @@ TEST_F(Dhcpv4SrvTest, clientClassify) {
     EXPECT_TRUE(srv_.selectSubnet(dis));
 }
 
+// Checks effect of persistency (aka always-true) flag on the PRL
+TEST_F(Dhcpv4SrvTest, prlPersistency) {
+    IfaceMgrTestConfig test_config(true);
+    IfaceMgr::instance().openSockets4();
+
+    ASSERT_NO_THROW(configure(CONFIGS[2]));
+
+    // Create a packet with enough to select the subnet and go through
+    // the DISCOVER processing
+    Pkt4Ptr query(new Pkt4(DHCPDISCOVER, 1234));
+    query->setRemoteAddr(IOAddress("192.0.2.1"));
+    OptionPtr clientid = generateClientId();
+    query->addOption(clientid);
+    query->setIface("eth1");
+
+    // Create and add a PRL option for another option
+    OptionUint8ArrayPtr prl(new OptionUint8Array(Option::V4,
+                                                 DHO_DHCP_PARAMETER_REQUEST_LIST));
+    ASSERT_TRUE(prl);
+    prl->addValue(DHO_ARP_CACHE_TIMEOUT);
+    query->addOption(prl);
+
+    // Create and add a host-name option to the query
+    OptionStringPtr hostname(new OptionString(Option::V4, 12, "foo"));
+    ASSERT_TRUE(hostname);
+    query->addOption(hostname);
+
+    // Let the server process it.
+    Pkt4Ptr response = srv_.processDiscover(query);
+
+    // Processing should add an ip-forwarding option
+    ASSERT_TRUE(response->getOption(DHO_IP_FORWARDING));
+    // But no default-ip-ttl
+    ASSERT_FALSE(response->getOption(DHO_DEFAULT_IP_TTL));
+    // Nor an arp-cache-timeout
+    ASSERT_FALSE(response->getOption(DHO_ARP_CACHE_TIMEOUT));
+
+    // Reset PRL adding default-ip-ttl
+    query->delOption(DHO_DHCP_PARAMETER_REQUEST_LIST);
+    prl->addValue(DHO_DEFAULT_IP_TTL);
+    query->addOption(prl);
+
+    // Let the server process it again.
+    response = srv_.processDiscover(query);
+
+    // Processing should add an ip-forwarding option
+    ASSERT_TRUE(response->getOption(DHO_IP_FORWARDING));
+    // and now a default-ip-ttl
+    ASSERT_TRUE(response->getOption(DHO_DEFAULT_IP_TTL));
+    // and still no arp-cache-timeout
+    ASSERT_FALSE(response->getOption(DHO_ARP_CACHE_TIMEOUT));
+}
+
 // Checks if relay IP address specified in the relay-info structure in
 // subnet4 is being used properly.
 TEST_F(Dhcpv4SrvTest, relayOverride) {

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

@@ -3260,6 +3260,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3267,6 +3268,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 23,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"01\",\n"
@@ -3354,6 +3356,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3361,6 +3364,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp4\"\n"
 "                    },\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
@@ -3426,6 +3430,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3433,6 +3438,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3519,6 +3525,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3526,6 +3533,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3595,6 +3603,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 222,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"11\",\n"
@@ -3602,6 +3611,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3609,6 +3619,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3713,6 +3724,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"AB\",\n"
@@ -3732,6 +3744,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3739,6 +3752,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp4\"\n"
 "                    },\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
@@ -3815,6 +3829,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"0102030405060708090A\",\n"
@@ -3848,6 +3863,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"FF\",\n"
@@ -3927,6 +3943,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 56,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"ABCDEF0105\",\n"
@@ -3934,6 +3951,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"01\",\n"
@@ -4010,6 +4028,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 56,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"ABCDEF0105\",\n"
@@ -4022,6 +4041,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"01\",\n"
@@ -4084,6 +4104,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -4091,6 +4112,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -4160,6 +4182,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 43,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"0104000004D20204C0A80201\",\n"
@@ -4167,6 +4190,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -4174,6 +4198,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -4269,12 +4294,14 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"1234\",\n"
 "                \"space\": \"vendor-1234\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -4350,6 +4377,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
@@ -4713,6 +4741,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.3.95\",\n"
@@ -4720,6 +4749,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\n"
@@ -4738,6 +4768,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.3.15\",\n"
@@ -4745,6 +4776,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"32\",\n"
@@ -4809,6 +4841,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.4.11\",\n"
@@ -4816,6 +4849,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"95\",\n"
@@ -4913,6 +4947,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 100,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"123\",\n"

File diff suppressed because it is too large
+ 856 - 859
src/bin/dhcp6/dhcp6_lexer.cc


+ 9 - 0
src/bin/dhcp6/dhcp6_lexer.ll

@@ -699,6 +699,15 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     }
 }
 
+\"always-send\" {
+    switch(driver.ctx_) {
+    case isc::dhcp::Parser6Context::OPTION_DATA:
+        return isc::dhcp::Dhcp6Parser::make_ALWAYS_SEND(driver.loc_);
+    default:
+        return isc::dhcp::Dhcp6Parser::make_STRING("always-send", driver.loc_);
+    }
+}
+
 \"pools\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:

File diff suppressed because it is too large
+ 1250 - 1260
src/bin/dhcp6/dhcp6_parser.cc


+ 117 - 117
src/bin/dhcp6/dhcp6_parser.h

@@ -219,7 +219,7 @@ namespace isc { namespace dhcp {
     /// Both variants must be built beforehand, because swapping the actual
     /// data requires reading it (with as()), and this is not possible on
     /// unconstructed variants: it would require some dynamic testing, which
-    /// should not be the variant's responsibility.
+    /// should not be the variant's responsability.
     /// Swapping between built and (possibly) non-built is done with
     /// variant::move ().
     template <typename T>
@@ -388,98 +388,98 @@ namespace isc { namespace dhcp {
         TOKEN_CODE = 296,
         TOKEN_SPACE = 297,
         TOKEN_CSV_FORMAT = 298,
-        TOKEN_RECORD_TYPES = 299,
-        TOKEN_ENCAPSULATE = 300,
-        TOKEN_ARRAY = 301,
-        TOKEN_POOLS = 302,
-        TOKEN_POOL = 303,
-        TOKEN_PD_POOLS = 304,
-        TOKEN_PREFIX = 305,
-        TOKEN_PREFIX_LEN = 306,
-        TOKEN_EXCLUDED_PREFIX = 307,
-        TOKEN_EXCLUDED_PREFIX_LEN = 308,
-        TOKEN_DELEGATED_LEN = 309,
-        TOKEN_USER_CONTEXT = 310,
-        TOKEN_SUBNET = 311,
-        TOKEN_INTERFACE = 312,
-        TOKEN_INTERFACE_ID = 313,
-        TOKEN_ID = 314,
-        TOKEN_RAPID_COMMIT = 315,
-        TOKEN_RESERVATION_MODE = 316,
-        TOKEN_DISABLED = 317,
-        TOKEN_OUT_OF_POOL = 318,
-        TOKEN_ALL = 319,
-        TOKEN_MAC_SOURCES = 320,
-        TOKEN_RELAY_SUPPLIED_OPTIONS = 321,
-        TOKEN_HOST_RESERVATION_IDENTIFIERS = 322,
-        TOKEN_CLIENT_CLASSES = 323,
-        TOKEN_TEST = 324,
-        TOKEN_CLIENT_CLASS = 325,
-        TOKEN_RESERVATIONS = 326,
-        TOKEN_IP_ADDRESSES = 327,
-        TOKEN_PREFIXES = 328,
-        TOKEN_DUID = 329,
-        TOKEN_HW_ADDRESS = 330,
-        TOKEN_HOSTNAME = 331,
-        TOKEN_FLEX_ID = 332,
-        TOKEN_RELAY = 333,
-        TOKEN_IP_ADDRESS = 334,
-        TOKEN_HOOKS_LIBRARIES = 335,
-        TOKEN_LIBRARY = 336,
-        TOKEN_PARAMETERS = 337,
-        TOKEN_EXPIRED_LEASES_PROCESSING = 338,
-        TOKEN_RECLAIM_TIMER_WAIT_TIME = 339,
-        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 340,
-        TOKEN_HOLD_RECLAIMED_TIME = 341,
-        TOKEN_MAX_RECLAIM_LEASES = 342,
-        TOKEN_MAX_RECLAIM_TIME = 343,
-        TOKEN_UNWARNED_RECLAIM_CYCLES = 344,
-        TOKEN_SERVER_ID = 345,
-        TOKEN_LLT = 346,
-        TOKEN_EN = 347,
-        TOKEN_LL = 348,
-        TOKEN_IDENTIFIER = 349,
-        TOKEN_HTYPE = 350,
-        TOKEN_TIME = 351,
-        TOKEN_ENTERPRISE_ID = 352,
-        TOKEN_DHCP4O6_PORT = 353,
-        TOKEN_CONTROL_SOCKET = 354,
-        TOKEN_SOCKET_TYPE = 355,
-        TOKEN_SOCKET_NAME = 356,
-        TOKEN_DHCP_DDNS = 357,
-        TOKEN_ENABLE_UPDATES = 358,
-        TOKEN_QUALIFYING_SUFFIX = 359,
-        TOKEN_SERVER_IP = 360,
-        TOKEN_SERVER_PORT = 361,
-        TOKEN_SENDER_IP = 362,
-        TOKEN_SENDER_PORT = 363,
-        TOKEN_MAX_QUEUE_SIZE = 364,
-        TOKEN_NCR_PROTOCOL = 365,
-        TOKEN_NCR_FORMAT = 366,
-        TOKEN_ALWAYS_INCLUDE_FQDN = 367,
-        TOKEN_OVERRIDE_NO_UPDATE = 368,
-        TOKEN_OVERRIDE_CLIENT_UPDATE = 369,
-        TOKEN_REPLACE_CLIENT_NAME = 370,
-        TOKEN_GENERATED_PREFIX = 371,
-        TOKEN_UDP = 372,
-        TOKEN_TCP = 373,
-        TOKEN_JSON = 374,
-        TOKEN_WHEN_PRESENT = 375,
-        TOKEN_NEVER = 376,
-        TOKEN_ALWAYS = 377,
-        TOKEN_WHEN_NOT_PRESENT = 378,
-        TOKEN_LOGGING = 379,
-        TOKEN_LOGGERS = 380,
-        TOKEN_OUTPUT_OPTIONS = 381,
-        TOKEN_OUTPUT = 382,
-        TOKEN_DEBUGLEVEL = 383,
-        TOKEN_SEVERITY = 384,
-        TOKEN_FLUSH = 385,
-        TOKEN_MAXSIZE = 386,
-        TOKEN_MAXVER = 387,
-        TOKEN_DHCP4 = 388,
-        TOKEN_DHCPDDNS = 389,
-        TOKEN_CONTROL_AGENT = 390,
+        TOKEN_ALWAYS_SEND = 299,
+        TOKEN_RECORD_TYPES = 300,
+        TOKEN_ENCAPSULATE = 301,
+        TOKEN_ARRAY = 302,
+        TOKEN_POOLS = 303,
+        TOKEN_POOL = 304,
+        TOKEN_PD_POOLS = 305,
+        TOKEN_PREFIX = 306,
+        TOKEN_PREFIX_LEN = 307,
+        TOKEN_EXCLUDED_PREFIX = 308,
+        TOKEN_EXCLUDED_PREFIX_LEN = 309,
+        TOKEN_DELEGATED_LEN = 310,
+        TOKEN_USER_CONTEXT = 311,
+        TOKEN_SUBNET = 312,
+        TOKEN_INTERFACE = 313,
+        TOKEN_INTERFACE_ID = 314,
+        TOKEN_ID = 315,
+        TOKEN_RAPID_COMMIT = 316,
+        TOKEN_RESERVATION_MODE = 317,
+        TOKEN_DISABLED = 318,
+        TOKEN_OUT_OF_POOL = 319,
+        TOKEN_ALL = 320,
+        TOKEN_MAC_SOURCES = 321,
+        TOKEN_RELAY_SUPPLIED_OPTIONS = 322,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 323,
+        TOKEN_CLIENT_CLASSES = 324,
+        TOKEN_TEST = 325,
+        TOKEN_CLIENT_CLASS = 326,
+        TOKEN_RESERVATIONS = 327,
+        TOKEN_IP_ADDRESSES = 328,
+        TOKEN_PREFIXES = 329,
+        TOKEN_DUID = 330,
+        TOKEN_HW_ADDRESS = 331,
+        TOKEN_HOSTNAME = 332,
+        TOKEN_FLEX_ID = 333,
+        TOKEN_RELAY = 334,
+        TOKEN_IP_ADDRESS = 335,
+        TOKEN_HOOKS_LIBRARIES = 336,
+        TOKEN_LIBRARY = 337,
+        TOKEN_PARAMETERS = 338,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 339,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 340,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 341,
+        TOKEN_HOLD_RECLAIMED_TIME = 342,
+        TOKEN_MAX_RECLAIM_LEASES = 343,
+        TOKEN_MAX_RECLAIM_TIME = 344,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 345,
+        TOKEN_SERVER_ID = 346,
+        TOKEN_LLT = 347,
+        TOKEN_EN = 348,
+        TOKEN_LL = 349,
+        TOKEN_IDENTIFIER = 350,
+        TOKEN_HTYPE = 351,
+        TOKEN_TIME = 352,
+        TOKEN_ENTERPRISE_ID = 353,
+        TOKEN_DHCP4O6_PORT = 354,
+        TOKEN_CONTROL_SOCKET = 355,
+        TOKEN_SOCKET_TYPE = 356,
+        TOKEN_SOCKET_NAME = 357,
+        TOKEN_DHCP_DDNS = 358,
+        TOKEN_ENABLE_UPDATES = 359,
+        TOKEN_QUALIFYING_SUFFIX = 360,
+        TOKEN_SERVER_IP = 361,
+        TOKEN_SERVER_PORT = 362,
+        TOKEN_SENDER_IP = 363,
+        TOKEN_SENDER_PORT = 364,
+        TOKEN_MAX_QUEUE_SIZE = 365,
+        TOKEN_NCR_PROTOCOL = 366,
+        TOKEN_NCR_FORMAT = 367,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 368,
+        TOKEN_OVERRIDE_NO_UPDATE = 369,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 370,
+        TOKEN_REPLACE_CLIENT_NAME = 371,
+        TOKEN_GENERATED_PREFIX = 372,
+        TOKEN_UDP = 373,
+        TOKEN_TCP = 374,
+        TOKEN_JSON = 375,
+        TOKEN_WHEN_PRESENT = 376,
+        TOKEN_NEVER = 377,
+        TOKEN_ALWAYS = 378,
+        TOKEN_WHEN_NOT_PRESENT = 379,
+        TOKEN_LOGGING = 380,
+        TOKEN_LOGGERS = 381,
+        TOKEN_OUTPUT_OPTIONS = 382,
+        TOKEN_OUTPUT = 383,
+        TOKEN_DEBUGLEVEL = 384,
+        TOKEN_SEVERITY = 385,
+        TOKEN_FLUSH = 386,
+        TOKEN_MAXSIZE = 387,
+        TOKEN_MAXVER = 388,
+        TOKEN_DHCP4 = 389,
+        TOKEN_DHCPDDNS = 390,
         TOKEN_TOPLEVEL_JSON = 391,
         TOKEN_TOPLEVEL_DHCP6 = 392,
         TOKEN_SUB_DHCP6 = 393,
@@ -780,6 +780,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_ALWAYS_SEND (const location_type& l);
+
+    static inline
+    symbol_type
     make_RECORD_TYPES (const location_type& l);
 
     static inline
@@ -1144,10 +1148,6 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
-    make_CONTROL_AGENT (const location_type& l);
-
-    static inline
-    symbol_type
     make_TOPLEVEL_JSON (const location_type& l);
 
     static inline
@@ -1415,8 +1415,8 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 800,     ///< Last index in yytable_.
-      yynnts_ = 334,  ///< Number of nonterminal symbols.
+      yylast_ = 799,     ///< Last index in yytable_.
+      yynnts_ = 333,  ///< Number of nonterminal symbols.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
@@ -1516,9 +1516,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 214: // db_type
       case 290: // hr_mode
-      case 410: // duid_type
-      case 443: // ncr_protocol_value
-      case 451: // replace_client_name_value
+      case 411: // duid_type
+      case 444: // ncr_protocol_value
+      case 452: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
@@ -1559,9 +1559,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 214: // db_type
       case 290: // hr_mode
-      case 410: // duid_type
-      case 443: // ncr_protocol_value
-      case 451: // replace_client_name_value
+      case 411: // duid_type
+      case 444: // ncr_protocol_value
+      case 452: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
@@ -1661,9 +1661,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 214: // db_type
       case 290: // hr_mode
-      case 410: // duid_type
-      case 443: // ncr_protocol_value
-      case 451: // replace_client_name_value
+      case 411: // duid_type
+      case 444: // ncr_protocol_value
+      case 452: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
@@ -1710,9 +1710,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 214: // db_type
       case 290: // hr_mode
-      case 410: // duid_type
-      case 443: // ncr_protocol_value
-      case 451: // replace_client_name_value
+      case 411: // duid_type
+      case 444: // ncr_protocol_value
+      case 452: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
@@ -2060,6 +2060,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_ALWAYS_SEND (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_ALWAYS_SEND, l);
+  }
+
+  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_RECORD_TYPES (const location_type& l)
   {
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
@@ -2606,12 +2612,6 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
-  Dhcp6Parser::make_CONTROL_AGENT (const location_type& l)
-  {
-    return symbol_type (token::TOKEN_CONTROL_AGENT, l);
-  }
-
-  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_TOPLEVEL_JSON (const location_type& l)
   {
     return symbol_type (token::TOKEN_TOPLEVEL_JSON, l);

+ 7 - 0
src/bin/dhcp6/dhcp6_parser.yy

@@ -85,6 +85,7 @@ using namespace std;
   CODE "code"
   SPACE "space"
   CSV_FORMAT "csv-format"
+  ALWAYS_SEND "always-send"
   RECORD_TYPES "record-types"
   ENCAPSULATE "encapsulate"
   ARRAY "array"
@@ -1121,6 +1122,7 @@ option_data_param: option_data_name
                  | option_data_code
                  | option_data_space
                  | option_data_csv_format
+                 | option_data_always_send
                  | unknown_map_entry
                  ;
 
@@ -1143,6 +1145,11 @@ option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
     ctx.stack_.back()->set("csv-format", space);
 };
 
+option_data_always_send: ALWAYS_SEND COLON BOOLEAN {
+    ElementPtr persist(new BoolElement($3, ctx.loc2pos(@3)));
+    ctx.stack_.back()->set("always-send", persist);
+};
+
 // ---- pools ------------------------------------
 
 // This defines the "pools": [ ... ] entry that may appear in subnet6.

+ 51 - 12
src/bin/dhcp6/dhcp6_srv.cc

@@ -918,20 +918,39 @@ void
 Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
                                   const CfgOptionList& co_list) {
 
+    // Unlikely short cut
+    if (co_list.empty()) {
+        return;
+    }
+
+    std::vector<uint16_t> requested_opts;
+
     // Client requests some options using ORO option. Try to
     // get this option from client's message.
     boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
         boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
         (question->getOption(D6O_ORO));
 
-    // If there is no ORO option, there is nothing more to do.
-    if (!option_oro) {
-        return;
-
-    }
-
     // Get the list of options that client requested.
-    const std::vector<uint16_t>& requested_opts = option_oro->getValues();
+    if (option_oro) {
+        requested_opts = option_oro->getValues();
+    }
+    // Iterate on the configured option list to add persistent options
+    for (CfgOptionList::const_iterator copts = co_list.begin();
+         copts != co_list.end(); ++copts) {
+        const OptionContainerPtr& opts = (*copts)->getAll(DHCP6_OPTION_SPACE);
+        if (!opts) {
+            continue;
+        }
+        // Get persistent options
+        const OptionContainerPersistIndex& idx = opts->get<2>();
+        const OptionContainerPersistRange& range = idx.equal_range(true);
+        for (OptionContainerPersistIndex::const_iterator desc = range.first;
+             desc != range.second; ++desc) {
+            // Add the persistent option code to requested options
+            requested_opts.push_back(desc->option_->getType());
+        }
+    }
 
     BOOST_FOREACH(uint16_t opt, requested_opts) {
         // Iterate on the configured option list
@@ -969,24 +988,44 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
         return;
     }
 
+    uint32_t vendor_id = vendor_req->getVendorId();
+    std::vector<uint16_t> requested_opts;
+
     // Let's try to get ORO within that vendor-option
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
     /// may have different policies.
     boost::shared_ptr<OptionUint16Array> oro =
         boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_ORO));
+    if (oro) {
+        requested_opts = oro->getValues();
+    }
+    // Iterate on the configured option list to add persistent options
+    for (CfgOptionList::const_iterator copts = co_list.begin();
+         copts != co_list.end(); ++copts) {
+        const OptionContainerPtr& opts = (*copts)->getAll(vendor_id);
+        if (!opts) {
+            continue;
+        }
+        // Get persistent options
+        const OptionContainerPersistIndex& idx = opts->get<2>();
+        const OptionContainerPersistRange& range = idx.equal_range(true);
+        for (OptionContainerPersistIndex::const_iterator desc = range.first;
+             desc != range.second; ++desc) {
+            // Add the persistent option code to requested options
+            requested_opts.push_back(desc->option_->getType());
+        }
+    }
 
-    // Option ORO not found. Don't do anything then.
-    if (!oro) {
+    // If there is nothing to add don't do anything then.
+    if (requested_opts.empty()) {
         return;
     }
 
-    uint32_t vendor_id = vendor_req->getVendorId();
-
     boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
 
     // Get the list of options that client requested.
     bool added = false;
-    const std::vector<uint16_t>& requested_opts = oro->getValues();
+
     BOOST_FOREACH(uint16_t opt, requested_opts) {
         for (CfgOptionList::const_iterator copts = co_list.begin();
              copts != co_list.end(); ++copts) {

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

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

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

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

+ 2 - 1
src/bin/dhcp6/simple_parser6.cc

@@ -45,7 +45,8 @@ const SimpleDefaults SimpleParser6::OPTION6_DEF_DEFAULTS = {
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
     { "space",        Element::string,  "dhcp6"},
-    { "csv-format",   Element::boolean, "true"}
+    { "csv-format",   Element::boolean, "true"},
+    { "always-send",  Element::boolean, "false"}
 };
 
 /// @brief This table defines default global values for DHCPv6

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

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

+ 69 - 1
src/bin/dhcp6/tests/classify_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -518,6 +518,74 @@ TEST_F(ClassifyTest, classGlobalPriority) {
     EXPECT_NE(0, opt->getUint8());
 }
 
+// Checks class options have the priority over global persistent options
+TEST_F(ClassifyTest, classGlobalPersistency) {
+    IfaceMgrTestConfig test_config(true);
+
+    NakedDhcpv6Srv srv(0);
+
+    // Subnet sets an ipv6-forwarding option in the response.
+    // The router class matches incoming packets with foo in a host-name
+    // option (code 1234) and sets an ipv6-forwarding option in the response.
+    // Note the persistency flag follows a "OR" semantic so to set
+    // it to false (or to leave the default) has no effect.
+    std::string config = "{ \"interfaces-config\": {"
+        "    \"interfaces\": [ \"*\" ] }, "
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"valid-lifetime\": 4000, "
+        "\"option-def\": [ "
+        "{   \"name\": \"host-name\","
+        "    \"code\": 1234,"
+        "    \"type\": \"string\" },"
+        "{   \"name\": \"ipv6-forwarding\","
+        "    \"code\": 2345,"
+        "    \"type\": \"boolean\" }],"
+        "\"option-data\": ["
+        "    {    \"name\": \"ipv6-forwarding\", "
+        "         \"data\": \"false\", "
+        "         \"always-send\": true } ], "
+        "\"subnet6\": [ "
+        "{   \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\", "
+        "    \"option-data\": ["
+        "        {    \"name\": \"ipv6-forwarding\", "
+        "             \"data\": \"false\", "
+        "             \"always-send\": false } ] } ] }";
+    ASSERT_NO_THROW(configure(config));
+
+    // Create a packet with enough to select the subnet and go through
+    // the SOLICIT processing
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+    query->setRemoteAddr(IOAddress("fe80::abcd"));
+    OptionPtr clientid = generateClientId();
+    query->addOption(clientid);
+    query->setIface("eth1");
+    query->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+
+    // Do not add an ORO.
+    OptionPtr oro = query->getOption(D6O_ORO);
+    EXPECT_FALSE(oro);
+
+    // Create and add a host-name option to the query
+    OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+    ASSERT_TRUE(hostname);
+    query->addOption(hostname);
+
+    // Process the query
+    Pkt6Ptr response = srv.processSolicit(query);
+
+    // Processing should add an ip-forwarding option
+    OptionPtr opt = response->getOption(2345);
+    ASSERT_TRUE(opt);
+    ASSERT_GT(opt->len(), opt->getHeaderLen());
+    // Global sets the value to true/1, subnet to false/0
+    // Here subnet has the priority
+    EXPECT_EQ(0, opt->getUint8());
+}
+
 // Checks if the client-class field is indeed used for subnet selection.
 // Note that packet classification is already checked in ClassifyTest
 // .*Classification above.

+ 147 - 1
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc

@@ -99,7 +99,33 @@ const char* CONFIGS[] = {
     "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
     "    \"subnet\": \"2001:db8:1::/48\" "
     " } ],"
-    "\"valid-lifetime\": 4000 }"
+    "\"valid-lifetime\": 4000 }",
+
+    // Configuration 2:
+    // - a single subnet
+    // - two global options (one enforced with always-send)
+    "{"
+    "    \"interfaces-config\": { \"interfaces\": [ \"*\" ] }, "
+    "    \"preferred-lifetime\": 3000, "
+    "    \"rebind-timer\": 2000, "
+    "    \"renew-timer\": 1000, "
+    "    \"valid-lifetime\": 4000, "
+    "    \"subnet6\": [ {"
+    "       \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+    "       \"subnet\": \"2001:db8:1::/48\""
+    "    } ], "
+    "    \"option-data\": ["
+    "    {"
+    "        \"name\": \"dns-servers\", "
+    "        \"data\": \"2001:db8:1234:FFFF::1\""
+    "    }, "
+    "    {"
+    "        \"name\": \"subscriber-id\", "
+    "         \"data\": \"1234\", "
+    "         \"always-send\": true"
+    "    }"
+    "    ]"
+    "}"
 };
 
 // This test verifies that incoming SOLICIT can be handled properly when
@@ -1502,6 +1528,54 @@ TEST_F(Dhcpv6SrvTest, portsRelayedTraffic) {
     EXPECT_EQ(DHCP6_SERVER_PORT, adv->getRemotePort());
 }
 
+// Checks effect of persistency (aka always-true) flag on the ORO
+TEST_F(Dhcpv6SrvTest, prlPersistency) {
+    IfaceMgrTestConfig test_config(true);
+
+    ASSERT_NO_THROW(configure(CONFIGS[2]));
+
+    // Create a packet with enough to select the subnet and go through
+    // the SOLICIT processing
+    Pkt6Ptr sol(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Create and add an ORO for another option
+    OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+    ASSERT_TRUE(oro);
+    oro->addValue(D6O_SNTP_SERVERS);
+    sol->addOption(oro);
+
+    // Let the server process it and generate a response.
+    Pkt6Ptr response = srv_.processSolicit(sol);
+
+    // The server should add a subscriber-id option
+    ASSERT_TRUE(response->getOption(D6O_SUBSCRIBER_ID));
+    // But no dns-servers
+    ASSERT_FALSE(response->getOption(D6O_NAME_SERVERS));
+    // Nor a sntp-servers
+    ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+
+    // Reset ORO adding dns-servers
+    sol->delOption(D6O_ORO);
+    oro->addValue(D6O_NAME_SERVERS);
+    sol->addOption(oro);
+
+    // Let the server process it again. This time the name-servers
+    // option should be present.
+    response = srv_.processSolicit(sol);
+
+    // Processing should add a subscriber-id option
+    ASSERT_TRUE(response->getOption(D6O_SUBSCRIBER_ID));
+    // and now a dns-servers
+    ASSERT_TRUE(response->getOption(D6O_NAME_SERVERS));
+    // and still no sntp-servers
+    ASSERT_FALSE(response->getOption(D6O_SNTP_SERVERS));
+}
+
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
 // @todo Uncomment this test as part of #3180 work.
 // Kea code currently fails to handle docsis traffic.
@@ -1666,6 +1740,78 @@ TEST_F(Dhcpv6SrvTest, vendorOptionsORO) {
     EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
 }
 
+// This test checks if Option Request Option (ORO) in docsis (vendor-id=4491)
+// vendor options is parsed correctly and the persistent options are actually assigned.
+TEST_F(Dhcpv6SrvTest, vendorPersistentOptions) {
+
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "    \"option-def\": [ {"
+        "        \"name\": \"config-file\","
+        "        \"code\": 33,"
+        "        \"type\": \"string\","
+        "        \"space\": \"vendor-4491\""
+        "     } ],"
+        "    \"option-data\": [ {"
+        "          \"name\": \"config-file\","
+        "          \"space\": \"vendor-4491\","
+        "          \"data\": \"normal_erouter_v6.cm\","
+        "          \"always-send\": true"
+        "        }],"
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"renew-timer\": 1000, "
+        "    \"rebind-timer\": 1000, "
+        "    \"preferred-lifetime\": 3000,"
+        "    \"valid-lifetime\": 4000,"
+        "    \"interface-id\": \"\","
+        "    \"interface\": \"eth0\""
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ASSERT_NO_THROW(configure(config));
+
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    OptionPtr clientid = generateClientId();
+    sol->addOption(clientid);
+
+    // Let's add a vendor-option (vendor-id=4491).
+    OptionPtr vendor(new OptionVendor(Option::V6, 4491));
+    sol->addOption(vendor);
+
+    // Pass it to the server and get an advertise
+    Pkt6Ptr adv = srv_.processSolicit(sol);
+
+    // check if we get response at all
+    ASSERT_TRUE(adv);
+
+    // Check if there is vendor option response
+    OptionPtr tmp = adv->getOption(D6O_VENDOR_OPTS);
+    ASSERT_TRUE(tmp);
+
+    // The response should be OptionVendor object
+    boost::shared_ptr<OptionVendor> vendor_resp =
+        boost::dynamic_pointer_cast<OptionVendor>(tmp);
+    ASSERT_TRUE(vendor_resp);
+
+    OptionPtr docsis33 = vendor_resp->getOption(33);
+    ASSERT_TRUE(docsis33);
+
+    OptionStringPtr config_file = boost::dynamic_pointer_cast<OptionString>(docsis33);
+    ASSERT_TRUE(config_file);
+    EXPECT_EQ("normal_erouter_v6.cm", config_file->getValue());
+}
+
 // Test checks whether it is possible to use option definitions defined in
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {

+ 28 - 0
src/bin/dhcp6/tests/get_config_unittest.cc

@@ -65,6 +65,7 @@ namespace {
 ///@{
 /// @brief extracted configurations
 const char* EXTRACTED_CONFIGS[] = {
+/// put this after const char* EXTRACTED_CONFIGS[] = {
     // CONFIGURATION 0
 "{\n"
 "        \"interfaces-config\": {\n"
@@ -3142,6 +3143,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3149,6 +3151,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 7,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"01\",\n"
@@ -3249,6 +3252,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 38,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3256,6 +3260,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp6\"\n"
 "                    },\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 7,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
@@ -3324,6 +3329,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3331,6 +3337,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3425,6 +3432,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 110,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3432,6 +3440,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 111,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3510,6 +3519,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"11\",\n"
@@ -3517,6 +3527,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 110,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
@@ -3524,6 +3535,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 111,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3652,6 +3664,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 38,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"0102030405060708090A\",\n"
@@ -3684,6 +3697,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 15,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"FFFEFDFCFB\",\n"
@@ -3774,6 +3788,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 38,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"112233445566\",\n"
@@ -3790,6 +3805,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 15,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"AABBCCDDEE\",\n"
@@ -3805,6 +3821,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 38,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"0102030405060708090A\",\n"
@@ -3817,6 +3834,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 15,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"FFFEFDFCFB\",\n"
@@ -3881,12 +3899,14 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"1234\",\n"
 "                \"space\": \"vendor-1234\"\n"
 "            },\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3970,6 +3990,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
@@ -4526,6 +4547,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:2::abbc\",\n"
@@ -4533,6 +4555,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"25\",\n"
@@ -4549,6 +4572,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:2::1111\",\n"
@@ -4556,6 +4580,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\n"
@@ -4600,6 +4625,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ ],\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:3::3333\",\n"
@@ -4607,6 +4633,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"33\",\n"
@@ -4705,6 +4732,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"option-data\": [\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 100,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\n"

+ 4 - 0
src/lib/dhcpsrv/cfg_option.cc

@@ -224,6 +224,8 @@ CfgOption::toElement() const {
                 std::string repr = util::encode::encodeHex(bin);
                 map->set("data", Element::create(repr));
             }
+            // Set the persistency flag
+            map->set("always-send", Element::create(opt->persistent_));
             // Push on the list
             result->add(map);
         }
@@ -265,6 +267,8 @@ CfgOption::toElement() const {
                 std::string repr = util::encode::encodeHex(bin);
                 map->set("data", Element::create(repr));
             }
+            // Set the persistency flag
+            map->set("always-send", Element::create(opt->persistent_));
             // Push on the list
             result->add(map);
         }

+ 5 - 0
src/lib/dhcpsrv/cfg_option.h

@@ -184,6 +184,11 @@ typedef std::pair<OptionContainerTypeIndex::const_iterator,
                   OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
 /// Type of the index #2 - option persistency flag.
 typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
+/// Pair of iterators to represent the range of options having the
+/// same persistency flag. The first element in this pair represents
+/// the beginning of the range, the second element represents the end.
+typedef std::pair<OptionContainerPersistIndex::const_iterator,
+                  OptionContainerPersistIndex::const_iterator> OptionContainerPersistRange;
 
 /// @brief Represents option data configuration for the DHCP server.
 ///

+ 16 - 2
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -303,6 +303,19 @@ OptionDataParser::extractSpace(ConstElementPtr parent) const {
     return (space);
 }
 
+OptionalValue<bool>
+OptionDataParser::extractPersistent(ConstElementPtr parent) const {
+    bool persist = false;
+    try {
+        persist = getBoolean(parent, "always-send");
+
+    } catch (...) {
+        return (OptionalValue<bool>(persist));
+    }
+
+    return (OptionalValue<bool>(persist, OptionalValueState(true)));
+}
+
 template<typename SearchKey>
 OptionDefinitionPtr
 OptionDataParser::findOptionDefinition(const std::string& option_space,
@@ -337,6 +350,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
     OptionalValue<uint32_t> code_param =  extractCode(option_data);
     OptionalValue<std::string> name_param = extractName(option_data);
     OptionalValue<bool> csv_format_param = extractCSVFormat(option_data);
+    OptionalValue<bool> persist_param = extractPersistent(option_data);
     std::string data_param = extractData(option_data);
     std::string space_param = extractSpace(option_data);
 
@@ -426,7 +440,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
                                     binary));
 
         desc.option_ = option;
-        desc.persistent_ = false;
+        desc.persistent_ = persist_param.isSpecified() && persist_param;
     } else {
 
         // Option name is specified it should match the name in the definition.
@@ -447,7 +461,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
                 def->optionFactory(universe, def->getCode(), data_tokens) :
                 def->optionFactory(universe, def->getCode(), binary);
             desc.option_ = option;
-            desc.persistent_ = false;
+            desc.persistent_ = persist_param.isSpecified() && persist_param;
             if (use_csv) {
                 desc.formatted_value_ = data_param;
             }

+ 5 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.h

@@ -462,6 +462,11 @@ private:
     /// @return Option space name.
     std::string extractSpace(data::ConstElementPtr parent) const;
 
+    /// @brief Retrieves persistent/always-send parameter as an optional value.
+    ///
+    /// @return Value of the persistent parameter, possibly unspecified.
+    util::OptionalValue<bool> extractPersistent(data::ConstElementPtr parent) const;
+
     /// @brief Address family: @c AF_INET or @c AF_INET6.
     uint16_t address_family_;
 };

+ 15 - 19
src/lib/dhcpsrv/tests/cfg_option_unittest.cc

@@ -449,9 +449,7 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
         // For each code we should get two instances of options->
-        std::pair<OptionContainerTypeIndex::const_iterator,
-                  OptionContainerTypeIndex::const_iterator> range =
-            idx.equal_range(code);
+        OptionContainerTypeRange range = idx.equal_range(code);
         // Distance between iterators indicates how many options
         // have been returned for the particular code.
         ASSERT_EQ(2, distance(range.first, range.second));
@@ -465,9 +463,7 @@ TEST_F(CfgOptionTest, addNonUniqueOptions) {
 
     // Let's try to find some non-exiting option.
     const uint16_t non_existing_code = 150;
-    std::pair<OptionContainerTypeIndex::const_iterator,
-              OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(non_existing_code);
+    OptionContainerTypeRange range = idx.equal_range(non_existing_code);
     // Empty set is expected.
     EXPECT_EQ(0, distance(range.first, range.second));
 }
@@ -501,17 +497,13 @@ TEST(Subnet6Test, addPersistentOption) {
     OptionContainerPersistIndex& idx = options->get<2>();
 
     // Get all persistent options->
-    std::pair<OptionContainerPersistIndex::const_iterator,
-              OptionContainerPersistIndex::const_iterator> range_persistent =
-        idx.equal_range(true);
-    // 3 out of 10 options have been flagged persistent.
+    OptionContainerPersistRange range_persistent = idx.equal_range(true);
+    // 7 out of 10 options have been flagged persistent.
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
     // Get all non-persistent options->
-    std::pair<OptionContainerPersistIndex::const_iterator,
-              OptionContainerPersistIndex::const_iterator> range_non_persistent =
-        idx.equal_range(false);
-    // 7 out of 10 options have been flagged persistent.
+    OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+    // 3 out of 10 options have been flagged not persistent.
     ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
 }
 
@@ -612,7 +604,7 @@ TEST_F(CfgOptionTest, unparse) {
     OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
     cfg.add(opt3, false, DHCP6_OPTION_SPACE);
     OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
-    cfg.add(opt4, false, "vendor-1234");
+    cfg.add(opt4, true, "vendor-1234");
 
     // Unparse
     std::string expected = "[\n"
@@ -620,23 +612,27 @@ TEST_F(CfgOptionTest, unparse) {
         "    \"code\": 100,\n"
         "    \"space\": \"dns\",\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"12121212\"\n"
+        "    \"data\": \"12121212\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "    \"code\": 101,\n"
         "    \"space\": \"dns\",\n"
         "    \"csv-format\": true,\n"
-        "    \"data\": \"12, 12, 12, 12\"\n"
+        "    \"data\": \"12, 12, 12, 12\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "    \"code\": 13,\n"
         "    \"name\": \"status-code\",\n"
         "    \"space\": \"dhcp6\",\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"0000\"\n"
+        "    \"data\": \"0000\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "    \"code\": 100,\n"
         "    \"space\": \"vendor-1234\",\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"21212121\"\n"
+        "    \"data\": \"21212121\",\n"
+        "    \"always-send\": true\n"
         "}]\n";
     isc::test::runToElementTest<CfgOption>(expected, cfg);
 }

+ 6 - 3
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -621,13 +621,15 @@ const SimpleDefaults ParseConfigTest::OPTION4_DEF_DEFAULTS = {
 /// This table defines default values for options in DHCPv6
 const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
     { "space",        Element::string,  "dhcp6"},
-    { "csv-format",   Element::boolean, "true"}
+    { "csv-format",   Element::boolean, "true"},
+    { "always-send",  Element::boolean,"false"}
 };
 
 /// This table defines default values for options in DHCPv4
 const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
     { "space",        Element::string,  "dhcp4"},
-    { "csv-format",   Element::boolean, "true"}
+    { "csv-format",   Element::boolean, "true"},
+    { "always-send",  Element::boolean, "false"}
 };
 
 /// This table defines default values for both DHCPv4 and DHCPv6
@@ -861,7 +863,8 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
         "    \"space\": \"isc\","
         "    \"code\": 100,"
         "    \"data\": \"192.0.2.0\","
-        "    \"csv-format\": true"
+        "    \"csv-format\": true,"
+        "    \"always-send\": false"
         " } ]"
         "}";
 

+ 8 - 2
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -984,7 +984,8 @@ TEST_F(HostReservationParserTest, options4) {
            "\"code\": 7,"
            "\"csv-format\": true,"
            "\"space\": \"dhcp4\","
-           "\"data\": \"172.16.15.23\""
+           "\"data\": \"172.16.15.23\","
+           "\"always-send\": false"
         "},"
         "{"
            "\"name\": \"default-ip-ttl\","
@@ -1034,11 +1035,13 @@ TEST_F(HostReservationParserTest, options4) {
     option->set("code", Element::create(DHO_NAME_SERVERS));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     option = config_element->get("option-data")->getNonConst(1);
     option = config_element->get("option-data")->getNonConst(2);
     option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     ElementPtr expected = Element::createList();
     expected->add(config_element);
 
@@ -1071,7 +1074,8 @@ TEST_F(HostReservationParserTest, options6) {
            "\"code\": 27,"
            "\"csv-format\": true,"
            "\"space\": \"dhcp6\","
-           "\"data\": \"2001:db8:1::1204\""
+           "\"data\": \"2001:db8:1::1204\","
+           "\"always-send\": true"
         "},"
         "{"
            "\"name\": \"preference\","
@@ -1122,11 +1126,13 @@ TEST_F(HostReservationParserTest, options6) {
     option->set("code", Element::create(D6O_NAME_SERVERS));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     option = config_element->get("option-data")->getNonConst(1);
     option = config_element->get("option-data")->getNonConst(2);
     option->set("code", Element::create(D6O_PREFERENCE));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     config = prettyPrint(config_element);
     boost::algorithm::to_lower(config);
     

+ 6 - 14
src/lib/dhcpsrv/tests/subnet_unittest.cc

@@ -944,9 +944,7 @@ TEST(Subnet6Test, addNonUniqueOptions) {
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
         // For each code we should get two instances of options->
-        std::pair<OptionContainerTypeIndex::const_iterator,
-                  OptionContainerTypeIndex::const_iterator> range =
-            idx.equal_range(code);
+        OptionContainerTypeRange range = idx.equal_range(code);
         // Distance between iterators indicates how many options
         // have been returned for the particular code.
         ASSERT_EQ(2, distance(range.first, range.second));
@@ -960,9 +958,7 @@ TEST(Subnet6Test, addNonUniqueOptions) {
 
     // Let's try to find some non-exiting option.
     const uint16_t non_existing_code = 150;
-    std::pair<OptionContainerTypeIndex::const_iterator,
-              OptionContainerTypeIndex::const_iterator> range =
-        idx.equal_range(non_existing_code);
+    OptionContainerTypeRange range = idx.equal_range(non_existing_code);
     // Empty set is expected.
     EXPECT_EQ(0, distance(range.first, range.second));
 }
@@ -994,17 +990,13 @@ TEST(Subnet6Test, addPersistentOption) {
     OptionContainerPersistIndex& idx = options->get<2>();
 
     // Get all persistent options->
-    std::pair<OptionContainerPersistIndex::const_iterator,
-              OptionContainerPersistIndex::const_iterator> range_persistent =
-        idx.equal_range(true);
-    // 3 out of 10 options have been flagged persistent.
+    OptionContainerPersistRange range_persistent = idx.equal_range(true);
+    // 7 out of 10 options have been flagged persistent.
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
     // Get all non-persistent options->
-    std::pair<OptionContainerPersistIndex::const_iterator,
-              OptionContainerPersistIndex::const_iterator> range_non_persistent =
-        idx.equal_range(false);
-    // 7 out of 10 options have been flagged persistent.
+    OptionContainerPersistRange range_non_persistent = idx.equal_range(false);
+    // 3 out of 10 options have been flagged not persistent.
     ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
 }