Browse Source

[master] Merge branch 'trac5357' (shared network parsers update)

# Conflicts:
#	src/bin/dhcp4/dhcp4_lexer.cc
#	src/bin/dhcp4/dhcp4_parser.cc
#	src/bin/dhcp4/dhcp4_parser.h
#	src/bin/dhcp6/dhcp6_lexer.cc
#	src/bin/dhcp6/dhcp6_parser.cc
#	src/bin/dhcp6/dhcp6_parser.h
Tomek Mrugalski 7 years ago
parent
commit
74b824cc80

+ 2 - 0
doc/Makefile.am

@@ -25,6 +25,7 @@ nobase_dist_doc_DATA += examples/kea4/mysql-reservations.json
 nobase_dist_doc_DATA += examples/kea4/pgsql-reservations.json
 nobase_dist_doc_DATA += examples/kea4/reservations.json
 nobase_dist_doc_DATA += examples/kea4/several-subnets.json
+nobase_dist_doc_DATA += examples/kea4/shared-network.json
 nobase_dist_doc_DATA += examples/kea4/single-subnet.json
 nobase_dist_doc_DATA += examples/kea4/with-ddns.json
 nobase_dist_doc_DATA += examples/kea6/advanced.json
@@ -40,6 +41,7 @@ nobase_dist_doc_DATA += examples/kea6/mysql-reservations.json
 nobase_dist_doc_DATA += examples/kea6/pgsql-reservations.json
 nobase_dist_doc_DATA += examples/kea6/reservations.json
 nobase_dist_doc_DATA += examples/kea6/several-subnets.json
+nobase_dist_doc_DATA += examples/kea6/shared-network.json
 nobase_dist_doc_DATA += examples/kea6/simple.json
 nobase_dist_doc_DATA += examples/kea6/softwire46.json
 nobase_dist_doc_DATA += examples/kea6/stateless.json

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

@@ -0,0 +1,121 @@
+// This is an example configuration file for DHCPv4 server in Kea.
+// It demonstrates an advanced feature called shared network. Typically, for
+// each physical link there is one IPv4 subnet that the server is expected
+// to manage. However, in some cases there is a need to configure more subnets
+// in the same physical location. The most common use case is an existing
+// subnet that grew past its original assumptions and ran out of addresses,
+// so the sysadmin needs to add another subnet on top of existing one.
+{
+    "Dhcp4": {
+
+        // As with any other configuration, you need to tell Kea the interface
+        // names, so it would listen to incoming traffic.
+        "interfaces-config": {
+            "interfaces": [ "ethX" ]
+        },
+
+        // You also need to tell where to store lease information.
+        // memfile is the backend that is easiest to set up.
+        "lease-database": {
+            "type": "memfile",
+            "lfc-interval": 3600
+        },
+
+        // The shared networks definition starts here. shared-networks can
+        // contain a list of shared networks. There are many parameters
+        // that can be specified here, so this example may be overwhelming
+        // at first, but the only mandatory parameter for each shared
+        // network is name. It must be unique. Typically, each shared
+        // network also needs to have at least two subnets to be functional,
+        // but if you really want to, you can define a degraded shared
+        // network that has 1 or even 0 subnets. This may come in handy
+        // when migrating between regular subnets and shared networks
+        // or when debugging a problem. It is not recommended to use
+        // 1 subnet per shared network, as there is extra processing
+        // overhead for shared networks.
+        "shared-networks": [
+        {
+            // Name of the shared network. It may be an arbitrary string
+            // and it must be unique among all shared networks.
+            "name": "frog",
+
+            // You may specify interface name if the shared network is
+            // reachable directly from the server.
+            "interface": "eth1",
+
+            // You can specify many parameters that are allowed in subnet scope
+            // here. It's useful to put them here if they apply to all subnets
+            // in this shared network. It's likely that the most common
+            // parameter here will be option values defined with option-data.
+            "match-client-id": false,
+            "option-data": [ ],
+            "rebind-timer": 150,
+
+            // If all the traffic coming from that shared network is reachable
+            // via relay and that relay always use the same IP address, you
+            // can specify that relay address here. Since this example shows
+            // a shared network reachable directly, we put 0.0.0.0 here.
+            // It would be better to skip the relay scope altogether, but
+            // it was left here for demonstration purposes.
+            "relay": {
+                "ip-address": "0.0.0.0"
+            },
+
+            // Timer values can be overridden here.
+            "renew-timer": 100,
+            "reservation-mode": "all",
+
+            // This starts a list of subnets allowed in this shared network.
+            // In our example, there are two subnets.
+            "subnet4": [
+                {
+                    "id": 1,
+                    "match-client-id": true,
+                    "next-server": "0.0.0.0",
+                    "option-data": [ ],
+                    "pools": [ ],
+                    "rebind-timer": 20,
+
+                    // You can override the value inherited from shared-network
+                    // here if your relay uses different IP addresses for
+                    // each subnet.
+                    "relay": {
+                        "ip-address": "0.0.0.0"
+                    },
+                    "renew-timer": 10,
+                    "reservation-mode": "all",
+                    "subnet": "10.0.0.0/8",
+                    "valid-lifetime": 30
+                },
+                {
+                    "id": 2,
+                    "match-client-id": true,
+                    "next-server": "0.0.0.0",
+                    "option-data": [ ],
+                    "pools": [ ],
+                    "rebind-timer": 20,
+                    "renew-timer": 10,
+                    "reservation-mode": "all",
+                    "subnet": "192.0.2.0/24",
+                    "valid-lifetime": 30
+                }
+            ],
+            "valid-lifetime": 200
+        } ], // end of shared-networks
+
+        // It is likely that in your network you'll have a mix of regular,
+        // "plain" subnets and shared networks. It is perfectly valid to mix
+        // them in the same config file.
+        //
+        // This is regular subnet. It's not part of any shared-network.
+        "subnet4": [
+            {
+                "pools": [ { "pool":  "192.0.3.1 - 192.0.3.200" } ],
+                "subnet": "192.0.3.0/24",
+                "interface": "eth0",
+                "id": 3
+            }
+        ]
+
+    } // end of Dhcp4
+}

+ 112 - 0
doc/examples/kea6/shared-network.json

@@ -0,0 +1,112 @@
+// This is an example configuration file for DHCPv6 server in Kea.
+// It demonstrates an advanced feature called shared network. Typically, for
+// each physical link there is one IPv6 subnet that the server is expected
+// to manage. However, in some cases there is a need to configure more subnets
+// in the same physical location. This may sound odd, as IPv6 is not expected
+// to run out of addresses. However, due to vast address space some deployments
+// experiment with various addressing schemes and later find out that the
+// initial proposal was not best and way to migrate to something else.
+{
+    "Dhcp6": {
+        // Kea is told to listen on ethX interface only.
+        "interfaces-config": {
+            "interfaces": [ "ethX" ]
+        },
+
+        // You also need to tell where to store lease information.
+        // memfile is the backend that is easiest to set up.
+        "lease-database": {
+            "type": "memfile",
+            "lfc-interval": 3600
+        },
+
+        // It is likely that in your network you'll have a mix of regular,
+        // "plain" subnets and shared networks. It is perfectly valid to mix
+        // them in the same config file.
+        //
+        // This is regular subnet. It's not part of any shared-network.
+        "subnet6": [
+            {
+                "pools": [ { "pool": "2001:db8:2::/80" } ],
+                "subnet": "2001:db8:2::/64",
+                "interface": "ethX"
+            }
+        ],
+
+        // Hhe shared networks definition starts here. shared-networks can
+        // contain a list of shared networks. There are many parameters
+        // that can be specified here, so this example may be overwhelming
+        // at first, but the only mandatory parameter for each shared
+        // network is name. It must be unique. Typically, each shared
+        // network also needs to have at least two subnets to be functional,
+        // but if you really want to, you can define a degraded shared
+        // network that has 1 or even 0 subnets. This may come in handy
+        // when migrating between regular subnets and shared networks
+        // or when debugging a problem. It is not recommended to use
+        // 1 subnet per shared network, as there is extra processing
+        // overhead for shared networks.
+        "shared-networks": [
+        {
+            "interface": "eth1",
+
+            // Similar to regular subnets, it is forbidden to define both
+            // interface and interface-id at the same time. That's because
+            // interface parameter expresses physical network interface
+            // for links available locally and interface-id identifies
+            // values inserted by relays, which are only used for
+            // remote traffic. A shared network cannot be both direct
+            // and relayed.
+            //"interface-id": "content of the option",
+
+            // Other parameters defined here will be inherited by the
+            // subnets.
+            "name": "frog",
+            "option-data": [ ],
+            "preferred-lifetime": 200,
+            "rapid-commit": true,
+            "rebind-timer": 150,
+            "relay": {
+                "ip-address": "2001:db8::1"
+            },
+            "renew-timer": 100,
+            "reservation-mode": "all",
+
+            // List of subnets belonging to this particular shared-network
+            // start here.
+            "subnet6": [
+
+                // This is the first subnet.
+                {
+                    "preferred-lifetime": 30,
+                    "rapid-commit": false,
+                    "rebind-timer": 20,
+                    // It is possible to override some values here.
+                    "relay": {
+                        "ip-address": "2001:db8:1::123"
+                    },
+                    "renew-timer": 10,
+                    "reservation-mode": "all",
+                    "subnet": "2001:db8:1::/64",
+                    "pools": [ { "pool": "2001:db8:1:abcd::/64" } ],
+                    "valid-lifetime": 40
+                },
+
+                // This is the second subnet.
+                {
+                    "preferred-lifetime": 30,
+                    "pools": [ { "pool": "3000:db8::/64" } ],
+                    "rapid-commit": false,
+                    "rebind-timer": 20,
+                    "relay": {
+                        "ip-address": "3000::1"
+                    },
+                    "renew-timer": 10,
+                    "reservation-mode": "all",
+                    "subnet": "3000::/16",
+                    "valid-lifetime": 40
+                }
+            ],
+            "valid-lifetime": 300
+        } ]
+    }
+}

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


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

@@ -30,6 +30,8 @@ bool start_token_flag = false;
 isc::dhcp::Parser4Context::ParserType start_token_value;
 unsigned int comment_start_line = 0;
 
+using namespace isc::dhcp;
+
 };
 
 /* To avoid the call to exit... oops! */
@@ -409,6 +411,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_VALID_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("valid-lifetime", driver.loc_);
@@ -419,6 +422,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_RENEW_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("renew-timer", driver.loc_);
@@ -429,6 +433,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_REBIND_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("rebind-timer", driver.loc_);
@@ -447,12 +452,22 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"subnet4\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_SUBNET4(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("subnet4", driver.loc_);
     }
 }
 
+\"shared-networks\" {
+    switch (driver.ctx_) {
+    case Parser4Context::DHCP4:
+        return Dhcp4Parser::make_SHARED_NETWORKS(driver.loc_);
+    default:
+        return Dhcp4Parser::make_STRING("shared-networks", driver.loc_);
+    }
+}
+
 \"option-def\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
@@ -470,6 +485,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
     case isc::dhcp::Parser4Context::CLIENT_CLASS:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
@@ -484,6 +500,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser4Context::OPTION_DATA:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
     case isc::dhcp::Parser4Context::CLIENT_CLASS:
+    case Parser4Context::SHARED_NETWORK:
     case isc::dhcp::Parser4Context::LOGGERS:
         return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
     default:
