Merge "Revert "Revert "Revert "Share radio settings across interfaces.""""
diff --git a/dm/binwifi.py b/dm/binwifi.py
index 56d3f98..5d3d7d0 100644
--- a/dm/binwifi.py
+++ b/dm/binwifi.py
@@ -88,6 +88,7 @@
     if landevice_i == 2:
       yield (wlankey + 'Enable'), True
       yield (wlankey + 'SSIDAdvertisementEnabled'), False
+      yield (wlankey + 'X_CATAWAMPUS-ORG_ClientIsolation'), True
       yield (wlankey + 'X_CATAWAMPUS-ORG_OverrideSSID'), 'GFiberSetupAutomation'
 
 
@@ -99,6 +100,7 @@
     if landevice_i == 2:
       yield (wlankey + 'Enable'), True
       yield (wlankey + 'SSIDAdvertisementEnabled'), True
+      yield (wlankey + 'X_CATAWAMPUS-ORG_ClientIsolation'), True
       yield (wlankey + 'X_CATAWAMPUS-ORG_OverrideSSID'), 'Google Fiber Wi-Fi'
 
   yield ('Device.CaptivePortal.URL',
@@ -670,6 +672,8 @@
   X_CATAWAMPUS_ORG_AutoChannelAlgorithm = tr.cwmptypes.TriggerEnum(
       ['LEGACY', 'INITIAL', 'DYNAMIC'], 'LEGACY')
 
+  X_CATAWAMPUS_ORG_ClientIsolation = tr.cwmptypes.TriggerBool(False)
+
   _RecommendedChannel_2G = tr.cwmptypes.Trigger(
       tr.cwmptypes.ReadOnly(
           tr.cwmptypes.FileBacked(
@@ -853,6 +857,9 @@
 
     if not self.SSIDAdvertisementEnabled:
       cmd += ['-H']
+    if self.X_CATAWAMPUS_ORG_ClientIsolation:
+      cmd += ['-C']
+
     if self.new_config.AutoChannelEnable:
       acalg = self.X_CATAWAMPUS_ORG_AutoChannelAlgorithm
       if acalg == 'INITIAL':
diff --git a/dm/binwifi_test.py b/dm/binwifi_test.py
index 2cd25c2..e684862 100644
--- a/dm/binwifi_test.py
+++ b/dm/binwifi_test.py
@@ -499,6 +499,20 @@
     # Same output as 'b'
     self.assertTrue('-p a/b' in ' '.join(buf.splitlines()))
 
+  def testClientIsolation(self):
+    bw = self.WlanConfiguration(
+        'wifi0', '_portal', 'br1', band='5', width_5g=80)
+    bw.StartTransaction()
+    bw.RadioEnabled = True
+    bw.Enable = True
+    bw.SSID = 'Test SSID 1'
+    _, buf = self.GatherOutput(bw)
+    self.assertFalse('-C' in ' '.join(buf.splitlines()))
+
+    bw.X_CATAWAMPUS_ORG_ClientIsolation = True
+    _, buf = self.GatherOutput(bw)
+    self.assertTrue('-C' in ' '.join(buf.splitlines()))
+
   def testWidth(self):
     bw = self.WlanConfiguration(
         'wifi0', '', 'br0', band='5', width_5g=80)
diff --git a/dm/brcmwifi.py b/dm/brcmwifi.py
index 6878800..d7f03b1 100644
--- a/dm/brcmwifi.py
+++ b/dm/brcmwifi.py
@@ -553,6 +553,7 @@
                    'X_CATAWAMPUS-ORG_AutoChanType',
                    'X_CATAWAMPUS-ORG_AllowAutoDisable',
                    'X_CATAWAMPUS-ORG_AutoDisableRecommended',
+                   'X_CATAWAMPUS-ORG_ClientIsolation',
                    'X_CATAWAMPUS-ORG_OverrideSSID',
                    'X_CATAWAMPUS-ORG_Suffix24G'])
 
diff --git a/dm/fakewifi.py b/dm/fakewifi.py
index 370d561..a883b94 100644
--- a/dm/fakewifi.py
+++ b/dm/fakewifi.py
@@ -127,6 +127,7 @@
   X_CATAWAMPUS_ORG_AutoChannelAlgorithm = tr.cwmptypes.Enum(
       ['LEGACY', 'INITIAL', 'DYNAMIC'], 'LEGACY')
   X_CATAWAMPUS_ORG_AutoChanType = tr.cwmptypes.ReadOnlyString('NONDFS')
+  X_CATAWAMPUS_ORG_ClientIsolation = tr.cwmptypes.Bool(False)
   X_CATAWAMPUS_ORG_Width24G = tr.cwmptypes.ReadOnlyString('20')
   X_CATAWAMPUS_ORG_Width5G = tr.cwmptypes.ReadOnlyString('40')
   X_CATAWAMPUS_ORG_AutoDisableRecommended = tr.cwmptypes.ReadOnlyBool(False)
diff --git a/dm/ghn.py b/dm/ghn.py
new file mode 100644
index 0000000..a0a2fe2
--- /dev/null
+++ b/dm/ghn.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# TR-069 has mandatory attribute names that don't comply with policy
+# pylint:disable=invalid-name
+#
+
+"""Implement the tr181 G.HN data model.
+
+All parameters have a description in catawampus/tr/schema/tr-181-2-4-0.xml
+Values are read from '/tmp/ghn/config' which is regularly populated by
+'ghn-periodic-stats'
+"""
+
+__author__ = 'germuth@google.com (Aaron Germuth)'
+
+import google3
+
+import tr.basemodel
+import tr.cwmptypes
+
+GHN_STATS_FILE = '/tmp/ghn/config'
+BASE181GHN = tr.basemodel.Device.Ghn
+
+
+def GetConfigValue(key, index=-1):
+  with open(GHN_STATS_FILE) as f:
+    lines = f.read().splitlines()
+    # match all lines that contain the key
+    # append an equals sign to avoid matching longer entries, for ex:
+    # SYSTEM.GENERAL.FW_VERSION and SYSTEM.GENERAL.FW_VERSION_CORE
+    matches = [x for x in lines if key + '=' in x]
+    if not matches:
+      print 'no matches was found in G.hn config file: %s' % GHN_STATS_FILE
+      return None
+    elif len(matches) > 1:
+      print 'multiple matchess found in G.hn config file: %s' % GHN_STATS_FILE
+      return None
+    else:
+      val = matches[0].split('=')[1]
+
+      # if index was supplied, grab element from comma-separated-list
+      if index != -1:
+        val_list = val.split(',')
+        if (0 > index) or (index >= len(val_list)):
+          print 'list index out of range'
+          return None
+        val = val_list[index]
+
+      # Try to determine what type we should return
+      if val == 'YES':
+        return True
+      elif val == 'NO':
+        return False
+      elif val.isdigit():
+        return int(val)
+      else:
+        return val
+
+
+class GhnInterfaceAssociatedDevice(BASE181GHN.Interface.AssociatedDevice):
+  """One Entry for each G.hn node connected to our local node.."""
+
+  def __init__(self, device_id):
+    super(GhnInterfaceAssociatedDevice, self).__init__()
+
+    self.DeviceId = device_id
+
+  @property
+  def MACAddress(self):
+    # List is ordered on device_id, device_id=0 is invalid
+    return GetConfigValue('DIDMNG.GENERAL.MACS', self.DeviceId - 1)
+
+  @property
+  def TxPhyRate(self):
+    # Formula taken from Spirit Configuration Tool source code
+    val = GetConfigValue('DIDMNG.GENERAL.TX_BPS', self.DeviceId)
+    return int(val) * 32 / 1000
+
+  @property
+  def RxPhyRate(self):
+    # Formula taken from Spirit Configuration Tool source code
+    val = GetConfigValue('DIDMNG.GENERAL.RX_BPS', self.DeviceId)
+    return int(val) * 32 / 1000
+
+  @property
+  def Active(self):
+    return GetConfigValue('DIDMNG.GENERAL.ACTIVE', self.DeviceId)
+
+
+class GhnInterfaceStats(BASE181GHN.Interface.Stats):
+  """Packet count information on eth1, we unexport every single parameter."""
+
+  def __init__(self):
+    super(GhnInterfaceStats, self).__init__()
+
+    # Packet counts to/from G.hn can be seen with:
+    # 'echo 1 > /sys/devices/platform/neta/gbe/cntrs'
+    # and caught with turbogrinder instead
+    self.Unexport(['BytesSent', 'BytesReceived', 'PacketsSent',
+                   'PacketsReceived', 'ErrorsSent', 'ErrorsReceived',
+                   'UnicastPacketsSent', 'UnicastPacketsReceived',
+                   'DiscardPacketsSent', 'DiscardPacketsReceived',
+                   'MulticastPacketsSent', 'MulticastPacketsReceived',
+                   'BroadcastPacketsSent', 'BroadcastPacketsReceived',
+                   'UnknownProtoPacketsReceived'])
+
+
+class GhnInterface(BASE181GHN.Interface):
+  """TR181 Ghn implementation for DS6923 optical module."""
+
+  Upstream = tr.cwmptypes.ReadOnlyBool(False)
+  ConnectionType = tr.cwmptypes.ReadOnlyString('Coax')
+  Stats = GhnInterfaceStats()  # empty
+
+  def __init__(self):
+    super(GhnInterface, self).__init__()
+
+    self.Unexport(['Status',  # Can't find equivalent inside configlayer
+                              # NTP.GENERAL.STATUS doesn't match desc
+                   'MaxBitRate',  # Can't find equivalent inside configlayer
+                   'LowerLayers',  # tr181 "expected that it will not be used"
+                   'NodeTypeDMConfig',  # Requests a node becomes domain_master
+                   'TargetDomainNames'])  # Used for changing target domain_name
+    self.Unexport(objects=['Stats'])
+
+  @property
+  def Enable(self):
+    return GetConfigValue('NODE.GENERAL.ENABLE')
+
+  @property
+  def Alias(self):
+    return GetConfigValue('NODE.GENERAL.DEVICE_ALIAS')
+
+  @property
+  def Name(self):
+    return GetConfigValue('SYSTEM.PRODUCTION.DEVICE_NAME')
+
+  @property
+  def LastChange(self):
+    return GetConfigValue('NODE.GENERAL.LAST_CHANGE')
+
+  @property
+  def MACAddress(self):
+    return GetConfigValue('SYSTEM.PRODUCTION.MAC_ADDR')
+
+  @property
+  def FirmwareVersion(self):
+    return GetConfigValue('SYSTEM.GENERAL.FW_VERSION')
+
+  @property
+  def DomainName(self):
+    return GetConfigValue('NODE.GENERAL.DOMAIN_NAME')
+
+  @property
+  def DomainNameIdentifier(self):
+    return GetConfigValue('NODE.GENERAL.DNI')
+
+  @property
+  def DomainId(self):
+    return GetConfigValue('NODE.GENERAL.DOMAIN_ID')
+
+  @property
+  def DeviceId(self):
+    return GetConfigValue('NODE.GENERAL.DEVICE_ID')
+
+  @property
+  def NodeTypeDMCapable(self):
+    return GetConfigValue('SYSTEM.GENERAL.DOMAIN_MASTER_CAPABLE')
+
+  @property
+  def NodeTypeDMStatus(self):
+    return GetConfigValue('NODE.GENERAL.NODE_TYPE') == 'DOMAIN_MASTER'
+
+  @property
+  def NodeTypeSCCapable(self):
+    return GetConfigValue('SYSTEM.GENERAL.SEC_CONTROLLER_CAPABLE')
+
+  @property
+  def NodeTypeSCStatus(self):
+    return GetConfigValue('SYSTEM.GENERAL.SEC_CONTROLLER_STATUS')
+
+  @property
+  def AssociatedDeviceNumberOfEntries(self):
+    num_dids = GetConfigValue('DIDMNG.GENERAL.NUM_DIDS')
+    if num_dids == 0:
+      return 0
+    else:
+      # We don't count ourselves as an associated device
+      return num_dids - 1
+
+  @property
+  def AssociatedDeviceList(self):
+    current = 1
+    neighbour_list = {}
+    if self.AssociatedDeviceNumberOfEntries < 1:
+      return neighbour_list
+
+    # Grab all device Ids on record, and check each one
+    ids = GetConfigValue('DIDMNG.GENERAL.DIDS').split(',')
+    for id_str in ids:
+      device_id = int(id_str)
+      if device_id != self.DeviceId and device_id != 0:
+        neighbour_list[str(current)] = GhnInterfaceAssociatedDevice(device_id)
+        current += 1
+    return neighbour_list
+
+
+class Ghn(BASE181GHN):
+  """Implementation of Ghn Module."""
+
+  def __init__(self):
+    super(Ghn, self).__init__()
+    self.InterfaceList = {'1': GhnInterface()}
+
+  @property
+  def InterfaceNumberOfEntries(self):
+    return len(self.InterfaceList)
diff --git a/dm/ghn_test.py b/dm/ghn_test.py
new file mode 100644
index 0000000..7ef1819
--- /dev/null
+++ b/dm/ghn_test.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+# Copyright 2017 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# unittest requires method names starting in 'test'
+# pylint:disable=invalid-name
+
+"""Unit tests for ghn.py implementation."""
+
+__author__ = 'germuth@google.com (Aaron Germuth)'
+
+import google3
+import ghn
+from tr.wvtest import unittest
+
+
+class GhnTest(unittest.TestCase):
+
+  def testGetConfigValue(self):
+    ghn.GHN_STATS_FILE = 'testdata/ghn/config'
+
+    self.assertEqual(ghn.GetConfigValue(''), None)
+    self.assertEqual(ghn.GetConfigValue('DIDMNG'), None)
+    self.assertEqual(ghn.GetConfigValue('NODE'), None)
+    self.assertEqual(ghn.GetConfigValue('NO MATCHES'), None)
+
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.ENABLE'),
+        True)
+    self.assertEqual(
+        ghn.GetConfigValue('NTP.GENERAL.STATUS'),
+        'Unsynchronized')
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DEVICE_ALIAS'),
+        'MARVELL GHN NODE')
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DEVICE_NAME'),
+        'MARVELL GHN NODE')
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.PRODUCTION.DEVICE_NAME'),
+        'DCP962C')
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.LAST_CHANGE'),
+        7162)
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.PRODUCTION.MAC_ADDR'),
+        'F4:F5:E8:02:C0:2E')
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.API_VERSION'),
+        'r521+1+1')
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.FW_VERSION'),
+        'dcp962c_v1_x-HN SPIRIT.v7_6_r521+1+1_cvs')
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.FW_VERSION_CORE'),
+        'dcp962c_v1_x')
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DOMAIN_NAME'),
+        'HomeGrid')
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DNI'),
+        5820)
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DOMAIN_ID'),
+        13)
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DEVICE_ID'),
+        2)
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.NODE_TYPE'),
+        'END_POINT')
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.DOMAIN_MASTER_CAPABLE'),
+        True)
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.SEC_CONTROLLER_CAPABLE'),
+        False)
+    self.assertEqual(
+        ghn.GetConfigValue('SYSTEM.GENERAL.SEC_CONTROLLER_STATUS'),
+        False)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.NUM_DIDS'),
+        2)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.DIDS'),
+        '0,1,2')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS'),
+        'F4:F5:E8:02:C0:29,F4:F5:E8:02:C0:2E')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.TX_BPS'),
+        '0,62773,0')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.RX_BPS'),
+        '0,62839,0')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.ACTIVE'),
+        'NO,YES,YES')
+
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DNI', 0),
+        5820)
+    self.assertEqual(
+        ghn.GetConfigValue('NODE.GENERAL.DNI', 1),
+        None)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS', -1),
+        'F4:F5:E8:02:C0:29,F4:F5:E8:02:C0:2E')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS', 0),
+        'F4:F5:E8:02:C0:29')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS', 1),
+        'F4:F5:E8:02:C0:2E')
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS', 2),
+        None)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.MACS', -2),
+        None)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.DIDS', 0),
+        0)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.DIDS', 2),
+        2)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.DIDS', 3),
+        None)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.DIDS', -3),
+        None)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.TX_BPS', 1),
+        62773)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.ACTIVE', 0),
+        False)
+    self.assertEqual(
+        ghn.GetConfigValue('DIDMNG.GENERAL.ACTIVE', 2),
+        True)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/dm/testdata/ghn/config b/dm/testdata/ghn/config
new file mode 100644
index 0000000..b9beae6
--- /dev/null
+++ b/dm/testdata/ghn/config
@@ -0,0 +1,26 @@
+eth1 F4:F5:E8:02:C0:2E 00:00:00:00:00:00 SCP
+
+NODE.GENERAL.ENABLE=YES
+NTP.GENERAL.STATUS=Unsynchronized
+NODE.GENERAL.DEVICE_ALIAS=MARVELL GHN NODE
+NODE.GENERAL.DEVICE_NAME=MARVELL GHN NODE
+SYSTEM.PRODUCTION.DEVICE_NAME=DCP962C
+NODE.GENERAL.LAST_CHANGE=7162
+SYSTEM.PRODUCTION.MAC_ADDR=F4:F5:E8:02:C0:2E
+SYSTEM.GENERAL.API_VERSION=r521+1+1
+SYSTEM.GENERAL.FW_VERSION=dcp962c_v1_x-HN SPIRIT.v7_6_r521+1+1_cvs
+SYSTEM.GENERAL.FW_VERSION_CORE=dcp962c_v1_x
+NODE.GENERAL.DOMAIN_NAME=HomeGrid
+NODE.GENERAL.DNI=5820
+NODE.GENERAL.DOMAIN_ID=13
+NODE.GENERAL.DEVICE_ID=2
+NODE.GENERAL.NODE_TYPE=END_POINT
+SYSTEM.GENERAL.DOMAIN_MASTER_CAPABLE=YES
+SYSTEM.GENERAL.SEC_CONTROLLER_CAPABLE=NO
+SYSTEM.GENERAL.SEC_CONTROLLER_STATUS=NO
+DIDMNG.GENERAL.NUM_DIDS=2
+DIDMNG.GENERAL.DIDS=0,1,2
+DIDMNG.GENERAL.MACS=F4:F5:E8:02:C0:29,F4:F5:E8:02:C0:2E
+DIDMNG.GENERAL.TX_BPS=0,62773,0
+DIDMNG.GENERAL.RX_BPS=0,62839,0
+DIDMNG.GENERAL.ACTIVE=NO,YES,YES
diff --git a/platform/gfonu/device.py b/platform/gfonu/device.py
index 0985ebc..1d7bd2a 100644
--- a/platform/gfonu/device.py
+++ b/platform/gfonu/device.py
@@ -32,6 +32,7 @@
 import dm.device_info
 import dm.ds6923_optical
 import dm.ethernet
+import dm.ghn
 import dm.igd_time
 import dm.mrvl88601_netstats
 import dm.periodic_statistics
@@ -343,7 +344,7 @@
         objects=['ATM', 'Bridging', 'BulkData', 'CaptivePortal',
                  'DHCPv4', 'DHCPv6', 'DLNA', 'DNS', 'DSL', 'DSLite',
                  'ETSIM2M', 'FaultMgmt', 'FAP', 'Firewall',
-                 'GatewayInfo', 'Ghn', 'HPNA', 'HomePlug', 'Hosts',
+                 'GatewayInfo', 'HPNA', 'HomePlug', 'Hosts',
                  'IEEE8021x', 'IP', 'IPsec', 'IPv6rd', 'LANConfigSecurity',
                  'MoCA', 'NAT', 'NeighborDiscovery', 'PPP', 'PTM',
                  'QoS', 'RouterAdvertisement', 'Routing', 'Security',
@@ -353,12 +354,20 @@
                  'WiFi'])
     self.Unexport(lists=['InterfaceStack'])
     self.Unexport(['InterfaceStackNumberOfEntries', 'RootDataModelVersion'])
+
     with open(PLATFORM_FILE) as f:
-      if f.read().strip() == 'GFLT110' or f.read().strip() == 'GFLT120':
+      name = f.read().strip()
+
+      if name == 'GFLT110' or name == 'GFLT120':
         self.Optical = dm.ds6923_optical.Ds6923Optical(GFLT110_OPTICAL_I2C_ADDR)
       else:
         self.Unexport(objects=['Optical'])
 
+      if name == 'GFLT400':
+        self.Ghn = dm.ghn.Ghn()
+      else:
+        self.Unexport(objects=['Ghn'])
+
     # DeficeInfo is defined under tr181.Device_v2_4,
     # not tr181.Device_v2_4.Device, so still need to Export here
     self.Export(objects=['DeviceInfo'])
diff --git a/tr/schema/x-cata098.xml b/tr/schema/x-cata098.xml
index caa34d5..5d1d678 100644
--- a/tr/schema/x-cata098.xml
+++ b/tr/schema/x-cata098.xml
@@ -133,6 +133,12 @@
           <boolean/>

         </syntax>

       </parameter>

+      <parameter name="X_CATAWAMPUS-ORG_ClientIsolation" access="readWrite">

+        <description>If ClientIsolation is True, CPE will isolate clients on the network from each other.</description>

+        <syntax>

+          <boolean/>

+        </syntax>

+      </parameter>

       <parameter name="X_CATAWAMPUS-ORG_AutoChannelAlgorithm" access="readWrite">

         <description>If LEGACY, use old-style /bin/wifi autochannel recommendation.  If INITIAL, use boot-time waveguide recommendation.  If DYNAMIC, use real-time waveguide recommendation.</description>

         <syntax>