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
 1282.	[bug]		fdupont
 	Now all interface service sockets are closed before interface
 	Now all interface service sockets are closed before interface
 	re-detection. Note if the re-configuration fails they remain
 	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",
             "name": "default-ip-ttl",
             "data": "0xf0"
             "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",
                 "name": "bootfile-param",
                 "data": "root=/dev/sda2, quiet, splash"
                 "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": [
       "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>
     <para>
     <para>
       The <command>name</command> parameter specifies the option name. For a
       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
         be skipped, unless you want to specify the option value as
         hexstring. Therefore the above example can be simplified to:
         hexstring. Therefore the above example can be simplified to:
         <screen>
         <screen>
-"Dhcp4": {
+"Dhcp6": {
     "option-data": [
     "option-data": [
         {
         {
            <userinput>"name": "dns-servers",
            <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>
 
 
-
     <para>
     <para>
       It is possible to override options on a per-subnet basis.  If
       It is possible to override options on a per-subnet basis.  If
       clients connected to most of your subnets are expected to get the
       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\" {
 \"pools\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
     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
     /// Both variants must be built beforehand, because swapping the actual
     /// data requires reading it (with as()), and this is not possible on
     /// data requires reading it (with as()), and this is not possible on
     /// unconstructed variants: it would require some dynamic testing, which
     /// 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
     /// Swapping between built and (possibly) non-built is done with
     /// variant::move ().
     /// variant::move ().
     template <typename T>
     template <typename T>
@@ -398,81 +398,81 @@ namespace isc { namespace dhcp {
         TOKEN_CODE = 306,
         TOKEN_CODE = 306,
         TOKEN_SPACE = 307,
         TOKEN_SPACE = 307,
         TOKEN_CSV_FORMAT = 308,
         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_JSON = 384,
         TOKEN_TOPLEVEL_DHCP4 = 385,
         TOKEN_TOPLEVEL_DHCP4 = 385,
         TOKEN_SUB_DHCP4 = 386,
         TOKEN_SUB_DHCP4 = 386,
@@ -812,6 +812,10 @@ namespace isc { namespace dhcp {
 
 
     static inline
     static inline
     symbol_type
     symbol_type
+    make_ALWAYS_SEND (const location_type& l);
+
+    static inline
+    symbol_type
     make_RECORD_TYPES (const location_type& l);
     make_RECORD_TYPES (const location_type& l);
 
 
     static inline
     static inline
@@ -1108,10 +1112,6 @@ namespace isc { namespace dhcp {
 
 
     static inline
     static inline
     symbol_type
     symbol_type
-    make_CONTROL_AGENT (const location_type& l);
-
-    static inline
-    symbol_type
     make_TOPLEVEL_JSON (const location_type& l);
     make_TOPLEVEL_JSON (const location_type& l);
 
 
     static inline
     static inline
@@ -1375,8 +1375,8 @@ namespace isc { namespace dhcp {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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.
       yyfinal_ = 24, ///< Termination state number.
       yyterror_ = 1,
       yyterror_ = 1,
       yyerrcode_ = 256,
       yyerrcode_ = 256,
@@ -1476,8 +1476,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 199: // socket_type
       case 209: // db_type
       case 209: // db_type
       case 286: // hr_mode
       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);
         value.copy< ElementPtr > (other.value);
         break;
         break;
 
 
@@ -1519,8 +1519,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 199: // socket_type
       case 209: // db_type
       case 209: // db_type
       case 286: // hr_mode
       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);
         value.copy< ElementPtr > (v);
         break;
         break;
 
 
@@ -1621,8 +1621,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 199: // socket_type
       case 209: // db_type
       case 209: // db_type
       case 286: // hr_mode
       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 > ();
         value.template destroy< ElementPtr > ();
         break;
         break;
 
 
@@ -1670,8 +1670,8 @@ namespace isc { namespace dhcp {
       case 199: // socket_type
       case 199: // socket_type
       case 209: // db_type
       case 209: // db_type
       case 286: // hr_mode
       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);
         value.move< ElementPtr > (s.value);
         break;
         break;
 
 
@@ -2078,6 +2078,12 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp4Parser::symbol_type
   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)
   Dhcp4Parser::make_RECORD_TYPES (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
@@ -2522,12 +2528,6 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp4Parser::symbol_type
   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)
   Dhcp4Parser::make_TOPLEVEL_JSON (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_TOPLEVEL_JSON, 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"
   CODE "code"
   SPACE "space"
   SPACE "space"
   CSV_FORMAT "csv-format"
   CSV_FORMAT "csv-format"
+  ALWAYS_SEND "always-send"
   RECORD_TYPES "record-types"
   RECORD_TYPES "record-types"
   ENCAPSULATE "encapsulate"
   ENCAPSULATE "encapsulate"
   ARRAY "array"
   ARRAY "array"
@@ -1134,6 +1135,7 @@ option_data_param: option_data_name
                  | option_data_code
                  | option_data_code
                  | option_data_space
                  | option_data_space
                  | option_data_csv_format
                  | option_data_csv_format
+                 | option_data_always_send
                  | unknown_map_entry
                  | unknown_map_entry
                  ;
                  ;
 
 
@@ -1156,6 +1158,11 @@ option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
     ctx.stack_.back()->set("csv-format", space);
     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 ------------------------------------
 // ---- pools ------------------------------------
 
 
 // This defines the "pools": [ ... ] entry that may appear in subnet4.
 // 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 query = ex.getQuery();
+    Pkt4Ptr resp = ex.getResponse();
+    std::vector<uint8_t> requested_opts;
 
 
     // try to get the 'Parameter Request List' option which holds the
     // try to get the 'Parameter Request List' option which holds the
     // codes of requested options.
     // codes of requested options.
     OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
     OptionUint8ArrayPtr option_prl = boost::dynamic_pointer_cast<
         OptionUint8Array>(query->getOption(DHO_DHCP_PARAMETER_REQUEST_LIST));
         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
     // For each requested option code get the instance of the option
     // to be returned to the client.
     // to be returned to the client.
     for (std::vector<uint8_t>::const_iterator opt = requested_opts.begin();
     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();
     uint32_t vendor_id = vendor_req->getVendorId();
+    std::vector<uint8_t> requested_opts;
 
 
     // Let's try to get ORO within that vendor-option
     // Let's try to get ORO within that vendor-option
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other
     /// vendors may have different policies.
     /// vendors may have different policies.
     OptionUint8ArrayPtr oro =
     OptionUint8ArrayPtr oro =
         boost::dynamic_pointer_cast<OptionUint8Array>(vendor_req->getOption(DOCSIS3_V4_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;
         return;
     }
     }
 
 
@@ -1304,8 +1344,6 @@ Dhcpv4Srv::appendRequestedVendorOptions(Dhcpv4Exchange& ex) {
 
 
     // Get the list of options that client requested.
     // Get the list of options that client requested.
     bool added = false;
     bool added = false;
-    const std::vector<uint8_t>& requested_opts = oro->getValues();
-
     for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
     for (std::vector<uint8_t>::const_iterator code = requested_opts.begin();
          code != requested_opts.end(); ++code) {
          code != requested_opts.end(); ++code) {
         if  (!vendor_rsp->getOption(*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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
 const SimpleDefaults SimpleParser4::OPTION4_DEFAULTS = {
     { "space",        Element::string,  "dhcp4"},
     { "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
 /// @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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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.
     // Expect a single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range.first, range.second));
     ASSERT_EQ(1, std::distance(range.first, range.second));
     const uint8_t foo_expected[] = {
     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.
     // Check if option is valid in terms of code and carried data.
     testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
     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.
     // Expect a single option with the code equal to 100.
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
     ASSERT_EQ(1, std::distance(range1.first, range1.second));
     const uint8_t foo_expected[] = {
     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.
     // Check if option is valid in terms of code and carried data.
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
     testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
@@ -2909,8 +2909,8 @@ TEST_F(Dhcp4ParserTest, optionDataMultiplePools) {
 
 
     const OptionContainerTypeIndex& idx2 = options2->get<1>();
     const OptionContainerTypeIndex& idx2 = options2->get<1>();
     std::pair<OptionContainerTypeIndex::const_iterator,
     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));
     ASSERT_EQ(1, std::distance(range2.first, range2.second));
     const uint8_t foo2_expected[] = {
     const uint8_t foo2_expected[] = {
         0x01
         0x01

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

@@ -93,8 +93,33 @@ const char* CONFIGS[] = {
         "    \"valid-lifetime\": 4000,"
         "    \"valid-lifetime\": 4000,"
         "    \"interface\": \"eth0\" "
         "    \"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
 // This test verifies that the destination address of the response
 // message is set to giaddr, when giaddr is set to non-zero address
 // 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()->getCfgSubnets4()->add(subnets->at(0));
     CfgMgr::instance().getStagingCfg()->setEchoClientId(false);
     CfgMgr::instance().getStagingCfg()->setEchoClientId(false);
     CfgMgr::instance().commit();
     CfgMgr::instance().commit();
-    
+
     offer = srv.processDiscover(dis);
     offer = srv.processDiscover(dis);
 
 
     // Check if we get response at all
     // Check if we get response at all
@@ -1451,6 +1476,89 @@ TEST_F(Dhcpv4SrvTest, vendorOptionsORO) {
     EXPECT_EQ("192.0.2.2", addrs[1].toText());
     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
 // Test checks whether it is possible to use option definitions defined in
 // src/lib/dhcp/docsis3_option_defs.h.
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
 TEST_F(Dhcpv4SrvTest, vendorOptionsDocsisDefinitions) {
@@ -1952,6 +2060,85 @@ TEST_F(Dhcpv4SrvTest, classGlobalPriority) {
     EXPECT_NE(0, opt->getUint8());
     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.
 // Checks if the client-class field is indeed used for subnet selection.
 // Note that packet classification is already checked in Dhcpv4SrvTest
 // Note that packet classification is already checked in Dhcpv4SrvTest
 // .*Classification above.
 // .*Classification above.
@@ -2008,6 +2195,59 @@ TEST_F(Dhcpv4SrvTest, clientClassify) {
     EXPECT_TRUE(srv_.selectSubnet(dis));
     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
 // Checks if relay IP address specified in the relay-info structure in
 // subnet4 is being used properly.
 // subnet4 is being used properly.
 TEST_F(Dhcpv4SrvTest, relayOverride) {
 TEST_F(Dhcpv4SrvTest, relayOverride) {

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

@@ -3260,6 +3260,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3267,6 +3268,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 23,\n"
 "                \"code\": 23,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"01\",\n"
 "                \"data\": \"01\",\n"
@@ -3354,6 +3356,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3361,6 +3364,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp4\"\n"
 "                        \"space\": \"dhcp4\"\n"
 "                    },\n"
 "                    },\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
 "                        \"data\": \"01\",\n"
@@ -3426,6 +3430,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3433,6 +3438,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3519,6 +3525,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3526,6 +3533,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3595,6 +3603,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 222,\n"
 "                \"code\": 222,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"11\",\n"
 "                \"data\": \"11\",\n"
@@ -3602,6 +3611,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3609,6 +3619,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3713,6 +3724,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 56,\n"
 "                \"code\": 56,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"AB\",\n"
 "                \"data\": \"AB\",\n"
@@ -3732,6 +3744,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3739,6 +3752,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp4\"\n"
 "                        \"space\": \"dhcp4\"\n"
 "                    },\n"
 "                    },\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
 "                        \"data\": \"01\",\n"
@@ -3815,6 +3829,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 56,\n"
 "                        \"code\": 56,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"0102030405060708090A\",\n"
 "                        \"data\": \"0102030405060708090A\",\n"
@@ -3848,6 +3863,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"next-server\": \"0.0.0.0\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 23,\n"
 "                        \"code\": 23,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"FF\",\n"
 "                        \"data\": \"FF\",\n"
@@ -3927,6 +3943,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                    {\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 56,\n"
 "                                \"code\": 56,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"ABCDEF0105\",\n"
 "                                \"data\": \"ABCDEF0105\",\n"
@@ -3934,6 +3951,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"01\",\n"
 "                                \"data\": \"01\",\n"
@@ -4010,6 +4028,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                    {\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 56,\n"
 "                                \"code\": 56,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"ABCDEF0105\",\n"
 "                                \"data\": \"ABCDEF0105\",\n"
@@ -4022,6 +4041,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                    {\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"01\",\n"
 "                                \"data\": \"01\",\n"
@@ -4084,6 +4104,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -4091,6 +4112,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -4160,6 +4182,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 43,\n"
 "                \"code\": 43,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"0104000004D20204C0A80201\",\n"
 "                \"data\": \"0104000004D20204C0A80201\",\n"
@@ -4167,6 +4190,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp4\"\n"
 "                \"space\": \"dhcp4\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 1,\n"
 "                \"code\": 1,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -4174,6 +4198,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "                \"space\": \"vendor-encapsulated-options-space\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 2,\n"
 "                \"code\": 2,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -4269,12 +4294,14 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
 "                \"space\": \"vendor-1234\"\n"
 "                \"space\": \"vendor-1234\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -4350,6 +4377,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        },\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
@@ -4713,6 +4741,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.3.95\",\n"
 "                                \"data\": \"192.0.3.95\",\n"
@@ -4720,6 +4749,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\n"
 "                                \"data\": \"11\",\n"
@@ -4738,6 +4768,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.3.15\",\n"
 "                                \"data\": \"192.0.3.15\",\n"
@@ -4745,6 +4776,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"32\",\n"
 "                                \"data\": \"32\",\n"
@@ -4809,6 +4841,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 5,\n"
 "                                \"code\": 5,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"192.0.4.11\",\n"
 "                                \"data\": \"192.0.4.11\",\n"
@@ -4816,6 +4849,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp4\"\n"
 "                                \"space\": \"dhcp4\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"95\",\n"
 "                                \"data\": \"95\",\n"
@@ -4913,6 +4947,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"next-server\": \"0.0.0.0\",\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 100,\n"
 "                                \"code\": 100,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"123\",\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\" {
 \"pools\" {
     switch(driver.ctx_) {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
     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
     /// Both variants must be built beforehand, because swapping the actual
     /// data requires reading it (with as()), and this is not possible on
     /// data requires reading it (with as()), and this is not possible on
     /// unconstructed variants: it would require some dynamic testing, which
     /// 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
     /// Swapping between built and (possibly) non-built is done with
     /// variant::move ().
     /// variant::move ().
     template <typename T>
     template <typename T>
@@ -388,98 +388,98 @@ namespace isc { namespace dhcp {
         TOKEN_CODE = 296,
         TOKEN_CODE = 296,
         TOKEN_SPACE = 297,
         TOKEN_SPACE = 297,
         TOKEN_CSV_FORMAT = 298,
         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_JSON = 391,
         TOKEN_TOPLEVEL_DHCP6 = 392,
         TOKEN_TOPLEVEL_DHCP6 = 392,
         TOKEN_SUB_DHCP6 = 393,
         TOKEN_SUB_DHCP6 = 393,
@@ -780,6 +780,10 @@ namespace isc { namespace dhcp {
 
 
     static inline
     static inline
     symbol_type
     symbol_type
+    make_ALWAYS_SEND (const location_type& l);
+
+    static inline
+    symbol_type
     make_RECORD_TYPES (const location_type& l);
     make_RECORD_TYPES (const location_type& l);
 
 
     static inline
     static inline
@@ -1144,10 +1148,6 @@ namespace isc { namespace dhcp {
 
 
     static inline
     static inline
     symbol_type
     symbol_type
-    make_CONTROL_AGENT (const location_type& l);
-
-    static inline
-    symbol_type
     make_TOPLEVEL_JSON (const location_type& l);
     make_TOPLEVEL_JSON (const location_type& l);
 
 
     static inline
     static inline
@@ -1415,8 +1415,8 @@ namespace isc { namespace dhcp {
     enum
     enum
     {
     {
       yyeof_ = 0,
       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.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyterror_ = 1,
       yyerrcode_ = 256,
       yyerrcode_ = 256,
@@ -1516,9 +1516,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 170: // map_value
       case 214: // db_type
       case 214: // db_type
       case 290: // hr_mode
       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);
         value.copy< ElementPtr > (other.value);
         break;
         break;
 
 
@@ -1559,9 +1559,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 170: // map_value
       case 214: // db_type
       case 214: // db_type
       case 290: // hr_mode
       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);
         value.copy< ElementPtr > (v);
         break;
         break;
 
 
@@ -1661,9 +1661,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 170: // map_value
       case 214: // db_type
       case 214: // db_type
       case 290: // hr_mode
       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 > ();
         value.template destroy< ElementPtr > ();
         break;
         break;
 
 
@@ -1710,9 +1710,9 @@ namespace isc { namespace dhcp {
       case 170: // map_value
       case 170: // map_value
       case 214: // db_type
       case 214: // db_type
       case 290: // hr_mode
       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);
         value.move< ElementPtr > (s.value);
         break;
         break;
 
 
@@ -2060,6 +2060,12 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp6Parser::symbol_type
   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)
   Dhcp6Parser::make_RECORD_TYPES (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
     return symbol_type (token::TOKEN_RECORD_TYPES, l);
@@ -2606,12 +2612,6 @@ namespace isc { namespace dhcp {
   }
   }
 
 
   Dhcp6Parser::symbol_type
   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)
   Dhcp6Parser::make_TOPLEVEL_JSON (const location_type& l)
   {
   {
     return symbol_type (token::TOKEN_TOPLEVEL_JSON, 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"
   CODE "code"
   SPACE "space"
   SPACE "space"
   CSV_FORMAT "csv-format"
   CSV_FORMAT "csv-format"
+  ALWAYS_SEND "always-send"
   RECORD_TYPES "record-types"
   RECORD_TYPES "record-types"
   ENCAPSULATE "encapsulate"
   ENCAPSULATE "encapsulate"
   ARRAY "array"
   ARRAY "array"
@@ -1121,6 +1122,7 @@ option_data_param: option_data_name
                  | option_data_code
                  | option_data_code
                  | option_data_space
                  | option_data_space
                  | option_data_csv_format
                  | option_data_csv_format
+                 | option_data_always_send
                  | unknown_map_entry
                  | unknown_map_entry
                  ;
                  ;
 
 
@@ -1143,6 +1145,11 @@ option_data_csv_format: CSV_FORMAT COLON BOOLEAN {
     ctx.stack_.back()->set("csv-format", space);
     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 ------------------------------------
 // ---- pools ------------------------------------
 
 
 // This defines the "pools": [ ... ] entry that may appear in subnet6.
 // 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,
 Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer,
                                   const CfgOptionList& co_list) {
                                   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
     // Client requests some options using ORO option. Try to
     // get this option from client's message.
     // get this option from client's message.
     boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
     boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
         boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
         boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >
         (question->getOption(D6O_ORO));
         (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.
     // 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) {
     BOOST_FOREACH(uint16_t opt, requested_opts) {
         // Iterate on the configured option list
         // Iterate on the configured option list
@@ -969,24 +988,44 @@ Dhcpv6Srv::appendRequestedVendorOptions(const Pkt6Ptr& question,
         return;
         return;
     }
     }
 
 
+    uint32_t vendor_id = vendor_req->getVendorId();
+    std::vector<uint16_t> requested_opts;
+
     // Let's try to get ORO within that vendor-option
     // Let's try to get ORO within that vendor-option
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
     /// @todo This is very specific to vendor-id=4491 (Cable Labs). Other vendors
     /// may have different policies.
     /// may have different policies.
     boost::shared_ptr<OptionUint16Array> oro =
     boost::shared_ptr<OptionUint16Array> oro =
         boost::dynamic_pointer_cast<OptionUint16Array>(vendor_req->getOption(DOCSIS3_V6_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;
         return;
     }
     }
 
 
-    uint32_t vendor_id = vendor_req->getVendorId();
-
     boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
     boost::shared_ptr<OptionVendor> vendor_rsp(new OptionVendor(Option::V6, vendor_id));
 
 
     // Get the list of options that client requested.
     // Get the list of options that client requested.
     bool added = false;
     bool added = false;
-    const std::vector<uint16_t>& requested_opts = oro->getValues();
+
     BOOST_FOREACH(uint16_t opt, requested_opts) {
     BOOST_FOREACH(uint16_t opt, requested_opts) {
         for (CfgOptionList::const_iterator copts = co_list.begin();
         for (CfgOptionList::const_iterator copts = co_list.begin();
              copts != co_list.end(); ++copts) {
              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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Locations for Bison parsers in C++
 // 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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Positions for Bison parsers in C++
 // 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.
 /// for those option-data declarations.
 const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
 const SimpleDefaults SimpleParser6::OPTION6_DEFAULTS = {
     { "space",        Element::string,  "dhcp6"},
     { "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
 /// @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.
 // A Bison parser, made by GNU Bison 3.0.4.
 
 
 // Stack handling for Bison parsers in C++
 // 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
 // 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
 // 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());
     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.
 // Checks if the client-class field is indeed used for subnet selection.
 // Note that packet classification is already checked in ClassifyTest
 // Note that packet classification is already checked in ClassifyTest
 // .*Classification above.
 // .*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\" } ],"
     "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
     "    \"subnet\": \"2001:db8:1::/48\" "
     "    \"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
 // 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());
     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
 // Checks if server is able to handle a relayed traffic from DOCSIS3.0 modems
 // @todo Uncomment this test as part of #3180 work.
 // @todo Uncomment this test as part of #3180 work.
 // Kea code currently fails to handle docsis traffic.
 // 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());
     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
 // Test checks whether it is possible to use option definitions defined in
 // src/lib/dhcp/docsis3_option_defs.h.
 // src/lib/dhcp/docsis3_option_defs.h.
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {
 TEST_F(Dhcpv6SrvTest, vendorOptionsDocsisDefinitions) {

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

@@ -65,6 +65,7 @@ namespace {
 ///@{
 ///@{
 /// @brief extracted configurations
 /// @brief extracted configurations
 const char* EXTRACTED_CONFIGS[] = {
 const char* EXTRACTED_CONFIGS[] = {
+/// put this after const char* EXTRACTED_CONFIGS[] = {
     // CONFIGURATION 0
     // CONFIGURATION 0
 "{\n"
 "{\n"
 "        \"interfaces-config\": {\n"
 "        \"interfaces-config\": {\n"
@@ -3142,6 +3143,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3149,6 +3151,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 7,\n"
 "                \"code\": 7,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"01\",\n"
 "                \"data\": \"01\",\n"
@@ -3249,6 +3252,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 38,\n"
 "                        \"code\": 38,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"ABCDEF0105\",\n"
 "                        \"data\": \"ABCDEF0105\",\n"
@@ -3256,6 +3260,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"space\": \"dhcp6\"\n"
 "                        \"space\": \"dhcp6\"\n"
 "                    },\n"
 "                    },\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 7,\n"
 "                        \"code\": 7,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"01\",\n"
 "                        \"data\": \"01\",\n"
@@ -3324,6 +3329,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3331,6 +3337,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 38,\n"
 "                \"code\": 38,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3425,6 +3432,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 110,\n"
 "                \"code\": 110,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3432,6 +3440,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 111,\n"
 "                \"code\": 111,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3510,6 +3519,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"11\",\n"
 "                \"data\": \"11\",\n"
@@ -3517,6 +3527,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"dhcp6\"\n"
 "                \"space\": \"dhcp6\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 110,\n"
 "                \"code\": 110,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
@@ -3524,6 +3535,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"space\": \"isc\"\n"
 "                \"space\": \"isc\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 111,\n"
 "                \"code\": 111,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"192.168.2.1\",\n"
 "                \"data\": \"192.168.2.1\",\n"
@@ -3652,6 +3664,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 38,\n"
 "                        \"code\": 38,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"0102030405060708090A\",\n"
 "                        \"data\": \"0102030405060708090A\",\n"
@@ -3684,6 +3697,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"interface-id\": \"\",\n"
 "                \"interface-id\": \"\",\n"
 "                \"option-data\": [\n"
 "                \"option-data\": [\n"
 "                    {\n"
 "                    {\n"
+"                        \"always-send\": false,\n"
 "                        \"code\": 15,\n"
 "                        \"code\": 15,\n"
 "                        \"csv-format\": false,\n"
 "                        \"csv-format\": false,\n"
 "                        \"data\": \"FFFEFDFCFB\",\n"
 "                        \"data\": \"FFFEFDFCFB\",\n"
@@ -3774,6 +3788,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 38,\n"
 "                                \"code\": 38,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"112233445566\",\n"
 "                                \"data\": \"112233445566\",\n"
@@ -3790,6 +3805,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"excluded-prefix-len\": 0,\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 15,\n"
 "                                \"code\": 15,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"AABBCCDDEE\",\n"
 "                                \"data\": \"AABBCCDDEE\",\n"
@@ -3805,6 +3821,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                    {\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 38,\n"
 "                                \"code\": 38,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"0102030405060708090A\",\n"
 "                                \"data\": \"0102030405060708090A\",\n"
@@ -3817,6 +3834,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                    {\n"
 "                    {\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 15,\n"
 "                                \"code\": 15,\n"
 "                                \"csv-format\": false,\n"
 "                                \"csv-format\": false,\n"
 "                                \"data\": \"FFFEFDFCFB\",\n"
 "                                \"data\": \"FFFEFDFCFB\",\n"
@@ -3881,12 +3899,14 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"1234\",\n"
 "                \"data\": \"1234\",\n"
 "                \"space\": \"vendor-1234\"\n"
 "                \"space\": \"vendor-1234\"\n"
 "            },\n"
 "            },\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": false,\n"
 "                \"csv-format\": false,\n"
 "                \"data\": \"ABCDEF0105\",\n"
 "                \"data\": \"ABCDEF0105\",\n"
@@ -3970,6 +3990,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"mac-sources\": [ \"any\" ],\n"
 "        \"option-data\": [\n"
 "        \"option-data\": [\n"
 "            {\n"
 "            {\n"
+"                \"always-send\": false,\n"
 "                \"code\": 100,\n"
 "                \"code\": 100,\n"
 "                \"csv-format\": true,\n"
 "                \"csv-format\": true,\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
 "                \"data\": \"this is a string vendor-opt\",\n"
@@ -4526,6 +4547,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
 "                        \"ip-addresses\": [ \"2001:db8:2::abcd\" ],\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:2::abbc\",\n"
 "                                \"data\": \"2001:db8:2::abbc\",\n"
@@ -4533,6 +4555,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"25\",\n"
 "                                \"data\": \"25\",\n"
@@ -4549,6 +4572,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:2::1111\",\n"
 "                                \"data\": \"2001:db8:2::1111\",\n"
@@ -4556,6 +4580,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\n"
 "                                \"data\": \"11\",\n"
@@ -4600,6 +4625,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ ],\n"
 "                        \"ip-addresses\": [ ],\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 23,\n"
 "                                \"code\": 23,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"2001:db8:3::3333\",\n"
 "                                \"data\": \"2001:db8:3::3333\",\n"
@@ -4607,6 +4633,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                                \"space\": \"dhcp6\"\n"
 "                                \"space\": \"dhcp6\"\n"
 "                            },\n"
 "                            },\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 7,\n"
 "                                \"code\": 7,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"33\",\n"
 "                                \"data\": \"33\",\n"
@@ -4705,6 +4732,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"ip-addresses\": [ \"2001:db8:2::1234\" ],\n"
 "                        \"option-data\": [\n"
 "                        \"option-data\": [\n"
 "                            {\n"
 "                            {\n"
+"                                \"always-send\": false,\n"
 "                                \"code\": 100,\n"
 "                                \"code\": 100,\n"
 "                                \"csv-format\": true,\n"
 "                                \"csv-format\": true,\n"
 "                                \"data\": \"11\",\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);
                 std::string repr = util::encode::encodeHex(bin);
                 map->set("data", Element::create(repr));
                 map->set("data", Element::create(repr));
             }
             }
+            // Set the persistency flag
+            map->set("always-send", Element::create(opt->persistent_));
             // Push on the list
             // Push on the list
             result->add(map);
             result->add(map);
         }
         }
@@ -265,6 +267,8 @@ CfgOption::toElement() const {
                 std::string repr = util::encode::encodeHex(bin);
                 std::string repr = util::encode::encodeHex(bin);
                 map->set("data", Element::create(repr));
                 map->set("data", Element::create(repr));
             }
             }
+            // Set the persistency flag
+            map->set("always-send", Element::create(opt->persistent_));
             // Push on the list
             // Push on the list
             result->add(map);
             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;
                   OptionContainerTypeIndex::const_iterator> OptionContainerTypeRange;
 /// Type of the index #2 - option persistency flag.
 /// Type of the index #2 - option persistency flag.
 typedef OptionContainer::nth_index<2>::type OptionContainerPersistIndex;
 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.
 /// @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);
     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>
 template<typename SearchKey>
 OptionDefinitionPtr
 OptionDefinitionPtr
 OptionDataParser::findOptionDefinition(const std::string& option_space,
 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<uint32_t> code_param =  extractCode(option_data);
     OptionalValue<std::string> name_param = extractName(option_data);
     OptionalValue<std::string> name_param = extractName(option_data);
     OptionalValue<bool> csv_format_param = extractCSVFormat(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 data_param = extractData(option_data);
     std::string space_param = extractSpace(option_data);
     std::string space_param = extractSpace(option_data);
 
 
@@ -426,7 +440,7 @@ OptionDataParser::createOption(ConstElementPtr option_data) {
                                     binary));
                                     binary));
 
 
         desc.option_ = option;
         desc.option_ = option;
-        desc.persistent_ = false;
+        desc.persistent_ = persist_param.isSpecified() && persist_param;
     } else {
     } else {
 
 
         // Option name is specified it should match the name in the definition.
         // 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(), data_tokens) :
                 def->optionFactory(universe, def->getCode(), binary);
                 def->optionFactory(universe, def->getCode(), binary);
             desc.option_ = option;
             desc.option_ = option;
-            desc.persistent_ = false;
+            desc.persistent_ = persist_param.isSpecified() && persist_param;
             if (use_csv) {
             if (use_csv) {
                 desc.formatted_value_ = data_param;
                 desc.formatted_value_ = data_param;
             }
             }

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

@@ -462,6 +462,11 @@ private:
     /// @return Option space name.
     /// @return Option space name.
     std::string extractSpace(data::ConstElementPtr parent) const;
     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.
     /// @brief Address family: @c AF_INET or @c AF_INET6.
     uint16_t address_family_;
     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.
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
     for (uint16_t code = 100; code < 110; ++ code) {
         // For each code we should get two instances of options->
         // 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
         // Distance between iterators indicates how many options
         // have been returned for the particular code.
         // have been returned for the particular code.
         ASSERT_EQ(2, distance(range.first, range.second));
         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.
     // Let's try to find some non-exiting option.
     const uint16_t non_existing_code = 150;
     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.
     // Empty set is expected.
     EXPECT_EQ(0, distance(range.first, range.second));
     EXPECT_EQ(0, distance(range.first, range.second));
 }
 }
@@ -501,17 +497,13 @@ TEST(Subnet6Test, addPersistentOption) {
     OptionContainerPersistIndex& idx = options->get<2>();
     OptionContainerPersistIndex& idx = options->get<2>();
 
 
     // Get all persistent options->
     // 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));
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
 
     // Get all non-persistent options->
     // 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));
     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)));
     OptionPtr opt3(new Option(Option::V6, D6O_STATUS_CODE, OptionBuffer(2, 0)));
     cfg.add(opt3, false, DHCP6_OPTION_SPACE);
     cfg.add(opt3, false, DHCP6_OPTION_SPACE);
     OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
     OptionPtr opt4(new Option(Option::V6, 100, OptionBuffer(4, 0x21)));
-    cfg.add(opt4, false, "vendor-1234");
+    cfg.add(opt4, true, "vendor-1234");
 
 
     // Unparse
     // Unparse
     std::string expected = "[\n"
     std::string expected = "[\n"
@@ -620,23 +612,27 @@ TEST_F(CfgOptionTest, unparse) {
         "    \"code\": 100,\n"
         "    \"code\": 100,\n"
         "    \"space\": \"dns\",\n"
         "    \"space\": \"dns\",\n"
         "    \"csv-format\": false,\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"12121212\"\n"
+        "    \"data\": \"12121212\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "},{\n"
         "    \"code\": 101,\n"
         "    \"code\": 101,\n"
         "    \"space\": \"dns\",\n"
         "    \"space\": \"dns\",\n"
         "    \"csv-format\": true,\n"
         "    \"csv-format\": true,\n"
-        "    \"data\": \"12, 12, 12, 12\"\n"
+        "    \"data\": \"12, 12, 12, 12\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "},{\n"
         "    \"code\": 13,\n"
         "    \"code\": 13,\n"
         "    \"name\": \"status-code\",\n"
         "    \"name\": \"status-code\",\n"
         "    \"space\": \"dhcp6\",\n"
         "    \"space\": \"dhcp6\",\n"
         "    \"csv-format\": false,\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"0000\"\n"
+        "    \"data\": \"0000\",\n"
+        "    \"always-send\": false\n"
         "},{\n"
         "},{\n"
         "    \"code\": 100,\n"
         "    \"code\": 100,\n"
         "    \"space\": \"vendor-1234\",\n"
         "    \"space\": \"vendor-1234\",\n"
         "    \"csv-format\": false,\n"
         "    \"csv-format\": false,\n"
-        "    \"data\": \"21212121\"\n"
+        "    \"data\": \"21212121\",\n"
+        "    \"always-send\": true\n"
         "}]\n";
         "}]\n";
     isc::test::runToElementTest<CfgOption>(expected, cfg);
     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
 /// This table defines default values for options in DHCPv6
 const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
 const SimpleDefaults ParseConfigTest::OPTION6_DEFAULTS = {
     { "space",        Element::string,  "dhcp6"},
     { "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
 /// This table defines default values for options in DHCPv4
 const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
 const SimpleDefaults ParseConfigTest::OPTION4_DEFAULTS = {
     { "space",        Element::string,  "dhcp4"},
     { "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
 /// This table defines default values for both DHCPv4 and DHCPv6
@@ -861,7 +863,8 @@ TEST_F(ParseConfigTest, basicOptionDataTest) {
         "    \"space\": \"isc\","
         "    \"space\": \"isc\","
         "    \"code\": 100,"
         "    \"code\": 100,"
         "    \"data\": \"192.0.2.0\","
         "    \"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,"
            "\"code\": 7,"
            "\"csv-format\": true,"
            "\"csv-format\": true,"
            "\"space\": \"dhcp4\","
            "\"space\": \"dhcp4\","
-           "\"data\": \"172.16.15.23\""
+           "\"data\": \"172.16.15.23\","
+           "\"always-send\": false"
         "},"
         "},"
         "{"
         "{"
            "\"name\": \"default-ip-ttl\","
            "\"name\": \"default-ip-ttl\","
@@ -1034,11 +1035,13 @@ TEST_F(HostReservationParserTest, options4) {
     option->set("code", Element::create(DHO_NAME_SERVERS));
     option->set("code", Element::create(DHO_NAME_SERVERS));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
     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(1);
     option = config_element->get("option-data")->getNonConst(2);
     option = config_element->get("option-data")->getNonConst(2);
     option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
     option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     ElementPtr expected = Element::createList();
     ElementPtr expected = Element::createList();
     expected->add(config_element);
     expected->add(config_element);
 
 
@@ -1071,7 +1074,8 @@ TEST_F(HostReservationParserTest, options6) {
            "\"code\": 27,"
            "\"code\": 27,"
            "\"csv-format\": true,"
            "\"csv-format\": true,"
            "\"space\": \"dhcp6\","
            "\"space\": \"dhcp6\","
-           "\"data\": \"2001:db8:1::1204\""
+           "\"data\": \"2001:db8:1::1204\","
+           "\"always-send\": true"
         "},"
         "},"
         "{"
         "{"
            "\"name\": \"preference\","
            "\"name\": \"preference\","
@@ -1122,11 +1126,13 @@ TEST_F(HostReservationParserTest, options6) {
     option->set("code", Element::create(D6O_NAME_SERVERS));
     option->set("code", Element::create(D6O_NAME_SERVERS));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
     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(1);
     option = config_element->get("option-data")->getNonConst(2);
     option = config_element->get("option-data")->getNonConst(2);
     option->set("code", Element::create(D6O_PREFERENCE));
     option->set("code", Element::create(D6O_PREFERENCE));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
     option->set("csv-format", Element::create(true));
     option->set("csv-format", Element::create(true));
+    option->set("always-send", Element::create(false));
     config = prettyPrint(config_element);
     config = prettyPrint(config_element);
     boost::algorithm::to_lower(config);
     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.
     // Look for the codes 100-109.
     for (uint16_t code = 100; code < 110; ++ code) {
     for (uint16_t code = 100; code < 110; ++ code) {
         // For each code we should get two instances of options->
         // 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
         // Distance between iterators indicates how many options
         // have been returned for the particular code.
         // have been returned for the particular code.
         ASSERT_EQ(2, distance(range.first, range.second));
         ASSERT_EQ(2, distance(range.first, range.second));
@@ -960,9 +958,7 @@ TEST(Subnet6Test, addNonUniqueOptions) {
 
 
     // Let's try to find some non-exiting option.
     // Let's try to find some non-exiting option.
     const uint16_t non_existing_code = 150;
     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.
     // Empty set is expected.
     EXPECT_EQ(0, distance(range.first, range.second));
     EXPECT_EQ(0, distance(range.first, range.second));
 }
 }
@@ -994,17 +990,13 @@ TEST(Subnet6Test, addPersistentOption) {
     OptionContainerPersistIndex& idx = options->get<2>();
     OptionContainerPersistIndex& idx = options->get<2>();
 
 
     // Get all persistent options->
     // 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));
     ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
 
 
     // Get all non-persistent options->
     // 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));
     ASSERT_EQ(3, distance(range_non_persistent.first, range_non_persistent.second));
 }
 }