r2141 - fix tagOnIdList()
[racktables] / inc / functions.php
index 63769e8ef8bbd0877b2a4532f97085b5d2228a2a..c7d0356c935887278fcb41069c9a56ebe2650cbd 100644 (file)
@@ -153,81 +153,6 @@ function markBestSpan (&$rackData, $i)
        return TRUE;
 }
 
-function delRow ($row_id = 0)
-{
-       if ($row_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a href='?op=del_row&row_id=${row_id}&confirmed=true'>here</a> to confirm rack row deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack row information: ';
-       $result = $dbxlink->query ("update RackRow set deleted = 'yes' where id=${row_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('RackRow', "id = ${row_id}");
-       echo "Information was deleted. You may return to <a href='?op=list_rows&editmode=on'>rack row list</a>.";
-}
-
-function delRack ($rack_id = 0)
-{
-       if ($rack_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a href='?op=del_rack&rack_id=${rack_id}&confirmed=true'>here</a> to confirm rack deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting rack information: ';
-       $result = $dbxlink->query ("update Rack set deleted = 'yes' where id=${rack_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('Rack', "id = ${rack_id}");
-       echo "Information was deleted. You may return to <a href='?op=list_racks&editmode=on'>rack list</a>.";
-}
-
-function delObject ($object_id = 0)
-{
-       if ($object_id == 0)
-       {
-               showError ('Not all required args are present.', __FUNCTION__);
-               return;
-       }
-       if (!isset ($_REQUEST['confirmed']) || $_REQUEST['confirmed'] != 'true')
-       {
-               echo "Press <a href='?op=del_object&object_id=${object_id}&confirmed=true'>here</a> to confirm object deletion.";
-               return;
-       }
-       global $dbxlink;
-       echo 'Deleting object information: ';
-       $result = $dbxlink->query ("update RackObject set deleted = 'yes' where id=${object_id} limit 1");
-       if ($result->rowCount() != 1)
-       {
-               showError ('Marked ' . $result.rowCount() . ' rows as deleted, but expected 1', __FUNCTION__);
-               return;
-       }
-       echo 'OK<br>';
-       recordHistory ('RackObject', "id = ${object_id}");
-       echo "Information was deleted. You may return to <a href='?op=list_objects&editmode=on'>object list</a>.";
-}
-
 // We can mount 'F' atoms and unmount our own 'T' atoms.
 function applyObjectMountMask (&$rackData, $object_id)
 {
@@ -344,407 +269,86 @@ function mergeGridFormToRack (&$rackData)
                }
 }
 
+// netmask conversion from length to number
 function binMaskFromDec ($maskL)
 {
-       $binmask=0;
-       for ($i=0; $i<$maskL; $i++)
-       {
-               $binmask*=2;
-               $binmask+=1;
-       }
-       for ($i=$maskL; $i<32; $i++)
-       {
-               $binmask*=2;
-       }
-       return $binmask;
-}
-
-function binInvMaskFromDec ($maskL)
-{
-       $binmask=0;
-       for ($i=0; $i<$maskL; $i++)
-       {
-               $binmask*=2;
-       }
-       for ($i=$maskL; $i<32; $i++)
-       {
-               $binmask*=2;
-               $binmask+=1;
-       }
-       return $binmask;
-}
-
-function addRange ($range='', $name='', $is_bcast = FALSE, $taglist = array())
-{
-       // $range is in x.x.x.x/x format, split into ip/mask vars
-       $rangeArray = explode('/', $range);
-       if (count ($rangeArray) != 2)
-               return "Invalid IP subnet '${range}'";
-       $ip = $rangeArray[0];
-       $mask = $rangeArray[1];
-
-       if (empty ($ip) or empty ($mask))
-               return "Invalid IP subnet '${range}'";
-       $ipL = ip2long($ip);
-       $maskL = ip2long($mask);
-       if ($ipL == -1 || $ipL === FALSE)
-               return 'Bad ip address';
-       if ($mask < 32 && $mask > 0)
-               $maskL = $mask;
-       else
-       {
-               $maskB = decbin($maskL);
-               if (strlen($maskB)!=32)
-                       return 'Bad mask';
-               $ones=0;
-               $zeroes=FALSE;
-               foreach( str_split ($maskB) as $digit)
-               {
-                       if ($digit == '0')
-                               $zeroes = TRUE;
-                       if ($digit == '1')
-                       {
-                               $ones++;
-                               if ($zeroes == TRUE)
-                                       return 'Bad mask';
-                       }
-               }
-               $maskL = $ones;
-       }
-       $binmask = binMaskFromDec($maskL);
-       $ipL = $ipL & $binmask;
-
-       $query =
-               "select ".
-               "id, ip, mask, name ".
-               "from IPRanges ";
-       
-
-       $result = useSelectBlade ($query);
-
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $otherip = $row['ip'];
-               $othermask = binMaskFromDec($row['mask']);
-//             echo "checking $otherip & $othermask ".($otherip & $othermask)." == $ipL & $othermask ".($ipL & $othermask)." ".decbin($otherip)." ".decbin($othermask)." ".decbin($otherip & $othermask)." ".decbin($ipL)." ".decbin($othermask)." ".decbin($ipL & $othermask)."\n";
-//             echo "checking $otherip & $binmask ".($otherip & $binmask)." == $ipL & $binmask ".($ipL & $binmask)." ".decbin($otherip)." ".decbin($binmask)." ".decbin($otherip & $binmask)." ".decbin($ipL)." ".decbin($binmask)." ".decbin($ipL & $binmask)."\n";
-//             echo "\n";
-//             flush();
-               if (($otherip & $othermask) == ($ipL & $othermask))
-                       return "This subnet intersects with ".long2ip($row['ip'])."/${row['mask']}";
-               if (($otherip & $binmask) == ($ipL & $binmask))
-                       return "This subnet intersects with ".long2ip($row['ip'])."/${row['mask']}";
-       }
-       $result->closeCursor();
-       unset ($result);
-       $result = useInsertBlade
-       (
-               'IPRanges',
-               array
-               (
-                       'ip' => sprintf ('%u', $ipL),
-                       'mask' => "'${maskL}'",
-                       'name' => "'${name}'"
-               )
+       $map_straight = array (
+               0  => 0x00000000,
+               1  => 0x80000000,
+               2  => 0xc0000000,
+               3  => 0xe0000000,
+               4  => 0xf0000000,
+               5  => 0xf8000000,
+               6  => 0xfc000000,
+               7  => 0xfe000000,
+               8  => 0xff000000,
+               9  => 0xff800000,
+               10 => 0xffc00000,
+               11 => 0xffe00000,
+               12 => 0xfff00000,
+               13 => 0xfff80000,
+               14 => 0xfffc0000,
+               15 => 0xfffe0000,
+               16 => 0xffff0000,
+               17 => 0xffff8000,
+               18 => 0xffffc000,
+               19 => 0xffffe000,
+               20 => 0xfffff000,
+               21 => 0xfffff800,
+               22 => 0xfffffc00,
+               23 => 0xfffffe00,
+               24 => 0xffffff00,
+               25 => 0xffffff80,
+               26 => 0xffffffc0,
+               27 => 0xffffffe0,
+               28 => 0xfffffff0,
+               29 => 0xfffffff8,
+               30 => 0xfffffffc,
+               31 => 0xfffffffe,
+               32 => 0xffffffff,
        );
-
-       if ($is_bcast and $maskL < 31)
-       {
-               $network_addr = long2ip ($ipL);
-               $broadcast_addr = long2ip ($ipL | binInvMaskFromDec ($maskL));
-               updateAddress ($network_addr, 'network', 'yes');
-               updateAddress ($broadcast_addr, 'broadcast', 'yes');
-       }
-       if (!count ($taglist))
-               return '';
-       if (($result = useSelectBlade ('select last_insert_id()')) == NULL) 
-               return 'Query #3 failed in ' . __FUNCTION__;
-       $row = $result->fetch (PDO::FETCH_NUM);
-       $netid = $row[0];
-       $result->closeCursor();
-       unset ($result);
-       $errcount = 0;
-       foreach ($taglist as $tag_id)
-               if (useInsertBlade
-               (
-                       'TagStorage',
-                       array
-                       (
-                               'target_realm' => "'ipv4net'",
-                               'target_id' => $netid,
-                               'tag_id' => $tag_id
-                       )
-               ) == FALSE)
-                       $errcount++;    
-       if (!$errcount)
-               return '';
-       else
-               return "Experienced ${errcount} errors adding tags for the network";
-}
-
-function getIPRange ($id = 0)
-{
-       global $dbxlink;
-       $query =
-               "select ".
-               "id as IPRanges_id, ".
-               "INET_NTOA(ip) as IPRanges_ip, ".
-               "mask as IPRanges_mask, ".
-               "name as IPRanges_name ".
-               "from IPRanges ".
-               "where id = '$id'";
-       $result = useSelectBlade ($query);
-       $ret = array();
-       $row = $result->fetch (PDO::FETCH_ASSOC);
-       if ($row == NULL)
-               return $ret;
-       $ret['id'] = $row['IPRanges_id'];
-       $ret['ip'] = $row['IPRanges_ip'];
-       $ret['ip_bin'] = ip2long ($row['IPRanges_ip']);
-       $ret['mask_bin'] = binMaskFromDec($row['IPRanges_mask']);
-       $ret['mask_bin_inv'] = binInvMaskFromDec($row['IPRanges_mask']);
-       $ret['name'] = $row['IPRanges_name'];
-       $ret['mask'] = $row['IPRanges_mask'];
-       $ret['addrlist'] = array();
-       $result->closeCursor();
-       unset ($result);
-       // We risk losing some significant bits in an unsigned 32bit integer,
-       // unless it is converted to a string.
-       $db_first = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] & $ret['mask_bin']) . "'";
-       $db_last  = "'" . sprintf ('%u', 0x00000000 + $ret['ip_bin'] | ($ret['mask_bin_inv'])) . "'";
-
-       // Don't try to build up the whole structure in a single pass. Request
-       // the list of user comments and reservations and merge allocations in
-       // at a latter point.
-       $query =
-               "select INET_NTOA(ip) as ip, name, reserved from IPAddress " .
-               "where ip between ${db_first} and ${db_last} " .
-               "and (reserved = 'yes' or name != '')";
-       $result = $dbxlink->query ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               $ret['addrlist'][$ip_bin] = $row;
-               $tmp = array();
-               foreach (array ('ip', 'name', 'reserved') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $tmp['references'] = array();
-               $tmp['lbrefs'] = array();
-               $tmp['rsrefs'] = array();
-               $ret['addrlist'][$ip_bin] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select INET_NTOA(ipb.ip) as ip, ro.id as object_id, " .
-               "ro.name as object_name, ipb.name, ipb.type, objtype_id, " .
-               "dict_value as objtype_name from " .
-               "IPBonds as ipb inner join RackObject as ro on ipb.object_id = ro.id " .
-               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
-               "where ip between ${db_first} and ${db_last} " .
-               "and chapter_name = 'RackObjectType'" .
-               "order by ipb.type, object_name";
-       $result = useSelectBlade ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = array();
-               foreach (array ('object_id', 'type', 'name') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $quasiobject['name'] = $row['object_name'];
-               $quasiobject['objtype_id'] = $row['objtype_id'];
-               $quasiobject['objtype_name'] = $row['objtype_name'];
-               $tmp['object_name'] = displayedName ($quasiobject);
-               $ret['addrlist'][$ip_bin]['references'][] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select vs_id, inet_ntoa(vip) as ip, vport, proto, " .
-               "object_id, objtype_id, ro.name, dict_value as objtype_name from " .
-               "IPVirtualService as vs inner join IPLoadBalancer as lb on vs.id = lb.vs_id " .
-               "inner join RackObject as ro on lb.object_id = ro.id " .
-               "left join Dictionary on objtype_id=dict_key " .
-               "natural join Chapter " .
-               "where vip between ${db_first} and ${db_last} " .
-               "and chapter_name = 'RackObjectType'" .
-               "order by vport, proto, ro.name, object_id";
-       $result = useSelectBlade ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = $qbject = array();
-               foreach (array ('object_id', 'vport', 'proto', 'vs_id') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               foreach (array ('name', 'objtype_id', 'objtype_name') as $cname)
-                       $qobject[$cname] = $row[$cname];
-               $tmp['object_name'] = displayedName ($qobject);
-               $ret['addrlist'][$ip_bin]['lbrefs'][] = $tmp;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select inet_ntoa(rsip) as ip, rsport, rspool_id, rsp.name as rspool_name from " .
-               "IPRealServer as rs inner join IPRSPool as rsp on rs.rspool_id = rsp.id " .
-               "where rsip between ${db_first} and ${db_last} " .
-               "order by ip, rsport, rspool_id";
-       $result = useSelectBlade ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ip_bin = ip2long ($row['ip']);
-               if (!isset ($ret['addrlist'][$ip_bin]))
-               {
-                       $ret['addrlist'][$ip_bin] = array();
-                       $ret['addrlist'][$ip_bin]['ip'] = $row['ip'];
-                       $ret['addrlist'][$ip_bin]['name'] = '';
-                       $ret['addrlist'][$ip_bin]['reserved'] = 'no';
-                       $ret['addrlist'][$ip_bin]['references'] = array();
-                       $ret['addrlist'][$ip_bin]['lbrefs'] = array();
-                       $ret['addrlist'][$ip_bin]['rsrefs'] = array();
-               }
-               $tmp = array();
-               foreach (array ('rspool_id', 'rsport', 'rspool_name') as $cname)
-                       $tmp[$cname] = $row[$cname];
-               $ret['addrlist'][$ip_bin]['rsrefs'][] = $tmp;
-       }
-
-       return $ret;
+       return $map_straight[$maskL];
 }
 
-// Don't require any records in IPAddress, but if there is one,
-// merge the data between getting allocation list. Collect enough data
-// to call displayedName() ourselves.
-function getIPAddress ($ip=0)
-{
-       $ret = array();
-       $ret['bonds'] = array();
-       // FIXME: below two aren't neither filled in with data nor rendered (ticket:23)
-       $ret['outpf'] = array();
-       $ret['inpf'] = array();
-       $ret['vslist'] = array();
-       $ret['rslist'] = array();
-       $ret['exists'] = 0;
-       $ret['name'] = '';
-       $ret['reserved'] = 'no';
-       global $dbxlink;
-       $query =
-               "select ".
-               "name, reserved ".
-               "from IPAddress ".
-               "where ip = INET_ATON('$ip') and (reserved = 'yes' or name != '')";
-       $result = $dbxlink->query ($query);
-       if ($result == NULL)
-       {
-               showError ('Query #1 failed', __FUNCTION__);
-               return NULL;
-       }
-       if ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ret['exists'] = 1;
-               $ret['name'] = $row['name'];
-               $ret['reserved'] = $row['reserved'];
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query =
-               "select ".
-               "IPBonds.object_id as object_id, ".
-               "IPBonds.name as name, ".
-               "IPBonds.type as type, ".
-               "objtype_id, dict_value as objtype_name, " .
-               "RackObject.name as object_name ".
-               "from IPBonds join RackObject on IPBonds.object_id=RackObject.id ".
-               "left join Dictionary on objtype_id=dict_key natural join Chapter " .
-               "where IPBonds.ip=INET_ATON('$ip') ".
-               "and chapter_name = 'RackObjectType' " .
-               "order by RackObject.id, IPBonds.name";
-       $result = $dbxlink->query ($query);
-       $count=0;
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $ret['bonds'][$count]['object_id'] = $row['object_id'];
-               $ret['bonds'][$count]['name'] = $row['name'];
-               $ret['bonds'][$count]['type'] = $row['type'];
-               $qo = array();
-               $qo['name'] = $row['object_name'];
-               $qo['objtype_id'] = $row['objtype_id'];
-               $qo['objtype_name'] = $row['objtype_name'];
-               $ret['bonds'][$count]['object_name'] = displayedName ($qo);
-               $count++;
-               $ret['exists'] = 1;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select id, vport, proto, name from IPVirtualService where vip = inet_aton('${ip}')";
-       $result = $dbxlink->query ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $new = $row;
-               $new['vip'] = $ip;
-               $ret['vslist'][] = $new;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       $query = "select inservice, rsport, IPRSPool.id as pool_id, IPRSPool.name as poolname from " .
-               "IPRealServer inner join IPRSPool on rspool_id = IPRSPool.id " .
-               "where rsip = inet_aton('${ip}')";
-       $result = $dbxlink->query ($query);
-       while ($row = $result->fetch (PDO::FETCH_ASSOC))
-       {
-               $new = $row;
-               $new['rsip'] = $ip;
-               $ret['rslist'][] = $new;
-       }
-       $result->closeCursor();
-       unset ($result);
-
-       return $ret;
-}
-       
-function bindIpToObject ($ip='', $object_id=0, $name='', $type='')
+// complementary value
+function binInvMaskFromDec ($maskL)
 {
-       global $dbxlink;
-
-       $range = getRangeByIp($ip);
-       if (!$range)
-               return 'Non-existant ip address. Try adding IP range first';
-
-       $result = useInsertBlade
-       (
-               'IPBonds',
-               array
-               (
-                       'ip' => "INET_ATON('$ip')",
-                       'object_id' => "'${object_id}'",
-                       'name' => "'${name}'",
-                       'type' => "'${type}'"
-               )
+       $map_compl = array (
+               0  => 0xffffffff,
+               1  => 0x7fffffff,
+               2  => 0x3fffffff,
+               3  => 0x1fffffff,
+               4  => 0x0fffffff,
+               5  => 0x07ffffff,
+               6  => 0x03ffffff,
+               7  => 0x01ffffff,
+               8  => 0x00ffffff,
+               9  => 0x007fffff,
+               10 => 0x003fffff,
+               11 => 0x001fffff,
+               12 => 0x000fffff,
+               13 => 0x0007ffff,
+               14 => 0x0003ffff,
+               15 => 0x0001ffff,
+               16 => 0x0000ffff,
+               17 => 0x00007fff,
+               18 => 0x00003fff,
+               19 => 0x00001fff,
+               20 => 0x00000fff,
+               21 => 0x000007ff,
+               22 => 0x000003ff,
+               23 => 0x000001ff,
+               24 => 0x000000ff,
+               25 => 0x0000007f,
+               26 => 0x0000003f,
+               27 => 0x0000001f,
+               28 => 0x0000000f,
+               29 => 0x00000007,
+               30 => 0x00000003,
+               31 => 0x00000001,
+               32 => 0x00000000,
        );
-       return $result ? '' : 'useInsertBlade() failed in bindIpToObject()';
+       return $map_compl[$maskL];
 }
 
 // This function looks up 'has_problems' flag for 'T' atoms
@@ -792,11 +396,12 @@ function mergeSearchResults (&$objects, $terms, $fieldname)
                $query .= "${fieldname} like '%$term%'";
                $count++;
        }
-       $result = useSelectBlade ($query);
+       $query .= " order by ${fieldname}";
+       $result = useSelectBlade ($query, __FUNCTION__);
 // FIXME: this dead call was executed 4 times per 1 object search!
 //     $typeList = getObjectTypeList();
        $clist = array ('id', 'name', 'label', 'asset_no', 'barcode', 'objtype_id', 'objtype_name');
-       while ($row = $result->fetch(PDO::FETCH_ASSOC))
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
                foreach ($clist as $cname)
                        $object[$cname] = $row[$cname];
@@ -1042,27 +647,33 @@ function buildPortCompatMatrixFromList ($portTypeList, $portCompatList)
 
 function newPortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto, $description)
 {
-       global $dbxlink;
-
-       $range = getRangeByIp($localip);
-       if (!$range)
+       if (NULL === getIPv4AddressNetworkId ($localip))
                return "$localip: Non existant ip";
-       
-       $range = getRangeByIp($remoteip);
-       if (!$range)
+       if (NULL === getIPv4AddressNetworkId ($localip))
                return "$remoteip: Non existant ip";
-       
        if ( ($localport <= 0) or ($localport >= 65536) )
                return "$localport: invaild port";
-
        if ( ($remoteport <= 0) or ($remoteport >= 65536) )
                return "$remoteport: invaild port";
 
-        $query =
-                "insert into PortForwarding set object_id='$object_id', localip=INET_ATON('$localip'), remoteip=INET_ATON('$remoteip'), localport='$localport', remoteport='$remoteport', proto='$proto', description='$description'";
-        $result = $dbxlink->exec ($query);
-
-       return '';
+       $result = useInsertBlade
+       (
+               'PortForwarding',
+               array
+               (
+                       'object_id' => $object_id,
+                       'localip' => "INET_ATON('${localip}')",
+                       'remoteip' => "INET_ATON('$remoteip')",
+                       'localport' => $localport,
+                       'remoteport' => $remoteport,
+                       'proto' => "'${proto}'",
+                       'description' => "'${description}'",
+               )
+       );
+       if ($result)
+               return '';
+       else
+               return __FUNCTION__ . ': Failed to insert the rule.';
 }
 
 function deletePortForwarding($object_id, $localip, $localport, $remoteip, $remoteport, $proto)
@@ -1105,7 +716,7 @@ function getNATv4ForObject ($object_id)
                "left join IPAddress as ipa2 on PortForwarding.remoteip = ipa2.ip " .
                "where object_id='$object_id' ".
                "order by localip, localport, proto, remoteip, remoteport";
-       $result = useSelectBlade ($query);
+       $result = useSelectBlade ($query, __FUNCTION__);
        $count=0;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1129,7 +740,7 @@ function getNATv4ForObject ($object_id)
                "from ((PortForwarding join IPBonds on remoteip=IPBonds.ip) join RackObject on PortForwarding.object_id=RackObject.id) ".
                "where IPBonds.object_id='$object_id' ".
                "order by remoteip, remoteport, proto, localip, localport";
-       $result = useSelectBlade ($query);
+       $result = useSelectBlade ($query, __FUNCTION__);
        $count=0;
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
        {
@@ -1152,11 +763,10 @@ function findAllEndpoints ($object_id, $fallback = '')
        foreach ($values as $record)
                if ($record['name'] == 'FQDN' && !empty ($record['value']))
                        return array ($record['value']);
-       $addresses = getObjectAddresses ($object_id);
        $regular = array();
-       foreach ($addresses as $idx => $address)
-               if ($address['type'] == 'regular')
-                       $regular[] = $address['ip'];
+       foreach (getObjectIPv4Allocations ($object_id) as $dottedquad => $alloc)
+               if ($alloc['type'] == 'regular')
+                       $regular[] = $dottedquad;
        if (!count ($regular) && !empty ($fallback))
                return array ($fallback);
        return $regular;
@@ -1174,7 +784,7 @@ function parseWikiLink ($line, $which, $strip_optgroup = FALSE)
        if (preg_match ('/^\[\[.+\]\]$/', $line) == 0)
        {
                if ($strip_optgroup)
-                       return ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $line));
+                       return ereg_replace ('^.+%GSKIP%', '', ereg_replace ('^(.+)%GPASS%', '\\1 ', $line));
                else
                        return $line;
        }
@@ -1182,7 +792,7 @@ function parseWikiLink ($line, $which, $strip_optgroup = FALSE)
        $s = explode ('|', $line);
        $o_value = trim ($s[0]);
        if ($strip_optgroup)
-               $o_value = ereg_replace ('^.+\^', '', ereg_replace ('^(.+)&', '\\1 ', $o_value));
+               $o_value = ereg_replace ('^.+%GSKIP%', '', ereg_replace ('^(.+)%GPASS%', '\\1 ', $o_value));
        $a_value = trim ($s[1]);
        if ($which == 'a')
                return "<a href='${a_value}'>${o_value}</a>";
@@ -1249,7 +859,8 @@ function getRSUforRackRow ($rowData = NULL)
        return ($counter['T'] + $counter['W'] + $counter['U']) / ($counter['T'] + $counter['W'] + $counter['U'] + $counter['F']);
 }
 