@@ -549,6 +566,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_INTERFACE(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("interface", driver.loc_);
@@ -585,6 +603,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"reservation-mode\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
+    case isc::dhcp::Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_RESERVATION_MODE(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("reservation-mode", driver.loc_);
@@ -874,6 +893,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"relay\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_RELAY(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("relay", driver.loc_);
@@ -1300,6 +1320,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_ECHO_CLIENT_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("echo-client-id", driver.loc_);
@@ -1310,6 +1331,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser4Context::DHCP4:
     case isc::dhcp::Parser4Context::SUBNET4:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_MATCH_CLIENT_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("match-client-id", driver.loc_);
@@ -1322,6 +1344,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser4Context::SUBNET4:
     case isc::dhcp::Parser4Context::RESERVATIONS:
     case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+    case Parser4Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp4Parser::make_NEXT_SERVER(driver.loc_);
     default:
         return isc::dhcp::Dhcp4Parser::make_STRING("next-server", driver.loc_);

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


+ 150 - 139
src/bin/dhcp4/dhcp4_parser.h

@@ -402,94 +402,95 @@ namespace isc { namespace dhcp {
         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_CONTROL_AGENT = 384,
-        TOKEN_TOPLEVEL_JSON = 385,
-        TOKEN_TOPLEVEL_DHCP4 = 386,
-        TOKEN_SUB_DHCP4 = 387,
-        TOKEN_SUB_INTERFACES4 = 388,
-        TOKEN_SUB_SUBNET4 = 389,
-        TOKEN_SUB_POOL4 = 390,
-        TOKEN_SUB_RESERVATION = 391,
-        TOKEN_SUB_OPTION_DEFS = 392,
-        TOKEN_SUB_OPTION_DEF = 393,
-        TOKEN_SUB_OPTION_DATA = 394,
-        TOKEN_SUB_HOOKS_LIBRARY = 395,
-        TOKEN_SUB_DHCP_DDNS = 396,
-        TOKEN_STRING = 397,
-        TOKEN_INTEGER = 398,
-        TOKEN_FLOAT = 399,
-        TOKEN_BOOLEAN = 400
+        TOKEN_SHARED_NETWORKS = 313,
+        TOKEN_POOLS = 314,
+        TOKEN_POOL = 315,
+        TOKEN_USER_CONTEXT = 316,
+        TOKEN_SUBNET = 317,
+        TOKEN_INTERFACE = 318,
+        TOKEN_INTERFACE_ID = 319,
+        TOKEN_ID = 320,
+        TOKEN_RAPID_COMMIT = 321,
+        TOKEN_RESERVATION_MODE = 322,
+        TOKEN_DISABLED = 323,
+        TOKEN_OUT_OF_POOL = 324,
+        TOKEN_ALL = 325,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 326,
+        TOKEN_CLIENT_CLASSES = 327,
+        TOKEN_TEST = 328,
+        TOKEN_CLIENT_CLASS = 329,
+        TOKEN_RESERVATIONS = 330,
+        TOKEN_DUID = 331,
+        TOKEN_HW_ADDRESS = 332,
+        TOKEN_CIRCUIT_ID = 333,
+        TOKEN_CLIENT_ID = 334,
+        TOKEN_HOSTNAME = 335,
+        TOKEN_FLEX_ID = 336,
+        TOKEN_RELAY = 337,
+        TOKEN_IP_ADDRESS = 338,
+        TOKEN_HOOKS_LIBRARIES = 339,
+        TOKEN_LIBRARY = 340,
+        TOKEN_PARAMETERS = 341,
+        TOKEN_EXPIRED_LEASES_PROCESSING = 342,
+        TOKEN_RECLAIM_TIMER_WAIT_TIME = 343,
+        TOKEN_FLUSH_RECLAIMED_TIMER_WAIT_TIME = 344,
+        TOKEN_HOLD_RECLAIMED_TIME = 345,
+        TOKEN_MAX_RECLAIM_LEASES = 346,
+        TOKEN_MAX_RECLAIM_TIME = 347,
+        TOKEN_UNWARNED_RECLAIM_CYCLES = 348,
+        TOKEN_DHCP4O6_PORT = 349,
+        TOKEN_CONTROL_SOCKET = 350,
+        TOKEN_SOCKET_TYPE = 351,
+        TOKEN_SOCKET_NAME = 352,
+        TOKEN_DHCP_DDNS = 353,
+        TOKEN_ENABLE_UPDATES = 354,
+        TOKEN_QUALIFYING_SUFFIX = 355,
+        TOKEN_SERVER_IP = 356,
+        TOKEN_SERVER_PORT = 357,
+        TOKEN_SENDER_IP = 358,
+        TOKEN_SENDER_PORT = 359,
+        TOKEN_MAX_QUEUE_SIZE = 360,
+        TOKEN_NCR_PROTOCOL = 361,
+        TOKEN_NCR_FORMAT = 362,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 363,
+        TOKEN_OVERRIDE_NO_UPDATE = 364,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 365,
+        TOKEN_REPLACE_CLIENT_NAME = 366,
+        TOKEN_GENERATED_PREFIX = 367,
+        TOKEN_TCP = 368,
+        TOKEN_JSON = 369,
+        TOKEN_WHEN_PRESENT = 370,
+        TOKEN_NEVER = 371,
+        TOKEN_ALWAYS = 372,
+        TOKEN_WHEN_NOT_PRESENT = 373,
+        TOKEN_LOGGING = 374,
+        TOKEN_LOGGERS = 375,
+        TOKEN_OUTPUT_OPTIONS = 376,
+        TOKEN_OUTPUT = 377,
+        TOKEN_DEBUGLEVEL = 378,
+        TOKEN_SEVERITY = 379,
+        TOKEN_FLUSH = 380,
+        TOKEN_MAXSIZE = 381,
+        TOKEN_MAXVER = 382,
+        TOKEN_DHCP6 = 383,
+        TOKEN_DHCPDDNS = 384,
+        TOKEN_CONTROL_AGENT = 385,
+        TOKEN_TOPLEVEL_JSON = 386,
+        TOKEN_TOPLEVEL_DHCP4 = 387,
+        TOKEN_SUB_DHCP4 = 388,
+        TOKEN_SUB_INTERFACES4 = 389,
+        TOKEN_SUB_SUBNET4 = 390,
+        TOKEN_SUB_POOL4 = 391,
+        TOKEN_SUB_RESERVATION = 392,
+        TOKEN_SUB_OPTION_DEFS = 393,
+        TOKEN_SUB_OPTION_DEF = 394,
+        TOKEN_SUB_OPTION_DATA = 395,
+        TOKEN_SUB_HOOKS_LIBRARY = 396,
+        TOKEN_SUB_DHCP_DDNS = 397,
+        TOKEN_STRING = 398,
+        TOKEN_INTEGER = 399,
+        TOKEN_FLOAT = 400,
+        TOKEN_BOOLEAN = 401
       };
     };
 
@@ -830,6 +831,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_SHARED_NETWORKS (const location_type& l);
+
+    static inline
+    symbol_type
     make_POOLS (const location_type& l);
 
     static inline
@@ -1385,12 +1390,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 756,     ///< Last index in yytable_.
-      yynnts_ = 321,  ///< Number of nonterminal symbols.
+      yylast_ = 787,     ///< Last index in yytable_.
+      yynnts_ = 329,  ///< Number of nonterminal symbols.
       yyfinal_ = 26, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 146  ///< Number of tokens.
+      yyntokens_ = 147  ///< Number of tokens.
     };
 
 
@@ -1447,9 +1452,9 @@ namespace isc { namespace dhcp {
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
      135,   136,   137,   138,   139,   140,   141,   142,   143,   144,
-     145
+     145,   146
     };
-    const unsigned int user_token_number_max_ = 400;
+    const unsigned int user_token_number_max_ = 401;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1482,29 +1487,29 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 160: // value
-      case 164: // map_value
-      case 202: // socket_type
-      case 212: // db_type
-      case 289: // hr_mode
-      case 424: // ncr_protocol_value
-      case 432: // replace_client_name_value
+      case 161: // value
+      case 165: // map_value
+      case 203: // socket_type
+      case 213: // db_type
+      case 290: // hr_mode
+      case 433: // ncr_protocol_value
+      case 441: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 145: // "boolean"
+      case 146: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 144: // "floating point"
+      case 145: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 143: // "integer"
+      case 144: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 142: // "constant string"
+      case 143: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1525,29 +1530,29 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 160: // value
-      case 164: // map_value
-      case 202: // socket_type
-      case 212: // db_type
-      case 289: // hr_mode
-      case 424: // ncr_protocol_value
-      case 432: // replace_client_name_value
+      case 161: // value
+      case 165: // map_value
+      case 203: // socket_type
+      case 213: // db_type
+      case 290: // hr_mode
+      case 433: // ncr_protocol_value
+      case 441: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 145: // "boolean"
+      case 146: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 144: // "floating point"
+      case 145: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 143: // "integer"
+      case 144: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 142: // "constant string"
+      case 143: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1627,29 +1632,29 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 160: // value
-      case 164: // map_value
-      case 202: // socket_type
-      case 212: // db_type
-      case 289: // hr_mode
-      case 424: // ncr_protocol_value
-      case 432: // replace_client_name_value
+      case 161: // value
+      case 165: // map_value
+      case 203: // socket_type
+      case 213: // db_type
+      case 290: // hr_mode
+      case 433: // ncr_protocol_value
+      case 441: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 145: // "boolean"
+      case 146: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 144: // "floating point"
+      case 145: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 143: // "integer"
+      case 144: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 142: // "constant string"
+      case 143: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1676,29 +1681,29 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 160: // value
-      case 164: // map_value
-      case 202: // socket_type
-      case 212: // db_type
-      case 289: // hr_mode
-      case 424: // ncr_protocol_value
-      case 432: // replace_client_name_value
+      case 161: // value
+      case 165: // map_value
+      case 203: // socket_type
+      case 213: // db_type
+      case 290: // hr_mode
+      case 433: // ncr_protocol_value
+      case 441: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 145: // "boolean"
+      case 146: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 144: // "floating point"
+      case 145: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 143: // "integer"
+      case 144: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 142: // "constant string"
+      case 143: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1771,7 +1776,7 @@ namespace isc { namespace dhcp {
      365,   366,   367,   368,   369,   370,   371,   372,   373,   374,
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      385,   386,   387,   388,   389,   390,   391,   392,   393,   394,
-     395,   396,   397,   398,   399,   400
+     395,   396,   397,   398,   399,   400,   401
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -2113,6 +2118,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp4Parser::symbol_type
+  Dhcp4Parser::make_SHARED_NETWORKS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_SHARED_NETWORKS, l);
+  }
+
+  Dhcp4Parser::symbol_type
   Dhcp4Parser::make_POOLS (const location_type& l)
   {
     return symbol_type (token::TOKEN_POOLS, l);
@@ -2643,7 +2654,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp4_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2647 "dhcp4_parser.h" // lalr1.cc:377
+#line 2658 "dhcp4_parser.h" // lalr1.cc:377
 
 
 

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

@@ -101,6 +101,8 @@ using namespace std;
   ENCAPSULATE "encapsulate"
   ARRAY "array"
 
+  SHARED_NETWORKS "shared-networks"
+
   POOLS "pools"
   POOL "pool"
   USER_CONTEXT "user-context"
@@ -408,6 +410,7 @@ global_param: valid_lifetime
             | rebind_timer
             | decline_probation_period
             | subnet4_list
+            | shared_networks
             | interfaces_config
             | lease_database
             | hosts_database
@@ -976,6 +979,55 @@ rapid_commit: RAPID_COMMIT COLON BOOLEAN {
     ctx.stack_.back()->set("rapid-commit", rc);
 };
 
+// ---- shared-networks ---------------------
+
+shared_networks: SHARED_NETWORKS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("shared-networks", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.SHARED_NETWORK);
+} COLON LSQUARE_BRACKET shared_networks_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This allows 0 or more shared network definitions.
+shared_networks_content: %empty
+                       | shared_networks_list
+                       ;
+
+// This allows 1 or more shared network definitions.
+shared_networks_list: shared_network
+                    | shared_networks_list COMMA shared_network
+                    ;
+
+shared_network: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} shared_network_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+}
+
+shared_network_params: shared_network_param
+                     | shared_network_params COMMA shared_network_param
+                     ;
+
+shared_network_param: name
+                    | subnet4_list
+                    | interface
+                    | renew_timer
+                    | rebind_timer
+                    | option_data_list
+                    | match_client_id
+                    | next_server
+                    | relay
+                    | reservation_mode
+                    | client_classes
+                    | valid_lifetime
+                    | unknown_map_entry
+                    ;
+
 // ---- option-def --------------------------
 
 // This defines the "option-def": [ ... ] entry that may appear

+ 153 - 5
src/bin/dhcp4/json_config_parser.cc

@@ -12,8 +12,8 @@
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/parsers/client_class_def_parser.h>
 #include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/parsers/client_class_def_parser.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/parsers/expiration_config_parser.h>
@@ -22,6 +22,7 @@
 #include <dhcpsrv/parsers/ifaces_config_parser.h>
 #include <dhcpsrv/parsers/option_data_parser.h>
 #include <dhcpsrv/parsers/simple_parser4.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
 #include <dhcpsrv/timer_mgr.h>
 #include <hooks/hooks_parser.h>
 #include <config/command_mgr.h>
