// This one is for VoIP devices only.
{
"pool": "192.0.4.1 - 192.0.4.200",
- "client-class": "VoIP"
+ "client-classes": [ "VoIP" ]
},
// This one doesn't have any client-class specified,
"pools": [
{
"pool": "2001:db8:3::/80",
- "client-class": "cable-modems"
+ "client-classes": [ "cable-modems" ]
} ],
"subnet": "2001:db8:4::/64",
"interface": "ethY"
<title>Configuring Pools With Class Information</title>
<para>
Similar to the subnets, it is possible to restrict access to the certain address
- or prefix pools to the clients belonging to a specific class, using
- the "client-class" parameter when defining the pool.
+ or prefix pools to the clients belonging to a specific class or
+ classes, using the "client-classes" parameter when defining the
+ pool. As opposed to subnets, the pools scope allows to define
+ more than one class that is allowed. The incoming packet has to
+ belong to only one of the classes listed to be able to get an
+ address from that pool.
</para>
<para>
"pools": [
{
"pool": "192.0.2.10 - 192.0.2.20",
- "client-class": "Client_foo"
+ "client-classes": [ "Client_foo" ]
}
]</userinput>
},
"pools": [
{
"pool": "2001:db8:1::-2001:db8:1::ffff",
- "client-class": "Client_foo"
+ "client-classes": [ "Client_foo" ]
}
]</userinput>
},
<para>
Client classification can also be used to restrict access to specific
pools within a subnet. This is useful when to segregate clients belonging
- to the same subnet into different address ranges.
+ to the same subnet into different address or prefix ranges.
</para>
<para>
\"client-classes\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::DHCP4:
+ case isc::dhcp::Parser4Context::POOLS:
case isc::dhcp::Parser4Context::RESERVATIONS:
return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASSES(driver.loc_);
default:
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
- case isc::dhcp::Parser4Context::POOLS:
case isc::dhcp::Parser4Context::SHARED_NETWORK:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
return isc::dhcp::Dhcp4Parser::make_CLIENT_CLASS(driver.loc_);
pool_param: pool_entry
| option_data_list
- | client_class
+ | client_class_names_list
| user_context
| comment
| unknown_map_entry
/// @todo probably need to add mac-address as well here
reservation_param: duid
- | reservation_client_classes
+ | client_class_names_list
| client_id_value
| circuit_id_value
| flex_id_value
ctx.leave();
};
-reservation_client_classes: CLIENT_CLASSES {
+client_class_names_list: CLIENT_CLASSES {
ElementPtr c(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("client-classes", c);
ctx.stack_.push_back(c);
"\"subnet4\": [ { "
" \"pools\": [ { "
" \"pool\": \"192.0.2.1 - 192.0.2.100\", "
- " \"client-class\": \"alpha\" "
+ " \"client-classes\": [ \"alpha\" ] "
" },"
" {"
" \"pool\": \"192.0.3.101 - 192.0.3.150\", "
- " \"client-class\": \"beta\" "
+ " \"client-classes\": [ \"beta\" ] "
" },"
" {"
" \"pool\": \"192.0.4.101 - 192.0.4.150\", "
- " \"client-class\": \"gamma\" "
+ " \"client-classes\": [ \"gamma\", \"alpha\" ] "
" },"
" {"
" \"pool\": \"192.0.5.101 - 192.0.5.150\" "
ASSERT_EQ(4, pools.size()); // We expect 4 pools
// Let's check if client belonging to alpha class is supported in pool[0]
- // and not supported in any other pool (except pool[3], which allows
- // everyone).
+ // and pool[2] (which allows 2 classes) and not supported in any other
+ // pool (except pool[3], which allows everyone).
ClientClasses classes;
classes.insert("alpha");
EXPECT_TRUE(pools.at(0)->clientSupported(classes));
EXPECT_FALSE(pools.at(1)->clientSupported(classes));
- EXPECT_FALSE(pools.at(2)->clientSupported(classes));
+ EXPECT_TRUE(pools.at(2)->clientSupported(classes));
EXPECT_TRUE(pools.at(3)->clientSupported(classes));
// Let's check if client belonging to beta class is supported in pool[1]
"\"subnet4\": [ "
"{ \"pools\": [ { "
" \"pool\": \"192.0.2.1 - 192.0.2.100\", "
- " \"client-class\": \"foo\" }, "
+ " \"client-classes\": [ \"foo\" ] }, "
" { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
- " \"client-class\": \"xyzzy\" } ], "
+ " \"client-classes\": [ \"xyzzy\" ] } ], "
" \"subnet\": \"192.0.0.0/16\" } "
"],"
"\"valid-lifetime\": 4000 }";
" {\n"
" \"pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"pool\": \"192.0.2.1 - 192.0.2.100\"\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"pool\": \"192.0.3.101 - 192.0.3.150\"\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"gamma\", \"alpha\" ],\n"
" \"pool\": \"192.0.4.101 - 192.0.4.150\"\n"
" },\n"
" {\n"
" \"option-data\": [ ],\n"
" \"pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.2.1-192.0.2.100\"\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.3.101-192.0.3.150\"\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"alpha\", \"gamma\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.4.101-192.0.4.150\"\n"
" },\n"
" \"pools\": ["
" {"
" \"pool\": \"192.0.2.1 - 192.0.2.63\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"192.0.2.100 - 192.0.2.100\""
" \"pools\": ["
" {"
" \"pool\": \"192.0.2.1 - 192.0.2.63\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"192.0.2.100 - 192.0.2.100\","
- " \"client-class\": \"b-devices\""
+ " \"client-classes\": [ \"b-devices\" ]"
" }"
" ]"
" }"
" \"pools\": ["
" {"
" \"pool\": \"192.0.2.1 - 192.0.2.63\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"192.0.2.100 - 192.0.2.100\""
" \"pools\": ["
" {"
" \"pool\": \"192.0.2.1 - 192.0.2.63\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"192.0.2.100 - 192.0.2.100\","
- " \"client-class\": \"b-devices\""
+ " \"client-classes\": [ \"b-devices\" ]"
" }"
" ]"
" }"
\"client-classes\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::DHCP6:
+ case isc::dhcp::Parser6Context::POOLS:
+ case isc::dhcp::Parser6Context::PD_POOLS:
case isc::dhcp::Parser6Context::RESERVATIONS:
return isc::dhcp::Dhcp6Parser::make_CLIENT_CLASSES(driver.loc_);
default:
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::SUBNET6:
- case isc::dhcp::Parser6Context::POOLS:
- case isc::dhcp::Parser6Context::PD_POOLS:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
case isc::dhcp::Parser6Context::SHARED_NETWORK:
return isc::dhcp::Dhcp6Parser::make_CLIENT_CLASS(driver.loc_);
pool_param: pool_entry
| option_data_list
- | client_class
+ | client_class_names_list
| user_context
| comment
| unknown_map_entry
| pd_prefix_len
| pd_delegated_len
| option_data_list
- | client_class
+ | client_class_names_list
| excluded_prefix
| excluded_prefix_len
| user_context
/// @todo probably need to add mac-address as well here
reservation_param: duid
- | reservation_client_classes
+ | client_class_names_list
| ip_addresses
| prefixes
| hw_address
ctx.leave();
};
-reservation_client_classes: CLIENT_CLASSES {
+client_class_names_list: CLIENT_CLASSES {
ElementPtr c(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("client-classes", c);
ctx.stack_.push_back(c);
ctx.leave();
};
+
// --- end of client classes ---------------------------------
// --- server-id ---------------------------------------------
" { \"pools\": [ "
" { "
" \"pool\": \"2001:db8:1::/64\", "
- " \"client-class\": \"foo\" "
+ " \"client-classes\": [ \"foo\" ] "
" }, "
" { "
" \"pool\": \"2001:db8:2::/64\", "
- " \"client-class\": \"xyzzy\" "
+ " \"client-classes\": [ \"xyzzy\" ] "
" } "
" ], "
" \"subnet\": \"2001:db8:2::/40\" "
"\"subnet6\": [ { "
" \"pools\": [ { "
" \"pool\": \"2001:db8:1::/80\", "
- " \"client-class\": \"alpha\" "
+ " \"client-classes\": [ \"alpha\" ] "
" },"
" {"
" \"pool\": \"2001:db8:2::/80\", "
- " \"client-class\": \"beta\" "
+ " \"client-classes\": [ \"beta\" ] "
" },"
" {"
" \"pool\": \"2001:db8:3::/80\", "
- " \"client-class\": \"gamma\" "
+ " \"client-classes\": [ \"gamma\" ] "
" },"
" {"
" \"pool\": \"2001:db8:4::/80\" "
" \"pd-pools\": [ { "
" \"prefix-len\": 48, "
" \"delegated-len\": 64, "
- " \"prefix\": \"2001:db8:1::\", "
- " \"client-class\": \"alpha\" "
+ " \"prefix\": \"2001:db8:1::\", \n"
+ " \"client-classes\": [ \"alpha\" ]\n "
" },"
" {"
" \"prefix-len\": 48, "
" \"delegated-len\": 64, "
" \"prefix\": \"2001:db8:2::\", "
- " \"client-class\": \"beta\" "
+ " \"client-classes\": [ \"beta\" ]\n "
" },"
" {"
" \"prefix-len\": 48, "
" \"delegated-len\": 64, "
" \"prefix\": \"2001:db8:3::\", "
- " \"client-class\": \"gamma\" "
+ " \"client-classes\": [ \"gamma\" ]\n "
" },"
" {"
" \"prefix-len\": 48, "
" {\n"
" \"pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"pool\": \"2001:db8:1::/80\"\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"pool\": \"2001:db8:2::/80\"\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"gamma\" ],\n"
" \"pool\": \"2001:db8:3::/80\"\n"
" },\n"
" {\n"
" {\n"
" \"pd-pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"delegated-len\": 64,\n"
" \"prefix\": \"2001:db8:1::\",\n"
" \"prefix-len\": 48\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"delegated-len\": 64,\n"
" \"prefix\": \"2001:db8:2::\",\n"
" \"prefix-len\": 48\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"gamma\" ],\n"
" \"delegated-len\": 64,\n"
" \"prefix\": \"2001:db8:3::\",\n"
" \"prefix-len\": 48\n"
" \"pd-pools\": [ ],\n"
" \"pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"2001:db8:1::/80\"\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"2001:db8:2::/80\"\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"gamma\" ],\n"
" \"option-data\": [ ],\n"
" \"pool\": \"2001:db8:3::/80\"\n"
" },\n"
" \"option-data\": [ ],\n"
" \"pd-pools\": [\n"
" {\n"
-" \"client-class\": \"alpha\",\n"
+" \"client-classes\": [ \"alpha\" ],\n"
" \"delegated-len\": 64,\n"
" \"option-data\": [ ],\n"
" \"prefix\": \"2001:db8:1::\",\n"
" \"prefix-len\": 48\n"
" },\n"
" {\n"
-" \"client-class\": \"beta\",\n"
+" \"client-classes\": [ \"beta\" ],\n"
" \"delegated-len\": 64,\n"
" \"option-data\": [ ],\n"
" \"prefix\": \"2001:db8:2::\",\n"
" \"prefix-len\": 48\n"
" },\n"
" {\n"
-" \"client-class\": \"gamma\",\n"
+" \"client-classes\": [ \"gamma\" ],\n"
" \"delegated-len\": 64,\n"
" \"option-data\": [ ],\n"
" \"prefix\": \"2001:db8:3::\",\n"
" \"pools\": ["
" {"
" \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
" \"pools\": ["
" {"
" \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
- " \"client-class\": \"b-devices\""
+ " \"client-classes\": [ \"b-devices\" ]"
" }"
" ]"
" }"
" \"pools\": ["
" {"
" \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\""
" \"pools\": ["
" {"
" \"pool\": \"2001:db8:1::20 - 2001:db8:1::20\","
- " \"client-class\": \"a-devices\""
+ " \"client-classes\": [ \"a-devices\" ]"
" },"
" {"
" \"pool\": \"2001:db8:1::50 - 2001:db8:1::50\","
- " \"client-class\": \"b-devices\""
+ " \"client-classes\": [ \"b-devices\" ]"
" }"
" ]"
" }"
///
/// @param subnet next address will be returned from pool of that subnet
/// @param client_classes list of classes client belongs to
-
/// @param duid Client's DUID
/// @param hint client's hint
///
}
// Client-class.
- ConstElementPtr client_class = pool_structure->get("client-class");
- if (client_class) {
- string cclass = client_class->stringValue();
- if (!cclass.empty()) {
- pool->allowClientClass(cclass);
+ ConstElementPtr class_list = pool_structure->get("client-classes");
+ if (class_list) {
+ BOOST_FOREACH(ConstElementPtr c, class_list->listValue()) {
+ string cclass = c->stringValue();
+ if (!cclass.empty()) {
+ pool->allowClientClass(cclass);
+ }
}
}
}
user_context_ = user_context;
}
- ConstElementPtr client_class = pd_pool_->get("client-class");
+ ConstElementPtr client_class = pd_pool_->get("client-classes");
if (client_class) {
client_class_ = client_class;
}
}
+ // If present, this is a list of class names
if (client_class_) {
- string cclass = client_class_->stringValue();
- if (!cclass.empty()) {
- pool_->allowClientClass(cclass);
+ BOOST_FOREACH(ConstElementPtr c, client_class_->listValue()) {
+ string cclass = c->stringValue();
+ if (!cclass.empty()) {
+ pool_->allowClientClass(cclass);
+ }
}
}
/// A storage for pool specific option values.
CfgOptionPtr options_;
+ /// @brief User context (optional, may be null)
+ ///
+ /// User context is arbitrary user data, to be used by hooks.
isc::data::ConstElementPtr user_context_;
+ /// @brief List of client classes a client has to be belong to to use this pd-pool
+ ///
+ /// If empty, everyone is allowed. This is a white-list
isc::data::ConstElementPtr client_class_;
};
// Set client-class
const ClientClasses& cclasses = getClientClasses();
- if (cclasses.size() > 1) {
- isc_throw(ToElementError, "client-class has too many items: "
- << cclasses.size());
- } else if (!cclasses.empty()) {
- map->set("client-class", Element::create(*cclasses.cbegin()));
+ if (!cclasses.empty()) {
+ ElementPtr list = Element::createList();
+ for (auto c : cclasses) {
+ list->add(Element::create(c));
+ }
+ map->set("client-classes", list);
}
return (map);
isc_throw(ToElementError, "invalid prefix range "
<< prefix.toText() << "-" << last.toText());
}
+ map->set("prefix-len", Element::create(prefix_len));
// Set delegated-len
uint8_t len = getLength();
uint8_t xlen = xopt->getExcludedPrefixLength();
map->set("excluded-prefix-len",
Element::create(static_cast<int>(xlen)));
- } else {
- map->set("excluded-prefix", Element::create(std::string("::")));
- map->set("excluded-prefix-len", Element::create(0));
}
+ // Let's not insert empty excluded-prefix values. If we ever
+ // decide to insert it after all, here's the code to do it:
+ // else {
+ // map->set("excluded-prefix", Element::create(std::string("::")));
+ // map->set("excluded-prefix-len", Element::create(0));
+ // }
break;
}
/// @brief Checks whether this pool supports client that belongs to
/// specified classes.
///
- /// @todo: currently doing the same than network which
- /// is known to be improved.
+ /// @todo: currently doing the same as network which needs improving.
///
/// @param client_classes list of all classes the client belongs to
/// @return true if client can be supported, false otherwise
/// @brief Optional definition of a client class
///
+ /// If empty, all classes are allowed. If non-empty, only those listed
+ /// here are allowed.
+ ///
/// @ref Network::white_list_
ClientClasses white_list_;
// Set pools
const PoolCollection& pools = getPools(Lease::TYPE_NA);
ElementPtr pool_list = Element::createList();
- for (PoolCollection::const_iterator pool = pools.cbegin();
- pool != pools.cend(); ++pool) {
- // Prepare the map for a pool (@todo move this code to pool.cc)
- ElementPtr pool_map = Element::createMap();
- // Set user-context
- (*pool)->contextToElement(pool_map);
- // Set pool
- const IOAddress& first = (*pool)->getFirstAddress();
- const IOAddress& last = (*pool)->getLastAddress();
- std::string range = first.toText() + "-" + last.toText();
- // Try to output a prefix (vs a range)
- int prefix_len = prefixLengthFromRange(first, last);
- if (prefix_len >= 0) {
- std::ostringstream oss;
- oss << first.toText() << "/" << prefix_len;
- range = oss.str();
- }
- pool_map->set("pool", Element::create(range));
- // Set pool options
- ConstCfgOptionPtr opts = (*pool)->getCfgOption();
- pool_map->set("option-data", opts->toElement());
- // Set client-class
- const ClientClasses& cclasses = (*pool)->getClientClasses();
- if (cclasses.size() > 1) {
- isc_throw(ToElementError, "client-class has too many items: "
- << cclasses.size());
- } else if (!cclasses.empty()) {
- pool_map->set("client-class", Element::create(*cclasses.cbegin()));
- }
- // Push on the pool list
- pool_list->add(pool_map);
+ for (auto pool : pools) {
+ pool_list->add(pool->toElement());
}
map->set("pools", pool_list);
+
// Set pd-pools
const PoolCollection& pdpools = getPools(Lease::TYPE_PD);
ElementPtr pdpool_list = Element::createList();
- for (PoolCollection::const_iterator pool = pdpools.cbegin();
- pool != pdpools.cend(); ++pool) {
- // Get it as a Pool6 (@todo move this code to pool.cc)
- const Pool6* pdpool = dynamic_cast<Pool6*>(pool->get());
- if (!pdpool) {
- isc_throw(ToElementError, "invalid pd-pool pointer");
- }
- // Prepare the map for a pd-pool
- ElementPtr pool_map = Element::createMap();
- // Set user-context
- pdpool->contextToElement(pool_map);
- // Set prefix
- const IOAddress& prefix = pdpool->getFirstAddress();
- pool_map->set("prefix", Element::create(prefix.toText()));
- // Set prefix-len (get it from min - max)
- const IOAddress& last = pdpool->getLastAddress();
- int prefix_len = prefixLengthFromRange(prefix, last);
- if (prefix_len < 0) {
- // The pool is bad: give up
- isc_throw(ToElementError, "invalid prefix range "
- << prefix.toText() << "-" << last.toText());
- }
- pool_map->set("prefix-len", Element::create(prefix_len));
- // Set delegated-len
- uint8_t len = pdpool->getLength();
- pool_map->set("delegated-len",
- Element::create(static_cast<int>(len)));
-
- // Set excluded prefix
- const Option6PDExcludePtr& xopt =
- pdpool->getPrefixExcludeOption();
- if (xopt) {
- const IOAddress& xprefix =
- xopt->getExcludedPrefix(prefix, len);
- pool_map->set("excluded-prefix",
- Element::create(xprefix.toText()));
- uint8_t xlen = xopt->getExcludedPrefixLength();
- pool_map->set("excluded-prefix-len",
- Element::create(static_cast<int>(xlen)));
- }
-
- // Set pool options
- ConstCfgOptionPtr opts = pdpool->getCfgOption();
- pool_map->set("option-data", opts->toElement());
- // Set client-class
- const ClientClasses& cclasses = pdpool->getClientClasses();
- if (cclasses.size() > 1) {
- isc_throw(ToElementError, "client-class has too many items: "
- << cclasses.size());
- } else if (!cclasses.empty()) {
- pool_map->set("client-class", Element::create(*cclasses.cbegin()));
- }
- // Push on the pool list
- pdpool_list->add(pool_map);
+ for (auto pool : pdpools) {
+ pdpool_list->add(pool->toElement());
}
+
map->set("pd-pools", pdpool_list);
return (map);
" },{\n"
" \"option-data\": [ ],\n"
" \"pool\": \"192.0.2.64/26\"\n,"
- " \"client-class\": \"bar\",\n"
+ " \"client-classes\": [ \"bar\" ],\n"
" \"user-context\": { \"foo\": \"bar\" }\n"
" }\n"
" ]\n"
" \"option-data\": [ ]\n"
" },{\n"
" \"pool\": \"2001:db8:1:1::/64\",\n"
- " \"client-class\": \"bar\",\n"
+ " \"client-classes\": [ \"bar\" ],\n"
" \"user-context\": { \"foo\": \"bar\" },\n"
" \"option-data\": [ ]\n"
" }\n"
" \"excluded-prefix\": \"2001:db8:3::\",\n"
" \"excluded-prefix-len\": 64,\n"
" \"option-data\": [ ],\n"
- " \"client-class\": \"bar\"\n"
+ " \"client-classes\": [ \"bar\" ]\n"
" }\n"
" ],\n"
" \"option-data\": [ ]\n"