-function getObjectCount ($rackData)
+// Return a list of object IDs, which can be found in the given rackspace block.
+function stuffInRackspace ($rackData)
 {
        $objects = array();
        for ($i = $rackData['height']; $i > 0; $i--)
@@ -1260,16 +871,7 @@ function getObjectCount ($rackData)
                                !in_array ($rackData[$i][$locidx]['object_id'], $objects)
                        )
                                $objects[] = $rackData[$i][$locidx]['object_id'];
-       return count ($objects);
-}
-
-// Perform substitutions and return resulting string
-function apply_macros ($macros, $subject)
-{
-       $ret = $subject;
-       foreach ($macros as $search => $replace)
-               $ret = str_replace ($search, $replace, $ret);
-       return $ret;
+       return $objects;
 }
 
 // Make sure the string is always wrapped with LF characters
@@ -1410,11 +1012,11 @@ function getOrphanedTags ()
        return array();
 }
 
-function serializeTags ($trail, $baseurl = '')
+function serializeTags ($chain, $baseurl = '')
 {
        $comma = '';
        $ret = '';
-       foreach ($trail as $taginfo)
+       foreach ($chain as $taginfo)
        {
                $ret .= $comma .
                        ($baseurl == '' ? '' : "<a href='${baseurl}tagfilter[]=${taginfo['id']}'>") .
@@ -1425,16 +1027,16 @@ function serializeTags ($trail, $baseurl = '')
        return $ret;
 }
 
-// a helper for getTrailExpansion()
-function traceTrail ($tree, $trail)
+// a helper for getTagChainExpansion()
+function traceTagChain ($tree, $chain)
 {
        // For each tag find its path from the root, then combine items
-       // of all paths and add them to the trail, if they aren't there yet.
+       // of all paths and add them to the chain, if they aren't there yet.
        $ret = array();
        foreach ($tree as $taginfo1)
        {
                $hit = FALSE;
-               foreach ($trail as $taginfo2)
+               foreach ($chain as $taginfo2)
                        if ($taginfo1['id'] == $taginfo2['id'])
                        {
                                $hit = TRUE;
@@ -1442,7 +1044,7 @@ function traceTrail ($tree, $trail)
                        }
                if (count ($taginfo1['kids']) > 0)
                {
-                       $subsearch = traceTrail ($taginfo1['kids'], $trail);
+                       $subsearch = traceTagChain ($taginfo1['kids'], $chain);
                        if (count ($subsearch))
                        {
                                $hit = TRUE;
@@ -1456,18 +1058,18 @@ function traceTrail ($tree, $trail)
 }
 
 // For each tag add all its parent tags onto the list. Don't expect anything
-// except user's tags on the trail.
-function getTrailExpansion ($trail)
+// except user's tags on the chain.
+function getTagChainExpansion ($chain)
 {
        global $tagtree;
-       return traceTrail ($tagtree, $trail);
+       return traceTagChain ($tagtree, $chain);
 }
 
 // Return the list of missing implicit tags.
 function getImplicitTags ($oldtags)
 {
        $ret = array();
-       $newtags = getTrailExpansion ($oldtags);
+       $newtags = getTagChainExpansion ($oldtags);
        foreach ($newtags as $newtag)
        {
                $already_exists = FALSE;
@@ -1484,8 +1086,8 @@ function getImplicitTags ($oldtags)
        return $ret;
 }
 
-// Minimize the trail: exclude all implicit tags and return the resulting trail.
-function getExplicitTagsOnly ($trail, $tree = NULL)
+// Minimize the chain: exclude all implicit tags and return the result.
+function getExplicitTagsOnly ($chain, $tree = NULL)
 {
        global $tagtree;
        if ($tree === NULL)
@@ -1495,7 +1097,7 @@ function getExplicitTagsOnly ($trail, $tree = NULL)
        {
                if (isset ($taginfo['kids']))
                {
-                       $harvest = getExplicitTagsOnly ($trail, $taginfo['kids']);
+                       $harvest = getExplicitTagsOnly ($chain, $taginfo['kids']);
                        if (count ($harvest) > 0)
                        {
                                $ret = array_merge ($ret, $harvest);
@@ -1503,7 +1105,7 @@ function getExplicitTagsOnly ($trail, $tree = NULL)
                        }
                }
                // This tag isn't implicit, test is for being explicit.
-               foreach ($trail as $testtag)
+               foreach ($chain as $testtag)
                        if ($taginfo['id'] == $testtag['id'])
                        {
                                $ret[] = $testtag;
@@ -1513,7 +1115,7 @@ function getExplicitTagsOnly ($trail, $tree = NULL)
        return $ret;
 }
 
-// Maximize the trail: for each tag add all tags, for which it is direct or indirect parent.
+// Maximize the chain: for each tag add all tags, for which it is direct or indirect parent.
 // Unlike other functions, this one accepts and returns a list of integer tag IDs, not
 // a list of tag structures.
 function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
@@ -1541,92 +1143,178 @@ function complementByKids ($idlist, $tree = NULL, $getall = FALSE)
        return $ret;
 }
 
-function loadRackObjectAutoTags()
+function getUserAutoTags ($username = NULL)
+{
+       global $remote_username, $accounts;
+       if ($username == NULL)
+               $username = $remote_username;
+       $ret = array();
+       $ret[] = array ('tag' => '$username_' . $username);
+       if (isset ($accounts[$username]['user_id']))
+               $ret[] = array ('tag' => '$userid_' . $accounts[$username]['user_id']);
+       return $ret;
+}
+
+function loadRackObjectAutoTags ()
 {
        assertUIntArg ('object_id', __FUNCTION__);
        $object_id = $_REQUEST['object_id'];
        $oinfo = getObjectInfo ($object_id);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['object_id']);
+       $ret[] = array ('tag' => '$id_' . $object_id);
+       $ret[] = array ('tag' => '$typeid_' . $oinfo['objtype_id']);
        $ret[] = array ('tag' => '$any_object');
+       if (validTagName ($oinfo['name']))
+               $ret[] = array ('tag' => '$cn_' . $oinfo['name']);
        return $ret;
 }
 
-function loadIPv4PrefixAutoTags()
+// Common code for both prefix and address tag listers.
+function getIPv4PrefixTags ($netid)
 {
-       assertUIntArg ('id', __FUNCTION__);
-       $subnet = getIPRange ($_REQUEST['id']);
+       $netinfo = getIPv4NetworkInfo ($netid);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
-       $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-' . $subnet['mask']);
-       // FIXME: find and list tags for all parent networks
-       $ret[] = array ('tag' => '$any_ipv4net');
+       $ret[] = array ('tag' => '$ip4net-' . str_replace ('.', '-', $netinfo['ip']) . '-' . $netinfo['mask']);
+       // FIXME: find and list tags for all parent networks?
+       $ret[] = array ('tag' => '$any_ip4net');
        $ret[] = array ('tag' => '$any_net');
        return $ret;
 }
 
-function loadRackAutoTags()
+function loadIPv4PrefixAutoTags ()
 {
-       assertUIntArg ('rack_id', __FUNCTION__);
-       $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['rack_id']);
-       $ret[] = array ('tag' => '$any_rack');
-       return $ret;
+       assertUIntArg ('id', __FUNCTION__);
+       return array_merge
+       (
+               array (array ('tag' => '$ip4netid_' . $_REQUEST['id'])),
+               getIPv4PrefixTags ($_REQUEST['id'])
+       );
 }
 
-function loadIPv4AddressAutoTags()
+function loadIPv4AddressAutoTags ()
 {
        assertIPv4Arg ('ip', __FUNCTION__);
+       return array_merge
+       (
+               array (array ('tag' => '$ip4net-' . str_replace ('.', '-', $_REQUEST['ip']) . '-32')),
+               getIPv4PrefixTags (getIPv4AddressNetworkId ($_REQUEST['ip']))
+       );
+}
+
+function loadRackAutoTags ()
+{
+       assertUIntArg ('rack_id', __FUNCTION__);
        $ret = array();
-       $ret[] = array ('tag' => '$ipv4net-' . str_replace ('.', '-', $subnet['ip']) . '-32');
-       // FIXME: find and list tags for all parent networks
-       $ret[] = array ('tag' => '$any_ipv4net');
-       $ret[] = array ('tag' => '$any_net');
+       $ret[] = array ('tag' => '$rackid_' . $_REQUEST['rack_id']);
+       $ret[] = array ('tag' => '$any_rack');
        return $ret;
 }
 
-function loadIPv4VSAutoTags()
+function loadIPv4VSAutoTags ()
 {
-       assertUIntArg ('id', __FUNCTION__);
+       assertUIntArg ('vs_id', __FUNCTION__);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['id']);
+       $ret[] = array ('tag' => '$ipv4vsid_' . $_REQUEST['vs_id']);
        $ret[] = array ('tag' => '$any_ipv4vs');
        $ret[] = array ('tag' => '$any_vs');
        return $ret;
 }
 
-function loadIPv4RSPoolAutoTags()
+function loadIPv4RSPoolAutoTags ()
 {
        assertUIntArg ('pool_id', __FUNCTION__);
        $ret = array();
-       $ret[] = array ('tag' => '$id_' . $_REQUEST['pool_id']);
-       $ret[] = array ('tag' => '$any_ipv4rspool');
-       $ret[] = array ('tag' => '$any_rspool');
+       $ret[] = array ('tag' => '$ipv4rspid_' . $_REQUEST['pool_id']);
+       $ret[] = array ('tag' => '$any_ipv4rsp');
+       $ret[] = array ('tag' => '$any_rsp');
        return $ret;
 }
 
-function getGlobalAutoTags()
+// Check, if the given tag is present on the chain (will only work
+// for regular tags with tag ID set.
+function tagOnChain ($taginfo, $tagchain)
 {
-       global $remote_username, $accounts;
-       $ret = array();
-       $user_id = 0;
-       foreach ($accounts as $a)
-               if ($a['user_name'] == $remote_username)
-               {
-                       $user_id = $a['user_id'];
-                       break;
-               }
-       $ret[] = array ('tag' => '$username_' . $remote_username);
-       $ret[] = array ('tag' => '$userid_' . $user_id);
-       return $ret;
+       if (!isset ($taginfo['id']))
+               return FALSE;
+       foreach ($tagchain as $test)
+               if ($test['id'] == $taginfo['id'])
+                       return TRUE;
+       return FALSE;
+}
+
+// Idem, but use ID list instead of chain.
+function tagOnIdList ($taginfo, $tagidlist)
+{
+       if (!isset ($taginfo['id']))
+               return FALSE;
+       foreach ($tagidlist as $tagid)
+               if ($taginfo['id'] == $tagid)
+                       return TRUE;
+       return FALSE;
+}
+
+// Return TRUE, if two tags chains differ (order of tags doesn't matter).
+// Assume, that neither of the lists contains duplicates.
+// FIXME: a faster, than O(x^2) method is possible for this calculation.
+function tagChainCmp ($chain1, $chain2)
+{
+       if (count ($chain1) != count ($chain2))
+               return TRUE;
+       foreach ($chain1 as $taginfo1)
+               if (!tagOnChain ($taginfo1, $chain2))
+                       return TRUE;
+       return FALSE;
+}
+
+// If the page-tab-op triplet is final, make $expl_tags and $impl_tags
+// hold all appropriate (explicit and implicit) tags respectively.
+// Otherwise some limited redirection is necessary (only page and tab
+// names are preserved, ophandler name change isn't handled).
+function fixContext ()
+{
+       global $pageno, $tabno, $auto_tags, $expl_tags, $impl_tags, $page;
+
+       $pmap = array
+       (
+               'accounts' => 'userlist',
+               'rspools' => 'ipv4rsplist',
+               'rspool' => 'ipv4rsp',
+               'vservices' => 'ipv4vslist',
+               'vservice' => 'ipv4vs',
+       );
+       $tmap = array();
+       $tmap['objects']['newmulti'] = 'addmore';
+       $tmap['objects']['newobj'] = 'addmore';
+       $tmap['object']['switchvlans'] = 'livevlans';
+       $tmap['object']['slb'] = 'editrspvs';
+       $tmap['object']['portfwrd'] = 'nat4';
+       $tmap['object']['network'] = 'ipv4';
+       if (isset ($pmap[$pageno]))
+               redirectUser ($pmap[$pageno], $tabno);
+       if (isset ($tmap[$pageno][$tabno]))
+               redirectUser ($pageno, $tmap[$pageno][$tabno]);
+
+       if (isset ($page[$pageno]['autotagloader']))
+               $auto_tags = $page[$pageno]['autotagloader'] ();
+       if
+       (
+               isset ($page[$pageno]['tagloader']) and
+               isset ($page[$pageno]['bypass']) and
+               isset ($_REQUEST[$page[$pageno]['bypass']])
+       )
+       {
+               $expl_tags = $page[$pageno]['tagloader'] ($_REQUEST[$page[$pageno]['bypass']]);
+               $impl_tags = getImplicitTags ($expl_tags);
+       }
 }
 
-// Build a tag trail from supplied tag id list and return it.
-function buildTrailFromIds ($tagidlist)
+// Take a list of user-supplied tag IDs to build a list of valid taginfo
+// records indexed by tag IDs (tag chain).
+function buildTagChainFromIds ($tagidlist)
 {
        global $taglist;
        $ret = array();
-       foreach ($tagidlist as $tag_id)
+       foreach (array_unique ($tagidlist) as $tag_id)
                if (isset ($taglist[$tag_id]))
                        $ret[] = $taglist[$tag_id];
        return $ret;
@@ -1670,10 +1358,207 @@ function getTagFilter ()
 function getTagFilterStr ($tagfilter = array())
 {
        $ret = '';
-       $tagfilter = isset ($_REQUEST['tagfilter']) ? $_REQUEST['tagfilter'] : array();
-       foreach (getExplicitTagsOnly (buildTrailFromIds ($tagfilter)) as $taginfo)
+       foreach (getExplicitTagsOnly (buildTagChainFromIds ($tagfilter)) as $taginfo)
                $ret .= "&tagfilter[]=" . $taginfo['id'];
        return $ret;
 }
 
+function buildWideRedirectURL ($log, $nextpage = NULL, $nexttab = NULL)
+{
+       global $root, $page, $pageno, $tabno;
+       if ($nextpage === NULL)
+               $nextpage = $pageno;
+       if ($nexttab === NULL)
+               $nexttab = $tabno;
+       $url = "${root}?page=${nextpage}&tab=${nexttab}";
+       if (isset ($page[$nextpage]['bypass']))
+               $url .= '&' . $page[$nextpage]['bypass'] . '=' . $_REQUEST[$page[$nextpage]['bypass']];
+       $url .= "&log=" . urlencode (base64_encode (serialize ($log)));
+       return $url;
+}
+
+function buildRedirectURL ($status, $args = array(), $nextpage = NULL, $nexttab = NULL)
+{
+       global $msgcode, $pageno, $tabno, $op;
+       if ($nextpage === NULL)
+               $nextpage = $pageno;
+       if ($nexttab === NULL)
+               $nexttab = $tabno;
+       return buildWideRedirectURL (oneLiner ($msgcode[$pageno][$tabno][$op][$status], $args), $nextpage, $nexttab);
+}
+
+// Return a message log consisting of only one message.
+function oneLiner ($code, $args = array())
+{
+       $ret = array ('v' => 2);
+       $ret['m'][] = count ($args) ? array ('c' => $code, 'a' => $args) : array ('c' => $code);
+       return $ret;
+}
+
+// Return mesage code by status code.
+function getMessageCode ($status)
+{
+       global $pageno, $tabno, $op, $msgcode;
+       return $msgcode[$pageno][$tabno][$op][$status];
+}
+
+function validTagName ($s, $allow_autotag = FALSE)
+{
+       if (1 == mb_ereg (TAGNAME_REGEXP, $s))
+               return TRUE;
+       if ($allow_autotag and 1 == mb_ereg (AUTOTAGNAME_REGEXP, $s))
+               return TRUE;
+       return FALSE;
+}
+
+function redirectUser ($p, $t)
+{
+       global $page, $root;
+       $l = "{$root}?page=${p}&tab=${t}";
+       if (isset ($page[$p]['bypass']) and isset ($_REQUEST[$page[$p]['bypass']]))
+               $l .= '&' . $page[$p]['bypass'] . '=' . $_REQUEST[$page[$p]['bypass']];
+       header ("Location: " . $l);
+       die;
+}
+
+function getRackCodeStats ()
+{
+       global $rackCode;
+       $defc = $grantc = 0;
+       foreach ($rackCode as $s)
+               switch ($s['type'])
+               {
+                       case 'SYNT_DEFINITION':
+                               $defc++;
+                               break;
+                       case 'SYNT_GRANT':
+                               $grantc++;
+                               break;
+                       default:
+                               break;
+               }
+       $ret = array ('Definition sentences' => $defc, 'Grant sentences' => $grantc);
+       return $ret;
+}
+
+function getRackImageWidth ()
+{
+       return 3 + getConfigVar ('rtwidth_0') + getConfigVar ('rtwidth_1') + getConfigVar ('rtwidth_2') + 3;
+}
+
+function getRackImageHeight ($units)
+{
+       return 3 + 3 + $units * 2;
+}
+
+// Perform substitutions and return resulting string
+// used solely by buildLVSConfig()
+function apply_macros ($macros, $subject)
+{
+       $ret = $subject;
+       foreach ($macros as $search => $replace)
+               $ret = str_replace ($search, $replace, $ret);
+       return $ret;
+}
+
+function buildLVSConfig ($object_id = 0)
+{
+       if ($object_id <= 0)
+       {
+               showError ('Invalid argument', __FUNCTION__);
+               return;
+       }
+       $oInfo = getObjectInfo ($object_id);
+       $lbconfig = getSLBConfig ($object_id);
+       if ($lbconfig === NULL)
+       {
+               showError ('getSLBConfig() failed', __FUNCTION__);
+               return;
+       }
+       $newconfig = "#\n#\n# This configuration has been generated automatically by RackTables\n";
+       $newconfig .= "# for object_id == ${object_id}\n# object name: ${oInfo['name']}\n#\n#\n\n\n";
+       foreach ($lbconfig as $vs_id => $vsinfo)
+       {
+               $newconfig .=  "########################################################\n" .
+                       "# VS (id == ${vs_id}): " . (empty ($vsinfo['vs_name']) ? 'NO NAME' : $vsinfo['vs_name']) . "\n" .
+                       "# RS pool (id == ${vsinfo['pool_id']}): " . (empty ($vsinfo['pool_name']) ? 'ANONYMOUS' : $vsinfo['pool_name']) . "\n" .
+                       "########################################################\n";
+               # The order of inheritance is: VS -> LB -> pool [ -> RS ]
+               $macros = array
+               (
+                       '%VIP%' => $vsinfo['vip'],
+                       '%VPORT%' => $vsinfo['vport'],
+                       '%PROTO%' => $vsinfo['proto'],
+                       '%VNAME%' =>  $vsinfo['vs_name'],
+                       '%RSPOOLNAME%' => $vsinfo['pool_name']
+               );
+               $newconfig .=  "virtual_server ${vsinfo['vip']} ${vsinfo['vport']} {\n";
+               $newconfig .=  "\tprotocol ${vsinfo['proto']}\n";
+               $newconfig .= apply_macros
+               (
+                       $macros,
+                       lf_wrap ($vsinfo['vs_vsconfig']) .
+                       lf_wrap ($vsinfo['lb_vsconfig']) .
+                       lf_wrap ($vsinfo['pool_vsconfig'])
+               );
+               foreach ($vsinfo['rslist'] as $rs)
+               {
+                       $macros['%RSIP%'] = $rs['rsip'];
+                       $macros['%RSPORT%'] = $rs['rsport'];
+                       $newconfig .=  "\treal_server ${rs['rsip']} ${rs['rsport']} {\n";
+                       $newconfig .= apply_macros
+                       (
+                               $macros,
+                               lf_wrap ($vsinfo['vs_rsconfig']) .
+                               lf_wrap ($vsinfo['lb_rsconfig']) .
+                               lf_wrap ($vsinfo['pool_rsconfig']) .
+                               lf_wrap ($rs['rs_rsconfig'])
+                       );
+                       $newconfig .=  "\t}\n";
+               }
+               $newconfig .=  "}\n\n\n";
+       }
+       return $newconfig;
+}
+
+// Indicate occupation state of each IP address: none, ordinary or problematic.
+function markupIPv4AddrList (&$addrlist)
+{
+       foreach (array_keys ($addrlist) as $ip_bin)
+       {
+               $singlealloc = 0;
+               $nvirtual = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'shared', 'eq');
+               $nloopback = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'virtual', 'eq');
+               $nconnected = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'regular', 'eq');
+               $nrouter = countRefsOfType ($addrlist[$ip_bin]['allocs'], 'router', 'eq');
+               $nsl = ($nvirtual + $nloopback > 0) ? 1 : 0;
+               $nrsv = ($addrlist[$ip_bin]['reserved'] == 'yes') ? 1 : 0;
+               $nrealms = $nrsv + $nsl + $nconnected + $nrouter;
+               
+               if ($nrealms == 1)
+                       $addrlist[$ip_bin]['class'] = 'trbusy';
+               elseif ($nrealms > 1)
+                       $addrlist[$ip_bin]['class'] = 'trerror';
+               else
+                       $addrlist[$ip_bin]['class'] = '';
+       }
+}
+
+// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
+function findRouters ($addrlist)
+{
+       $ret = array();
+       foreach ($addrlist as $addr)
+               foreach ($addr['allocs'] as $alloc)
+                       if ($alloc['type'] == 'router')
+                               $ret[] = array
+                               (
+                                       'id' => $alloc['object_id'],
+                                       'iface' => $alloc['name'],
+                                       'dname' => $alloc['object_name'],
+                                       'addr' => $addr['ip']
+                               );
+       return $ret;
+}
+
 ?>