@@ -47,7 +48,12 @@ using namespace isc::hooks;
 
 namespace {
 
-/// @brief Parser that takes care of global DHCPv4 parameters.
+/// @brief Parser that takes care of global DHCPv4 parameters and utility
+///        functions that work on global level.
+///
+/// This class is a collection of utility method that either handle
+/// global parameters (see @ref parse), or conducts operations on
+/// global level (see @ref sanityChecks and @ref copySubnets4).
 ///
 /// See @ref parse method for a list of supported parameters.
 class Dhcp4ConfigParser : public isc::data::SimpleParser {
@@ -66,7 +72,7 @@ public:
     ///
     /// @throw DhcpConfigError if parameters are missing or
     /// or having incorrect values.
-    void parse(SrvConfigPtr cfg, ConstElementPtr global) {
+    void parse(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
 
         // Set whether v4 server is supposed to echo back client-id
         // (yes = RFC6842 compatible, no = backward compatibility)
@@ -82,6 +88,132 @@ public:
         uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
         cfg->setDhcp4o6Port(dhcp4o6_port);
     }
+
+    /// @brief Copies subnets from shared networks to regular subnets container
+    ///
+    /// @param from pointer to shared networks container (copy from here)
+    /// @param dest pointer to cfg subnets4 (copy to here)
+    /// @throw BadValue if any pointer is missing
+    /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
+    void
+    copySubnets4(const CfgSubnets4Ptr& dest, const CfgSharedNetworks4Ptr& from) {
+
+        if (!dest || !from) {
+            isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
+        }
+
+        const SharedNetwork4Collection* networks = from->getAll();
+        if (!networks) {
+            // Nothing to copy. Technically, it should return a pointer to empty
+            // container, but let's handle null pointer as well.
+            return;
+        }
+
+        // Let's go through all the networks one by one
+        for (auto net = networks->begin(); net != networks->end(); ++net) {
+
+            // For each network go through all the subnets in it.
+            const Subnet4Collection* subnets = (*net)->getAllSubnets();
+            if (!subnets) {
+                // Shared network without subnets it weird, but we decided to
+                // accept such configurations.
+                continue;
+            }
+
+            // For each subnet, add it to a list of regular subnets.
+            for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                dest->add(*subnet);
+            }
+        }
+    }
+
+    /// @brief Conducts global sanity checks
+    ///
+    /// This method is very simple now, but more sanity checks are expected
+    /// in the future.
+    ///
+    /// @param cfg - the parsed structure
+    /// @param global global Dhcp4 scope
+    /// @throw DhcpConfigError in case of issues found
+    void
+    sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
+
+        /// Shared network sanity checks
+        const SharedNetwork4Collection* networks = cfg->getCfgSharedNetworks4()->getAll();
+        if (networks) {
+            sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
+        }
+    }
+
+    /// @brief Sanity checks for shared networks
+    ///
+    /// This method verifies if there are no issues with shared networks.
+    /// @param networks pointer to shared networks being checked
+    /// @param json shared-networks element
+    /// @throw DhcpConfigError if issues are encountered
+    void
+    sharedNetworksSanityChecks(const SharedNetwork4Collection& networks,
+                               ConstElementPtr json) {
+
+        /// @todo: in case of errors, use json to extract line numbers.
+        if (!json) {
+            // No json? That means that the shared-networks was never specified
+            // in the config.
+            return;
+        }
+
+        // Used for names uniqueness checks.
+        std::set<string> names;
+
+        // Let's go through all the networks one by one
+        for (auto net = networks.begin(); net != networks.end(); ++net) {
+            string txt;
+
+            // Let's check if all subnets have either the same interface
+            // or don't have the interface specified at all.
+            string iface = (*net)->getIface();
+
+            const Subnet4Collection* subnets = (*net)->getAllSubnets();
+            if (subnets) {
+                // For each subnet, add it to a list of regular subnets.
+                for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                    if (iface.empty()) {
+                        iface = (*subnet)->getIface();
+                        continue;
+                    }
+
+                    if ((*subnet)->getIface().empty()) {
+                        continue;
+                    }
+
+                    if (iface != (*subnet)->getIface()) {
+                        isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
+                                  << " has specified interface " << (*subnet)->getIface()
+                                  << ", but earlier subnet in the same shared-network"
+                                  << " or the shared-network itself used " << iface);
+                    }
+
+                    // Let's collect the subnets in case we later find out the
+                    // subnet doesn't have a mandatory name.
+                    txt += (*subnet)->toText() + " ";
+                }
+            }
+
+            // Next, let's check name of the shared network.
+            if ((*net)->getName().empty()) {
+                isc_throw(DhcpConfigError, "Shared-network with subnets "
+                          << txt << " is missing mandatory 'name' parameter");
+            }
+
+            // Is it unique?
+            if (names.find((*net)->getName()) != names.end()) {
+                isc_throw(DhcpConfigError, "A shared-network with "
+                          "name " << (*net)->getName() << " defined twice.");
+            }
+            names.insert((*net)->getName());
+
+        }
+    }
 };
 
 } // anonymous namespace
@@ -189,6 +321,10 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
             parser.parse(cfg_option_def, option_defs);
         }
 
+        // This parser is used in several places, so it should be available
+        // early.
+        Dhcp4ConfigParser global_parser;
+
         // Make parsers grouping.
         const std::map<std::string, ConstElementPtr>& values_map =
                                                         mutable_cfg->mapValue();
@@ -291,11 +427,19 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
             }
 
             if (config_pair.first == "shared-networks") {
-                /// @todo We need to create instance of SharedNetworks4ListParser
+
+                /// We need to create instance of SharedNetworks4ListParser
                 /// and parse the list of the shared networks into the
                 /// CfgSharedNetworks4 object. One additional step is then to
                 /// add subnets from the CfgSharedNetworks4 into CfgSubnets4
                 /// as well.
+                SharedNetworks4ListParser parser;
+                CfgSharedNetworks4Ptr cfg = srv_cfg->getCfgSharedNetworks4();
+                parser.parse(cfg, config_pair.second);
+
+                // We also need to put the subnets it contains into normal
+                // subnets list.
+                global_parser.copySubnets4(srv_cfg->getCfgSubnets4(), cfg);
                 continue;
             }
 
@@ -322,9 +466,13 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
         }
 
         // Apply global options in the staging config.
-        Dhcp4ConfigParser global_parser;
         global_parser.parse(srv_cfg, mutable_cfg);
 
+        // This method conducts final sanity checks and tweaks. In particular,
+        // it checks that there is no conflict between plain subnets and those
+        // defined as part of shared networks.
+        global_parser.sanityChecks(srv_cfg, mutable_cfg);
+
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp4_logger, DHCP4_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());

+ 2 - 0
src/bin/dhcp4/parser_context.cc

@@ -190,6 +190,8 @@ Parser4Context::contextName()
         return ("ncr-format");
     case REPLACE_CLIENT_NAME:
         return ("replace-client-name");
+    case SHARED_NETWORK:
+        return ("shared-networks");
     default:
         return ("__unknown__");
     }

+ 3 - 0
src/bin/dhcp4/parser_context.h

@@ -232,6 +232,9 @@ public:
         /// Used while parsing Dhcp4/Subnet4 structures.
         SUBNET4,
 
+        /// Used while parsing shared-networks structures.
+        SHARED_NETWORK,
+
         /// Used while parsing Dhcp4/Subnet4/reservation-mode.
         RESERVATION_MODE,
 

+ 390 - 28
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -181,6 +181,40 @@ public:
         EXPECT_EQ(expected_code, rcode_);
     }
 
+    /// @brief Convenience method for running configuration
+    ///
+    /// This method does not throw, but signals errors using gtest macros.
+    ///
+    /// @param config text to be parsed as JSON
+    /// @param expected_code expected code (see cc/command_interpreter.h)
+    /// @param exp_error expected text error (check skipped if empty)
+    void configure(std::string config, int expected_code,
+                   std::string exp_error = "") {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+        ConstElementPtr status;
+        EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+        ASSERT_TRUE(status);
+
+        int rcode;
+        ConstElementPtr comment = parseAnswer(rcode, status);
+        EXPECT_EQ(expected_code, rcode);
+
+        string text;
+        ASSERT_NO_THROW(text = comment->stringValue());
+
+        if (expected_code != rcode) {
+            std::cout << "Reported status: " << text << std::endl;
+        }
+
+        if ((rcode != 0)) {
+            if (!exp_error.empty()) {
+                EXPECT_EQ(exp_error, text);
+            }
+        }
+    }
+
     ~Dhcp4ParserTest() {
         resetConfiguration();
 
@@ -570,6 +604,31 @@ public:
         return (ReturnType());
     }
 
+    /// @brief Checks if specified subnet is part of the collection
+    ///
+    /// @param col collection of subnets to be inspected
+    /// @param subnet text notation (e.g. 192.0.2.0/24)
+    /// @param t1 expected renew-timer value
+    /// @param t2 expected rebind-timer value
+    /// @param valid expected valid-lifetime value
+    /// @return the subnet that was examined
+    Subnet4Ptr
+    checkSubnet(const Subnet4Collection& col, std::string subnet,
+                uint32_t t1, uint32_t t2, uint32_t valid) {
+        const auto& index = col.get<SubnetPrefixIndexTag>();
+        auto subnet_it = index.find(subnet);
+        if (subnet_it == index.cend()) {
+            ADD_FAILURE() << "Unable to find expected subnet " << subnet;
+            return (Subnet4Ptr());
+        }
+        Subnet4Ptr s = *subnet_it;
+
+        EXPECT_EQ(t1, s->getT1());
+        EXPECT_EQ(t2, s->getT2());
+        EXPECT_EQ(valid, s->getValid());
+
+        return (s);
+    }
 
     /// @brief This utility method attempts to configure using specified
     ///        config and then returns requested pool from requested subnet
@@ -1023,8 +1082,6 @@ TEST_F(Dhcp4ParserTest, reconfigureRemoveSubnet) {
 
     /// CASE 2: Configure 4 subnets, then reconfigure and remove one
     /// from in between (not first, not last)
-
-    /// @todo: Uncomment subnet removal test as part of #3281.
     ASSERT_NO_THROW(json = parseDHCP4(config4));
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -4927,21 +4984,10 @@ TEST_F(Dhcp4ParserTest, invalidPoolRange) {
         " } ] \n"
         "} \n";
 
-    ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseDHCP4(config, true));
-
-    ConstElementPtr status;
-    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(status);
-    int rcode;
-    ConstElementPtr comment = parseAnswer(rcode, status);
-    string text;
-    ASSERT_NO_THROW(text = comment->stringValue());
-
-    EXPECT_EQ(1, rcode);
     string expected = "Failed to create pool defined by: "
         "192.0.2.1-19.2.0.200 (<string>:6:26)";
-    EXPECT_EQ(expected, text);
+
+    configure(config, CONTROL_RESULT_ERROR, expected);
 }
 
 // Test verifies the error message for an outside subnet pool range
@@ -4957,23 +5003,339 @@ TEST_F(Dhcp4ParserTest, outsideSubnetPool) {
         " } ] \n"
         "} \n";
 
-    ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseDHCP4(config, true));
-
-    ConstElementPtr status;
-    EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
-    ASSERT_TRUE(status);
-    int rcode;
-    ConstElementPtr comment = parseAnswer(rcode, status);
-    string text;
-    ASSERT_NO_THROW(text = comment->stringValue());
-
-    EXPECT_EQ(1, rcode);
     string expected = "subnet configuration failed: "
         "a pool of type V4, with the following address range: "
         "192.0.2.1-192.0.2.100 does not match the prefix of a subnet: "
         "10.0.2.0/24 to which it is being added (<string>:5:14)";
-    EXPECT_EQ(expected, text);
+
+    configure(config, CONTROL_RESULT_ERROR, expected);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworksEmpty) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+        "    \"subnet\": \"10.0.2.0/24\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+}
+
+// Test verifies that if a shared network is defined, it at least has to have
+// a name.
+TEST_F(Dhcp4ParserTest, sharedNetworksNoName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+        "    \"subnet\": \"10.0.2.0/24\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { } ]\n"
+        "} \n";
+
+    EXPECT_THROW(parseDHCP4(config, true), Dhcp4ParseError);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworksEmptyName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+        "    \"subnet\": \"10.0.2.0/24\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_ERROR,
+              "Shared-network with subnets  is missing mandatory 'name' parameter");
+}
+
+// Test verifies that a degenerated shared-network (no subnets) is
+// accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworksName) {
+    string config = "{\n"
+        "\"subnet4\": [ {  \n"
+        "    \"pools\": [ { \"pool\": \"10.0.2.1 - 10.0.2.100\" } ], \n"
+        "    \"subnet\": \"10.0.2.0/24\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"foo\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks4();
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork4Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+    SharedNetwork4Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // Verify that there are no subnets in this shared-network
+    const Subnet4Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(0, subs->size());
+}
+
+// Test verifies that a degenerated shared-network (just one subnet) is
+// accepted.
+TEST_F(Dhcp4ParserTest, sharedNetworks1subnet) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet4\": [ { \n"
+        "        \"subnet\": \"192.0.2.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ]\n"
+        "    } ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks4();
+    ASSERT_TRUE(cfg_net);
+
+    // There should be exactly one shared subnet.
+    const SharedNetwork4Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork4Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // It should have one subnet.
+    const Subnet4Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+    checkSubnet(*subs, "192.0.2.0/24", 1000, 2000, 4000);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    ASSERT_TRUE(subnets4);
+
+    subs = subnets4->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "192.0.2.0/24", 1000, 2000, 4000);
+}
+
+// Test verifies that a proper shared-network (three subnets) is
+// accepted. It verifies several things:
+// - that more than one subnet can be added to shared subnets
+// - that each subnet being part of the shared subnets is also stored in
+//   global subnets collection
+// - that a subnet can inherit global values
+// - that subnet can override global parameters
+// - that overridden parameters only affect one subnet and not others
+TEST_F(Dhcp4ParserTest, sharedNetworks3subnets) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet4\": [\n"
+        "    { \n"
+        "        \"subnet\": \"192.0.1.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"192.0.2.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
+        "        \"renew-timer\": 2,\n"
+        "        \"rebind-timer\": 22,\n"
+        "        \"valid-lifetime\": 222\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"192.0.3.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n"
+        "    }\n"
+        "    ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks4();
+
+    // There is expected one shared subnet.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork4Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork4Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+
+    EXPECT_EQ("foo", net->getName());
+
+    const Subnet4Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(3, subs->size());
+    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000);
+    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222);
+    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets4Ptr subnets4 = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    ASSERT_TRUE(subnets4);
+
+    subs = subnets4->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "192.0.1.0/24", 1000, 2000, 4000);
+    checkSubnet(*subs, "192.0.2.0/24", 2, 22, 222);
+    checkSubnet(*subs, "192.0.3.0/24", 1000, 2000, 4000);
+}
+
+// This test checks if parameters are derived properly:
+// - global to shared network
+// - shared network to subnet
+// Also, it tests that more than one shared network can be defined.
+TEST_F(Dhcp4ParserTest, sharedNetworksDerive) {
+
+    // We need to fake the interfaces present, because we want to test
+    // interface names inheritance. However, there are sanity checks
+    // on subnet level that would refuse the value if the interface
+    // is not present.
+    IfaceMgrTestConfig iface_config(true);
+
+    // This config is structured in a way that the first shared
+    // subnet have many parameters defined. The first subnet
+    // should inherit them. The second subnet overrides all
+    // values and those values should be used, not those from
+    // shared network scope.
+    string config = "{\n"
+        "\"renew-timer\": 1, \n" // global values here
+        "\"rebind-timer\": 2, \n"
+        "\"valid-lifetime\": 4, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n," // shared network values here
+        "    \"interface\": \"eth0\",\n"
+        "    \"match-client-id\": false,\n"
+        "    \"next-server\": \"1.2.3.4\",\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"5.6.7.8\"\n"
+        "    },\n"
+        "    \"reservation-mode\": \"out-of-pool\",\n"
+        "    \"renew-timer\": 10,\n"
+        "    \"rebind-timer\": 20,\n"
+        "    \"valid-lifetime\": 40,\n"
+        "    \"subnet4\": [\n"
+        "    { \n"
+        "        \"subnet\": \"192.0.1.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.1.1-192.0.1.10\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"192.0.2.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.2.1-192.0.2.10\" } ],\n"
+        "        \"renew-timer\": 100,\n"
+        "        \"rebind-timer\": 200,\n"
+        "        \"valid-lifetime\": 400,\n"
+        "        \"match-client-id\": true,\n"
+        "        \"next-server\": \"11.22.33.44\",\n"
+        "        \"relay\": {\n"
+        "            \"ip-address\": \"55.66.77.88\"\n"
+        "        },\n"
+        "        \"reservation-mode\": \"disabled\"\n"
+        "    }\n"
+        "    ]\n"
+        " },\n"
+        "{ // second shared-network starts here\n"
+        "    \"name\": \"bar\",\n"
+        "    \"subnet4\": [\n"
+        "    {\n"
+        "        \"subnet\": \"192.0.3.0/24\",\n"
+        "        \"pools\": [ { \"pool\": \"192.0.3.1-192.0.3.10\" } ]\n"
+        "    }\n"
+        "    ]\n"
+
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks4Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks4();
+
+    // Two shared networks are expected.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork4Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(2, nets->size());
+
+    SharedNetwork4Ptr net = nets->at(0);
+    ASSERT_TRUE(net);
+
+    // The first shared network has two subnets.
+    const Subnet4Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(2, subs->size());
+
+    // For the first subnet, the renew-timer should be 10, because it was
+    // derived from shared-network level. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet4 level.
+    Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 10, 20, 40);
+    ASSERT_TRUE(s);
+
+    // These are values derived from shared network scope:
+    EXPECT_EQ("eth0", s->getIface());
+    EXPECT_EQ(false, s->getMatchClientId());
+    EXPECT_EQ(IOAddress("1.2.3.4"), s->getSiaddr());
+    EXPECT_EQ(IOAddress("5.6.7.8"), s->getRelayInfo().addr_);
+    EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
+
+    // For the second subnet, the renew-timer should be 100, because it
+    // was specified explicitly. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet4 level.
+    s = checkSubnet(*subs, "192.0.2.0/24", 100, 200, 400);
+
+    // These are values derived from shared network scope:
+    EXPECT_EQ("eth0", s->getIface());
+    EXPECT_EQ(true, s->getMatchClientId());
+    EXPECT_EQ(IOAddress("11.22.33.44"), s->getSiaddr());
+    EXPECT_EQ(IOAddress("55.66.77.88"), s->getRelayInfo().addr_);
+    EXPECT_EQ(Network::HR_DISABLED, s->getHostReservationMode());
+
+    // Ok, now check the second shared subnet.
+    net = nets->at(1);
+    ASSERT_TRUE(net);
+
+    subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+
+    // This subnet should derive its renew-timer from global scope.
+    // All other parameters should have default values.
+    s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
+    EXPECT_EQ("", s->getIface());
+    EXPECT_EQ(true, s->getMatchClientId());
+    EXPECT_EQ(IOAddress("0.0.0.0"), s->getSiaddr());
+    EXPECT_EQ(IOAddress("0.0.0.0"), s->getRelayInfo().addr_);
+    EXPECT_EQ(Network::HR_ALL, s->getHostReservationMode());
 }
 
 }

+ 1 - 1
src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc

@@ -1328,7 +1328,7 @@ TEST_F(CtrlChannelDhcpv4SrvTest, longResponse) {
         while (response.tellp() < long_response_size) {
             std::string partial;
             const unsigned int timeout = 5;
-            ASSERT_TRUE(client->getResponse(partial, 5));
+            ASSERT_TRUE(client->getResponse(partial, timeout));
             response << partial;
         }
 

+ 58 - 8
src/bin/dhcp4/tests/get_config_unittest.cc

@@ -12,11 +12,11 @@
 #include <cc/cfg_to_element.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/parsers/simple_parser4.h>
 #include <dhcp4/tests/dhcp4_test_utils.h>
 #include <dhcp4/tests/get_config_unittest.h>
 #include <dhcp4/dhcp4_srv.h>
 #include <dhcp4/json_config_parser.h>
+#include <dhcpsrv/parsers/simple_parser4.h>
 
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
@@ -1592,6 +1592,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 1
@@ -1634,6 +1635,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -1701,6 +1703,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -1768,6 +1771,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -1835,6 +1839,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -1974,6 +1979,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2113,6 +2119,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2180,6 +2187,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2247,6 +2255,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2314,6 +2323,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2381,6 +2391,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2448,6 +2459,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2539,6 +2551,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2630,6 +2643,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2697,6 +2711,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2796,6 +2811,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -2873,6 +2889,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 17
@@ -2925,6 +2942,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"record\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 18
@@ -2986,6 +3004,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 19
@@ -3038,6 +3057,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"uint32\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 20
@@ -3090,6 +3110,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"uint32\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 21
@@ -3142,6 +3163,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"string\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 22
@@ -3194,6 +3216,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"string\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 23
@@ -3253,6 +3276,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3320,6 +3344,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3431,6 +3456,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"uint32\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3534,6 +3560,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 27
@@ -3629,6 +3656,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3705,6 +3733,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3789,6 +3818,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3898,6 +3928,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -3982,6 +4013,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4107,6 +4139,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 33
@@ -4193,6 +4226,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"ipv4-address\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4275,6 +4309,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            }\n"
 "        ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4361,6 +4396,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"string\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4428,6 +4464,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 37
@@ -4470,6 +4507,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 38
@@ -4512,6 +4550,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4579,6 +4618,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4646,6 +4686,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4874,6 +4915,7 @@ const char* UNPARSED_CONFIGS[] = {
 "                \"type\": \"uint32\"\n"
 "            }\n"
 "        ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -4961,6 +5003,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5100,6 +5143,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 44
@@ -5142,6 +5186,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 45
@@ -5184,6 +5229,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 46
@@ -5226,6 +5272,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [ ]\n"
 "    }\n",
     // CONFIGURATION 47
@@ -5268,6 +5315,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5335,6 +5383,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5402,6 +5451,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"ethX\",\n"
@@ -5469,6 +5519,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"ethX\",\n"
@@ -5536,6 +5587,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5626,6 +5678,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5693,6 +5746,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5760,6 +5814,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5828,6 +5883,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -5900,6 +5956,7 @@ const char* UNPARSED_CONFIGS[] = {
 "        },\n"
 "        \"option-data\": [ ],\n"
 "        \"option-def\": [ ],\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet4\": [\n"
 "            {\n"
 "                \"4o6-interface\": \"\",\n"
@@ -6135,7 +6192,6 @@ public:
 };
 
 /// Test a configuration
-
 TEST_P(Dhcp4GetConfigTest, run) {
     // configurations have not been extracted yet
     if (max_config_counter == 0) {
@@ -6200,14 +6256,8 @@ TEST_P(Dhcp4GetConfigTest, run) {
     EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
 }
 
-#if 0
-// This test is temporarily disabled. The shared subnets structures have been
-// implemented (#5305), but the parsers are not there yet, so grammar will fail
-// when parseDHCP4 is called. That's comping up in #5357.
-
 /// Define the parameterized test loop
 INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
                         ::testing::Range(static_cast<size_t>(0), max_config_counter));
-#endif
 
 };

+ 1 - 0
src/bin/dhcp4/tests/parser_unittest.cc

@@ -252,6 +252,7 @@ TEST(ParserTest, file) {
                                "pgsql-reservations.json",
                                "reservations.json",
                                "several-subnets.json",
+                               "shared-network.json",
                                "single-subnet.json",
                                "with-ddns.json" };
 

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


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

@@ -30,6 +30,8 @@ bool start_token_flag = false;
 isc::dhcp::Parser6Context::ParserType start_token_value;
 unsigned int comment_start_line = 0;
 
+using namespace isc::dhcp;
+
 };
 
 /* To avoid the call to exit... oops! */
@@ -601,6 +603,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_PREFERRED_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("preferred-lifetime", driver.loc_);
@@ -611,6 +614,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_VALID_LIFETIME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("valid-lifetime", driver.loc_);
@@ -621,6 +625,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RENEW_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("renew-timer", driver.loc_);
@@ -631,6 +636,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_REBIND_TIMER(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("rebind-timer", driver.loc_);
@@ -649,12 +655,22 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"subnet6\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_SUBNET6(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("subnet6", driver.loc_);
     }
 }
 
+\"shared-networks\" {
+    switch (driver.ctx_) {
+    case Parser6Context::DHCP6:
+        return Dhcp6Parser::make_SHARED_NETWORKS(driver.loc_);
+    default:
+        return Dhcp6Parser::make_STRING("shared-networks", driver.loc_);
+    }
+}
+
 \"option-def\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::DHCP6:
@@ -673,6 +689,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser6Context::RESERVATIONS:
     case isc::dhcp::Parser6Context::CLIENT_CLASSES:
     case isc::dhcp::Parser6Context::CLIENT_CLASS:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_OPTION_DATA(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("option-data", driver.loc_);
@@ -688,6 +705,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
     case isc::dhcp::Parser6Context::CLIENT_CLASSES:
     case isc::dhcp::Parser6Context::CLIENT_CLASS:
     case isc::dhcp::Parser6Context::LOGGERS:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_NAME(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("name", driver.loc_);
@@ -807,6 +825,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_INTERFACE(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("interface", driver.loc_);
@@ -816,6 +835,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"interface-id\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_INTERFACE_ID(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("interface-id", driver.loc_);
@@ -834,6 +854,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"rapid-commit\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RAPID_COMMIT(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("rapid-commit", driver.loc_);
@@ -843,6 +864,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"reservation-mode\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RESERVATION_MODE(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("reservation-mode", driver.loc_);
@@ -1150,6 +1172,7 @@ ControlCharacterFill            [^"\\]|\\{JSONEscapeSequence}
 \"relay\" {
     switch(driver.ctx_) {
     case isc::dhcp::Parser6Context::SUBNET6:
+    case Parser6Context::SHARED_NETWORK:
         return isc::dhcp::Dhcp6Parser::make_RELAY(driver.loc_);
     default:
         return isc::dhcp::Dhcp6Parser::make_STRING("relay", driver.loc_);

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


+ 150 - 139
src/bin/dhcp6/dhcp6_parser.h

@@ -410,94 +410,95 @@ namespace isc { namespace dhcp {
         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_CONTROL_AGENT = 391,
-        TOKEN_TOPLEVEL_JSON = 392,
-        TOKEN_TOPLEVEL_DHCP6 = 393,
-        TOKEN_SUB_DHCP6 = 394,
-        TOKEN_SUB_INTERFACES6 = 395,
-        TOKEN_SUB_SUBNET6 = 396,
-        TOKEN_SUB_POOL6 = 397,
-        TOKEN_SUB_PD_POOL = 398,
-        TOKEN_SUB_RESERVATION = 399,
-        TOKEN_SUB_OPTION_DEFS = 400,
-        TOKEN_SUB_OPTION_DEF = 401,
-        TOKEN_SUB_OPTION_DATA = 402,
-        TOKEN_SUB_HOOKS_LIBRARY = 403,
-        TOKEN_SUB_DHCP_DDNS = 404,
-        TOKEN_STRING = 405,
-        TOKEN_INTEGER = 406,
-        TOKEN_FLOAT = 407,
-        TOKEN_BOOLEAN = 408
+        TOKEN_SHARED_NETWORKS = 321,
+        TOKEN_MAC_SOURCES = 322,
+        TOKEN_RELAY_SUPPLIED_OPTIONS = 323,
+        TOKEN_HOST_RESERVATION_IDENTIFIERS = 324,
+        TOKEN_CLIENT_CLASSES = 325,
+        TOKEN_TEST = 326,
+        TOKEN_CLIENT_CLASS = 327,
+        TOKEN_RESERVATIONS = 328,
+        TOKEN_IP_ADDRESSES = 329,
+        TOKEN_PREFIXES = 330,
+        TOKEN_DUID = 331,
+        TOKEN_HW_ADDRESS = 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_SERVER_ID = 347,
+        TOKEN_LLT = 348,
+        TOKEN_EN = 349,
+        TOKEN_LL = 350,
+        TOKEN_IDENTIFIER = 351,
+        TOKEN_HTYPE = 352,
+        TOKEN_TIME = 353,
+        TOKEN_ENTERPRISE_ID = 354,
+        TOKEN_DHCP4O6_PORT = 355,
+        TOKEN_CONTROL_SOCKET = 356,
+        TOKEN_SOCKET_TYPE = 357,
+        TOKEN_SOCKET_NAME = 358,
+        TOKEN_DHCP_DDNS = 359,
+        TOKEN_ENABLE_UPDATES = 360,
+        TOKEN_QUALIFYING_SUFFIX = 361,
+        TOKEN_SERVER_IP = 362,
+        TOKEN_SERVER_PORT = 363,
+        TOKEN_SENDER_IP = 364,
+        TOKEN_SENDER_PORT = 365,
+        TOKEN_MAX_QUEUE_SIZE = 366,
+        TOKEN_NCR_PROTOCOL = 367,
+        TOKEN_NCR_FORMAT = 368,
+        TOKEN_ALWAYS_INCLUDE_FQDN = 369,
+        TOKEN_OVERRIDE_NO_UPDATE = 370,
+        TOKEN_OVERRIDE_CLIENT_UPDATE = 371,
+        TOKEN_REPLACE_CLIENT_NAME = 372,
+        TOKEN_GENERATED_PREFIX = 373,
+        TOKEN_UDP = 374,
+        TOKEN_TCP = 375,
+        TOKEN_JSON = 376,
+        TOKEN_WHEN_PRESENT = 377,
+        TOKEN_NEVER = 378,
+        TOKEN_ALWAYS = 379,
+        TOKEN_WHEN_NOT_PRESENT = 380,
+        TOKEN_LOGGING = 381,
+        TOKEN_LOGGERS = 382,
+        TOKEN_OUTPUT_OPTIONS = 383,
+        TOKEN_OUTPUT = 384,
+        TOKEN_DEBUGLEVEL = 385,
+        TOKEN_SEVERITY = 386,
+        TOKEN_FLUSH = 387,
+        TOKEN_MAXSIZE = 388,
+        TOKEN_MAXVER = 389,
+        TOKEN_DHCP4 = 390,
+        TOKEN_DHCPDDNS = 391,
+        TOKEN_CONTROL_AGENT = 392,
+        TOKEN_TOPLEVEL_JSON = 393,
+        TOKEN_TOPLEVEL_DHCP6 = 394,
+        TOKEN_SUB_DHCP6 = 395,
+        TOKEN_SUB_INTERFACES6 = 396,
+        TOKEN_SUB_SUBNET6 = 397,
+        TOKEN_SUB_POOL6 = 398,
+        TOKEN_SUB_PD_POOL = 399,
+        TOKEN_SUB_RESERVATION = 400,
+        TOKEN_SUB_OPTION_DEFS = 401,
+        TOKEN_SUB_OPTION_DEF = 402,
+        TOKEN_SUB_OPTION_DATA = 403,
+        TOKEN_SUB_HOOKS_LIBRARY = 404,
+        TOKEN_SUB_DHCP_DDNS = 405,
+        TOKEN_STRING = 406,
+        TOKEN_INTEGER = 407,
+        TOKEN_FLOAT = 408,
+        TOKEN_BOOLEAN = 409
       };
     };
 
@@ -870,6 +871,10 @@ namespace isc { namespace dhcp {
 
     static inline
     symbol_type
+    make_SHARED_NETWORKS (const location_type& l);
+
+    static inline
+    symbol_type
     make_MAC_SOURCES (const location_type& l);
 
     static inline
@@ -1425,12 +1430,12 @@ namespace isc { namespace dhcp {
     enum
     {
       yyeof_ = 0,
-      yylast_ = 804,     ///< Last index in yytable_.
-      yynnts_ = 338,  ///< Number of nonterminal symbols.
+      yylast_ = 836,     ///< Last index in yytable_.
+      yynnts_ = 346,  ///< Number of nonterminal symbols.
       yyfinal_ = 28, ///< Termination state number.
       yyterror_ = 1,
       yyerrcode_ = 256,
-      yyntokens_ = 154  ///< Number of tokens.
+      yyntokens_ = 155  ///< Number of tokens.
     };
 
 
@@ -1487,9 +1492,9 @@ namespace isc { namespace dhcp {
      115,   116,   117,   118,   119,   120,   121,   122,   123,   124,
      125,   126,   127,   128,   129,   130,   131,   132,   133,   134,
      135,   136,   137,   138,   139,   140,   141,   142,   143,   144,
-     145,   146,   147,   148,   149,   150,   151,   152,   153
+     145,   146,   147,   148,   149,   150,   151,   152,   153,   154
     };
-    const unsigned int user_token_number_max_ = 408;
+    const unsigned int user_token_number_max_ = 409;
     const token_number_type undef_token_ = 2;
 
     if (static_cast<int>(t) <= yyeof_)
@@ -1522,29 +1527,29 @@ namespace isc { namespace dhcp {
   {
       switch (other.type_get ())
     {
-      case 169: // value
-      case 173: // map_value
-      case 217: // db_type
-      case 293: // hr_mode
-      case 416: // duid_type
-      case 449: // ncr_protocol_value
-      case 457: // replace_client_name_value
+      case 170: // value
+      case 174: // map_value
+      case 218: // db_type
+      case 294: // hr_mode
+      case 425: // duid_type
+      case 458: // ncr_protocol_value
+      case 466: // replace_client_name_value
         value.copy< ElementPtr > (other.value);
         break;
 
-      case 153: // "boolean"
+      case 154: // "boolean"
         value.copy< bool > (other.value);
         break;
 
-      case 152: // "floating point"
+      case 153: // "floating point"
         value.copy< double > (other.value);
         break;
 
-      case 151: // "integer"
+      case 152: // "integer"
         value.copy< int64_t > (other.value);
         break;
 
-      case 150: // "constant string"
+      case 151: // "constant string"
         value.copy< std::string > (other.value);
         break;
 
@@ -1565,29 +1570,29 @@ namespace isc { namespace dhcp {
     (void) v;
       switch (this->type_get ())
     {
-      case 169: // value
-      case 173: // map_value
-      case 217: // db_type
-      case 293: // hr_mode
-      case 416: // duid_type
-      case 449: // ncr_protocol_value
-      case 457: // replace_client_name_value
+      case 170: // value
+      case 174: // map_value
+      case 218: // db_type
+      case 294: // hr_mode
+      case 425: // duid_type
+      case 458: // ncr_protocol_value
+      case 466: // replace_client_name_value
         value.copy< ElementPtr > (v);
         break;
 
-      case 153: // "boolean"
+      case 154: // "boolean"
         value.copy< bool > (v);
         break;
 
-      case 152: // "floating point"
+      case 153: // "floating point"
         value.copy< double > (v);
         break;
 
-      case 151: // "integer"
+      case 152: // "integer"
         value.copy< int64_t > (v);
         break;
 
-      case 150: // "constant string"
+      case 151: // "constant string"
         value.copy< std::string > (v);
         break;
 
@@ -1667,29 +1672,29 @@ namespace isc { namespace dhcp {
     // Type destructor.
     switch (yytype)
     {
-      case 169: // value
-      case 173: // map_value
-      case 217: // db_type
-      case 293: // hr_mode
-      case 416: // duid_type
-      case 449: // ncr_protocol_value
-      case 457: // replace_client_name_value
+      case 170: // value
+      case 174: // map_value
+      case 218: // db_type
+      case 294: // hr_mode
+      case 425: // duid_type
+      case 458: // ncr_protocol_value
+      case 466: // replace_client_name_value
         value.template destroy< ElementPtr > ();
         break;
 
-      case 153: // "boolean"
+      case 154: // "boolean"
         value.template destroy< bool > ();
         break;
 
-      case 152: // "floating point"
+      case 153: // "floating point"
         value.template destroy< double > ();
         break;
 
-      case 151: // "integer"
+      case 152: // "integer"
         value.template destroy< int64_t > ();
         break;
 
-      case 150: // "constant string"
+      case 151: // "constant string"
         value.template destroy< std::string > ();
         break;
 
@@ -1716,29 +1721,29 @@ namespace isc { namespace dhcp {
     super_type::move(s);
       switch (this->type_get ())
     {
-      case 169: // value
-      case 173: // map_value
-      case 217: // db_type
-      case 293: // hr_mode
-      case 416: // duid_type
-      case 449: // ncr_protocol_value
-      case 457: // replace_client_name_value
+      case 170: // value
+      case 174: // map_value
+      case 218: // db_type
+      case 294: // hr_mode
+      case 425: // duid_type
+      case 458: // ncr_protocol_value
+      case 466: // replace_client_name_value
         value.move< ElementPtr > (s.value);
         break;
 
-      case 153: // "boolean"
+      case 154: // "boolean"
         value.move< bool > (s.value);
         break;
 
-      case 152: // "floating point"
+      case 153: // "floating point"
         value.move< double > (s.value);
         break;
 
-      case 151: // "integer"
+      case 152: // "integer"
         value.move< int64_t > (s.value);
         break;
 
-      case 150: // "constant string"
+      case 151: // "constant string"
         value.move< std::string > (s.value);
         break;
 
@@ -1812,7 +1817,7 @@ namespace isc { namespace dhcp {
      375,   376,   377,   378,   379,   380,   381,   382,   383,   384,
      385,   386,   387,   388,   389,   390,   391,   392,   393,   394,
      395,   396,   397,   398,   399,   400,   401,   402,   403,   404,
-     405,   406,   407,   408
+     405,   406,   407,   408,   409
     };
     return static_cast<token_type> (yytoken_number_[type]);
   }
@@ -2202,6 +2207,12 @@ namespace isc { namespace dhcp {
   }
 
   Dhcp6Parser::symbol_type
+  Dhcp6Parser::make_SHARED_NETWORKS (const location_type& l)
+  {
+    return symbol_type (token::TOKEN_SHARED_NETWORKS, l);
+  }
+
+  Dhcp6Parser::symbol_type
   Dhcp6Parser::make_MAC_SOURCES (const location_type& l)
   {
     return symbol_type (token::TOKEN_MAC_SOURCES, l);
@@ -2732,7 +2743,7 @@ namespace isc { namespace dhcp {
 
 #line 14 "dhcp6_parser.yy" // lalr1.cc:377
 } } // isc::dhcp
-#line 2736 "dhcp6_parser.h" // lalr1.cc:377
+#line 2747 "dhcp6_parser.h" // lalr1.cc:377
 
 
 

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

@@ -109,6 +109,7 @@ using namespace std;
   DISABLED "disabled"
   OUT_OF_POOL "out-of-pool"
   ALL "all"
+  SHARED_NETWORKS "shared-networks"
 
   MAC_SOURCES "mac-sources"
   RELAY_SUPPLIED_OPTIONS "relay-supplied-options"
@@ -417,6 +418,7 @@ global_param: preferred_lifetime
             | rebind_timer
             | decline_probation_period
             | subnet6_list
+            | shared_networks
             | interfaces_config
             | lease_database
             | hosts_database
@@ -963,6 +965,57 @@ rapid_commit: RAPID_COMMIT COLON BOOLEAN {
     ctx.stack_.back()->set("rapid-commit", rc);
 };
 
+
+// ---- shared-networks ---------------------
+
+shared_networks: SHARED_NETWORKS {
+    ElementPtr l(new ListElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->set("shared-networks", l);
+    ctx.stack_.push_back(l);
+    ctx.enter(ctx.SHARED_NETWORK);
+} COLON LSQUARE_BRACKET shared_networks_content RSQUARE_BRACKET {
+    ctx.stack_.pop_back();
+    ctx.leave();
+};
+
+// This allows 0 or more shared network definitions.
+shared_networks_content: %empty
+                    | shared_networks_list
+                    ;
+
+// This allows 1 or more shared network definitions.
+shared_networks_list: shared_network
+                    | shared_networks_list COMMA shared_network
+                    ;
+
+shared_network: LCURLY_BRACKET {
+    ElementPtr m(new MapElement(ctx.loc2pos(@1)));
+    ctx.stack_.back()->add(m);
+    ctx.stack_.push_back(m);
+} shared_network_params RCURLY_BRACKET {
+    ctx.stack_.pop_back();
+}
+
+shared_network_params: shared_network_param
+                     | shared_network_params COMMA shared_network_param
+                     ;
+
+shared_network_param: name
+                    | subnet6_list
+                    | interface
+                    | interface_id
+                    | renew_timer
+                    | rebind_timer
+                    | option_data_list
+                    | relay
+                    | reservation_mode
+                    | client_classes
+                    | preferred_lifetime
+                    | rapid_commit
+                    | valid_lifetime
+                    | unknown_map_entry
+                    ;
+
 // ---- option-def --------------------------
 
 // This defines the "option-def": [ ... ] entry that may appear

+ 156 - 7
src/bin/dhcp6/json_config_parser.cc

@@ -31,6 +31,7 @@
 #include <dhcpsrv/parsers/ifaces_config_parser.h>
 #include <dhcpsrv/parsers/option_data_parser.h>
 #include <dhcpsrv/parsers/simple_parser6.h>
+#include <dhcpsrv/parsers/shared_networks_list_parser.h>
 #include <hooks/hooks_parser.h>
 #include <log/logger_support.h>
 #include <util/encode/hex.h>
@@ -82,7 +83,7 @@ public:
     ///
     /// @param value pointer to the content of parsed values
     /// @param cfg server configuration (RSOO will be stored here)
-    void parse(SrvConfigPtr cfg, isc::data::ConstElementPtr value) {
+    void parse(const SrvConfigPtr& cfg, const isc::data::ConstElementPtr& value) {
         try {
             BOOST_FOREACH(ConstElementPtr source_elem, value->listValue()) {
                 std::string option_str = source_elem->stringValue();
@@ -129,7 +130,12 @@ public:
     }
 };
 
-/// @brief Parser that takes care of global DHCPv6 parameters.
+/// @brief Parser that takes care of global DHCPv6 parameters and utility
+///        functions that work on global level.
+///
+/// This class is a collection of utility method that either handle
+/// global parameters (see @ref parse), or conducts operations on
+/// global level (see @ref sanityChecks and @ref copySubnets6).
 ///
 /// See @ref parse method for a list of supported parameters.
 class Dhcp6ConfigParser : public isc::data::SimpleParser {
@@ -147,7 +153,7 @@ public:
     ///
     /// @throw DhcpConfigError if parameters are missing or
     /// or having incorrect values.
-    void parse(SrvConfigPtr srv_config, ConstElementPtr global) {
+    void parse(const SrvConfigPtr& srv_config, const ConstElementPtr& global) {
 
         // Set the probation period for decline handling.
         uint32_t probation_period =
@@ -158,6 +164,134 @@ public:
         uint16_t dhcp4o6_port = getUint16(global, "dhcp4o6-port");
         srv_config->setDhcp4o6Port(dhcp4o6_port);
     }
+
+    /// @brief Copies subnets from shared networks to regular subnets container
+    ///
+    /// @param from pointer to shared networks container (copy from here)
+    /// @param dest pointer to cfg subnets6 (copy to here)
+    /// @throw BadValue if any pointer is missing
+    /// @throw DhcpConfigError if there are duplicates (or other subnet defects)
+    void
+    copySubnets6(const CfgSubnets6Ptr& dest, const CfgSharedNetworks6Ptr& from) {
+
+        if (!dest || !from) {
+            isc_throw(BadValue, "Unable to copy subnets: at least one pointer is null");
+        }
+
+        const SharedNetwork6Collection* networks = from->getAll();
+        if (!networks) {
+            // Nothing to copy. Technically, it should return a pointer to empty
+            // container, but let's handle null pointer as well.
+            return;
+        }
+
+        // Let's go through all the networks one by one
+        for (auto net = networks->begin(); net != networks->end(); ++net) {
+
+            // For each network go through all the subnets in it.
+            const Subnet6Collection* subnets = (*net)->getAllSubnets();
+            if (!subnets) {
+                // Shared network without subnets it weird, but we decided to
+                // accept such configurations.
+                continue;
+            }
+
+            // For each subnet, add it to a list of regular subnets.
+            for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                dest->add(*subnet);
+            }
+        }
+    }
+
+    /// @brief Conducts global sanity checks
+    ///
+    /// This method is very simple now, but more sanity checks are expected
+    /// in the future.
+    ///
+    /// @param cfg - the parsed structure
+    /// @param global global Dhcp4 scope
+    /// @throw DhcpConfigError in case of issues found
+    void
+    sanityChecks(const SrvConfigPtr& cfg, const ConstElementPtr& global) {
+
+        /// Shared network sanity checks
+        const SharedNetwork6Collection* networks = cfg->getCfgSharedNetworks6()->getAll();
+        if (networks) {
+            sharedNetworksSanityChecks(*networks, global->get("shared-networks"));
+        }
+    }
+
+    /// @brief Sanity checks for shared networks
+    ///
+    /// This method verifies if there are no issues with shared networks.
+    /// @param networks pointer to shared networks being checked
+    /// @param json shared-networks element
+    /// @throw DhcpConfigError if issues are encountered
+    void
+    sharedNetworksSanityChecks(const SharedNetwork6Collection& networks,
+                               ConstElementPtr json) {
+
+        /// @todo: in case of errors, use json to extract line numbers.
+        if (!json) {
+            // No json? That means that the shared-networks was never specified
+            // in the config.
+            return;
+        }
+
+        // Used for names uniqueness checks.
+        std::set<string> names;
+
+        // Let's go through all the networks one by one
+        for (auto net = networks.begin(); net != networks.end(); ++net) {
+            string txt;
+
+            // Let's check if all subnets have either the same interface
+            // or don't have the interface specified at all.
+            string iface = (*net)->getIface();
+
+            const Subnet6Collection* subnets = (*net)->getAllSubnets();
+            if (subnets) {
+                // For each subnet, add it to a list of regular subnets.
+                for (auto subnet = subnets->begin(); subnet != subnets->end(); ++subnet) {
+                    if (iface.empty()) {
+                        iface = (*subnet)->getIface();
+                        continue;
+                    }
+
+                    if ((*subnet)->getIface().empty()) {
+                        continue;
+                    }
+
+                    if (iface != (*subnet)->getIface()) {
+                        isc_throw(DhcpConfigError, "Subnet " << (*subnet)->toText()
+                                  << " has specified interface " << (*subnet)->getIface()
+                                  << ", but earlier subnet in the same shared-network"
+                                  << " or the shared-network itself used " << iface);
+                    }
+
+                    // Let's collect the subnets in case we later find out the
+                    // subnet doesn't have a mandatory name.
+                    txt += (*subnet)->toText() + " ";
+                }
+            }
+
+            // Next, let's check name of the shared network.
+            if ((*net)->getName().empty()) {
+                isc_throw(DhcpConfigError, "Shared-network with subnets "
+                          << txt << " is missing mandatory 'name' parameter");
+            }
+
+            // Is it unique?
+            if (names.find((*net)->getName()) != names.end()) {
+                isc_throw(DhcpConfigError, "A shared-network with "
+                          "name " << (*net)->getName() << " defined twice.");
+            }
+            names.insert((*net)->getName());
+
+        }
+    }
+    
+    
 };
 
 } // anonymous namespace
@@ -270,6 +404,10 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
             parser.parse(cfg_option_def, option_defs);
         }
 
+        // This parser is used in several places, so it should be available
+        // early.
+        Dhcp6ConfigParser global_parser;
+
         BOOST_FOREACH(config_pair, values_map) {
             // In principle we could have the following code structured as a series
             // of long if else if clauses. That would give a marginal performance
@@ -375,19 +513,26 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
             }
 
             if (config_pair.first == "subnet6") {
-                SrvConfigPtr srv_cfg = CfgMgr::instance().getStagingCfg();
                 Subnets6ListConfigParser subnets_parser;
                 // parse() returns number of subnets parsed. We may log it one day.
-                subnets_parser.parse(srv_cfg, config_pair.second);
+                subnets_parser.parse(srv_config, config_pair.second);
                 continue;
             }
 
             if (config_pair.first == "shared-networks") {
-                /// @todo We need to create instance of SharedNetworks4ListParser
+                /// We need to create instance of SharedNetworks4ListParser
                 /// and parse the list of the shared networks into the
                 /// CfgSharedNetworks4 object. One additional step is then to
                 /// add subnets from the CfgSharedNetworks6 into CfgSubnets6
                 /// as well.
+
+                SharedNetworks6ListParser parser;
+                CfgSharedNetworks6Ptr cfg = srv_config->getCfgSharedNetworks6();
+                parser.parse(cfg, config_pair.second);
+
+                // We also need to put the subnets it contains into normal
+                // subnets list.
+                global_parser.copySubnets6(srv_config->getCfgSubnets6(), cfg);
                 continue;
             }
 
@@ -417,9 +562,13 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
         }
 
         // Apply global options in the staging config.
-        Dhcp6ConfigParser global_parser;
         global_parser.parse(srv_config, mutable_cfg);
 
+        // This method conducts final sanity checks and tweaks. In particular,
+        // it checks that there is no conflict between plain subnets and those
+        // defined as part of shared networks.
+        global_parser.sanityChecks(srv_config, mutable_cfg);
+        
     } catch (const isc::Exception& ex) {
         LOG_ERROR(dhcp6_logger, DHCP6_PARSER_FAIL)
                   .arg(config_pair.first).arg(ex.what());

+ 3 - 1
src/bin/dhcp6/parser_context.cc

@@ -194,7 +194,9 @@ Parser6Context::contextName()
         return ("ncr-format");
     case REPLACE_CLIENT_NAME:
         return ("replace-client-name");
-    default:
+    case SHARED_NETWORK:
+        return ("shared-networks");
+     default:
         return ("__unknown__");
     }
 }

+ 3 - 0
src/bin/dhcp6/parser_context.h

@@ -235,6 +235,9 @@ public:
         /// Used while parsing Dhcp6/Subnet6 structures.
         SUBNET6,
 
+        /// Used while parsing shared-networks structures.
+        SHARED_NETWORK,
+
         /// Used while parsing Dhcp6/Subnet6/reservation-mode.
         RESERVATION_MODE,
 

+ 474 - 4
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -280,6 +280,68 @@ public:
         }
     }
 
+    /// @brief Convenience method for running configuration
+    ///
+    /// This method does not throw, but signals errors using gtest macros.
+    ///
+    /// @param config text to be parsed as JSON
+    /// @param expected_code expected code (see cc/command_interpreter.h)
+    /// @param exp_error expected text error (check skipped if empty)
+    void configure(std::string config, int expected_code,
+                   std::string exp_error = "") {
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP6(config, true));
+
+        ConstElementPtr status;
+        EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+        ASSERT_TRUE(status);
+
+        int rcode;
+        ConstElementPtr comment = parseAnswer(rcode, status);
+        EXPECT_EQ(expected_code, rcode);
+
+        string text;
+        ASSERT_NO_THROW(text = comment->stringValue());
+
+        if (expected_code != rcode) {
+            std::cout << "Reported status: " << text << std::endl;
+        }
+
+        if ((rcode != 0)) {
+            if (!exp_error.empty()) {
+                EXPECT_EQ(exp_error, text);
+            }
+        }
+    }
+
+    /// @brief Checks if specified subnet is part of the collection
+    ///
+    /// @param col collection of subnets to be inspected
+    /// @param subnet text notation (e.g. 192.0.2.0/24)
+    /// @param t1 expected renew-timer value
+    /// @param t2 expected rebind-timer value
+    /// @param preferred expected preferred-lifetime value
+    /// @param valid expected valid-lifetime value
+    /// @return the subnet that was examined
+    Subnet6Ptr
+    checkSubnet(const Subnet6Collection& col, std::string subnet,
+                uint32_t t1, uint32_t t2, uint32_t pref, uint32_t valid) {
+        const auto& index = col.get<SubnetPrefixIndexTag>();
+        auto subnet_it = index.find(subnet);
+        if (subnet_it == index.cend()) {
+            ADD_FAILURE() << "Unable to find expected subnet " << subnet;
+            return (Subnet6Ptr());
+        }
+        Subnet6Ptr s = *subnet_it;
+
+        EXPECT_EQ(t1, s->getT1());
+        EXPECT_EQ(t2, s->getT2());
+        EXPECT_EQ(pref, s->getPreferred());
+        EXPECT_EQ(valid, s->getValid());
+
+        return (s);
+    }
+
     /// @brief Returns an interface configuration used by the most of the
     /// unit tests.
     std::string genIfaceConfig() const {
@@ -807,7 +869,7 @@ TEST_F(Dhcp6ParserTest, emptySubnet) {
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
-    
+
 
     // returned value should be 0 (success)
     checkResult(status, 0);
@@ -5065,9 +5127,9 @@ TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
         "       \"bogus\": \"bad\" \n"
         "   } \n"
         "], \n"
-        "\"subnet4\": [ {  \n"
-        "    \"pools\": [ { \"pool\":  \"192.0.2.1 - 192.0.2.100\" } ], \n"
-        "    \"subnet\": \"192.0.2.0/24\"  \n"
+        "\"subnet6\": [ {  \n"
+        "    \"pools\": [ { \"pool\":  \"2001:db8::1 - 2001:db8::ffff\" } ], \n"
+        "    \"subnet\": \"2001:db8::/64\"  \n"
         " } ] \n"
         "} \n";
 
@@ -5308,4 +5370,412 @@ TEST_F(Dhcp6ParserTest, outsideSubnetPool) {
     EXPECT_EQ(expected, text);
 }
 
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmpty) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+}
+
+// Test verifies that if a shared network is defined, it at least has to have
+// a name.
+TEST_F(Dhcp6ParserTest, sharedNetworksNoName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { } ]\n"
+        "} \n";
+
+    EXPECT_THROW(parseDHCP6(config, true), Dhcp6ParseError);
+}
+
+// Test verifies that empty shared networks are accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksEmptyName) {
+    string config = "{\n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"renew-timer\": 1000, \n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\"  \n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_ERROR,
+              "Shared-network with subnets  is missing mandatory 'name' parameter");
+}
+
+// Test verifies that a degenerated shared-network (no subnets) is
+// accepted.
+TEST_F(Dhcp6ParserTest, sharedNetworksName) {
+    string config = "{\n"
+        "\"subnet6\": [ {  \n"
+        "    \"subnet\": \"2001:db8::/48\",\n"
+        "    \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+        " } ],\n"
+        "\"shared-networks\": [ { \"name\": \"foo\" } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // Verify that there are no subnets in this shared-network
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(0, subs->size());
+}
+
+// Test verifies that a degenerated shared-network (just one subnet) is
+// accepted. Also tests that, unless explicitly specified, the subnet
+// gets default values.
+TEST_F(Dhcp6ParserTest, sharedNetworks1subnet) {
+    string config = "{\n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet6\": [ { \n"
+        "        \"subnet\": \"2001:db8::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db8::1 - 2001:db8::ffff\" } ]\n"
+        "    } ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+    ASSERT_TRUE(cfg_net);
+
+    // There should be exactly one shared subnet.
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+    EXPECT_EQ("foo", net->getName());
+
+    // It should have one subnet. The subnet should have default values.
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+    checkSubnet(*subs, "2001:db8::/48", 900, 1800, 3600, 7200);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+    ASSERT_TRUE(subnets6);
+
+    subs = subnets6->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "2001:db8::/48", 900, 1800, 3600, 7200);
+}
+
+// Test verifies that a proper shared-network (three subnets) is
+// accepted. It verifies several things:
+// - that more than one subnet can be added to shared subnets
+// - that each subnet being part of the shared subnets is also stored in
+//   global subnets collection
+// - that a subnet can inherit global values
+// - that subnet can override global parameters
+// - that overridden parameters only affect one subnet and not others
+TEST_F(Dhcp6ParserTest, sharedNetworks3subnets) {
+    string config = "{\n"
+        "\"renew-timer\": 1000, \n"
+        "\"rebind-timer\": 2000, \n"
+        "\"preferred-lifetime\": 3000, \n"
+        "\"valid-lifetime\": 4000, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"subnet6\": [\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db1::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db2::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+        "        \"renew-timer\": 2,\n"
+        "        \"rebind-timer\": 22,\n"
+        "        \"preferred-lifetime\": 222,\n"
+        "        \"valid-lifetime\": 2222\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db3::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+        "    }\n"
+        "    ]\n"
+        " } ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+
+    // There is expected one shared subnet.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(1, nets->size());
+
+    SharedNetwork6Ptr net = *(nets->begin());
+    ASSERT_TRUE(net);
+
+    EXPECT_EQ("foo", net->getName());
+
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(3, subs->size());
+    checkSubnet(*subs, "2001:db1::/48", 1000, 2000, 3000, 4000);
+    checkSubnet(*subs, "2001:db2::/48", 2, 22, 222, 2222);
+    checkSubnet(*subs, "2001:db3::/48", 1000, 2000, 3000, 4000);
+
+    // Now make sure the subnet was added to global list of subnets.
+    CfgSubnets6Ptr subnets6 = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+    ASSERT_TRUE(subnets6);
+
+    subs = subnets6->getAll();
+    ASSERT_TRUE(subs);
+    checkSubnet(*subs, "2001:db1::/48", 1000, 2000, 3000, 4000);
+    checkSubnet(*subs, "2001:db2::/48", 2, 22, 222, 2222);
+    checkSubnet(*subs, "2001:db3::/48", 1000, 2000, 3000, 4000);
+}
+
+// This test checks if parameters are derived properly:
+// - global to shared network
+// - shared network to subnet
+// Also, it tests that more than one shared network can be defined.
+TEST_F(Dhcp6ParserTest, sharedNetworksDerive) {
+
+    // We need to fake the interfaces present, because we want to test
+    // interface names inheritance. However, there are sanity checks
+    // on subnet level that would refuse the value if the interface
+    // is not present.
+    IfaceMgrTestConfig iface_config(true);
+
+    // Build some expected interface-id values.
+    const string text1 = "oneone";
+    const string text2 = "twotwo";
+    OptionBuffer buffer1 = OptionBuffer(text1.begin(), text1.end());
+    OptionBuffer buffer2 = OptionBuffer(text2.begin(), text2.end());
+    Option iface_id1(Option::V6, D6O_INTERFACE_ID, buffer1);
+    Option iface_id2(Option::V6, D6O_INTERFACE_ID, buffer2);
+
+    string config = "{\n"
+        "\"renew-timer\": 1, \n"
+        "\"rebind-timer\": 2, \n"
+        "\"preferred-lifetime\": 3,\n"
+        "\"valid-lifetime\": 4, \n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"renew-timer\": 10,\n"
+        "    \"rebind-timer\": 20, \n"
+        "    \"preferred-lifetime\": 30,\n"
+        "    \"valid-lifetime\": 40, \n"
+        "    \"interface-id\": \"oneone\",\n"
+        "    \"relay\": {\n"
+        "        \"ip-address\": \"1111::1\"\n"
+        "    },\n"
+        "    \"rapid-commit\": true,\n"
+        "    \"reservation-mode\": \"disabled\",\n"
+        "    \"subnet6\": [\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db1::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db2::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+        "        \"renew-timer\": 100\n,"
+        "        \"rebind-timer\": 200, \n"
+        "        \"preferred-lifetime\": 300,\n"
+        "        \"relay\": {\n"
+        "            \"ip-address\": \"2222::2\"\n"
+        "        },\n"
+        "        \"valid-lifetime\": 400, \n"
+        "        \"interface-id\": \"twotwo\",\n"
+        "        \"rapid-commit\": false,\n"
+        "        \"reservation-mode\": \"out-of-pool\"\n"
+        "    }\n"
+        "    ]\n"
+        " },\n"
+        "{ // second shared-network starts here\n"
+        "    \"name\": \"bar\",\n"
+        "    \"subnet6\": [\n"
+        "    {\n"
+        "        \"subnet\": \"2001:db3::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+        "    }\n"
+        "    ]\n"
+        "} ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+
+    // Two shared networks are expeced.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(2, nets->size());
+
+    // Let's check the first one.
+    SharedNetwork6Ptr net = nets->at(0);
+    ASSERT_TRUE(net);
+
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(2, subs->size());
+
+    // For the first subnet, the renew-timer should be 10, because it was
+    // derived from shared-network level. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 10, 20, 30, 40);
+    ASSERT_TRUE(s);
+    ASSERT_TRUE(s->getInterfaceId());
+    EXPECT_TRUE(iface_id1.equals(s->getInterfaceId()));
+    EXPECT_EQ(IOAddress("1111::1"), s->getRelayInfo().addr_);
+    EXPECT_EQ(true, s->getRapidCommit());
+    EXPECT_EQ(Network::HR_DISABLED, s->getHostReservationMode());
+
+    // For the second subnet, the renew-timer should be 100, because it
+    // was specified explicitly. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    s = checkSubnet(*subs, "2001:db2::/48", 100, 200, 300, 400);
+    ASSERT_TRUE(s->getInterfaceId());
+    EXPECT_TRUE(iface_id2.equals(s->getInterfaceId()));
+    EXPECT_EQ(IOAddress("2222::2"), s->getRelayInfo().addr_);
+    EXPECT_EQ(false, s->getRapidCommit());
+    EXPECT_EQ(Network::HR_OUT_OF_POOL, s->getHostReservationMode());
+
+    // Ok, now check the second shared subnet.
+    net = nets->at(1);
+    ASSERT_TRUE(net);
+
+    subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+
+    // This subnet should derive its renew-timer from global scope.
+    s = checkSubnet(*subs, "2001:db3::/48", 1, 2, 3, 4);
+    EXPECT_FALSE(s->getInterfaceId());
+    EXPECT_EQ(IOAddress("::"), s->getRelayInfo().addr_);
+    EXPECT_EQ(false, s->getRapidCommit());
+    EXPECT_EQ(Network::HR_ALL, s->getHostReservationMode());
+}
+
+// Since it is not allowed to define both interface-id and interface
+// for the same subnet, we need dedicated test that will check
+// interface separately.
+TEST_F(Dhcp6ParserTest, sharedNetworksDeriveInterfaces) {
+
+    // We need to fake the interfaces present, because we want to test
+    // interface names inheritance. However, there are sanity checks
+    // on subnet level that would refuse the value if the interface
+    // is not present.
+    IfaceMgrTestConfig iface_config(true);
+
+    string config = "{\n"
+        "\"shared-networks\": [ {\n"
+        "    \"name\": \"foo\"\n,"
+        "    \"interface\": \"eth0\",\n"
+        "    \"subnet6\": [\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db1::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db1::/64\" } ]\n"
+        "    },\n"
+        "    { \n"
+        "        \"subnet\": \"2001:db2::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db2::/64\" } ],\n"
+        "        \"interface\": \"eth0\"\n"
+        "    }\n"
+        "    ]\n"
+        " },\n"
+        "{ // second shared-network starts here\n"
+        "    \"name\": \"bar\",\n"
+        "    \"subnet6\": [\n"
+        "    {\n"
+        "        \"subnet\": \"2001:db3::/48\",\n"
+        "        \"pools\": [ { \"pool\": \"2001:db3::/64\" } ]\n"
+        "    }\n"
+        "    ]\n"
+        "} ]\n"
+        "} \n";
+
+    configure(config, CONTROL_RESULT_SUCCESS, "");
+
+    // Now verify that the shared network was indeed configured.
+    CfgSharedNetworks6Ptr cfg_net = CfgMgr::instance().getStagingCfg()
+        ->getCfgSharedNetworks6();
+
+    // Two shared networks are expeced.
+    ASSERT_TRUE(cfg_net);
+    const SharedNetwork6Collection* nets = cfg_net->getAll();
+    ASSERT_TRUE(nets);
+    ASSERT_EQ(2, nets->size());
+
+    // Let's check the first one.
+    SharedNetwork6Ptr net = nets->at(0);
+    ASSERT_TRUE(net);
+
+    const Subnet6Collection * subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(2, subs->size());
+
+    // For the first subnet, the renew-timer should be 10, because it was
+    // derived from shared-network level. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 900, 1800, 3600, 7200);
+    ASSERT_TRUE(s);
+    EXPECT_EQ("eth0", s->getIface());
+
+    // For the second subnet, the renew-timer should be 100, because it
+    // was specified explicitly. Other parameters a derived
+    // from global scope to shared-network level and later again to
+    // subnet6 level.
+    checkSubnet(*subs, "2001:db2::/48", 900, 1800, 3600, 7200);
+    EXPECT_EQ("eth0", s->getIface());
+
+    // Ok, now check the second shared subnet.
+    net = nets->at(1);
+    ASSERT_TRUE(net);
+
+    subs = net->getAllSubnets();
+    ASSERT_TRUE(subs);
+    EXPECT_EQ(1, subs->size());
+
+    // This subnet should derive its renew-timer from global scope.
+    s = checkSubnet(*subs, "2001:db3::/48", 900, 1800, 3600, 7200);
+    EXPECT_EQ("", s->getIface());
+}
+
 };

+ 1 - 1
src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc

@@ -1349,7 +1349,7 @@ TEST_F(CtrlChannelDhcpv6SrvTest, longResponse) {
         while (response.tellp() < long_response_size) {
             std::string partial;
             const unsigned int timeout = 5;
-            ASSERT_TRUE(client->getResponse(partial, 5));
+            ASSERT_TRUE(client->getResponse(partial, timeout));
             response << partial;
         }
 

+ 52 - 8
src/bin/dhcp6/tests/get_config_unittest.cc

@@ -11,12 +11,12 @@
 #include <cc/simple_parser.h>
 #include <cc/cfg_to_element.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
-#include <dhcpsrv/parsers/simple_parser6.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp6/tests/dhcp6_test_utils.h>
 #include <dhcp6/tests/get_config_unittest.h>
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcp6/json_config_parser.h>
+#include <dhcpsrv/parsers/simple_parser6.h>
 
 #include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
@@ -65,7 +65,6 @@ namespace {
 ///@{
 /// @brief extracted configurations
 const char* EXTRACTED_CONFIGS[] = {
-/// put this after const char* EXTRACTED_CONFIGS[] = {
     // CONFIGURATION 0
 "{\n"
 "        \"interfaces-config\": {\n"
@@ -1514,6 +1513,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 1
@@ -1565,6 +1565,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -1639,6 +1640,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -1779,6 +1781,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1024,\n"
@@ -1919,6 +1922,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2059,6 +2063,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2133,6 +2138,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2208,6 +2214,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2283,6 +2290,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2387,6 +2395,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2461,6 +2470,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2537,6 +2547,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2615,6 +2626,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2708,6 +2720,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -2794,6 +2807,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 15
@@ -2855,6 +2869,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 16
@@ -2925,6 +2940,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 17
@@ -2986,6 +3002,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 18
@@ -3047,6 +3064,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 19
@@ -3115,6 +3133,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3189,6 +3208,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3307,6 +3327,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3417,6 +3438,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 23
@@ -3521,6 +3543,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3595,6 +3618,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3709,6 +3733,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3851,6 +3876,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -3944,6 +3970,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -4018,6 +4045,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 29
@@ -4069,6 +4097,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 30
@@ -4120,6 +4149,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -4194,6 +4224,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"client-class\": \"alpha\",\n"
@@ -4337,6 +4368,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -4411,6 +4443,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 123,\n"
@@ -4614,6 +4647,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 234,\n"
@@ -4701,6 +4735,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 36
@@ -4752,6 +4787,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 37
@@ -4803,6 +4839,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -4943,6 +4980,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 39
@@ -4994,6 +5032,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 40
@@ -5045,6 +5084,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 41
@@ -5096,6 +5136,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 42
@@ -5147,6 +5188,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [ ]\n"
 "    }\n",
     // CONFIGURATION 43
@@ -5212,6 +5254,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5286,6 +5329,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5360,6 +5404,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5435,6 +5480,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5515,6 +5561,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5595,6 +5642,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5671,6 +5719,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -5748,6 +5797,7 @@ const char* UNPARSED_CONFIGS[] = {
 "            \"time\": 0,\n"
 "            \"type\": \"LLT\"\n"
 "        },\n"
+"        \"shared-networks\": [ ],\n"
 "        \"subnet6\": [\n"
 "            {\n"
 "                \"id\": 1,\n"
@@ -6051,14 +6101,8 @@ TEST_P(Dhcp6GetConfigTest, run) {
     EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
 }
 
-#if 0
-// This test is temporarily disabled. The shared subnets structures have been
-// implemented (#5305), but the parsers are not there yet, so grammar will fail
-// when parseDHCP4 is called. That's comping up in #5357.
-
 /// Define the parameterized test loop
 INSTANTIATE_TEST_CASE_P(Dhcp6GetConfigTest, Dhcp6GetConfigTest,
                         ::testing::Range(static_cast<size_t>(0), max_config_counter));
-#endif
 
 };

+ 1 - 0
src/bin/dhcp6/tests/parser_unittest.cc

@@ -256,6 +256,7 @@ TEST(ParserTest, file) {
     configs.push_back("pgsql-reservations.json");
     configs.push_back("reservations.json");
     configs.push_back("several-subnets.json");
+    configs.push_back("shared-network.json");
     configs.push_back("simple.json");
     configs.push_back("stateless.json");
     configs.push_back("with-ddns.json");

+ 79 - 7
src/lib/dhcpsrv/parsers/simple_parser4.cc

@@ -7,6 +7,7 @@
 #include <dhcpsrv/parsers/simple_parser4.h>
 #include <cc/data.h>
 #include <boost/foreach.hpp>
+#include <iostream>
 
 using namespace isc::data;
 
@@ -66,6 +67,12 @@ const SimpleDefaults SimpleParser4::GLOBAL4_DEFAULTS = {
 };
 
 /// @brief This table defines default values for each IPv4 subnet.
+///
+/// Note: When updating this array, please also update SHARED_SUBNET4_DEFAULTS
+/// below. In most cases, those two should be kept in sync, except cases
+/// where a parameter can be derived from shared-networks, but is not
+/// defined on global level. Currently there are two such parameters:
+/// interface and reservation-mode
 const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
     { "id",               Element::integer, "0" }, // 0 means autogenerate
     { "interface",        Element::string,  "" },
@@ -76,24 +83,51 @@ const SimpleDefaults SimpleParser4::SUBNET4_DEFAULTS = {
     { "4o6-subnet",       Element::string,  "" },
 };
 
+/// @brief This table defines default values for each IPv4 subnet that is
+///        part of a shared network
+///
+/// This is mostly the same as @ref SUBNET4_DEFAULTS, except two parameters
+/// that can be derived from shared-network, but cannot from global scope.
+/// Those are: interface and reservation-mode.
+const SimpleDefaults SimpleParser4::SHARED_SUBNET4_DEFAULTS = {
+    { "id",               Element::integer, "0" }, // 0 means autogenerate
+    { "client-class",     Element::string,  "" },
+    { "4o6-interface",    Element::string,  "" },
+    { "4o6-interface-id", Element::string,  "" },
+    { "4o6-subnet",       Element::string,  "" },
+};
+
+/// @brief This table defines default values for each IPv4 shared network.
+const SimpleDefaults SimpleParser4::SHARED_NETWORK4_DEFAULTS = {
+    { "interface",        Element::string, "" },
+    { "reservation-mode", Element::string, "all" }
+};
+
 /// @brief This table defines default values for interfaces for DHCPv4.
 const SimpleDefaults SimpleParser4::IFACE4_DEFAULTS = {
     { "re-detect", Element::boolean, "true" }
 };
 
-/// @brief List of parameters that can be inherited from the global to subnet4 scope.
+/// @brief List of parameters that can be inherited to subnet4 scope.
 ///
 /// Some parameters may be defined on both global (directly in Dhcp4) and
 /// subnet (Dhcp4/subnet4/...) scope. If not defined in the subnet scope,
 /// the value is being inherited (derived) from the global scope. This
 /// array lists all of such parameters.
-const ParamsList SimpleParser4::INHERIT_GLOBAL_TO_SUBNET4 = {
-    "renew-timer",
-    "rebind-timer",
-    "valid-lifetime",
+///
+/// This list is also used for inheriting from global to shared networks
+/// and from shared networks to subnets within it.
+const ParamsList SimpleParser4::INHERIT_TO_SUBNET4 = {
+    "interface",
     "match-client-id",
-    "next-server"
+    "next-server",
+    "rebind-timer",
+    "relay",
+    "renew-timer",
+    "reservation-mode",
+    "valid-lifetime"
 };
+
 /// @}
 
 /// ---------------------------------------------------------------------------
@@ -133,6 +167,20 @@ size_t SimpleParser4::setAllDefaults(isc::data::ElementPtr global) {
         cnt += setDefaults(mutable_cfg, IFACE4_DEFAULTS);
     }
 
+    // Set defaults for shared networks
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+
+            cnt += setDefaults(net, SHARED_NETWORK4_DEFAULTS);
+
+            ConstElementPtr subs = net->get("subnet4");
+            if (subs) {
+                cnt += setListDefaults(subs, SHARED_SUBNET4_DEFAULTS);
+            }
+        }
+    }
+
     return (cnt);
 }
 
@@ -144,7 +192,31 @@ size_t SimpleParser4::deriveParameters(isc::data::ElementPtr global) {
     if (subnets) {
         BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
             cnt += SimpleParser::deriveParams(global, single_subnet,
-                                              INHERIT_GLOBAL_TO_SUBNET4);
+                                              INHERIT_TO_SUBNET4);
+        }
+    }
+
+    // Deriving parameters for shared networks is a bit more involved.
+    // First, the shared-network level derives from global, and then
+    // subnets within derive from it.
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+            // First try to inherit the parameters from shared network,
+            // if defined there.
+            // Then try to inherit them from global.
+            cnt += SimpleParser::deriveParams(global, net,
+                                              INHERIT_TO_SUBNET4);
+
+            // Now we need to go thrugh all the subnets in this net.
+            subnets = net->get("subnet4");
+            if (subnets) {
+                BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+                    cnt += SimpleParser::deriveParams(net, single_subnet,
+                                                      INHERIT_TO_SUBNET4);
+                }
+            }
+
         }
     }
 

+ 3 - 1
src/lib/dhcpsrv/parsers/simple_parser4.h

@@ -41,8 +41,10 @@ public:
     static const isc::data::SimpleDefaults OPTION4_DEFAULTS;
     static const isc::data::SimpleDefaults GLOBAL4_DEFAULTS;
     static const isc::data::SimpleDefaults SUBNET4_DEFAULTS;
+    static const isc::data::SimpleDefaults SHARED_SUBNET4_DEFAULTS;
+    static const isc::data::SimpleDefaults SHARED_NETWORK4_DEFAULTS;
     static const isc::data::SimpleDefaults IFACE4_DEFAULTS;
-    static const isc::data::ParamsList INHERIT_GLOBAL_TO_SUBNET4;
+    static const isc::data::ParamsList INHERIT_TO_SUBNET4;
 };
 
 };

+ 64 - 5
src/lib/dhcpsrv/parsers/simple_parser6.cc

@@ -73,6 +73,21 @@ const SimpleDefaults SimpleParser6::SUBNET6_DEFAULTS = {
     { "interface-id",     Element::string,  "" },
 };
 
+/// @brief This table defines default values for each IPv6 subnet.
+const SimpleDefaults SimpleParser6::SHARED_SUBNET6_DEFAULTS = {
+    { "id",               Element::integer, "0" }, // 0 means autogenerate
+    { "client-class",     Element::string,  "" }
+};
+
+/// @brief This table defines default values for each IPv6 shared network.
+const SimpleDefaults SimpleParser6::SHARED_NETWORK6_DEFAULTS = {
+    { "interface",        Element::string, "" },
+    { "interface-id",     Element::string,  "" },
+    { "reservation-mode", Element::string, "all" },
+    { "rapid-commit",     Element::boolean, "false" } // rapid-commit disabled by default
+};
+
+
 /// @brief This table defines default values for interfaces for DHCPv6.
 const SimpleDefaults SimpleParser6::IFACE6_DEFAULTS = {
     { "re-detect", Element::boolean, "true" }
@@ -84,10 +99,18 @@ const SimpleDefaults SimpleParser6::IFACE6_DEFAULTS = {
 /// subnet (Dhcp6/subnet6/...) scope. If not defined in the subnet scope,
 /// the value is being inherited (derived) from the global scope. This
 /// array lists all of such parameters.
-const ParamsList SimpleParser6::INHERIT_GLOBAL_TO_SUBNET6 = {
-    "renew-timer",
-    "rebind-timer",
+///
+/// This list is also used for inheriting from global to shared networks
+/// and from shared networks to subnets within it.
+const ParamsList SimpleParser6::INHERIT_TO_SUBNET6 = {
+    "interface",
+    "interface-id",
     "preferred-lifetime",
+    "rapid-commit",
+    "rebind-timer",
+    "relay",
+    "renew-timer",
+    "reservation-mode",
     "valid-lifetime"
 };
 /// @}
@@ -119,7 +142,6 @@ size_t SimpleParser6::setAllDefaults(isc::data::ElementPtr global) {
     }
 
     // Now set the defaults for defined subnets
-    // Now set the defaults for defined subnets
     ConstElementPtr subnets = global->get("subnet6");
     if (subnets) {
         cnt += setListDefaults(subnets, SUBNET6_DEFAULTS);
@@ -132,6 +154,20 @@ size_t SimpleParser6::setAllDefaults(isc::data::ElementPtr global) {
         cnt += setDefaults(mutable_cfg, IFACE6_DEFAULTS);
     }
 
+    // Set defaults for shared networks
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+
+            cnt += setDefaults(net, SHARED_NETWORK6_DEFAULTS);
+
+            ConstElementPtr subs = net->get("subnet6");
+            if (subs) {
+                cnt += setListDefaults(subs, SHARED_SUBNET6_DEFAULTS);
+            }
+        }
+    }
+
     return (cnt);
 }
 
@@ -142,7 +178,30 @@ size_t SimpleParser6::deriveParameters(isc::data::ElementPtr global) {
     if (subnets) {
         BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
             cnt += SimpleParser::deriveParams(global, single_subnet,
-                                              INHERIT_GLOBAL_TO_SUBNET6);
+                                              INHERIT_TO_SUBNET6);
+        }
+    }
+
+    // Deriving parameters for shared networks is a bit more involved.
+    // First, the shared-network level derives from global, and then
+    // subnets within derive from it.
+    ConstElementPtr shared = global->get("shared-networks");
+    if (shared) {
+        BOOST_FOREACH(ElementPtr net, shared->listValue()) {
+            // First try to inherit the parameters from shared network,
+            // if defined there.
+            // Then try to inherit them from global.
+            cnt += SimpleParser::deriveParams(global, net,
+                                              INHERIT_TO_SUBNET6);
+
+            // Now we need to go thrugh all the subnets in this net.
+            subnets = net->get("subnet6");
+            if (subnets) {
+                BOOST_FOREACH(ElementPtr single_subnet, subnets->listValue()) {
+                    cnt += SimpleParser::deriveParams(net, single_subnet,
+                                                      INHERIT_TO_SUBNET6);
+                }
+            }
         }
     }
 

+ 3 - 1
src/lib/dhcpsrv/parsers/simple_parser6.h

@@ -42,8 +42,10 @@ public:
     static const isc::data::SimpleDefaults OPTION6_DEFAULTS;
     static const isc::data::SimpleDefaults GLOBAL6_DEFAULTS;
     static const isc::data::SimpleDefaults SUBNET6_DEFAULTS;
+    static const isc::data::SimpleDefaults SHARED_SUBNET6_DEFAULTS;
+    static const isc::data::SimpleDefaults SHARED_NETWORK6_DEFAULTS;
     static const isc::data::SimpleDefaults IFACE6_DEFAULTS;
-    static const isc::data::ParamsList INHERIT_GLOBAL_TO_SUBNET6;
+    static const isc::data::ParamsList INHERIT_TO_SUBNET6;
 };
 
 };