r4004 new feature: IPv6 support
authorAlexey Andriyanov <alan@al-an.info>
Wed, 17 Nov 2010 09:34:07 +0000 (09:34 +0000)
committerAlexey Andriyanov <alan@al-an.info>
Wed, 17 Nov 2010 09:34:07 +0000 (09:34 +0000)
Classes:
 * new class IPv6Address. Stores, formats, parses v6 addresses, does simple arythmetics.

DB schema:
 * IPv6Address table branched from IPv4Address
 * IPv6Allocation table branched from IPv4Allocation
 * IPv6Network table branched from IPv4Network
 * VLANIPv6 table branched from VLANIPv4
 * enum field `entity_type` extended to 'ipv6net' in table FileLink
 * enum field `entity_realm` extended to 'ipv6net' in table TagStorage

Ophandlers:
 * updIPv6Allocation added (brached from updIPv4Allocation)
 * delIPv6Allocation added (brached from delIPv4Allocation)
 * addIPv6Allocation added (brached from addIPv4Allocation)
 * addIPv6Prefix added (brached from addIPv4Prefix)
 * delIPv6Prefix added (brached from delIPv4Prefix)
 * updIPv6Prefix added (brached from updIPv4Prefix)
 * editvv6Address added (brached from editAddress)
 * bindVLANtoIPv6Net added (branched from bindVLANtoIPv4Net)
 * unbindVLANtoIPv6Net added (branched from unbindVLANtoIPv4Net)

inc/navidagion.php:
 * new page 'ipv6space' added to main page layout
 * new tab 'ipv6' added to page 'object'
 * titles from 'IPv4/IPv6 space' pages removed, they are now calculated in dynamic_title_decoder
 * new page 'ipv6address' added
 * new report 'ipv6' added
 * new tab 'ipv6' added to 'vlan' page
 * triggers for 'ipv4/ipv6' tabs added to 'vlan' page

inc/interface.php:
 * renderIndexItem now asks dynamic_title_decoder for page title unless it is set in $pages array
 * renderRackObject:
    * renders both ipv4 and ipv6 allocationd of default tab
* groups allocations by iface name and sorts iface names using sortPortList
* new http req arg 'hl_ipv6_addr
 * renderIPv6ForObject added (branched from renderIPv4ForObject)
 * new status codes for IPv6 added
 * renderIPv6Space added (branched from renderIPv4Space)
 * renderIPv6SpaceRecords added (branched from renderIPv4SpaceRecords)
 * formatIPv6NetUsage added. It generates text like '3 of 4G /64 nets' by used address count and v6net prefix length.
 * renderIPv4SpaceEditor: rendering of delete buttons siplified, the same logic was keeped
 * renderIPv6SpaceEditor added (branched from renderIPv4SpaceEditor)
 * renderIPv6Network added with satellites (renderIPv6NetworkAddresses, renderEmptyIPv6, renderSeparator, getPageNumOfIPv6)
 * renderSearchResults extended to support searching by IPv6 net/addr info
 * renderIPv6Reports added
 * renderTagStats extended to support new realm 'ipv6net'
 * printRoutersTD: router can have v6 address
 * showPathAndSearch now supports ancors in 'params' array (key must be named as '#')
 * dynamic_title_decoder:
    * decodes titles for 'ipv6address' and 'ipv6net' pages
* link to ipv[46]space page now support targeting on matching network with highlight and target support
 * renderVLANInfo shows v6 nets
 * function renderIPv4NetworkProperties renamed to renderIPNetworkProperties for handling both v4/v6 nets
* function printIPv4NetInfoTDs renamed to printIPNetInfoTDs, idem
 * function renderVLANIPv4 renamed to renderVLANIPLinks, idem
 * function render renderIPv4Address renamed to renderIPAddress, idem
 * function renderIPv4AddressProperties renamed to renderIPAddressProperties, idem
 * function renderIPv4AddressAllocations renamed to renderIPAddressAllocations, idem

inc/database.php:
 * scanIPv6Space added, branched from scanIPv4Space
 * getIPv6Address added
 * bindIPv6ToObject added
 * getIPv6AddressNetworkId added
 * new meta-function updateAddress, decides between updateV6Address and updateV4Address
 * updateIPv6Bond added
 * unbindIPv6FromObject added
 * getIPv6PrefixSearchResult added
 * getIPv6AddressSearchResult added
 * getObjectIfacesSearchResults searches for both v4/v6 addresses
 * getIPv6Stats added
 * getRackspaceStats: calculate sum of v4/v6 nets count, keyed with 'ipnet'
 * createIPv6Prefix added
 * destroyIPv6Prefix added
 * getDomainVLANs: calculate sum of v4/v6 nets count, fetches both v4 and v6 net ids
 * getVLANIPv6Options added
 * commitSupplementVLANIPv6 added
 * commitReduceVLANIPv6 added
 * getIPv6Network8021QBindings added

inc/functions.php:
 * assertIPArg: new meta-function, used to decide which type of argument was passed (v4/v6 address)
 * assertIPv6Arg: new function, parses v6 address, throws exception otherwise
 * IPv6NetworkCmp added. simple bitstring compare function
 * ipv6tree_fill added (branched from ipv4tree_fill)
 * ipv6tree_construct added (branched from ipv4tree_construct)
 * iptree_embed: removed 'db_first' and 'db_last' keys cause they are not used any more
 * ipv6tree_embed added (branched from iptree_embed)
 * countOwnIPv4Addresses: removed unused old code for searching in DB. Now that function only counts net width, without used addresses.
 * loadOwnIPv4Addresses: same logic, but calclulation of db_first/db_last moved here from iptree_embed
 * loadIPv6AddrList addded
 * loadOwnIPv6Addresses added (branched from loadOwnIPv4Addresses)
 * prepareIPv6Tree added (branched from prepareIPv4Tree)

inc/triggers.php:
 * new trigger trigger_ipv6 (branched from trigger_ipv4)
 * new trigger trigger_ipv6net_vlanconfig (branched from trigger_ipv4net_vlanconfig)
 * new triggers trigger_vlan_ipv4net
 * new trigger_vlan_ipv6net

12 files changed:
ChangeLog
inc/IPv6.php [new file with mode: 0644]
inc/database.php
inc/functions.php
inc/init.php
inc/interface.php
inc/navigation.php
inc/ophandlers.php
inc/triggers.php
install/init-structure.sql
pix/addressspacev6.png [new file with mode: 0644]
upgrade.php

index 1490d28..bca18ce 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,5 @@
 0.19.0
+       new feature: IPv6 support, except of LivePTR, NAT and SLB functionality (by Alexey Andriyanov)
        update: cache image files thumbnails and make them JPEGs (#369, by Matt Mills)
 0.18.6
        bugfix: draw administrator's attention to missing LDAP extension (by Matt Mills)
diff --git a/inc/IPv6.php b/inc/IPv6.php
new file mode 100644 (file)
index 0000000..87dd128
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+
+class IPv6Address
+{
+       const zero_address = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
+       protected $words = self::zero_address;
+
+function __construct ($bin_str = self::zero_address)
+{
+       if (strlen ($bin_str) < 16)
+               $bin_str .= substr (self::zero_address, 0, 16 - strlen ($bin_str));
+       elseif (strlen ($bin_str) > 16)
+               $bin_str = substr ($bin_str, 0, 16);
+       $this->words = $bin_str;
+}
+
+// object in string context interpolates as 16-byte binary string 
+function __toString ()
+{
+       return $this->words;
+}
+
+private static function set_word_value (&$haystack, $nword, $hexvalue)
+{
+       // check that $hexvalue is like /^[0-9a-fA-F]*$/
+       for ($i = 0; $i < strlen ($hexvalue); $i++)
+       {
+               $char = ord ($hexvalue[$i]);
+               if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
+                       return FALSE;
+       }
+       $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
+       return TRUE;
+}
+
+// returns bool - was the object modified or not.
+// return true only if address syntax is completely correct.
+function parse ($str_ipv6)
+{
+       $result = self::zero_address;
+       // remove one of double beginning/tailing colons
+       if (substr ($str_ipv6, 0, 2) == '::')
+               $str_ipv6 = substr ($str_ipv6, 1);
+       elseif (substr ($str_ipv6, -2, 2) == '::')
+               $str_ipv6 = substr ($str_ipv6, 0, strlen ($str_ipv6) - 1);
+
+       $tokens = explode (':', $str_ipv6);
+       if (empty ($tokens))
+               return FALSE;
+
+       $last_token = $tokens[count ($tokens) - 1];
+       $split = explode ('.', $last_token);
+       if (count ($split) == 4)
+       {
+               $hex_tokens = array();
+               $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
+               $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
+               array_splice ($tokens, -1, 1, $hex_tokens);
+       }
+       if (count ($tokens) > 8)
+               return FALSE;
+       for ($i = 0; $i < count ($tokens); $i++)
+       {
+               if ($tokens[$i] != '')
+               {
+                       if (! self::set_word_value ($result, $i, $tokens[$i]))
+                               return FALSE;
+               }
+               else
+               {
+                       $k = 8; //index in result string (last word)
+                       for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
+                               if ($tokens[$j] == '')
+                                       break;
+                               elseif (! self::set_word_value ($result, --$k, $tokens[$j]))
+                                       return FALSE;
+                       if ($i != $j)
+                               return FALSE; //error, more than 1 '::' range
+                       break;
+               }
+       }
+       if (! isset ($k) && count ($tokens) != 8)
+               return FALSE;
+       $this->words = $result;
+       return TRUE;
+}
+
+function format ()
+{
+       // maybe this is IPv6-to-IPv4 address?
+       if (substr ($this->words, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
+               return '::ffff:' . implode ('.', unpack ('C*', substr ($this->words, 12, 4)));
+
+       $result = array();
+       $hole_index = NULL;
+       $max_hole_index = NULL;
+       $hole_length = 0;
+       $max_hole_length = 0;
+
+       for ($i = 0; $i < 8; $i++)
+       {
+               $value = array_shift (unpack ('n', substr ($this->words, $i * 2, 2)));
+               $result[] = dechex ($value & 0xffff);
+               if ($value != 0)
+               {
+                       unset ($hole_index);
+                       $hole_length = 0;
+               }
+               else
+               {
+                       if (! isset ($hole_index))
+                               $hole_index = $i;
+                       if (++$hole_length >= $max_hole_length)
+                       {
+                               $max_hole_index = $hole_index;
+                               $max_hole_length = $hole_length;
+                       }
+               }
+       }
+       if (isset ($max_hole_index))
+       {
+               array_splice ($result, $max_hole_index, $max_hole_length, array (''));
+               if ($max_hole_index == 0 && $max_hole_length == 8)
+                       return '::';
+               elseif ($max_hole_index == 0)
+                       return ':' . implode (':', $result);
+               elseif ($max_hole_index + $max_hole_length == 8)
+                       return implode (':', $result) . ':';
+       }
+       return implode (':', $result);
+}
+
+// returns new object with applied mask, or NULL if mask is incorrect
+function get_first_subnet_address ($prefix_length)
+{
+       if ($prefix_length < 0 || $prefix_length > 128)
+               return NULL;
+       $result = clone $this;
+       if ($prefix_length == 128)
+               return $result;
+       $char_num = intval ($prefix_length / 8);
+       if (0xff00 != $bitmask = 0xff00 >> ($prefix_length % 8))
+       {
+               $result->words[$char_num] = chr (ord ($result->words[$char_num]) & $bitmask);
+               ++$char_num;
+       }
+       for ($i = $char_num; $i < 16; $i++)
+               $result->words[$i] = "\0";
+       return $result;
+}
+
+// returns new object with applied mask, or NULL if mask is incorrect
+function get_last_subnet_address ($prefix_length)
+{
+       if ($prefix_length < 0 || $prefix_length > 128)
+               return NULL;
+       $result = clone $this;
+       if ($prefix_length == 128)
+               return $result;
+       $char_num = intval ($prefix_length / 8);
+       if (0xff != $bitmask = 0xff >> ($prefix_length % 8))
+       {
+               $result->words[$char_num] = chr (ord ($result->words[$char_num]) | $bitmask);
+               ++$char_num;
+       }
+       for ($i = $char_num; $i < 16; $i++)
+               $result->words[$i] = "\xff";
+       return $result;
+}
+
+// returns new object
+function next ()
+{
+       $result = clone $this;
+       for ($i = 15; $i >= 0; $i--)
+       {
+               if ($result->words[$i] == "\xff")
+                       $result->words[$i] = "\0";
+               else
+               {
+                       $result->words[$i] = chr (ord ($result->words[$i]) + 1);
+                       break;
+               }
+       }
+       return $result;
+}
+
+// returns new object
+function prev ()
+{
+       $result = clone $this;
+       for ($i = 15; $i >= 0; $i--)
+       {
+               if ($result->words[$i] == "\0")
+                       $result->words[$i] = "\xff";
+               else
+               {
+                       $result->words[$i] = chr (ord ($result->words[$i]) - 1);
+                       break;
+               }
+       }
+       return $result;
+}
+
+
+} // class IPv6Address
+
+?>
index d687450..d217217 100644 (file)
@@ -59,6 +59,22 @@ $SQLSchema = array
                'keycolumn' => 'id',
                'ordcolumns' => array ('IPv4Network.ip', 'IPv4Network.mask'),
        ),
+       'ipv6net' => array
+       (
+               'table' => 'IPv6Network',
+               'columns' => array
+               (
+                       'id' => 'id',
+                       'ip_bin' => 'ip',
+                       'mask' => 'mask',
+                       'name' => 'name',
+                       'comment' => 'comment',
+                       'parent_id' => '(SELECT id FROM IPv6Network AS subt WHERE IPv6Network.ip >= subt.ip AND IPv6Network.last_ip <= subt.last_ip AND IPv6Network.mask > subt.mask ORDER BY subt.mask DESC limit 1)',
+                       'vlanc' => '(SELECT COUNT(*) FROM VLANIPv6 WHERE ipv6net_id = id)',
+               ),
+               'keycolumn' => 'id',
+               'ordcolumns' => array ('IPv6Network.ip', 'IPv6Network.mask'),
+       ),
        'file' => array
        (
                'table' => 'File',
@@ -306,8 +322,10 @@ function listCells ($realm, $parent_id = 0)
                        $ret[$entity_id]['ip_bin'] = ip2long ($ret[$entity_id]['ip']);
                        $ret[$entity_id]['mask_bin'] = binMaskFromDec ($ret[$entity_id]['mask']);
                        $ret[$entity_id]['mask_bin_inv'] = binInvMaskFromDec ($ret[$entity_id]['mask']);
-                       $ret[$entity_id]['db_first'] = sprintf ('%u', 0x00000000 + $ret[$entity_id]['ip_bin'] & $ret[$entity_id]['mask_bin']);
-                       $ret[$entity_id]['db_last'] = sprintf ('%u', 0x00000000 + $ret[$entity_id]['ip_bin'] | ($ret[$entity_id]['mask_bin_inv']));
+                       break;
+               case 'ipv6net':
+                       $ret[$entity_id]['ip_bin'] = new IPv6Address ($ret[$entity_id]['ip_bin']);
+                       $ret[$entity_id]['ip'] = $ret[$entity_id]['ip_bin']->format();
                        break;
                default:
                        break;
@@ -384,8 +402,10 @@ function spotEntity ($realm, $id)
                $ret['ip_bin'] = ip2long ($ret['ip']);
                $ret['mask_bin'] = binMaskFromDec ($ret['mask']);
                $ret['mask_bin_inv'] = binInvMaskFromDec ($ret['mask']);
-               $ret['db_first'] = sprintf ('%u', 0x00000000 + $ret['ip_bin'] & $ret['mask_bin']);
-               $ret['db_last'] = sprintf ('%u', 0x00000000 + $ret['ip_bin'] | ($ret['mask_bin_inv']));
+               break;
+       case 'ipv6net':
+               $ret['ip_bin'] = new IPv6Address ($ret['ip_bin']);
+               $ret['ip'] = $ret['ip_bin']->format();
                break;
        default:
                break;
@@ -402,6 +422,7 @@ function amplifyCell (&$record, $dummy = NULL)
        case 'object':
                $record['ports'] = getObjectPortsAndLinks ($record['id']);
                $record['ipv4'] = getObjectIPv4Allocations ($record['id']);
+               $record['ipv6'] = getObjectIPv6Allocations ($record['id']);
                $record['nat4'] = getNATv4ForObject ($record['id']);
                $record['ipv4rspools'] = getRSPoolsForObject ($record['id']);
                $record['files'] = getFilesOfEntity ($record['realm'], $record['id']);
@@ -495,6 +516,9 @@ function amplifyCell (&$record, $dummy = NULL)
        case 'ipv4net':
                $record['8021q'] = getIPv4Network8021QBindings ($record['id']);
                break;
+       case 'ipv6net':
+               $record['8021q'] = getIPv6Network8021QBindings ($record['id']);
+               break;
        default:
        }
 }
@@ -1016,11 +1040,31 @@ function getObjectIPv4Allocations ($object_id = 0)
        return $ret;
 }
 
+// Return all IPv6 addresses allocated to the objects. Attach detailed
+// info about address to each alocation records. Index result by binary string of IPv6
+function getObjectIPv6Allocations ($object_id = 0)
+{
+       $ret = array();
+       $result = usePreparedSelectBlade
+       (
+               'SELECT name AS osif, type, ip AS ip FROM IPv6Allocation ' .
+               'WHERE object_id = ? ORDER BY ip',
+               array ($object_id)
+       );
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[$row['ip']] = array ('osif' => $row['osif'], 'type' => $row['type']);
+       unset ($result);
+       foreach (array_keys ($ret) as $ip_bin)
+               $ret[$ip_bin]['addrinfo'] = getIPv6Address (new IPv6Address ($ip_bin));
+       return $ret;
+}
+
 // Return minimal IPv4 address, optionally with "ip" key set, if requested.
 function constructIPv4Address ($dottedquad = NULL)
 {
        $ret = array
        (
+               'version' => 4,
                'name' => '',
                'reserved' => 'no',
                'outpf' => array(),
@@ -1034,6 +1078,21 @@ function constructIPv4Address ($dottedquad = NULL)
        return $ret;
 }
 
+// Return minimal IPv6 address, optionally with "ip" key set, if requested.
+function constructIPv6Address ($bin_ip = NULL)
+{
+       $ret = array
+       (
+               'version' => 6,
+               'name' => '',
+               'reserved' => 'no',
+               'allocs' => array(),
+       );
+       if ($bin_ip != NULL)
+               $ret['ip'] = $bin_ip->format();
+       return $ret;
+}
+
 // Check the range requested for meaningful IPv4 records, build them
 // into a list and return. Return an empty list if nothing matched.
 // Both arguments are expected in signed int32 form. The resulting list
@@ -1199,19 +1258,102 @@ function scanIPv4Space ($pairlist)
        return $ret;
 }
 
+// Check the range requested for meaningful IPv6 records, build them
+// into a list and return. Return an empty list if nothing matched.
+// Both arguments are expected as instances of IPv6Address class. The resulting list
+// is keyed by uint32 form of each IP address, items aren't sorted.
+function scanIPv6Space ($pairlist)
+{
+       $ret = array();
+       $wheres = array();
+       foreach ($pairlist as $pair)
+       {
+               $wheres[] = "ip >= ? AND ip <= ?";
+               $qparams[] = (string) $pair['first'];
+               $qparams[] = (string) $pair['last'];
+       }
+       if (! count ($wheres))  // this is normal for a network completely divided into smaller parts
+               return $ret;
+       $whereexpr = '(' .implode (' OR ', $wheres) . ')';
+
+       // 1. collect labels and reservations
+       $query = "select ip, name, reserved from IPv6Address ".
+               "where ${whereexpr} and (reserved = 'yes' or name != '')";
+       $result = usePreparedSelectBlade ($query, $qparams);
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+       {
+               $ip_bin = new IPv6Address ($row['ip']);
+               $key = (string)$ip_bin;
+               if (!isset ($ret[$key]))
+                       $ret[$key] = constructIPv6Address ($ip_bin);
+               $ret[$key]['name'] = $row['name'];
+               $ret[$key]['reserved'] = $row['reserved'];
+       }
+       unset ($result);
+
+       // 2. check for allocations
+       $query =
+               "select ip, object_id, name, type " .
+               "from IPv6Allocation where ${whereexpr} order by type";
+       $result = usePreparedSelectBlade ($query, $qparams);
+       // release DBX early to avoid issues with nested spotEntity() calls
+       $allRows = $result->fetchAll (PDO::FETCH_ASSOC);
+       unset ($result);
+       foreach ($allRows as $row)
+       {
+               $ip_bin = new IPv6Address ($row['ip']);
+               $key = (string)$ip_bin;
+               if (!isset ($ret[$key]))
+                       $ret[$key] = constructIPv6Address ($ip_bin);
+               $oinfo = spotEntity ('object', $row['object_id']);
+               $ret[$key]['allocs'][] = array
+               (
+                       'type' => $row['type'],
+                       'name' => $row['name'],
+                       'object_id' => $row['object_id'],
+                       'object_name' => $oinfo['dname'],
+               );
+       }
+       return $ret;
+}
+
+// this is a wrapper around getIPv4Address and getIPv6Address
+// You can pass dotted IPv4, human representation of IPv6, or instance of IPv6Address
+function getIPAddress ($ip)
+{
+       if (is_a ($ip, 'IPv6Address'))
+               return getIPv6Address ($ip);
+       $ipv6 = new IPv6Address;
+       if ($ipv6->parse ($ip))
+               return getIPv6Address ($ipv6);
+       return getIPv4Address ($ip);
+}
+
 function getIPv4Address ($dottedquad = '')
 {
        if ($dottedquad == '')
                throw new InvalidArgException ('$dottedquad', $dottedquad);
        $i32 = ip2long ($dottedquad); // signed 32 bit
        $scanres = scanIPv4Space (array (array ('i32_first' => $i32, 'i32_last' => $i32)));
-       if (!isset ($scanres[$i32]))
+       if (empty ($scanres))
                //$scanres[$i32] = constructIPv4Address ($dottedquad); // XXX: this should be verified to not break things
                return constructIPv4Address ($dottedquad);
-       markupIPv4AddrList ($scanres);
+       markupIPAddrList ($scanres);
        return $scanres[$i32];
 }
 
+// returns the array of structure described by constructIPv6Address
+function getIPv6Address ($v6addr)
+{
+       if (! is_object ($v6addr))
+               throw new InvalidArgException ('$v6addr', $v6addr);
+       $scanres = scanIPv6Space (array (array ('first' => $v6addr, 'last' => $v6addr)));
+       if (empty ($scanres))
+               return constructIPv6Address ($v6addr);
+       markupIPAddrList ($scanres);
+       return array_shift ($scanres);
+}
+
 function bindIpToObject ($ip = '', $object_id = 0, $name = '', $type = '')
 {
        return usePreparedExecuteBlade
@@ -1221,28 +1363,21 @@ function bindIpToObject ($ip = '', $object_id = 0, $name = '', $type = '')
        );
 }
 
+function bindIPv6ToObject ($ip = '', $object_id = 0, $name = '', $type = '')
+{
+       return usePreparedExecuteBlade
+       (
+               'INSERT INTO IPv6Allocation (ip, object_id, name, type) VALUES (?, ?, ?, ?)',
+               array ($ip, $object_id, $name, $type)
+       );
+}
+
 // Return the id of the smallest IPv4 network containing the given IPv4 address
 // or NULL, if nothing was found. When finding the covering network for
 // another network, it is important to filter out matched records with longer
 // masks (they aren't going to be the right pick).
 function getIPv4AddressNetworkId ($dottedquad, $masklen = 32)
 {
-// N.B. To perform the same for IPv6 address and networks, some pre-requisites
-// are necessary and a different query. IPv6 addresses are 128 bit long, which
-// is too much for both PHP and MySQL data types. These values must be split
-// into 4 32-byte long parts (b_u32_0, b_u32_1, b_u32_2, b_u32_3).
-// Then each network must have its 128-bit netmask split same way and either
-// stored right in its record or JOINed from decoder and accessible as m_u32_0,
-// m_u32_1, m_u32_2, m_u32_3. After that the query to pick the smallest network
-// covering the given address would look as follows:
-// $query = 'select id from IPv6Network as n where ' .
-// "(${b_u32_0} & n.m_u32_0 = n.b_u32_0) and " .
-// "(${b_u32_1} & n.m_u32_1 = n.b_u32_1) and " .
-// "(${b_u32_2} & n.m_u32_2 = n.b_u32_2) and " .
-// "(${b_u32_3} & n.m_u32_3 = n.b_u32_3) and " .
-// "mask < ${masklen} " .
-// 'order by mask desc limit 1';
-
        $query = 'select id from IPv4Network where ' .
                "inet_aton(?) & (4294967295 >> (32 - mask)) << (32 - mask) = ip " .
                "and mask < ? " .
@@ -1253,15 +1388,44 @@ function getIPv4AddressNetworkId ($dottedquad, $masklen = 32)
        return NULL;
 }
 
+// Return the id of the smallest IPv6 network containing the given IPv6 address
+// ($ip is an instance of IPv4Address class) or NULL, if nothing was found.
+function getIPv6AddressNetworkId ($ip, $masklen = 128)
+{
+       $query = 'select id from IPv6Network where ip <= ? AND last_ip >= ? and mask < ? order by mask desc limit 1';
+       $result = usePreparedSelectBlade ($query, array ((string)$ip, (string)$ip, $masklen));
+       if ($row = $result->fetch (PDO::FETCH_ASSOC))
+               return $row['id'];
+       return NULL;
+}
+
+
 function updateIPv4Network_real ($id = 0, $name = '', $comment = '')
 {
        return usePreparedExecuteBlade ('UPDATE IPv4Network SET name = ?, comment = ? WHERE id = ?', array ($name, $comment, $id));
 }
 
+function updateIPv6Network_real ($id = 0, $name = '', $comment = '')
+{
+       return usePreparedExecuteBlade ('UPDATE IPv6Network SET name = ?, comment = ? WHERE id = ?', array ($name, $comment, $id));
+}
+
+// It is a wrapper around updateV4Address and updateV6Address.
+// You can pass dotted IPv4, human representation of IPv6, or instance of IPv6Address
+function updateAddress ($ip = 0, $name = '', $reserved = 'no')
+{
+       if (is_a ($ip, 'IPv6Address'))
+               return updateV6Address ($ip, $name, $reserved);
+       $ipv6 = new IPv6Address;
+       if ($ipv6->parse ($ip))
+               return updateV6Address ($ipv6, $name, $reserved);
+       return updateV4Address ($ip, $name, $reserved);
+}
+
 // This function is actually used not only to update, but also to create records,
 // that's why ON DUPLICATE KEY UPDATE was replaced by DELETE-INSERT pair
 // (MySQL 4.0 workaround).
-function updateAddress ($ip = 0, $name = '', $reserved = 'no')
+function updateV4Address ($ip = 0, $name = '', $reserved = 'no')
 {
        usePreparedExecuteBlade ('DELETE FROM IPv4Address WHERE ip = INET_ATON(?)', array ($ip));
        // INSERT may appear not necessary.
@@ -1275,6 +1439,20 @@ function updateAddress ($ip = 0, $name = '', $reserved = 'no')
        return $ret !== FALSE ? '' : (__FUNCTION__ . 'query failed');
 }
 
+function updateV6Address ($ip, $name = '', $reserved = 'no')
+{
+       usePreparedExecuteBlade ('DELETE FROM IPv6Address WHERE ip = ?', array ($ip));
+       // INSERT may appear not necessary.
+       if ($name == '' and $reserved == 'no')
+               return '';
+       $ret = usePreparedExecuteBlade
+       (
+               'INSERT INTO IPv6Address (name, reserved, ip) VALUES (?, ?, ?)',
+               array ($name, $reserved, $ip)
+       );
+       return $ret !== FALSE ? '' : (__FUNCTION__ . 'query failed');
+}
+
 function updateBond ($ip='', $object_id=0, $name='', $type='')
 {
        return usePreparedExecuteBlade
@@ -1284,7 +1462,16 @@ function updateBond ($ip='', $object_id=0, $name='', $type='')
        );
 }
 
-function unbindIpFromObject ($ip='', $object_id=0)
+function updateIPv6Bond ($ip='', $object_id=0, $name='', $type='')
+{
+       return usePreparedExecuteBlade
+       (
+               'UPDATE IPv6Allocation SET name=?, type=? WHERE ip=? AND object_id=?',
+               array ($name, $type, $ip, $object_id)
+       );
+}
+
+function unbindIpFromObject ($ip, $object_id)
 {
        return usePreparedExecuteBlade
        (
@@ -1293,6 +1480,15 @@ function unbindIpFromObject ($ip='', $object_id=0)
        );
 }
 
+function unbindIPv6FromObject ($ip, $object_id)
+{
+       return usePreparedExecuteBlade
+       (
+               'DELETE FROM IPv6Allocation WHERE ip=? AND object_id=?',
+               array ($ip, $object_id)
+       );
+}
+
 function getIPv4PrefixSearchResult ($terms)
 {
        $byname = getSearchResultByField
@@ -1309,6 +1505,22 @@ function getIPv4PrefixSearchResult ($terms)
        return $ret;
 }
 
+function getIPv6PrefixSearchResult ($terms)
+{
+       $byname = getSearchResultByField
+       (
+               'IPv6Network',
+               array ('id'),
+               'name',
+               $terms,
+               'ip'
+       );
+       $ret = array();
+       foreach ($byname as $row)
+               $ret[] = spotEntity ('ipv6net', $row['id']);
+       return $ret;
+}
+
 function getIPv4AddressSearchResult ($terms)
 {
        $query = "select inet_ntoa(ip) as ip, name from IPv4Address where ";
@@ -1327,6 +1539,24 @@ function getIPv4AddressSearchResult ($terms)
        return $ret;
 }
 
+function getIPv6AddressSearchResult ($terms)
+{
+       $query = "select ip, name from IPv6Address where ";
+       $or = '';
+       $qparams = array();
+       foreach (explode (' ', $terms) as $term)
+       {
+               $query .= $or . "name like ?";
+               $or = ' or ';
+               $qparams[] = "%${term}%";
+       }
+       $result = usePreparedSelectBlade ($query, $qparams);
+       $ret = array();
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret[] = $row;
+       return $ret;
+}
+
 function getIPv4RSPoolSearchResult ($terms)
 {
        $byname = getSearchResultByField
@@ -1645,7 +1875,7 @@ function getPortSearchResults ($what)
 function getObjectIfacesSearchResults ($what)
 {
        $ret = array();
-       $ifaces = getSearchResultByField
+       $ifaces4 = getSearchResultByField
        (
                'IPv4Allocation',
                array ('object_id', 'name'),
@@ -1653,7 +1883,15 @@ function getObjectIfacesSearchResults ($what)
                $what,
                'object_id'
        );
-       foreach ($ifaces as $row)
+       $ifaces6 = getSearchResultByField
+       (
+               'IPv6Allocation',
+               array ('object_id', 'name'),
+               'name',
+               $what,
+               'object_id'
+       );
+       foreach (array_merge ($ifaces4, $ifaces6) as $row)
        {
                $ret[$row['object_id']]['id'] = $row['object_id'];
                $ret[$row['object_id']]['by_iface'][] = $row['name'];
@@ -1826,6 +2064,24 @@ function getIPv4Stats ()
        return $ret;
 }
 
+function getIPv6Stats ()
+{
+       $ret = array();
+       $subject = array();
+       $subject[] = array ('q' => 'select count(id) from IPv6Network', 'txt' => 'Networks');
+       $subject[] = array ('q' => 'select count(ip) from IPv6Address', 'txt' => 'Addresses commented/reserved');
+       $subject[] = array ('q' => 'select count(ip) from IPv6Allocation', 'txt' => 'Addresses allocated');
+
+       foreach ($subject as $item)
+       {
+               $result = usePreparedSelectBlade ($item['q']);
+               $row = $result->fetch (PDO::FETCH_NUM);
+               $ret[$item['txt']] = $row[0];
+               unset ($result);
+       }
+       return $ret;
+}
+
 function getRackspaceStats ()
 {
        $ret = array();
@@ -2785,6 +3041,12 @@ function getTagList ()
                {
                        $ret[$row['id']]['refcnt'][$row['realm']] = $row['refcnt'];
                        $ret[$row['id']]['refcnt']['total'] += $row['refcnt'];
+                       // introduce the 'pseudo'-ream 'ipnet' which combines 'ipv4net' and 'ipv6net' realms.
+                       if ($row['realm'] == 'ipv4net' || $row['realm'] == 'ipv6net')
+                               if (isset ($ret[$row['id']]['refcnt']['ipnet']))
+                                       $ret[$row['id']]['refcnt']['ipnet'] += $row['refcnt'];
+                               else
+                                       $ret[$row['id']]['refcnt']['ipnet'] = $row['refcnt'];
                }
        }
        return $ret;
@@ -2843,7 +3105,8 @@ function deleteTagForEntity ($entity_realm, $entity_id, $tag_id)
 // Push a record into TagStorage unconditionally.
 function addTagForEntity ($realm = '', $entity_id, $tag_id)
 {
-       if (!in_array ($realm, array ('file', 'ipv4net', 'ipv4vs', 'ipv4rspool', 'object', 'rack', 'user')))
+       global $SQLSchema;
+       if (! isset ($SQLSchema[$realm]))
                return FALSE;
        return usePreparedInsertBlade
        (
@@ -2957,12 +3220,44 @@ function createIPv4Prefix ($range = '', $name = '', $is_bcast = FALSE, $taglist
        {
                $network_addr = long2ip ($ipL);
                $broadcast_addr = long2ip ($ipL | binInvMaskFromDec ($maskL));
-               updateAddress ($network_addr, 'network', 'yes');
-               updateAddress ($broadcast_addr, 'broadcast', 'yes');
+               updateV4Address ($network_addr, 'network', 'yes');
+               updateV4Address ($broadcast_addr, 'broadcast', 'yes');
        }
        return produceTagsForLastRecord ('ipv4net', $taglist);
 }
 
+function createIPv6Prefix ($range = '', $name = '', $taglist = array())
+{
+       // $range is in aaa0:b::c:d/x format, split into ip/mask vars
+       $rangeArray = explode ('/', $range);
+       if (count ($rangeArray) != 2)
+               return "Invalid IPv6 prefix '${range}'";
+       $ip = $rangeArray[0];
+       $mask = $rangeArray[1];
+       $address = new IPv6Address;
+       if (!strlen ($ip) or !strlen ($mask) or ! $address->parse ($ip))
+               return "Invalid IPv6 prefix '${range}'";
+       $network_addr = $address->get_first_subnet_address ($mask);
+       $broadcast_addr = $address->get_last_subnet_address ($mask);
+       if (! $network_addr || ! $broadcast_addr)
+               return 'Invalid netmask';
+       $result = usePreparedInsertBlade
+       (
+               'IPv6Network',
+               array
+               (
+                       'ip' => $network_addr,
+                       'last_ip' => $broadcast_addr,
+                       'mask' => $mask,
+                       'name' => $name
+               )
+       );
+       if ($result != TRUE)
+               return "Could not add ${range} (already exists?).";
+
+       return produceTagsForLastRecord ('ipv6net', $taglist);
+}
+
 // FIXME: This function doesn't wipe relevant records from IPv4Address table.
 function destroyIPv4Prefix ($id = 0)
 {
@@ -2974,6 +3269,17 @@ function destroyIPv4Prefix ($id = 0)
        return '';
 }
 
+// FIXME: This function doesn't wipe relevant records from IPv6Address table.
+function destroyIPv6Prefix ($id = 0)
+{
+       releaseFiles ('ipv6net', $id);
+       if (FALSE === usePreparedDeleteBlade ('IPv6Network', array ('id' => $id)))
+               return __FUNCTION__ . ': SQL query #1 failed';
+       if (FALSE === destroyTagsForEntity ('ipv6net', $id))
+               return __FUNCTION__ . ': SQL query #2 failed';
+       return '';
+}
+
 function loadScript ($name)
 {
        $result = usePreparedSelectBlade ("select script_text from Script where script_name = ?", array ($name));
@@ -3227,8 +3533,8 @@ function getFileLinks ($file_id = 0)
                // get info of the parent
                switch ($row['entity_type'])
                {
-                       case 'ipv4net':
-                               $page = 'ipv4net';
+                       case 'ipv4net': case 'ipv6net':
+                               $page = $row['entity_type'];
                                $id_name = 'id';
                                $parent = spotEntity ($row['entity_type'], $row['entity_id']);
                                $name = sprintf("%s (%s/%s)", $parent['name'], $parent['ip'], $parent['mask']);
@@ -3625,10 +3931,11 @@ function getDomainVLANs ($vdom_id)
        $result = usePreparedSelectBlade
        (
                'SELECT vlan_id, vlan_type, vlan_descr, ' .
-               'ifNull(NETS.C, 0) as netc, ' .
+               'ifNull(NETS4.C, 0) + ifNull(NETS6.C, 0) as netc, ' .
                'ifNull(PORTS.C, 0) as portc ' .
                'FROM VLANDescription AS VD ' .
-               'LEFT JOIN (SELECT vlan_id, COUNT(ipv4net_id) AS C FROM VLANIPv4 WHERE domain_id = ? GROUP BY domain_id, vlan_id) AS NETS USING(vlan_id) ' .
+               'LEFT JOIN (SELECT vlan_id, COUNT(ipv4net_id) AS C FROM VLANIPv4 WHERE domain_id = ? GROUP BY domain_id, vlan_id) AS NETS4 USING(vlan_id) ' .
+               'LEFT JOIN (SELECT vlan_id, COUNT(ipv6net_id) AS C FROM VLANIPv6 WHERE domain_id = ? GROUP BY domain_id, vlan_id) AS NETS6 USING(vlan_id) ' .
                'LEFT JOIN ' .
                '(   SELECT PAV.vlan_id AS vlan_id, COUNT(port_name) AS C ' .
                '    FROM VLANSwitch AS VS ' .
@@ -3638,7 +3945,7 @@ function getDomainVLANs ($vdom_id)
                ') AS PORTS USING(vlan_id) ' .
                'WHERE domain_id = ? ' .
                'ORDER BY vlan_id',
-               array ($vdom_id, $vdom_id, $vdom_id)
+               array ($vdom_id, $vdom_id, $vdom_id, $vdom_id)
        );
        $ret = array();
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
@@ -3760,11 +4067,18 @@ function getVLANInfo ($vlan_ck)
                throw new EntityNotFoundException ('VLAN', $vlan_ck);
        $ret['vlan_ck'] = $vlan_ck;
        $ret['ipv4nets'] = array();
+       $ret['ipv6nets'] = array();
        unset ($result);
        $query = 'SELECT ipv4net_id FROM VLANIPv4 WHERE domain_id = ? AND vlan_id = ? ORDER BY ipv4net_id';
        $result = usePreparedSelectBlade ($query, array ($vdom_id, $vlan_id));
        while ($row = $result->fetch (PDO::FETCH_ASSOC))
                $ret['ipv4nets'][] = $row['ipv4net_id'];
+       unset ($result);
+       $query = 'SELECT ipv6net_id FROM VLANIPv6 WHERE domain_id = ? AND vlan_id = ? ORDER BY ipv6net_id';
+       $result = usePreparedSelectBlade ($query, array ($vdom_id, $vlan_id));
+       while ($row = $result->fetch (PDO::FETCH_ASSOC))
+               $ret['ipv6nets'][] = $row['ipv6net_id'];
+
        return $ret;
 }
 
@@ -3784,6 +4098,22 @@ function getVLANIPv4Options ($except_vdid)
        return $ret;
 }
 
+// return list of network IDs, which are not bound to the given VLAN domain
+function getVLANIPv6Options ($except_vdid)
+{
+       $ret = array();
+       $prepared = usePreparedSelectBlade
+       (
+               'SELECT id FROM IPv6Network WHERE id NOT IN ' .
+               '(SELECT ipv6net_id FROM VLANIPv6 WHERE domain_id = ?)' .
+               'ORDER BY ip, mask',
+               array ($except_vdid)
+       );
+       while ($row = $prepared->fetch (PDO::FETCH_ASSOC))
+               $ret[] = $row['id'];
+       return $ret;
+}
+
 function commitSupplementVLANIPv4 ($vlan_ck, $ipv4net_id)
 {
        list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck);
@@ -3799,6 +4129,21 @@ function commitSupplementVLANIPv4 ($vlan_ck, $ipv4net_id)
        );
 }
 
+function commitSupplementVLANIPv6 ($vlan_ck, $ipv6net_id)
+{
+       list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck);
+       return usePreparedInsertBlade
+       (
+               'VLANIPv6',
+               array
+               (
+                       'domain_id' => $vdom_id,
+                       'vlan_id' => $vlan_id,
+                       'ipv6net_id' => $ipv6net_id,
+               )
+       );
+}
+
 function commitReduceVLANIPv4 ($vlan_ck, $ipv4net_id)
 {
        list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck);
@@ -3814,6 +4159,21 @@ function commitReduceVLANIPv4 ($vlan_ck, $ipv4net_id)
        );
 }
 
+function commitReduceVLANIPv6 ($vlan_ck, $ipv6net_id)
+{
+       list ($vdom_id, $vlan_id) = decodeVLANCK ($vlan_ck);
+       return usePreparedDeleteBlade
+       (
+               'VLANIPv6',
+               array
+               (
+                       'domain_id' => $vdom_id,
+                       'vlan_id' => $vlan_id,
+                       'ipv6net_id' => $ipv6net_id,
+               )
+       );
+}
+
 // Return a list of switches, which have specific VLAN configured on
 // any port (each switch with the list of such ports).
 function getVLANConfiguredPorts ($vlan_ck)
@@ -4005,6 +4365,17 @@ function getIPv4Network8021QBindings ($ipv4net_id)
        return $prepared->fetchAll (PDO::FETCH_ASSOC);
 }
 
+function getIPv6Network8021QBindings ($ipv6net_id)
+{
+       $prepared = usePreparedSelectBlade
+       (
+               'SELECT domain_id, vlan_id FROM VLANIPv6 ' .
+               'WHERE ipv6net_id = ? ORDER BY domain_id',
+               array ($ipv6net_id)
+       );
+       return $prepared->fetchAll (PDO::FETCH_ASSOC);
+}
+
 // Return entity ID, if its 'name' column equals to provided string, or NULL otherwise (nothing
 // found or more, than one row returned by query due to some odd reason).
 function lookupEntityByString ($realm, $value, $column = 'name')
index bdb44bf..3db14ec 100644 (file)
@@ -28,6 +28,7 @@ $templateWidth[5] = 1;
 $etype_by_pageno = array
 (
        'ipv4net' => 'ipv4net',
+       'ipv6net' => 'ipv6net',
        'ipv4rspool' => 'ipv4rspool',
        'ipv4vs' => 'ipv4vs',
        'object' => 'object',
@@ -152,6 +153,27 @@ function assertBoolArg ($argname, $ok_if_empty = FALSE)
                throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is an empty string');
 }
 
+// function returns IPv6Address object, null if arg is correct IPv4, or throws an exception
+function assertIPArg ($argname, $ok_if_empty = FALSE)
+{
+       assertStringArg ($argname, $ok_if_empty);
+       $ip = $_REQUEST[$argname];
+       if (FALSE !== strpos ($ip, ':'))
+       {
+               $v6address = new IPv6Address;
+               $result = $v6address->parse ($ip);
+               $ret = $v6address;
+       }
+       else
+       {
+               $result = long2ip (ip2long ($ip)) === $ip;
+               $ret = NULL;
+       }
+       if (! $result)
+               throw new InvalidRequestArgException ($argname, $ip, 'parameter is not a valid IPv4 or IPv6 address');
+       return $ret;
+}
+
 function assertIPv4Arg ($argname, $ok_if_empty = FALSE)
 {
        assertStringArg ($argname, $ok_if_empty);
@@ -159,6 +181,16 @@ function assertIPv4Arg ($argname, $ok_if_empty = FALSE)
                throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv4 address');
 }
 
+// function returns IPv6Address object, or throws an exception
+function assertIPv6Arg ($argname, $ok_if_empty = FALSE)
+{
+       assertStringArg ($argname, $ok_if_empty);
+       $ipv6 = new IPv6Address;
+       if (strlen ($_REQUEST[$argname]) and ! $ok_if_empty and ! $ipv6->parse ($_REQUEST[$argname]))
+               throw new InvalidRequestArgException($argname, $_REQUEST[$argname], 'parameter is not a valid ipv6 address');
+       return $ipv6;
+}
+
 function assertPCREArg ($argname)
 {
        assertStringArg ($argname, TRUE); // empty pattern is Ok
@@ -995,6 +1027,11 @@ function generateEntityAutoTags ($cell)
                        $ret[] = array ('tag' => '$any_ip4net');
                        $ret[] = array ('tag' => '$any_net');
                        break;
+               case 'ipv6net':
+                       $ret[] = array ('tag' => '$ip6netid_' . $cell['id']);
+                       $ret[] = array ('tag' => '$any_ip6net');
+                       $ret[] = array ('tag' => '$any_net');
+                       break;
                case 'ipv4vs':
                        $ret[] = array ('tag' => '$ipv4vsid_' . $cell['id']);
                        $ret[] = array ('tag' => '$any_ipv4vs');
@@ -1215,7 +1252,7 @@ function getObjectiveTagTree ($tree, $realm, $preselect)
 
 // Preprocess tag tree to get only tags which can effectively reduce given filter result,
 // than passes shrinked tag tree to getObjectiveTagTree and return its result.
-// This makes sense only if andor mode is 'and', otherwhise function does not modify tree.
+// This makes sense only if andor mode is 'and', otherwise function does not modify tree.
 // 'Given filter' is a pair of $entity_list(filter result) and $preselect(filter data).
 // 'Effectively' means reduce to non-empty result.
 function getShrinkedTagTree($entity_list, $realm, $preselect) {
@@ -1615,7 +1652,7 @@ function buildLVSConfig ($object_id = 0)
 }
 
 // Indicate occupation state of each IP address: none, ordinary or problematic.
-function markupIPv4AddrList (&$addrlist)
+function markupIPAddrList (&$addrlist)
 {
        foreach (array_keys ($addrlist) as $ip_bin)
        {
@@ -1641,7 +1678,7 @@ function markupIPv4AddrList (&$addrlist)
        }
 }
 
-// Scan the given address list (returned by scanIPv4Space) and return a list of all routers found.
+// Scan the given address list (returned by scanIPv4Space/scanIPv6Space) and return a list of all routers found.
 function findRouters ($addrlist)
 {
        $ret = array();
@@ -1704,6 +1741,11 @@ function IPv4NetworkCmp ($netA, $netB)
        }
 }
 
+function IPv6NetworkCmp ($netA, $netB)
+{
+       return strcmp ($netA['ip_bin'], $netB['ip_bin']);
+}
+
 // Modify the given tag tree so, that each level's items are sorted alphabetically.
 function sortTree (&$tree, $sortfunc = '')
 {
@@ -1733,6 +1775,22 @@ function iptree_fill (&$netdata)
        $netdata['kidc'] = count ($netdata['kids']);
 }
 
+function ipv6tree_fill (&$netdata)
+{
+       if (!isset ($netdata['kids']) or !count ($netdata['kids']))
+               return;
+       // If we really have nested prefixes, they must fit into the tree.
+       $worktree = array
+       (
+               'ip_bin' => $netdata['ip_bin'],
+               'mask' => $netdata['mask']
+       );
+       foreach ($netdata['kids'] as $pfx)
+               ipv6tree_embed ($worktree, $pfx);
+       $netdata['kids'] = ipv6tree_construct ($worktree);
+       $netdata['kidc'] = count ($netdata['kids']);
+}
+
 function iptree_construct ($node)
 {
        $self = __FUNCTION__;
@@ -1752,6 +1810,25 @@ function iptree_construct ($node)
                return array_merge ($self ($node['left']), $self ($node['right']));
 }
 
+function ipv6tree_construct ($node)
+{
+       $self = __FUNCTION__;
+
+       if (!isset ($node['right']))
+       {
+               if (!isset ($node['ip']))
+               {
+                       $node['ip'] = $node['ip_bin']->format();
+                       $node['kids'] = array();
+                       $node['kidc'] = 0;
+                       $node['name'] = '';
+               }
+               return array ($node);
+       }
+       else
+               return array_merge ($self ($node['left']), $self ($node['right']));
+}
+
 function iptree_embed (&$node, $pfx)
 {
        $self = __FUNCTION__;
@@ -1768,16 +1845,11 @@ function iptree_embed (&$node, $pfx)
        // split?
        if (!isset ($node['right']))
        {
-               // Fill in db_first/db_last to make it possible to run scanIPv4Space() on the node.
                $node['left']['mask'] = $node['mask'] + 1;
                $node['left']['ip_bin'] = $node['ip_bin'];
-               $node['left']['db_first'] = sprintf ('%u', $node['left']['ip_bin']);
-               $node['left']['db_last'] = sprintf ('%u', $node['left']['ip_bin'] | binInvMaskFromDec ($node['left']['mask']));
 
                $node['right']['mask'] = $node['mask'] + 1;
                $node['right']['ip_bin'] = $node['ip_bin'] + binInvMaskFromDec ($node['mask'] + 1) + 1;
-               $node['right']['db_first'] = sprintf ('%u', $node['right']['ip_bin']);
-               $node['right']['db_last'] = sprintf ('%u', $node['right']['ip_bin'] | binInvMaskFromDec ($node['right']['mask']));
        }
 
        // repeat!
@@ -1789,6 +1861,38 @@ function iptree_embed (&$node, $pfx)
                throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
 }
 
+function ipv6tree_embed (&$node, $pfx)
+{
+       $self = __FUNCTION__;
+
+       // hit?
+       if ($node['ip_bin'] == $pfx['ip_bin'] and $node['mask'] == $pfx['mask'])
+       {
+               $node = $pfx;
+               return;
+       }
+       if ($node['mask'] == $pfx['mask'])
+               throw new RackTablesError ('the recurring loop lost control', RackTablesError::INTERNAL);
+
+       // split?
+       if (!isset ($node['right']))
+       {
+               $node['left']['mask'] = $node['mask'] + 1;
+               $node['left']['ip_bin'] = $node['ip_bin'];
+
+               $node['right']['mask'] = $node['mask'] + 1;
+               $node['right']['ip_bin'] = $node['ip_bin']->get_last_subnet_address ($node['mask'] + 1)->next();
+       }
+
+       // repeat!
+       if (($node['left']['ip_bin']->get_first_subnet_address ($node['left']['mask'])) == ($pfx['ip_bin']->get_first_subnet_address ($node['left']['mask'])))
+               $self ($node['left'], $pfx);
+       elseif (($node['right']['ip_bin']->get_first_subnet_address ($node['right']['mask'])) == ($pfx['ip_bin']->get_first_subnet_address ($node['left']['mask'])))
+               $self ($node['right'], $pfx);
+       else
+               throw new RackTablesError ('cannot decide between left and right', RackTablesError::INTERNAL);
+}
+
 function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
 {
        if (!strlen ($func))
@@ -1806,32 +1910,18 @@ function treeApplyFunc (&$tree, $func = '', $stopfunc = '')
 function loadIPv4AddrList (&$netinfo)
 {
        loadOwnIPv4Addresses ($netinfo);
-       markupIPv4AddrList ($netinfo['addrlist']);
+       markupIPAddrList ($netinfo['addrlist']);
 }
 
 function countOwnIPv4Addresses (&$node)
 {
-       $toscan = array();
        $node['addrt'] = 0;
-       $node['mask_bin'] = binMaskFromDec ($node['mask']);
-       $node['mask_bin_inv'] = binInvMaskFromDec ($node['mask']);
-       $node['db_first'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] & $node['mask_bin']);
-       $node['db_last'] = sprintf ('%u', 0x00000000 + $node['ip_bin'] | ($node['mask_bin_inv']));
        if (empty ($node['kids']))
-       {
-               $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
                $node['addrt'] = binInvMaskFromDec ($node['mask']) + 1;
-       }
        else
                foreach ($node['kids'] as $nested)
                        if (!isset ($nested['id'])) // spare
-                       {
-                               $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
                                $node['addrt'] += binInvMaskFromDec ($nested['mask']) + 1;
-                       }
-       // Don't do anything more, because the displaying function will load the addresses anyway.
-       return;
-       $node['addrc'] = count (scanIPv4Space ($toscan));
 }
 
 function nodeIsCollapsed ($node)
@@ -1839,19 +1929,55 @@ function nodeIsCollapsed ($node)
        return $node['symbol'] == 'node-collapsed';
 }
 
+// implies countOwnIPv4Addresses
 function loadOwnIPv4Addresses (&$node)
 {
        $toscan = array();
-       if (!isset ($node['kids']) or !count ($node['kids']))
-               $toscan[] = array ('i32_first' => $node['db_first'], 'i32_last' => $node['db_last']);
+       $node['addrt'] = 0;
+       if (empty ($node['kids']))
+       {
+               $mask_bin = binMaskFromDec ($node['mask']);
+               $mask_bin_inv = binInvMaskFromDec ($node['mask']);
+               $db_first = sprintf ('%u', 0x00000000 + $node['ip_bin'] & $mask_bin);
+               $db_last  = sprintf ('%u', 0x00000000 + $node['ip_bin'] | $mask_bin_inv);
+               $node['addrt'] = $mask_bin_inv + 1;
+               $toscan[] = array ('i32_first' => $db_first, 'i32_last' => $db_last);
+       }
        else
                foreach ($node['kids'] as $nested)
                        if (!isset ($nested['id'])) // spare
-                               $toscan[] = array ('i32_first' => $nested['db_first'], 'i32_last' => $nested['db_last']);
+                       {
+                               $mask_bin = binMaskFromDec ($nested['mask']);
+                               $mask_bin_inv = binInvMaskFromDec ($nested['mask']);
+                               $db_first = sprintf ('%u', 0x00000000 + $nested['ip_bin'] & $mask_bin);
+                               $db_last  = sprintf ('%u', 0x00000000 + $nested['ip_bin'] | $mask_bin_inv);
+                               $node['addrt'] += $mask_bin_inv + 1;
+                               $toscan[] = array ('i32_first' => $db_first, 'i32_last' => $db_last);
+                       }
        $node['addrlist'] = scanIPv4Space ($toscan);
        $node['addrc'] = count ($node['addrlist']);
 }
 
+function loadIPv6AddrList (&$netinfo)
+{
+       loadOwnIPv6Addresses ($netinfo);
+       markupIPAddrList ($netinfo['addrlist']);
+}
+
+function loadOwnIPv6Addresses (&$node)
+{
+       $toscan = array();
+       $node['addrt'] = 0;
+       if (empty ($node['kids']))
+               $toscan[] = array ('first' => $node['ip_bin'], 'last' => $node['ip_bin']->get_last_subnet_address ($node['mask']));
+       else
+               foreach ($node['kids'] as $nested)
+                       if (!isset ($nested['id'])) // spare
+                               $toscan[] = array ('first' => $nested['ip_bin'], 'last' => $nested['ip_bin']->get_last_subnet_address ($nested['mask']));
+       $node['addrlist'] = scanIPv6Space ($toscan);
+       $node['addrc'] = count ($node['addrlist']);
+}
+
 function prepareIPv4Tree ($netlist, $expanded_id = 0)
 {
        // treeFromList() requires parent_id to be correct for an item to get onto the tree,
@@ -1871,6 +1997,23 @@ function prepareIPv4Tree ($netlist, $expanded_id = 0)
        return $tree;
 }
 
+function prepareIPv6Tree ($netlist, $expanded_id = 0)
+{
+       // treeFromList() requires parent_id to be correct for an item to get onto the tree,
+       // so perform necessary pre-processing to make orphans belong to root. This trick
+       // was earlier performed by getIPv4NetworkList().
+       $netids = array_keys ($netlist);
+       foreach ($netids as $cid)
+               if (!in_array ($netlist[$cid]['parent_id'], $netids))
+                       $netlist[$cid]['parent_id'] = NULL;
+       $tree = treeFromList ($netlist); // medium call
+       sortTree ($tree, 'IPv6NetworkCmp');
+       // complement the tree before markup to make the spare networks have "symbol" set
+       treeApplyFunc ($tree, 'ipv6tree_fill');
+       iptree_markup_collapsion ($tree, getConfigVar ('TREE_THRESHOLD'), $expanded_id);
+       return $tree;
+}
+
 // Check all items of the tree recursively, until the requested target id is
 // found. Mark all items leading to this item as "expanded", collapsing all
 // the rest, which exceed the given threshold (if the threshold is given).
index 59ae293..7206e89 100644 (file)
@@ -207,4 +207,6 @@ $impl_tags = array();
 // Initial chain for the current target.
 $target_given_tags = array();
 
+require_once 'inc/IPv6.php';
+
 ?>
index 2fc1ceb..4306d2e 100644 (file)
@@ -94,6 +94,9 @@ $image['files']['height'] = 200;
 $image['ipv4space']['path'] = 'pix/addressspace.png';
 $image['ipv4space']['width'] = 218;
 $image['ipv4space']['height'] = 200;
+$image['ipv6space']['path'] = 'pix/addressspacev6.png';
+$image['ipv6space']['width'] = 218;
+$image['ipv6space']['height'] = 200;
 $image['ipv4slb']['path'] = 'pix/slb.png';
 $image['ipv4slb']['width'] = 218;
 $image['ipv4slb']['height'] = 200;
@@ -339,8 +342,11 @@ $attrtypes = array
 function renderIndexItem ($ypageno) {
   global $page;
   if (permitted($ypageno)) {
+       $title = isset ($page[$ypageno]['title']) ? $page[$ypageno]['title'] : dynamic_title_decoder ($ypageno);
+       if (is_array ($title))
+               $title = $title['name'];
     print "          <td>\n";          
-    print "            <h1><a href='".makeHref(array('page'=>$ypageno))."'>".$page[$ypageno]['title']."<br>\n";
+    print "            <h1><a href='".makeHref(array('page'=>$ypageno))."'>".$title."<br>\n";
     printImageHREF ($ypageno);
     print "</a></h1>\n";
     print "          </td>\n";
@@ -1020,82 +1026,118 @@ function renderRackObject ($object_id)
                finishPortlet();
        }
 
-       $alloclist = $info['ipv4'];
-       if (count ($alloclist))
+       if (count ($info['ipv4']) + count ($info['ipv6']))
        {
-               startPortlet ('IPv4 addresses');
+               startPortlet ('IP addresses');
                echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>\n";
                if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
                        echo "<tr><th>OS interface</th><th>IP address</th><th>network</th><th>routed by</th><th>peers</th></tr>\n";
                else
                        echo "<tr><th>OS interface</th><th>IP address</th><th>peers</th></tr>\n";
-               $hl_ipv4_addr = '';
-               if (isset ($_REQUEST['hl_ipv4_addr']))
+               $hl_ip_addr = '';
+               if (isset ($_REQUEST['hl_ipv6_addr']))
                {
-                       assertIPv4Arg ('hl_ipv4_addr');
-                       $hl_ipv4_addr = $_REQUEST['hl_ipv4_addr'];
+                       if ($hl_ipv6 = assertIPv6Arg ('hl_ipv6_addr'))
+                               $hl_ip_addr = $hl_ipv6->format();
                }
-               foreach ($alloclist as $dottedquad => $alloc)
+               elseif (isset ($_REQUEST['hl_ipv4_addr']))
+                       $hl_ip_addr = $_REQUEST['hl_ipv4_addr'];
+
+               // group IP allocations by interface name instead of address family
+               $allocs_by_iface = array();
+               foreach (array ('ipv4', 'ipv6') as $ip_v)
+                       foreach ($info[$ip_v] as $dottedquad => $alloc)
+                               $allocs_by_iface[$alloc['osif']][$dottedquad] = $alloc;
+                               
+               // sort allocs array by portnames
+               foreach (sortPortList ($allocs_by_iface) as $iface_name => $alloclist)
                {
-                       $address_name = niftyString ($alloc['addrinfo']['name']);
-                       $class = $alloc['addrinfo']['class'];
-                       $secondclass = ($hl_ipv4_addr == $dottedquad) ? 'tdleft port_highlight' : 'tdleft';
-                       $netid = getIPv4AddressNetworkId ($dottedquad);
-                       if (NULL !== $netid)
-                       {
-                               $netinfo = spotEntity ('ipv4net', $netid);
-                               loadIPv4AddrList ($netinfo);
-                       }
-                       echo "<tr class='${class}' valign=top><td class=tdleft>${alloc['osif']}</td><td class='${secondclass}'>";
-                       if (NULL !== $netid)
-                               echo "<a href='".makeHref(array('page'=>'ipaddress', 'ip'=>$dottedquad, 'hl_object_id'=>$object_id))."'>${dottedquad}</a>";
-                       else
-                               echo $dottedquad;
-                       if (getConfigVar ('EXT_IPV4_VIEW') != 'yes')
-                               echo '<small>/' . (NULL === $netid ? '??' : $netinfo['mask']) . '</small>';
-                       echo '&nbsp;' . $aac[$alloc['type']];
-                       if (strlen ($alloc['addrinfo']['name']))
-                               echo ' (' . niftyString ($alloc['addrinfo']['name']) . ')';
-                       echo '</td>';
-                       if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+                       $is_first_row = TRUE;
+                       foreach ($alloclist as $dottedquad => $alloc)
                        {
-                               if (NULL === $netid)
-                                       echo '<td class=sparenetwork>N/A</td><td class=sparenetwork>N/A</td>';
+                               if ($alloc['addrinfo']['version'] == 6)
+                               {
+                                       $addr_page_name = 'ipv6address';
+                                       $ipv6_address = new IPv6Address ($dottedquad);
+                                       $dottedquad = $ipv6_address->format();
+                                       if ($netid = getIPv6AddressNetworkId ($ipv6_address))
+                                       {
+                                               $netinfo = spotEntity ('ipv6net', $netid);
+                                               loadIPv6AddrList ($netinfo);
+                                       }
+                               }
+                               else
+                               {
+                                       $addr_page_name = 'ipaddress';
+                                       if ($netid = getIPv4AddressNetworkId ($dottedquad))
+                                       {
+                                               $netinfo = spotEntity ('ipv4net', $netid);
+                                               loadIPv4AddrList ($netinfo);
+                                       }
+                               }
+                               $address_name = niftyString ($alloc['addrinfo']['name']);
+                               $class = $alloc['addrinfo']['class'];
+                               $secondclass = ($hl_ip_addr == $dottedquad) ? 'tdleft port_highlight' : 'tdleft';
+                               echo "<tr class='${class}' valign=top>";
+
+                               // display iface name (common cell for both address families)
+                               if ($is_first_row)
+                               {
+                                       $rowspan = count ($alloclist) > 1 ? 'rowspan="' . count ($alloclist) . '"' : '';
+                                       echo "<td class=tdleft $rowspan>$iface_name</td>";
+                                       $is_first_row = FALSE;
+                               }
+                               echo "<td class='${secondclass}'>";
+                               if (NULL !== $netid)
+                                       echo "<a href='index.php?page=$addr_page_name&hl_object_id=$object_id&ip=$dottedquad'>${dottedquad}</a>";
                                else
+                                       echo $dottedquad;
+                               if (getConfigVar ('EXT_IPV4_VIEW') != 'yes')
+                                       echo '<small>/' . (NULL === $netid ? '??' : $netinfo['mask']) . '</small>';
+                               echo '&nbsp;' . $aac[$alloc['type']];
+                               if (strlen ($alloc['addrinfo']['name']))
+                                       echo ' (' . niftyString ($alloc['addrinfo']['name']) . ')';
+                               echo '</td>';
+                               if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
                                {
-                                       echo "<td class='${secondclass}'>";
-                                       renderCell ($netinfo);
-                                       echo "</td>";
-                                       // filter out self-allocation
-                                       $other_routers = array();
-                                       foreach (findRouters ($netinfo['addrlist']) as $router)
-                                               if ($router['id'] != $object_id)
-                                                       $other_routers[] = $router;
-                                       if (count ($other_routers))
-                                               printRoutersTD ($other_routers);
+                                       if (NULL === $netid)
+                                               echo '<td class=sparenetwork>N/A</td><td class=sparenetwork>N/A</td>';
                                        else
-                                               echo "<td class='${secondclass}'>&nbsp;</td>";
+                                       {
+                                               echo "<td class='${secondclass}'>";
+                                               renderCell ($netinfo);
+                                               echo "</td>";
+                                               // filter out self-allocation
+                                               $other_routers = array();
+                                               foreach (findRouters ($netinfo['addrlist']) as $router)
+                                                       if ($router['id'] != $object_id)
+                                                               $other_routers[] = $router;
+                                               if (count ($other_routers))
+                                                       printRoutersTD ($other_routers);
+                                               else
+                                                       echo "<td class='${secondclass}'>&nbsp;</td>";
+                                       }
                                }
+                               // peers
+                               echo "<td class='${secondclass}'>\n";
+                               $prefix = '';
+                               if ($alloc['addrinfo']['reserved'] == 'yes')
+                               {
+                                       echo $prefix . '<strong>RESERVED</strong>';
+                                       $prefix = '; ';
+                               }
+                               foreach ($alloc['addrinfo']['allocs'] as $allocpeer)
+                               {
+                                       if ($allocpeer['object_id'] == $object_id)
+                                               continue;
+                                       echo $prefix . "<a href='" . makeHref (array ('page' => 'object', 'object_id' => $allocpeer['object_id'])) . "'>";
+                                       if (isset ($allocpeer['osif']) and strlen ($allocpeer['osif']))
+                                               echo $allocpeer['osif'] . '@';
+                                       echo $allocpeer['object_name'] . '</a>';
+                                       $prefix = '; ';
+                               }
+                               echo "</td></tr>\n";
                        }
-                       // peers
-                       echo "<td class='${secondclass}'>\n";
-                       $prefix = '';
-                       if ($alloc['addrinfo']['reserved'] == 'yes')
-                       {
-                               echo $prefix . '<strong>RESERVED</strong>';
-                               $prefix = '; ';
-                       }
-                       foreach ($alloc['addrinfo']['allocs'] as $allocpeer)
-                       {
-                               if ($allocpeer['object_id'] == $object_id)
-                                       continue;
-                               echo $prefix . "<a href='".makeHref(array('page'=>'object', 'object_id'=>$allocpeer['object_id']))."'>";
-                               if (isset ($allocpeer['osif']) and strlen ($allocpeer['osif']))
-                                       echo $allocpeer['osif'] . '@';
-                               echo $allocpeer['object_name'] . '</a>';
-                               $prefix = '; ';
-                       }
-                       echo "</td></tr>\n";
                }
                echo "</table><br>\n";
                finishPortlet();
@@ -1489,6 +1531,112 @@ function renderIPv4ForObject ($object_id)
 
 }
 
+function renderIPv6ForObject ($object_id)
+{
+       function printNewItemTR ()
+       {
+               global $aat;
+               printOpFormIntro ('addIPv6Allocation');
+               echo "<tr><td>";
+               printImageHREF ('add', 'allocate', TRUE);
+               echo "</td>";
+               echo "<td class=tdleft><input type='text' size='10' name='bond_name' tabindex=100></td>\n";
+               echo "<td class=tdleft><input type=text name='ip' tabindex=101></td>\n";
+               echo "<td colspan=2>&nbsp;</td><td>";
+               printSelect ($aat, array ('name' => 'bond_type', 'tabindex' => 102), 'regular');
+               echo "</td><td>&nbsp;</td><td>";
+               printImageHREF ('add', 'allocate', TRUE, 103);
+               echo "</td></tr></form>";
+       }
+       $focus = spotEntity ('object', $object_id);
+       amplifyCell ($focus);
+       global $aat;
+       startPortlet ('Allocations');
+       echo "<table cellspacing=0 cellpadding='5' align='center' class='widetable'>\n";
+       echo '<tr><th>&nbsp;</th><th>OS interface</th><th>IP address</th>';
+       if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+               echo '<th>network</th><th>routed by</th>';
+       echo '<th>type</th><th>misc</th><th>&nbsp</th></tr>';
+
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+               printNewItemTR();
+       foreach ($focus['ipv6'] as $bin_str => $alloc)
+       {
+               $ipv6 = new IPv6Address ($bin_str);
+               $dottedquad = $ipv6->format();
+               $class = $alloc['addrinfo']['class'];
+               $netid = getIPv6AddressNetworkId ($ipv6);
+               if (NULL !== $netid)
+               {
+                       $netinfo = spotEntity ('ipv6net', $netid);
+                       loadIPv6AddrList ($netinfo);
+               }
+               printOpFormIntro ('updIPv6Allocation', array ('ip' => $dottedquad));
+               echo "<tr class='$class' valign=top><td><a href='".makeHrefProcess(array('op'=>'delIPv6Allocation', 'ip'=>$dottedquad, 'object_id'=>$object_id))."'>";
+               printImageHREF ('delete', 'Delete this IPv6 address');
+               echo "</a></td>";
+               echo "<td class=tdleft><input type='text' name='bond_name' value='${alloc['osif']}' size=10></td><td class=tdleft>";
+               if (NULL !== $netid)
+                       echo "<a href='index.php?page=ipv6address&ip=$dottedquad'>$dottedquad</a>";
+               else
+                       echo $dottedquad;
+               if (getConfigVar ('EXT_IPV4_VIEW') != 'yes')
+                       echo '<small>/' . (NULL === $netid ? '??' : $netinfo['mask']) . '</small>';
+               if (strlen ($alloc['addrinfo']['name']))
+                       echo ' (' . niftyString ($alloc['addrinfo']['name']) . ')';
+               echo '</td>';
+               // FIXME: this a copy-and-paste from renderRackObject()
+               if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+               {
+                       if (NULL === $netid)
+                               echo '<td class=sparenetwork>N/A</td><td class=sparenetwork>N/A</td>';
+                       else
+                       {
+                               echo '<td>';
+                               renderCell ($netinfo);
+                               echo '</td>';
+                               // filter out self-allocation
+                               $other_routers = array();
+                               foreach (findRouters ($netinfo['addrlist']) as $router)
+                                       if ($router['id'] != $object_id)
+                                               $other_routers[] = $router;
+                               if (count ($other_routers))
+                                       printRoutersTD ($other_routers);
+                               else
+                                       echo "<td>&nbsp;</td>";
+                       }
+               }
+               echo '<td>';
+               printSelect ($aat, array ('name' => 'bond_type'), $alloc['type']);
+               echo "</td><td>";
+               $prefix = '';
+               if ($alloc['addrinfo']['reserved'] == 'yes')
+               {
+                       echo $prefix . '<strong>RESERVED</strong>';
+                       $prefix = '; ';
+               }
+               foreach ($alloc['addrinfo']['allocs'] as $allocpeer)
+               {
+                       if ($allocpeer['object_id'] == $object_id)
+                               continue;
+                       echo $prefix . "<a href='".makeHref(array('page'=>'object', 'object_id'=>$allocpeer['object_id']))."'>";
+                       if (isset ($allocpeer['osif']) and strlen ($allocpeer['osif']))
+                               echo $allocpeer['osif'] . '@';
+                       echo $allocpeer['object_name'] . '</a>';
+                       $prefix = '; ';
+               }
+               echo "</td><td>";
+               printImageHREF ('save', 'Save changes', TRUE);
+               echo "</td></form></tr>\n";
+       }
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+               printNewItemTR();
+
+       echo "</table><br>\n";
+       finishPortlet();
+
+}
+
 // Log structure versions:
 // 1: the whole structure is a list of code-message pairs
 // 2 and later: there's a "v" field set, which indicates the version
@@ -1594,6 +1742,9 @@ function showMessageOrError ()
                                81 => array ('code' => 'success', 'format' => "SNMP: completed '%s' work"),
                                82 => array ('code' => 'success', 'format' => "Bulk port creation was successful. %u ports created, %u failed"),
                                83 => array ('code' => 'success', 'format' => 'Object "%s" was reset successfully'),
+                               84 => array ('code' => 'success', 'format' => 'IPv6 prefix successfully added'),
+                               85 => array ('code' => 'success', 'format' => 'IPv6 prefix deleted'),
+                               86 => array ('code' => 'success', 'format' => 'IPv6 prefix updated'),
 // records 100~199 with fatal error messages
                                100 => array ('code' => 'error', 'format' => '%s'),
                                101 => array ('code' => 'error', 'format' => 'Port name cannot be empty'),
@@ -2128,7 +2279,9 @@ function renderIPv4SpaceRecords ($tree, $baseurl, $target = 0, $knight, $level =
                        elseif ($item['symbol'] == 'node-expanded')
                                $decor['symbolurl'] = $baseurl . ($item['parent_id'] ? "&eid=${item['parent_id']}#netid${item['parent_id']}" : '');
                        echo "<tr valign=top>";
-                       printIPv4NetInfoTDs ($item, $decor);
+                       if ($target == $item['id'] && isset ($_REQUEST['hl_net']))
+                               $decor['tdclass'] .= ' port_highlight';
+                       printIPNetInfoTDs ($item, $decor);
                        echo "<td class=tdcenter>";
                        if ($target == $item['id'])
                                echo "<a name=netid${target}></a>";
@@ -2165,7 +2318,7 @@ function renderIPv4SpaceRecords ($tree, $baseurl, $target = 0, $knight, $level =
                else
                {
                        echo "<tr valign=top>";
-                       printIPv4NetInfoTDs ($item, array ('indent' => $level, 'knight' => $knight, 'tdclass' => 'sparenetwork'));
+                       printIPNetInfoTDs ($item, array ('indent' => $level, 'knight' => $knight, 'tdclass' => 'sparenetwork'));
                        echo "<td class=tdcenter>";
                        if (getConfigVar ('IPV4_TREE_SHOW_USAGE') == 'yes')
                        {
@@ -2181,6 +2334,115 @@ function renderIPv4SpaceRecords ($tree, $baseurl, $target = 0, $knight, $level =
        }
 }
 
+function renderIPv6SpaceRecords ($tree, $baseurl, $target = 0, $knight, $level = 1)
+{
+       $self = __FUNCTION__;
+       static $vdomlist = NULL;
+       if ($vdomlist == NULL and getConfigVar ('IPV4_TREE_SHOW_VLAN') == 'yes')
+               $vdomlist = getVLANDomainOptions();
+       foreach ($tree as $item)
+       {
+               if (getConfigVar ('IPV4_TREE_SHOW_USAGE') == 'yes')
+                       loadIPv6AddrList ($item); // necessary to compute router list and address counter
+               else
+               {
+                       $item['addrlist'] = array();
+                       $item['addrc'] = 0;
+               }
+               if (isset ($item['id']))
+               {
+                       $decor = array ('indent' => $level);
+                       if ($item['symbol'] == 'node-collapsed')
+                               $decor['symbolurl'] = "${baseurl}&eid=" . $item['id'] . "#netid" . $item['id'];
+                       elseif ($item['symbol'] == 'node-expanded')
+                               $decor['symbolurl'] = $baseurl . ($item['parent_id'] ? "&eid=${item['parent_id']}#net6id${item['parent_id']}" : '');
+                       echo "<tr valign=top>";
+                       if ($target == $item['id'] && isset ($_REQUEST['hl_net']))
+                               $decor['tdclass'] .= ' port_highlight';
+                       printIPNetInfoTDs ($item, $decor);
+                       echo "<td class=tdcenter>";
+                       if ($target == $item['id'])
+                               echo "<a name=netid${target}></a>";
+                       // show net usage
+                       echo formatIPv6NetUsage ($item['addrc'], $item['mask']);
+                       echo "</td>";
+                       if (getConfigVar ('IPV4_TREE_SHOW_VLAN') == 'yes')
+                       {
+                               echo '<td class=tdleft>';
+                               if (count ($item['8021q']))
+                               {
+                                       echo '<ul>';
+                                       foreach ($item['8021q'] as $binding)
+                                       {
+                                               echo '<li><a href="' . makeHref (array ('page' => 'vlan', 'vlan_ck' => $binding['domain_id'] . '-' . $binding['vlan_id'])) . '">';
+                                               // FIXME: would formatVLANName() do this?
+                                               echo $binding['vlan_id'] . '@' . niftyString ($vdomlist[$binding['domain_id']], 15) . '</a></li>';
+                                       }
+                                       echo '</ul>';
+                               }
+                               echo '</td>';
+                       }
+                       if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+                               printRoutersTD (findRouters ($item['addrlist']), getConfigVar ('IPV4_TREE_RTR_AS_CELL'));
+                       echo "</tr>";
+                       if ($item['symbol'] == 'node-expanded' or $item['symbol'] == 'node-expanded-static')
+                               $self ($item['kids'], $baseurl, $target, $knight, $level + 1);
+               }
+               /* do not display spare networks
+               else
+               { // display spare networks
+                       echo "<tr valign=top>";
+                       printIPNetInfoTDs ($item, array ('indent' => $level, 'knight' => $knight, 'tdclass' => 'sparenetwork'));
+                       echo "<td class=tdcenter>";
+                       echo formatIPv6NetUsage ($item['addrc'], $item['mask']);
+                       if (getConfigVar ('IPV4_TREE_SHOW_VLAN') == 'yes')
+                               echo '</td><td>&nbsp;</td>';
+                       echo "</td><td>&nbsp;</td></tr>";
+               }*/
+       }
+}
+
+// if $used is NULL, returns only human-formatted mask.
+// Otherwise returns "$used in/of " . human-formatted-mask
+function formatIPv6NetUsage ($used, $mask)
+{
+       $prefixes = array
+       (
+               0 =>  '',
+               3 =>  'k',
+               6 =>  'M',
+               9 =>  'G',
+               12 => 'T',
+               15 => 'P',
+               18 => 'E',
+               21 => 'Z',
+               24 => 'Y',
+       );
+
+       if ($mask <= 64)
+       {
+               $what = '/64 net';
+               $preposition = 'in';
+               $mask += 64;
+       }
+       else
+       {
+               $what = 'IP';
+               $preposition = 'of';
+       }
+       $what .= (0 == $mask % 64 ? '' : 's');
+       $addrc = isset ($used) ? "$used $preposition " : '';
+
+       $dec_order = intval ((128 - $mask) / 10) * 3;
+       $mult = isset ($prefixes[$dec_order]) ? $prefixes[$dec_order] : '??';
+       
+       $cnt = 1 << ((128 - $mask) % 10);
+       if ($cnt == 1 && $mult == '')
+               $cnt = 'single';
+
+       return "<small>${addrc}${cnt}${mult} ${what}</small>";
+}
+
 function renderIPv4Space ()
 {
        global $pageno, $tabno;
@@ -2235,6 +2497,60 @@ function renderIPv4Space ()
        echo "</td></tr></table>\n";
 }
 
+function renderIPv6Space ()
+{
+       global $pageno, $tabno;
+       $cellfilter = getCellFilter();
+       $netlist = listCells ('ipv6net');
+       $allcount = count ($netlist);
+       $netlist = filterCellList ($netlist, $cellfilter['expression']);
+       array_walk ($netlist, 'amplifyCell');
+
+       $netcount = count ($netlist);
+       // expand request can take either natural values or "ALL". Zero means no expanding.
+       $eid = isset ($_REQUEST['eid']) ? $_REQUEST['eid'] : 0;
+       $tree = prepareIPv6Tree ($netlist, $eid);
+
+       echo "<table border=0 class=objectview>\n";
+       echo "<tr><td class=pcleft>";
+       if (! renderEmptyResults($cellfilter, 'IPv6 nets', count($tree)))
+       {
+               startPortlet ("networks (${netcount})");
+               echo '<h4>';
+               if ($eid === 0)
+                       echo 'auto-collapsing at threshold ' . getConfigVar ('TREE_THRESHOLD') .
+                               " (<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno, 'eid'=>'ALL')) .
+                               $cellfilter['urlextra'] . "'>expand all</a>)";
+               elseif ($eid === 'ALL')
+                       echo "expanding all (<a href='".makeHref(array('page'=>$pageno, 'tab'=>$tabno)) .
+                               $cellfilter['urlextra'] . "'>auto-collapse</a>)";
+               else
+               {
+                       $netinfo = spotEntity ('ipv6net', $eid);
+                       echo "expanding ${netinfo['ip']}/${netinfo['mask']} (<a href='" .
+                               makeHref (array ('page' => $pageno, 'tab' => $tabno)) .
+                               $cellfilter['urlextra'] . "'>auto-collapse</a> / <a href='" .
+                               makeHref (array ('page' => $pageno, 'tab' => $tabno, 'eid' => 'ALL')) .
+                               $cellfilter['urlextra'] . "'>expand&nbsp;all</a>)";
+               }
+               echo "</h4><table class='widetable' border=0 cellpadding=5 cellspacing=0 align='center'>\n";
+               echo "<tr><th>prefix</th><th>name/tags</th><th>capacity</th>";
+               if (getConfigVar ('IPV4_TREE_SHOW_VLAN') == 'yes')
+                       echo '<th>VLAN</th>';
+               if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+                       echo "<th>routed by</th>";
+               echo "</tr>\n";
+               $baseurl = makeHref(array('page'=>$pageno, 'tab'=>$tabno)) . $cellfilter['urlextra'];
+               renderIPv6SpaceRecords ($tree, $baseurl, $eid, $netcount == $allcount and getConfigVar ('IPV4_ENABLE_KNIGHT') == 'yes');
+               echo "</table>\n";
+               finishPortlet();
+       }
+
+       echo '</td><td class=pcright>';
+       renderCellFilterPortlet ($cellfilter, 'ipv6net', $netlist);
+       echo "</td></tr></table>\n";
+}
+
 function renderSLBDefConfig()
 {
        $defaults = getSLBDefaults ();
@@ -2374,32 +2690,100 @@ function renderIPv4SpaceEditor ()
                        $maxdirect = $netinfo['addrt'];
                        $maxtotal = binInvMaskFromDec ($netinfo['mask']) + 1;
                        echo "<tr valign=top><td>";
-                       if (getConfigVar ('IPV4_JAYWALK') == 'yes')
+                       if (count ($netinfo['addrlist']) && getConfigVar ('IPV4_JAYWALK') == 'no')
+                               printImageHREF ('nodestroy', 'There are ' . count ($netinfo['addrlist']) . ' allocations inside');
+                       else
                        {
                                echo "<a href='".makeHrefProcess(array('op'=>'delIPv4Prefix', 'id'=>$netinfo['id']))."'>";
                                printImageHREF ('destroy', 'Delete this prefix');
                                echo "</a>";
                        }
-                       else // only render clickable image for empty networks
-                       {
-                               if (count ($netinfo['addrlist']))
-                                       printImageHREF ('nodestroy', 'There are ' . count ($netinfo['addrlist']) . ' allocations inside');
-                               else
-                               {
-                                       echo "<a href='".makeHrefProcess(array('op'=>'delIPv4Prefix', 'id'=>$netinfo['id']))."'>";
-                                       printImageHREF ('destroy', 'Delete this prefix');
-                                       echo "</a>";
-                               }
+                       echo '</td><td class=tdleft><a href="' . makeHref (array ('page' => 'ipv4net', 'id' => $netinfo['id'])) . '">';
+                       echo "${netinfo['ip']}/${netinfo['mask']}</a></td>";
+                       echo '<td class=tdleft>' . niftyString ($netinfo['name']);
+                       if (count ($netinfo['etags']))
+                               echo '<br><small>' . serializeTags ($netinfo['etags']) . '</small>';
+                       echo '</td><td>';
+                       renderProgressBar ($maxdirect ? $used/$maxdirect : 0);
+                       echo "<br><small>${used}/${maxdirect}" . ($maxdirect == $maxtotal ? '' : "/${maxtotal}") . '</small></td>';
+                       echo '</tr>';
+               }
+               echo "</table>";
+               finishPortlet();
+       }
+       if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
+               printNewItemTR();
+}
+
+function renderIPv6SpaceEditor ()
+{
+       // IPv6 validator
+?>
+       <script type="text/javascript">
+       function init() {
+               document.add_new_range.range.setAttribute ('match', '^[a-fA-F0-9:]*:[a-fA-F0-9:\\.]*\/\\d{1,3}$');
+
+               Validate.init();
+       }
+       window.onload=init;
+       </script>
+<?php
 
+       function printNewItemTR ()
+       {
+               startPortlet ('Add new');
+               echo '<table border=0 cellpadding=10 align=center>';
+               // This form requires a name, so JavaScript validator can find it.
+               // No printOpFormIntro() hence
+               echo "<form method=post name='add_new_range' action='".makeHrefProcess()."'>\n";
+               echo "<input type=hidden name=op value=addIPv6Prefix>\n";
+               // tags column
+               echo '<tr><td rowspan=4><h3>assign tags</h3>';
+               renderNewEntityTags ('ipv4net');
+               echo '</td>';
+               // inputs column
+               $prefix_value = empty ($_REQUEST['set-prefix']) ? '' : $_REQUEST['set-prefix'];
+               echo "<th class=tdright>prefix</th><td class=tdleft><input type=text name='range' size=36 class='live-validate' tabindex=1 value='${prefix_value}'></td>";
+               echo "<tr><th class=tdright>name</th><td class=tdleft><input type=text name='name' size='20' tabindex=2></td></tr>";
+               echo "<tr><td colspan=2>";
+               printImageHREF ('CREATE', 'Add a new network', TRUE, 4);
+               echo '</td></tr>';
+               echo "</form></table><br><br>\n";
+               finishPortlet();
+       }
+
+       if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
+               printNewItemTR();
+       if (count ($addrspaceList = listCells ('ipv6net')))
+       {
+               startPortlet ('Manage existing (' . count ($addrspaceList) . ')');
+               echo "<table class='widetable' border=0 cellpadding=5 cellspacing=0 align='center'>\n";
+               echo "<tr><th>&nbsp;</th><th>prefix</th><th>name</th><th>capacity</th></tr>";
+               array_walk ($addrspaceList, 'amplifyCell');
+               $tree = prepareIPv6Tree ($addrspaceList, 'ALL');
+               // this is only called for having "trace" set
+               treeFromList ($addrspaceList);
+               foreach ($addrspaceList as $netinfo)
+               {
+                       $netinfo = peekNode ($tree, $netinfo['trace'], $netinfo['id']);
+                       // now we have all subnets listed in netinfo
+                       loadIPv6AddrList ($netinfo);
+                       echo "<tr valign=top><td>";
+                       if (count ($netinfo['addrlist']) && getConfigVar ('IPV4_JAYWALK') == 'no')
+                               printImageHREF ('nodestroy', 'There are ' . count ($netinfo['addrlist']) . ' allocations inside');
+                       else
+                       {
+                               echo "<a href='".makeHrefProcess (array ('op' => 'delIPv6Prefix', 'id' => $netinfo['id'])) . "'>";
+                               printImageHREF ('destroy', 'Delete this prefix');
+                               echo "</a>";
                        }
-                       echo '</td><td class=tdleft><a href="' . makeHref (array ('page' => 'ipv4net', 'id' => $netinfo['id'])) . '">';
+                       echo '</td><td class=tdleft><a href="' . makeHref (array ('page' => 'ipv6net', 'id' => $netinfo['id'])) . '">';
                        echo "${netinfo['ip']}/${netinfo['mask']}</a></td>";
                        echo '<td class=tdleft>' . niftyString ($netinfo['name']);
                        if (count ($netinfo['etags']))
                                echo '<br><small>' . serializeTags ($netinfo['etags']) . '</small>';
                        echo '</td><td>';
-                       renderProgressBar ($maxdirect ? $used/$maxdirect : 0);
-                       echo "<br><small>${used}/${maxdirect}" . ($maxdirect == $maxtotal ? '' : "/${maxtotal}") . '</small></td>';
+                       echo formatIPv6NetUsage ($netinfo['addrc'], $netinfo['mask']);
                        echo '</tr>';
                }
                echo "</table>";
@@ -2602,9 +2986,230 @@ function renderIPv4Network ($id)
        echo "</td></tr></table>\n";
 }
 
-function renderIPv4NetworkProperties ($id)
+// based on renderIPv4Network
+function renderIPv6Network ($id)
+{
+       $range = spotEntity ('ipv6net', $id);
+       amplifyCell ($range);
+       loadIPv6AddrList ($range);
+       echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
+       echo "<tr><td colspan=2 align=center><h1>${range['ip']}/${range['mask']}</h1><h2>";
+       echo htmlspecialchars ($range['name'], ENT_QUOTES, 'UTF-8') . "</h2></td></tr>\n";
+
+       echo "<tr><td class=pcleft width='50%'>";
+       startPortlet ('summary');
+       echo "<table border=0 cellspacing=0 cellpadding=3 width='100%'>\n";
+       echo "<tr><th width='50%' class=tdright>%% used:</th><td class=tdleft>";
+       echo "&nbsp;" . formatIPv6NetUsage (count ($range['addrlist']), $range['mask']) . "</td></tr>\n";
+
+       if (getConfigVar ('EXT_IPV4_VIEW') == 'yes')
+       {
+               // Build a backtrace from all parent networks.
+               $backtrace = array();
+               $current = $range;
+               while ($current['parent_id'])
+               {
+                       $current = spotEntity ('ipv6net', $current['parent_id']);
+                       $backtrace[] = $current;
+               }
+               $arrows = count ($backtrace);
+               foreach (array_reverse ($backtrace) as $ainfo)
+               {
+                       echo "<tr><th width='50%' class=tdright>";
+                       for ($i = 0; $i < $arrows; $i++)
+                               echo '&uarr;';
+                       $arrows--;
+                       echo "</th><td class=tdleft>";
+                       renderCell ($ainfo);
+                       echo "</td></tr>";
+               }
+               echo "<tr><th width='50%' class=tdright>&rarr;</th>";
+               echo "<td class=tdleft>";
+               renderCell ($range);
+               echo "</td></tr>";
+               // FIXME: get and display nested networks
+       }
+
+       foreach ($range['8021q'] as $item)
+       {
+               $vlaninfo = getVLANInfo ($item['domain_id'] . '-' . $item['vlan_id']);
+               echo '<tr><th width="50%" class=tdright>VLAN:</th><td class=tdleft><a href="';
+               echo makeHref (array ('page' => 'vlan', 'vlan_ck' => $vlaninfo['vlan_ck'])) . '">';
+               echo formatVLANName ($vlaninfo, 'markup long');
+               echo '</a></td></tr>';
+       }
+       if (getConfigVar ('EXT_IPV4_VIEW') == 'yes' and count ($routers = findRouters ($range['addrlist'])))
+       {
+               echo "<tr><th width='50%' class=tdright>Routed by:</th>";
+               printRoutersTD ($routers);
+               echo "</tr>\n";
+       }
+
+       printTagTRs ($range, makeHref (array ('page' => 'ipv6space', 'tab' => 'default')) . "&");
+       echo "</table><br>\n";
+       finishPortlet();
+
+       if (strlen ($range['comment']))
+       {
+               startPortlet ('Comment');
+               echo '<div class=commentblock>' . string_insert_hrefs (htmlspecialchars ($range['comment'], ENT_QUOTES, 'UTF-8')) . '</div>';
+               finishPortlet ();
+       }
+
+       renderFilesPortlet ('ipv6net', $id);
+       echo "</td>\n";
+
+       // render address list
+       echo "<td class=pcright>";
+       startPortlet ('details');
+       renderIPv6NetworkAddresses ($range);
+       finishPortlet();
+       echo "</td></tr></table>\n";
+}
+
+// Used solely by renderSeparator
+function renderEmptyIPv6 ($ip, $hl_ip)
+{
+       $class = 'tdleft';
+       if (isset ($hl_ip) && $ip == $hl_ip)
+               $class .= ' port_highlight';
+       $fmt = $ip->format();
+       echo "<tr><td class=tdleft><a href='" . makeHref (array ('page' => 'ipv6address', 'ip' => $fmt)) . "'>" . $fmt;
+       echo "</a></td><td class='${class}'>&nbsp;</td><td class='${class}'>&nbsp;</td></tr>\n";
+}
+
+// Renders empty table line to shrink empty IPv6 address ranges.
+// If the range consists of single address, renders the address instead of empty line.
+// Renders address $hl_ip inside the range.
+// Used solely by renderIPv6NetworkAddresses
+function renderSeparator ($first, $after, $hl_ip)
+{
+       $self = __FUNCTION__;
+       if (strcmp ($first, $after) >= 0)
+               return;
+       if ($first->next() == $after)
+               renderEmptyIPv6 ($first, $hl_ip);
+       elseif (isset ($hl_ip) && strcmp ($hl_ip, $first) >= 0 && strcmp ($hl_ip, $after) < 0)
+       { // $hl_ip is inside the range $first - ($after-1)
+               $self ($first, $hl_ip, $hl_ip);
+               renderEmptyIPv6 ($hl_ip, $hl_ip);
+               $self ($hl_ip->next(), $after, $hl_ip);
+       }
+       else
+               echo "<tr><td colspan=3 class=tdleft></td></tr>\n";
+}
+
+// calculates page number which contains given $ip (used by renderIPv6NetworkAddresses)
+function getPageNumOfIPv6 ($list, $ip, $maxperpage)
+{
+       if (intval ($maxperpage) <= 0 || count ($list) <= $maxperpage)
+               return 0;
+       $bin_ip = (string)$ip;
+       $keys = array_keys ($list);
+       for ($i = 1; $i <= count ($keys); $i++)
+               if (strcmp ($keys[$i-1], $bin_ip) >= 0)
+                       return intval ($i / $maxperpage);
+       return intval (count ($list) / $maxperpage);
+}
+
+function renderIPv6NetworkAddresses ($netinfo)
+{
+       global $pageno, $tabno, $aac2;
+       echo "<table class='widetable' border=0 cellspacing=0 cellpadding=5 align='center' width='100%'>\n";
+       echo "<tr><th>Address</th><th>Name</th><th>Allocation</th></tr>\n";
+
+       $hl_ip = new IPv6Address;
+       if (! isset ($_REQUEST['hl_ipv6_addr']) || ! $hl_ip->parse ($_REQUEST['hl_ipv6_addr']))
+               $hl_ip = NULL;
+
+       $prev_ip = $netinfo['ip_bin']; // really this is the next to previosly seen ip.
+       $addresses = $netinfo['addrlist'];
+       ksort ($addresses);
+
+       // pager
+       $maxperpage = getConfigVar ('IPV4_ADDRS_PER_PAGE');
+       if (count ($addresses) > $maxperpage && $maxperpage > 0)
+       {
+               $page = isset ($_REQUEST['pg']) ? $_REQUEST['pg'] : (isset ($hl_ip) ? getPageNumOfIPv6 ($addresses, $hl_ip, $maxperpage) : 0);
+               $numpages = ceil (count ($addresses) / $maxperpage);
+               echo "<center><h3>$numpages pages:</h3>";
+               for ($i=0; $i<$numpages; $i++)
+               {
+                       if ($i == $page)
+                               echo "<b>$i</b> ";
+                       else
+                               echo "<a href='" . makeHref (array ('page' => $pageno, 'tab' => $tabno, 'id' => $netinfo['id'], 'pg' => $i)) . "'>$i</a> ";
+               }
+               echo "</center>";
+       }
+
+       $i = 0;
+       $interruped = FALSE; 
+       foreach ($addresses as $bin_ip => $addr)
+       {
+               if (isset ($page))
+               {
+                       ++$i;
+                       if ($i <= $maxperpage * $page)
+                               continue;
+                       elseif ($i > $maxperpage * ($page + 1))
+                       {
+                               $interruped = TRUE;
+                               break;
+                       }
+               }
+
+               $ipv6 = new IPv6Address ($bin_ip);
+               if ($ipv6 != $prev_ip)
+                       renderSeparator ($prev_ip, $ipv6, $hl_ip);
+               $prev_ip = $ipv6->next();
+               
+               $secondstyle = 'tdleft';
+               if (isset ($hl_ip) && $hl_ip == $ipv6)
+                       $secondstyle .= ' port_highlight';
+               echo "<tr class='${addr['class']}'>";
+               echo "<td class=tdleft><a href='" . makeHref (array ('page' => 'ipv6address', 'ip' => $addr['ip'])) . "'>${addr['ip']}</a></td>";
+               echo "<td class='${secondstyle}'>${addr['name']}</td><td class='${secondstyle}'>";
+               $delim = '';
+               $prologue = '';
+               if ( $addr['reserved'] == 'yes')
+               {
+                       echo "<strong>RESERVED</strong> ";
+                       $delim = '; ';
+               }
+               foreach ($addr['allocs'] as $ref)
+               {
+                       echo $delim . $aac2[$ref['type']];
+                       echo "<a href='" . makeHref (array ('page' => 'object', 'object_id' => $ref['object_id'], 'hl_ipv6_addr' => $addr['ip'])) . "'>";
+                       echo $ref['name'] . (!strlen ($ref['name']) ? '' : '@');
+                       echo "${ref['object_name']}</a>";
+                       $delim = '; ';
+               }
+               if ($delim != '')
+               {
+                       $delim = '';
+                       $prologue = '<br>';
+               }
+               echo "</td></tr>\n";
+       }
+       if (! $interruped)
+               renderSeparator ($prev_ip, $netinfo['ip_bin']->get_last_subnet_address ($netinfo['mask'])->next(), $hl_ip);
+       if (isset ($page))
+       { // bottom pager
+               echo "<tr><td colspan=3>";
+               if ($page > 0)
+                       echo "<a href='" . makeHref (array ('page' => $pageno, 'tab' => $tabno, 'id' => $netinfo['id'], 'pg' => $page - 1)) . "'><< prev</a> ";
+               if ($page < $numpages - 1)
+                       echo "<a href='" . makeHref (array ('page' => $pageno, 'tab' => $tabno, 'id' => $netinfo['id'], 'pg' => $page + 1)) . "'>next >></a> ";
+               echo "</td></tr>";
+       }
+       echo "</table>";
+}
+
+function renderIPNetworkProperties ($id)
 {
-       $netdata = spotEntity ('ipv4net', $id);
+       global $pageno;
+       $netdata = spotEntity ($pageno, $id);
        echo "<center><h1>${netdata['ip']}/${netdata['mask']}</h1></center>\n";
        echo "<table border=0 cellpadding=10 cellpadding=1 align='center'>\n";
        printOpFormIntro ('editRange');
@@ -2619,12 +3224,12 @@ function renderIPv4NetworkProperties ($id)
        echo "</td></form></tr></table>\n";
 }
 
-function renderIPv4Address ($dottedquad)
+function renderIPAddress ($dottedquad)
 {
        global $aat, $nextorder;
-       $address = getIPv4Address ($dottedquad);
+       $address = getIPAddress ($dottedquad);
        echo "<table border=0 class=objectview cellspacing=0 cellpadding=0>";
-       echo "<tr><td colspan=2 align=center><h1>${dottedquad}</h1></td></tr>\n";
+       echo "<tr><td colspan=2 align=center><h1>${address['ip']}</h1></td></tr>\n";
 
        echo "<tr><td class=pcleft>";
        startPortlet ('summary');
@@ -2632,16 +3237,18 @@ function renderIPv4Address ($dottedquad)
        if (strlen ($address['name']))
                echo "<tr><th width='50%' class=tdright>Comment:</th><td class=tdleft>${address['name']}</td></tr>";
        echo "<tr><th width='50%' class=tdright>Allocations:</th><td class=tdleft>" . count ($address['allocs']) . "</td></tr>\n";
-       echo "<tr><th width='50%' class=tdright>Originated NAT connections:</th><td class=tdleft>" . count ($address['outpf']) . "</td></tr>\n";
-       echo "<tr><th width='50%' class=tdright>Arriving NAT connections:</th><td class=tdleft>" . count ($address['inpf']) . "</td></tr>\n";
-       echo "<tr><th width='50%' class=tdright>SLB virtual services:</th><td class=tdleft>" . count ($address['lblist']) . "</td></tr>\n";
-       echo "<tr><th width='50%' class=tdright>SLB real servers:</th><td class=tdleft>" . count ($address['rslist']) . "</td></tr>\n";
+       if ($address['version'] == 4)
+       {
+               echo "<tr><th width='50%' class=tdright>Originated NAT connections:</th><td class=tdleft>" . count ($address['outpf']) . "</td></tr>\n";
+               echo "<tr><th width='50%' class=tdright>Arriving NAT connections:</th><td class=tdleft>" . count ($address['inpf']) . "</td></tr>\n";
+               echo "<tr><th width='50%' class=tdright>SLB virtual services:</th><td class=tdleft>" . count ($address['lblist']) . "</td></tr>\n";
+               echo "<tr><th width='50%' class=tdright>SLB real servers:</th><td class=tdleft>" . count ($address['rslist']) . "</td></tr>\n";
+       }
        echo "</table><br>\n";
        finishPortlet();
        echo "</td>\n";
 
        echo "<td class=pcright>";
-
        if (isset ($address['class']))
        {
                startPortlet ('allocations');
@@ -2657,7 +3264,7 @@ function renderIPv4Address ($dottedquad)
                                $secondclass = 'tdleft port_highlight';
                        else
                                $secondclass = 'tdleft';
-                       echo "<tr class='$class'><td class=tdleft><a href='".makeHref(array('page'=>'object', 'object_id'=>$bond['object_id'], 'hl_ipv4_addr'=>$dottedquad))."'>${bond['object_name']}</td><td class='${secondclass}'>${bond['name']}</td><td class='${secondclass}'><strong>";
+                       echo "<tr class='$class'><td class=tdleft><a href='" . makeHref (array ('page' => 'object', 'object_id' => $bond['object_id'], 'hl_ipv' . $address['version'] . '_addr' => $address['ip'])) . "'>${bond['object_name']}</td><td class='${secondclass}'>${bond['name']}</td><td class='${secondclass}'><strong>";
                        echo $aat[$bond['type']];
                        echo "</strong></td></tr>\n";
                }
@@ -2667,7 +3274,7 @@ function renderIPv4Address ($dottedquad)
 
        // FIXME: The returned list is structured differently, than we expect it to be. One of the sides
        // must be fixed.
-       if (count ($address['lblist']))
+       if (! empty ($address['lblist']))
        {
                startPortlet ('Virtual services (' . count ($address['lblist']) . ')');
                echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center' width='100%'>\n";
@@ -2686,7 +3293,7 @@ function renderIPv4Address ($dottedquad)
                finishPortlet();
        }
 
-       if (count ($address['rslist']))
+       if (! empty ($address['rslist']))
        {
                startPortlet ('Real servers (' . count ($address['rslist']) . ')');
                echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center' width='100%'>\n";
@@ -2705,7 +3312,7 @@ function renderIPv4Address ($dottedquad)
                finishPortlet();
        }
 
-       if (count ($address['outpf']))
+       if (! empty ($address['outpf']))
        {
                startPortlet ('departing NAT rules');
                echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center' width='100%'>\n";
@@ -2716,7 +3323,7 @@ function renderIPv4Address ($dottedquad)
                finishPortlet();
        }
 
-       if (count ($address['inpf']))
+       if (! empty ($address['inpf']))
        {
                startPortlet ('arriving NAT rules');
                echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center' width='100%'>\n";
@@ -2731,10 +3338,10 @@ function renderIPv4Address ($dottedquad)
        echo "</table>\n";
 }
 
-function renderIPv4AddressProperties ($dottedquad)
+function renderIPAddressProperties ($dottedquad)
 {
-       $address = getIPv4Address ($dottedquad);
-       echo "<center><h1>$dottedquad</h1></center>\n";
+       $address = getIPAddress ($dottedquad);
+       echo "<center><h1>${address['ip']}</h1></center>\n";
 
        startPortlet ('update');
        echo "<table border=0 cellpadding=10 cellpadding=1 align='center'>\n";
@@ -2757,12 +3364,12 @@ function renderIPv4AddressProperties ($dottedquad)
        finishPortlet();
 }
 
-function renderIPv4AddressAllocations ($dottedquad)
+function renderIPAddressAllocations ($dottedquad)
 {
-       function printNewItemTR ()
+       function printNewItemTR ($opname)
        {
                global $aat;
-               printOpFormIntro ('addIPv4Allocation');
+               printOpFormIntro ($opname);
                echo "<tr><td>";
                printImageHREF ('add', 'allocate', TRUE);
                echo "</td><td>";
@@ -2775,14 +3382,14 @@ function renderIPv4AddressAllocations ($dottedquad)
        }
        global $aat;
 
-       $address = getIPv4Address ($dottedquad);
-
-       echo "<center><h1>${dottedquad}</h1></center>\n";
+       $address = getIPAddress ($dottedquad);
+       $opname = $address['version'] == 6 ? 'addIPv6Allocation' : 'addIPv4Allocation';
+       echo "<center><h1>${address['ip']}</h1></center>\n";
        echo "<table class='widetable' cellpadding=5 cellspacing=0 border=0 align='center'>\n";
        echo "<tr><th>&nbsp;</th><th>object</th><th>OS interface</th><th>allocation type</th><th>&nbsp;</th></tr>\n";
 
        if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
-               printNewItemTR();
+               printNewItemTR($opname);
        if (isset ($address['class']))
        {
                $class = $address['class'];
@@ -2791,11 +3398,25 @@ function renderIPv4AddressAllocations ($dottedquad)
                foreach ($address['allocs'] as $bond)
                {
                        echo "<tr class='$class'>";
-                       printOpFormIntro ('updIPv4Allocation', array ('object_id' => $bond['object_id']));
-                       echo "<td><a href='".makeHrefProcess(array('op'=>'delIPv4Allocation', 'ip'=>$dottedquad, 'object_id'=>$bond['object_id']))."'>";
+                       printOpFormIntro
+                       (
+                               $address['version'] == 6 ? 'updIPv6Allocation' : 'updIPv4Allocation',
+                               array ('object_id' => $bond['object_id'])
+                       );
+                       echo "<td><a href='"
+                               . makeHrefProcess
+                               (
+                                       array
+                                       (
+                                               'op' => $address['version'] == 6 ? 'delIPv6Allocation' : 'delIPv4Allocation',
+                                               'ip' => $address['ip'],
+                                               'object_id' => $bond['object_id']
+                                       )
+                               )
+                               . "'>";
                        printImageHREF ('delete', 'Unallocate address');
                        echo "</a></td>";
-                       echo "<td><a href='".makeHref(array('page'=>'object', 'object_id'=>$bond['object_id'], 'hl_ipv4_addr'=>$dottedquad))."'>${bond['object_name']}</td>";
+                       echo "<td><a href='" . makeHref (array ('page' => 'object', 'object_id' => $bond['object_id'], 'hl_ipv' . $address['version'] . '_addr' => $address['ip'])) . "'>${bond['object_name']}</td>";
                        echo "<td><input type='text' name='bond_name' value='${bond['name']}' size=10></td><td>";
                        printSelect ($aat, array ('name' => 'bond_type'), $bond['type']);
                        echo "</td><td>";
@@ -2804,7 +3425,7 @@ function renderIPv4AddressAllocations ($dottedquad)
                }
        }
        if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
-               printNewItemTR();
+               printNewItemTR($opname);
        echo "</table><br><br>";
 }
 
@@ -3004,6 +3625,7 @@ function renderSearchResults ()
        if (!permitted ('depot', 'default'))
                throw new RackTablesError ('You are not authorized for viewing information about objects.', RackTablesError::NOT_AUTHORIZED);
        $nhits = 0;
+       $ipv6 = new IPv6Address;
        if (preg_match (RE_IP4_ADDR, $terms))
        // Search for IPv4 address.
        {
@@ -3014,6 +3636,16 @@ function renderSearchResults ()
                        $summary['ipv4addressbydq'][] = $terms;
                }
        }
+       elseif ($ipv6->parse ($terms))
+       // Search for IPv6 address
+       {
+               if (NULL !== $net_id = getIPv6AddressNetworkId ($ipv6))
+               {
+                       $nhits++;
+                       $lasthit = 'ipv6addressbydq';
+                       $summary['ipv6addressbydq'][] = array ('net_id' => $net_id, 'ip' => $ipv6);
+               }
+       }
        elseif (preg_match (RE_IP4_NET, $terms))
        // Search for IPv4 network
        {
@@ -3025,6 +3657,16 @@ function renderSearchResults ()
                        $summary['ipv4network'][] = spotEntity ('ipv4net', $tmp);
                }
        }
+       elseif (preg_match ('@(.*)/(\d+)$@', $terms, $matches) && $ipv6->parse ($matches[1]))
+       // Search for IPv6 network
+       {
+               if (NULL !== ($tmp = getIPv6AddressNetworkId ($ipv6, $matches[2] + 1)))
+               {
+                       $nhits++;
+                       $lasthit = 'ipv6network';
+                       $summary['ipv6network'][] = spotEntity ('ipv6net', $tmp);
+               }
+       }
        else
        // Search for objects, addresses, networks, virtual services and RS pools by their description.
        {
@@ -3042,6 +3684,13 @@ function renderSearchResults ()
                        $lasthit = 'ipv4addressbydescr';
                        $summary['ipv4addressbydescr'] = $tmp;
                }
+               $tmp = getIPv6AddressSearchResult ($terms);
+               if (count ($tmp))
+               {
+                       $nhits += count ($tmp);
+                       $lasthit = 'ipv6addressbydescr';
+                       $summary['ipv6addressbydescr'] = $tmp;
+               }
                $tmp = getIPv4PrefixSearchResult ($terms);
                if (count ($tmp))
                {
@@ -3049,6 +3698,13 @@ function renderSearchResults ()
                        $lasthit = 'ipv4network';
                        $summary['ipv4network'] = $tmp;
                }
+               $tmp = getIPv6PrefixSearchResult ($terms);
+               if (count ($tmp))
+               {
+                       $nhits += count ($tmp);
+                       $lasthit = 'ipv6network';
+                       $summary['ipv6network'] = $tmp;
+               }
                $tmp = getIPv4RSPoolSearchResult ($terms);
                if (count ($tmp))
                {
@@ -3105,6 +3761,10 @@ function renderSearchResults ()
                                else
                                        echo "<script language='Javascript'>document.location='index.php?page=ipaddress&ip=${record}';//</script>";
                                break;
+                       case 'ipv6addressbydq':
+                               $v6_ip_dq = $record['ip']->format();
+                               echo "<script language='Javascript'>document.location='index.php?page=ipv6net&tab=default&id=${record['net_id']}&hl_ipv6_addr=${v6_ip_dq}';//</script>";
+                               break;
                        case 'ipv4addressbydescr':
                                $parentnet = getIPv4AddressNetworkId ($record['ip']);
                                if ($parentnet !== NULL)
@@ -3112,11 +3772,25 @@ function renderSearchResults ()
                                else
                                        echo "<script language='Javascript'>document.location='index.php?page=ipaddress&ip=${record['ip']}';//</script>";
                                break;
+                       case 'ipv6addressbydescr':
+                               $v6_ip = new IPv6Address ($record['ip']);
+                               $v6_ip_dq = $v6_ip->format();
+                               $parentnet = getIPv6AddressNetworkId ($v6_ip);
+                               if ($parentnet !== NULL)
+                                       echo "<script language='Javascript'>document.location='index.php?page=ipv4net&tab=default&id=${parentnet}&hl_ipv6_addr=${v6_ip_dq}';//</script>";
+                               else
+                                       echo "<script language='Javascript'>document.location='index.php?page=ipaddress&ip=${v6_ip_dq}';//</script>";
+                               break;
                        case 'ipv4network':
                                echo "<script language='Javascript'>document.location='index.php?page=ipv4net";
                                echo "&id=${record['id']}";
                                echo "';//</script>";
                                break;
+                       case 'ipv6network':
+                               echo "<script language='Javascript'>document.location='index.php?page=ipv6net";
+                               echo "&id=${record['id']}";
+                               echo "';//</script>";
+                               break;
                        case 'object':
                                if (isset ($record['by_port']) and 1 == count ($record['by_port']))
                                        $hl = '&hl_port_id=' . key ($record['by_port']);
@@ -3220,6 +3894,10 @@ function renderSearchResults ()
                                        break;
                                case 'ipv4network':
                                        startPortlet ("<a href='index.php?page=ipv4space'>IPv4 networks</a>");
+                               case 'ipv6network':
+                                       if ($where == 'ipv6network')
+                                               startPortlet ("<a href='index.php?page=ipv6space'>IPv6 networks</a>");
+
                                        echo '<table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
                                        foreach ($what as $cell)
                                        {
@@ -3250,6 +3928,27 @@ function renderSearchResults ()
                                        echo '</table>';
                                        finishPortlet();
                                        break;
+                               case 'ipv6addressbydescr':
+                                       startPortlet ('IPv6 addresses');
+                                       echo '<table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
+                                       // FIXME: address, parent network, routers (if extended view is enabled)
+                                       echo '<tr><th>Address</th><th>Description</th></tr>';
+                                       foreach ($what as $addr)
+                                       {
+                                               echo "<tr class=row_${order}><td class=tdleft>";
+                                               $v6_ip = new IPv6Address ($addr['ip']);
+                                               $v6_ip_dq = $v6_ip->format();
+                                               $parentnet = getIPv6AddressNetworkId ($v6_ip);
+                                               if ($parentnet !== NULL)
+                                                       echo "<a href='index.php?page=ipv6net&tab=default&id=${parentnet}&hl_ipv6_addr=${v6_ip_dq}'>${v6_ip_dq}</a></td>";
+                                               else
+                                                       echo "<a href='index.php?page=ipaddress&ip=${v6_ip_dq}'>${v6_ip_dq}</a></td>";
+                                               echo "<td class=tdleft>${addr['name']}</td></tr>";
+                                               $order = $nextorder[$order];
+                                       }
+                                       echo '</table>';
+                                       finishPortlet();
+                                       break;
                                case 'ipv4rspool':
                                        startPortlet ("<a href='index.php?page=ipv4rsplist'>RS pools</a>");
                                        echo '<table border=0 cellpadding=5 cellspacing=0 align=center class=cooltable>';
@@ -3952,6 +4651,20 @@ function renderIPv4Reports ()
        renderReports ($tmp);
 }
 
+function renderIPv6Reports ()
+{
+       $tmp = array
+       (
+               array
+               (
+                       'title' => 'Stats',
+                       'type' => 'counters',
+                       'func' => 'getIPv6Stats'
+               ),
+       );
+       renderReports ($tmp);
+}
+
 function renderPortsReport ()
 {
        $tmp = array();
@@ -4021,12 +4734,13 @@ function renderReports ($what)
 function renderTagStats ()
 {
        global $taglist;
-       echo '<table border=1><tr><th>tag</th><th>total</th><th>objects</th><th>IPv4 nets</th><th>racks</th>';
-       echo '<th>IPv4 VS</th><th>IPv4 RS pools</th><th>users</th><th>files</th></tr>';
+       echo '<table border=1><tr><th>tag</th><th>total</th><th>objects</th><th>IPv4 nets</th><th>IPv6 nets</th>';
+       echo '<th>racks</th><th>IPv4 VS</th><th>IPv4 RS pools</th><th>users</th><th>files</th></tr>';
        $pagebyrealm = array
        (
                'file' => 'files&tab=default',
                'ipv4net' => 'ipv4space&tab=default',
+               'ipv6net' => 'ipv6space&tab=default',
                'ipv4vs' => 'ipv4vslist&tab=default',
                'ipv4rspool' => 'ipv4rsplist&tab=default',
                'object' => 'depot&tab=default',
@@ -4036,7 +4750,7 @@ function renderTagStats ()
        foreach (getTagChart (getConfigVar ('TAGS_TOPLIST_SIZE')) as $taginfo)
        {
                echo "<tr><td>${taginfo['tag']}</td><td>" . $taginfo['refcnt']['total'] . "</td>";
-               foreach (array ('object', 'ipv4net', 'rack', 'ipv4vs', 'ipv4rspool', 'user', 'file') as $realm)
+               foreach (array ('object', 'ipv4net', 'ipv6net', 'rack', 'ipv4vs', 'ipv4rspool', 'user', 'file') as $realm)
                {
                        echo '<td>';
                        if (!isset ($taginfo['refcnt'][$realm]))
@@ -5941,7 +6655,7 @@ function printRoutersTD ($rlist, $as_cell = 'yes')
        $rtrclass = 'tdleft';
        foreach ($rlist as $rtr)
        {
-               $tmp = getIPv4Address ($rtr['addr']);
+               $tmp = getIPAddress ($rtr['addr']);
                if ($tmp['class'] == 'trerror')
                {
                        $rtrclass = 'tdleft trerror';
@@ -5963,8 +6677,10 @@ function printRoutersTD ($rlist, $as_cell = 'yes')
 }
 
 // Same as for routers, but produce two TD cells to lay the content out better.
-function printIPv4NetInfoTDs ($netinfo, $decor = array())
+function printIPNetInfoTDs ($netinfo, $decor = array())
 {
+       $ip_ver = is_a ($netinfo['ip_bin'], 'IPv6Address') ? 6 : 4;
+       $formatted = $netinfo['ip'] . "/" . $netinfo['mask'];
        if ($netinfo['symbol'] == 'spacer')
        {
                $decor['indent']++;
@@ -5983,8 +6699,8 @@ function printIPv4NetInfoTDs ($netinfo, $decor = array())
                        echo '</a>';
        }
        if (isset ($netinfo['id']))
-               echo "<a href='index.php?page=ipv4net&id=${netinfo['id']}'>";
-       echo "${netinfo['ip']}/${netinfo['mask']}";
+               echo "<a href='index.php?page=ipv${ip_ver}net&id=${netinfo['id']}'>";
+       echo $formatted;
        if (isset ($netinfo['id']))
                echo '</a>';
        echo '</td><td class="tdleft';
@@ -5998,9 +6714,9 @@ function printIPv4NetInfoTDs ($netinfo, $decor = array())
                {
                        echo '<a href="' . makeHref (array
                        (
-                               'page' => 'ipv4space',
+                               'page' => "ipv${ip_ver}space",
                                'tab' => 'newrange',
-                               'set-prefix' => $netinfo['ip'] . '/' . $netinfo['mask'],
+                               'set-prefix' => $formatted,
                        )) . '">';
                        printImageHREF ('knight', 'create network here', TRUE);
                        echo '</a>';
@@ -6010,7 +6726,7 @@ function printIPv4NetInfoTDs ($netinfo, $decor = array())
        {
                echo niftyString ($netinfo['name']);
                if (count ($netinfo['etags']))
-                       echo '<br><small>' . serializeTags ($netinfo['etags'], "index.php?page=ipv4space&tab=default&") . '</small>';
+                       echo '<br><small>' . serializeTags ($netinfo['etags'], "index.php?page=ipv${ip_ver}space&tab=default&") . '</small>';
        }
        echo "</td>";
 }
@@ -6090,19 +6806,30 @@ function renderCell ($cell)
                echo "</td></tr></table>";
                break;
        case 'ipv4net':
+       case 'ipv6net':
                echo "<table class='slbcell vscell'><tr><td rowspan=3 width='5%'>";
                printImageHREF ('NET');
                echo '</td>';
-               echo "<td><a href='index.php?page=ipv4net&id=${cell['id']}'>${cell['ip']}/${cell['mask']}</a>";
+               echo "<td><a href='index.php?page={$cell['realm']}&id=${cell['id']}'>${cell['ip']}/${cell['mask']}</a>";
                if (getConfigVar ('IPV4_TREE_SHOW_USAGE') == 'yes')
                {
-                       countOwnIPv4Addresses ($cell);
-                       loadOwnIPv4Addresses ($cell);
-                       $used = $cell['addrc'];
-                       $maxdirect = $cell['addrt'];
                        echo '<div class="net-usage">';
-                       echo "<small>$used/$maxdirect</small> ";
-                       renderProgressBar ($maxdirect ? $used/$maxdirect : 0);
+                       if ($cell['realm'] == 'ipv4net')
+                       {
+                               loadOwnIPv4Addresses ($cell);
+                               $used = $cell['addrc'];
+                               $maxdirect = $cell['addrt'];
+                               
+                               echo "<small>$used/$maxdirect</small> ";
+                               renderProgressBar ($maxdirect ? $used/$maxdirect : 0);
+                               
+                       }
+                       elseif ($cell['realm'] == 'ipv6net')
+                       {
+                               loadOwnIPv6Addresses ($cell);
+                               $used = $cell['addrc'];
+                               echo formatIPv6NetUsage ($used, $cell['mask']);
+                       }
                        echo '</div>';
                }
                echo '</td></tr>';
@@ -6180,7 +6907,9 @@ function renderRouterCell ($dottedquad, $ifname, $cell)
        if (strlen ($ifname))
                echo '@' . $ifname;
        echo "</td>";
-       echo "<td><a href='index.php?page=object&object_id=${cell['id']}&hl_ipv4_addr=${dottedquad}'><strong>${cell['dname']}</strong></a></td>";
+       $ipv6 = new IPv6Address;
+       $ip_type = $ipv6->parse ($dottedquad) ? 'ipv6' : 'ipv4';
+       echo "<td><a href='index.php?page=object&object_id=${cell['id']}&hl_${ip_type}_addr=${dottedquad}'><strong>${cell['dname']}</strong></a></td>";
        echo "</td></tr><tr><td>";
        printImageHREF ('router');
        echo "</td></tr><tr><td>";
@@ -6276,7 +7005,8 @@ function showPathAndSearch ($pageno)
        // Path.
        echo "<td class=activemenuitem width='99%'>" . getConfigVar ('enterprise');
        $path = getPath ($pageno);
-       foreach ($path as $no)
+       $items = array();
+       foreach (array_reverse ($path) as $no)
        {
                if (isset ($page[$no]['title']))
                        $title = array
@@ -6286,11 +7016,28 @@ function showPathAndSearch ($pageno)
                        );
                else
                        $title = dynamic_title_decoder ($no);
-               echo ": <a href='index.php?page=${no}&tab=default";
+               $item = "<a href='index.php?";
+               if (! isset ($title['params']['page']))
+                       $title['params']['page'] = $no;
+               if (! isset ($title['params']['tab']))
+                       $title['params']['tab'] = 'default';
+               $is_first = TRUE;
+               $ancor_tail = '';
                foreach ($title['params'] as $param_name => $param_value)
-                       echo "&${param_name}=${param_value}";
-               echo "'>" . $title['name'] . "</a>";
+               {
+                       if ($param_name == '#')
+                       {
+                               $ancor_tail = '#' . $param_value;
+                               continue;
+                       }
+                       $item .= ($is_first ? '' : '&') . "${param_name}=${param_value}";
+                       $is_first = FALSE;
+               }
+               $item .= $ancor_tail;
+               $item .= "'>" . $title['name'] . "</a>";
+               $items[] = $item;
        }
+       echo ' : ' . implode(' : ', array_reverse ($items));
        echo "</td>";
        // Search form.
        echo "<td><table border=0 cellpadding=0 cellspacing=0><tr><td>Search:</td>";
@@ -6347,6 +7094,7 @@ function showTabs ($pageno, $tabno)
 function dynamic_title_decoder ($path_position)
 {
        global $sic;
+       static $net_id;
        switch ($path_position)
        {
        case 'index':
@@ -6439,44 +7187,72 @@ function dynamic_title_decoder ($path_position)
                        'params' => array ('file_id' => $_REQUEST['file_id'])
                );
        case 'ipaddress':
-               assertIPv4Arg ('ip');
                $address = getIPv4Address ($_REQUEST['ip']);
                return array
                (
                        'name' => $_REQUEST['ip'] . ($address['name'] != '' ? ' (' . $address['name'] . ')' : ''),
                        'params' => array ('ip' => $_REQUEST['ip'])
                );
+       case 'ipv6address':
+               $address = getIPv6Address (assertIPArg ('ip'));
+               return array
+               (
+                       'name' => $address['ip'] . ($address['name'] != '' ? ' (' . $address['name'] . ')' : ''),
+                       'params' => array ('ip' => $address['ip'])
+               );
        case 'ipv4net':
-               global $pageno;
-               switch ($pageno)
-               {
-               case 'ipv4net':
-                       assertUIntArg ('id');
-                       $range = spotEntity ('ipv4net', $_REQUEST['id']);
-                       return array
-                       (
-                               'name' => $range['ip'] . '/' . $range['mask'],
-                               'params' => array ('id' => $_REQUEST['id'])
-                       );
-               case 'ipaddress':
-                       assertIPv4Arg ('ip');
-                       $range = spotEntity ('ipv4net', getIPv4AddressNetworkId ($_REQUEST['ip']));
-                       return array
-                       (
-                               'name' => $range['ip'] . '/' . $range['mask'],
-                               'params' => array
+       case 'ipv6net':
+        global $pageno;
+        switch ($pageno)
+               {
+                       case 'ipaddress':
+                               $range = spotEntity ('ipv4net', getIPv4AddressNetworkId ($_REQUEST['ip']));
+                               $net_id = $range['id'];
+                               return array
                                (
-                                       'id' => $range['id'],
-                                       'hl_ipv4_addr' => $_REQUEST['ip']
-                               )
-                       );
-               default:
-                       return array
-                       (
-                               'name' => __FUNCTION__ . '() failure',
-                               'params' => array()
-                       );
+                                       'name' => $range['ip'] . '/' . $range['mask'],
+                                       'params' => array
+                                       (
+                                               'id' => $range['id'],
+                                               'page' => 'ipv4net',
+                                               'hl_ipv4_addr' => $_REQUEST['ip']
+                                       )
+                               );
+                       case 'ipv6address':
+                               $ipv6 = assertIPArg ('ip');
+                               $range = spotEntity ('ipv6net', getIPv6AddressNetworkId ($ipv6));
+                               $net_id = $range['id'];
+                               return array
+                               (
+                                       'name' => $range['ip'] . '/' . $range['mask'],
+                                       'params' => array
+                                       (
+                                               'id' => $range['id'],
+                                               'page' => 'ipv6net',
+                                               'hl_ipv6_addr' => $_REQUEST['ip']
+                                       )
+                               );
+                       default:
+                               assertUIntArg ('id');
+                               $range = spotEntity ($path_position, $_REQUEST['id']);
+                               $net_id = $range['id'];
+                               return array
+                               (
+                                       'name' => $range['ip'] . '/' . $range['mask'],
+                                       'params' => array ('id' => $_REQUEST['id'])
+                               );
                }
+       case 'ipv4space':
+       case 'ipv6space':
+               global $pageno;
+               $ip_ver = preg_replace ('/[^\d]*/', '', $path_position);
+               $params = isset ($net_id) ? array ('eid' => $net_id, 'hl_net' => 1, '#' => "netid${net_id}") : array();
+               unset ($net_id);
+               return array
+               (
+                       'name' => "IPv$ip_ver space",
+                       'params' => $params,
+               );
        case 'row':
                global $pageno;
                switch ($pageno)
@@ -7389,11 +8165,11 @@ function renderVLANInfo ($vlan_ck)
        echo "<tr><th width='50%' class=tdright>Propagation:</th><td class=tdleft>" . $vtoptions[$vlan['vlan_prop']] . "</td></tr>";
        echo '</table>';
        finishPortlet();
-       if (!count ($vlan['ipv4nets']))
+       if (0 == count ($vlan['ipv4nets']) + count ($vlan['ipv6nets']))
                startPortlet ('no networks');
        else
        {
-               startPortlet ('networks (' . count ($vlan['ipv4nets']) . ')');
+               startPortlet ('networks (' . (count ($vlan['ipv4nets']) + count ($vlan['ipv6nets'])) . ')');
                $order = 'odd';
                echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
                echo '<tr><th>';
@@ -7401,9 +8177,10 @@ function renderVLANInfo ($vlan_ck)
                echo '</th><th>';
                printImageHREF ('text');
                echo '</th></tr>';
-               foreach ($vlan['ipv4nets'] as $netid)
+               foreach (array ('ipv4net', 'ipv6net') as $nettype)
+               foreach ($vlan[$nettype . 's'] as $netid)
                {
-                       $net = spotEntity ('ipv4net', $netid);
+                       $net = spotEntity ($nettype, $netid);
                        #echo "<tr class=row_${order}><td>";
                        echo '<tr><td>';
                        renderCell ($net);
@@ -7441,52 +8218,60 @@ function renderVLANInfo ($vlan_ck)
        echo '</td></tr></table>';
 }
 
-function renderVLANIPv4 ($some_id)
+function renderVLANIPLinks ($some_id)
 {
-       function printNewItemTR ($sname, $options)
+       function printNewItemTR ($sname, $options, $extra = array())
        {
                if (!count ($options))
                        return;
-               printOpFormIntro ('bind');
+               printOpFormIntro ('bind', $extra);
                echo '<tr><td>' . getNiftySelect ($options, array ('name' => $sname, 'tabindex' => 101, 'size' => getConfigVar ('MAXSELSIZE')));
                echo '</td><td>' . getImageHREF ('ATTACH', 'bind', TRUE, 102) . '</td></tr></form>';
        }
-       global $pageno;
-       $minuslines = array();
-       $plusoptions = array();
+       global $pageno, $tabno;
        echo '<table cellspacing=0 cellpadding=5 align=center class=widetable>';
        echo '<tr>';
+
+       // fill $minuslines, $plusoptions, $select_name
+       $minuslines = array();
+       $plusoptions = array();
+       $extra = array();
        switch ($pageno)
        {
        case 'vlan':
+               $ip_ver = $tabno == 'ipv6' ? 'ipv6' : 'ipv4';
                echo '<th>' . getImageHREF ('net') . '</th>';
                $vlan = getVLANInfo ($some_id);
-               foreach ($vlan['ipv4nets'] as $net_id)
+               foreach ($vlan[$ip_ver . "nets"] as $net_id)
                        $minuslines[] = array
                        (
-                               'ipv4net_id' => $net_id,
+                               'net_id' => $net_id,
                                'domain_id' => $vlan['domain_id'],
                                'vlan_id' => $vlan['vlan_id'],
                        );
                // Any VLAN can link to any network, which isn't yet linked to current domain.
-               foreach (getVLANIPv4Options ($vlan['domain_id']) as $net_id)
+               // get free IP nets
+               $netlist_func  = $ip_ver == 'ipv6' ? 'getVLANIPv6Options' : 'getVLANIPv4Options';
+               foreach ($netlist_func ($vlan['domain_id']) as $net_id)
                {
-                       $netinfo = spotEntity ('ipv4net', $net_id);
+                       $netinfo = spotEntity ($ip_ver . 'net', $net_id);
                        if (considerConfiguredConstraint ($netinfo, 'VLANIPV4NET_LISTSRC'))
                                $plusoptions['other'][$net_id] =
                                        $netinfo['ip'] . '/' . $netinfo['mask'] . ' ' . $netinfo['name'];
                }
                $select_name = 'id';
+               $extra = array ('vlan_ck' => $vlan['domain_id'] . '-' . $vlan['vlan_id']);
                break;
        case 'ipv4net':
+       case 'ipv6net':
                echo '<th>VLAN</th>';
-               $netinfo = spotEntity ('ipv4net', $some_id);
+               $netinfo = spotEntity ($pageno, $some_id);
                amplifyCell ($netinfo);
                // find out list of VLAN domains, where the current network is already linked
                foreach ($netinfo['8021q'] as $item)
                        $minuslines[] = array
                        (
-                               'ipv4net_id' => $netinfo['id'],
+                               'net_id' => $netinfo['id'],
                                'domain_id' => $item['domain_id'],
                                'vlan_id' => $item['vlan_id'],
                        );
@@ -7497,20 +8282,22 @@ function renderVLANIPv4 ($some_id)
                                        $plusoptions[$dominfo['description']][$dominfo['id']. '-' . $vlaninfo['vlan_id']] =
                                                $vlaninfo['vlan_id'] . ' (' . $vlaninfo['netc'] . ') ' . $vlaninfo['vlan_descr'];
                $select_name = 'vlan_ck';
+               $extra = array ('id' => $netinfo['id']);
                break;
        }
        echo '<th>&nbsp;</th></tr>';
        if (getConfigVar ('ADDNEW_AT_TOP') == 'yes')
-               printNewItemTR ($select_name, $plusoptions);
+               printNewItemTR ($select_name, $plusoptions, $extra);
        foreach ($minuslines as $item)
        {
                echo '<tr class=trbusy><td>';
                switch ($pageno)
                {
                case 'vlan':
-                       renderCell (spotEntity ('ipv4net', $item['ipv4net_id']));
+                       renderCell (spotEntity ($ip_ver . 'net', $item['net_id']));
                        break;
                case 'ipv4net':
+               case 'ipv6net':
                        $vlaninfo = getVLANInfo ($item['domain_id'] . '-' . $item['vlan_id']);
                        echo formatVLANName ($vlaninfo, 'markup long');
                        break;
@@ -7520,15 +8307,16 @@ function renderVLANIPv4 ($some_id)
                (
                        array
                        (
+                               'id' => $some_id,
                                'op' => 'unbind',
-                               'id' => $item['ipv4net_id'],
+                               'id' => $item['net_id'],
                                'vlan_ck' => $item['domain_id'] . '-' . $item['vlan_id']
                        )
                );
                echo '">' . getImageHREF ('Cut', 'unbind') . '</a></td></tr>';
        }
        if (getConfigVar ('ADDNEW_AT_TOP') != 'yes')
-               printNewItemTR ($select_name, $plusoptions);
+               printNewItemTR ($select_name, $plusoptions, $extra);
        echo '</table>';
 }
 
index 2831598..0525a15 100644 (file)
@@ -14,8 +14,9 @@ $delayauth = array();
 
 $indexlayout = array
 (
-       array ('rackspace', 'depot', 'ipv4space', 'files'),
-       array ('config', 'reports', 'ipv4slb', '8021q'),
+       array ('rackspace', 'depot', 'ipv4space', 'ipv6space'),
+       array ('files', 'reports', 'ipv4slb', '8021q'),
+       array ('config'),
 );
 
 $page['index']['title'] = 'Main page';
@@ -88,6 +89,7 @@ $tab['object']['edit'] = 'Properties';
 $tab['object']['rackspace'] = 'Rackspace';
 $tab['object']['ports'] = 'Ports';
 $tab['object']['ipv4'] = 'IPv4';
+$tab['object']['ipv6'] = 'IPv6';
 $tab['object']['nat4'] = 'NATv4';
 $tab['object']['livevlans'] = 'Live VLANs';
 $tab['object']['livecdp'] = 'Live CDP';
@@ -107,6 +109,7 @@ $tabhandler['object']['edit'] = 'renderEditObjectForm';
 $tabhandler['object']['rackspace'] = 'renderRackSpaceForObject';
 $tabhandler['object']['ports'] = 'renderPortsForObject';
 $tabhandler['object']['ipv4'] = 'renderIPv4ForObject';
+$tabhandler['object']['ipv6'] = 'renderIPv6ForObject';
 $tabhandler['object']['nat4'] = 'renderNATv4ForObject';
 $tabhandler['object']['livevlans'] = 'renderVLANMembership';
 $tabhandler['object']['livecdp'] = 'renderDiscoveredNeighbors';
@@ -123,6 +126,7 @@ $tabhandler['object']['8021qports'] = 'renderObject8021QPorts';
 $tabhandler['object']['8021qsync'] = 'renderObject8021QSync';
 $trigger['object']['rackspace'] = 'trigger_rackspace';
 $trigger['object']['ipv4'] = 'trigger_ipv4';
+$trigger['object']['ipv6'] = 'trigger_ipv6';
 $trigger['object']['nat4'] = 'trigger_natv4';
 $trigger['object']['livevlans'] = 'trigger_livevlans';
 $trigger['object']['livecdp'] = 'trigger_LiveCDP';
@@ -148,6 +152,9 @@ $ophandler['object']['ports']['useup'] = 'useupPort';
 $ophandler['object']['ipv4']['updIPv4Allocation'] = 'updIPv4Allocation';
 $ophandler['object']['ipv4']['addIPv4Allocation'] = 'addIPv4Allocation';
 $ophandler['object']['ipv4']['delIPv4Allocation'] = 'delIPv4Allocation';
+$ophandler['object']['ipv6']['updIPv6Allocation'] = 'updIPv6Allocation';
+$ophandler['object']['ipv6']['addIPv6Allocation'] = 'addIPv6Allocation';
+$ophandler['object']['ipv6']['delIPv6Allocation'] = 'delIPv6Allocation';
 $ophandler['object']['edit']['clearSticker'] = 'clearSticker';
 $ophandler['object']['edit']['update'] = 'updateObject';
 $ophandler['object']['edit']['resetObject'] = 'resetObject';
@@ -176,7 +183,6 @@ $ophandler['object']['8021qsync']['resolve8021QConflicts'] = 'resolve8021QConfli
 $delayauth['object']['8021qports']['save8021QConfig'] = TRUE;
 $delayauth['object']['livevlans']['setPortVLAN'] = TRUE;
 
-$page['ipv4space']['title'] = 'IPv4 space';
 $page['ipv4space']['parent'] = 'index';
 $tab['ipv4space']['default'] = 'Browse';
 $tab['ipv4space']['newrange'] = 'Manage';
@@ -185,6 +191,14 @@ $tabhandler['ipv4space']['newrange'] = 'renderIPv4SpaceEditor';
 $ophandler['ipv4space']['newrange']['addIPv4Prefix'] = 'addIPv4Prefix';
 $ophandler['ipv4space']['newrange']['delIPv4Prefix'] = 'delIPv4Prefix';
 
+$page['ipv6space']['parent'] = 'index';
+$tab['ipv6space']['default'] = 'Browse';
+$tab['ipv6space']['newrange'] = 'Manage';
+$tabhandler['ipv6space']['default'] = 'renderIPv6Space';
+$tabhandler['ipv6space']['newrange'] = 'renderIPv6SpaceEditor';
+$ophandler['ipv6space']['newrange']['addIPv6Prefix'] = 'addIPv6Prefix';
+$ophandler['ipv6space']['newrange']['delIPv6Prefix'] = 'delIPv6Prefix';
+
 $page['ipv4net']['parent'] = 'ipv4space';
 $page['ipv4net']['bypass'] = 'id';
 $page['ipv4net']['bypass_type'] = 'uint';
@@ -195,11 +209,11 @@ $tab['ipv4net']['tags'] = 'Tags';
 $tab['ipv4net']['files'] = 'Files';
 $tab['ipv4net']['8021q'] = '802.1Q';
 $tabhandler['ipv4net']['default'] = 'renderIPv4Network';
-$tabhandler['ipv4net']['properties'] = 'renderIPv4NetworkProperties';
+$tabhandler['ipv4net']['properties'] = 'renderIPNetworkProperties';
 $tabhandler['ipv4net']['liveptr'] = 'renderLivePTR';
 $tabhandler['ipv4net']['tags'] = 'renderEntityTags';
 $tabhandler['ipv4net']['files'] = 'renderFilesForEntity';
-$tabhandler['ipv4net']['8021q'] = 'renderVLANIPv4';
+$tabhandler['ipv4net']['8021q'] = 'renderVLANIPLinks';
 $trigger['ipv4net']['tags'] = 'trigger_tags';
 $trigger['ipv4net']['8021q'] = 'trigger_ipv4net_vlanconfig';
 $ophandler['ipv4net']['properties']['editRange'] = 'updIPv4Prefix';
@@ -211,20 +225,57 @@ $ophandler['ipv4net']['files']['unlinkFile'] = 'unlinkFile';
 $ophandler['ipv4net']['8021q']['bind'] = 'bindVLANtoIPv4';
 $ophandler['ipv4net']['8021q']['unbind'] = 'unbindVLANfromIPv4';
 
+$page['ipv6net']['parent'] = 'ipv6space';
+$page['ipv6net']['bypass'] = 'id';
+$page['ipv6net']['bypass_type'] = 'uint';
+$tab['ipv6net']['default'] = 'Browse';
+$tab['ipv6net']['properties'] = 'Properties';
+$tab['ipv6net']['tags'] = 'Tags';
+$tab['ipv6net']['files'] = 'Files';
+$tab['ipv6net']['8021q'] = '802.1Q';
+$tabhandler['ipv6net']['default'] = 'renderIPv6Network';
+$tabhandler['ipv6net']['properties'] = 'renderIPNetworkProperties';
+$tabhandler['ipv6net']['tags'] = 'renderEntityTags';
+$tabhandler['ipv6net']['files'] = 'renderFilesForEntity';
+$tabhandler['ipv6net']['8021q'] = 'renderVLANIPLinks';
+$trigger['ipv6net']['tags'] = 'trigger_tags';
+$trigger['ipv6net']['8021q'] = 'trigger_ipv6net_vlanconfig';
+$ophandler['ipv6net']['properties']['editRange'] = 'updIPv6Prefix';
+$ophandler['ipv6net']['tags']['saveTags'] = 'saveEntityTags';
+$ophandler['ipv6net']['files']['addFile'] = 'addFileToEntity';
+$ophandler['ipv6net']['files']['linkFile'] = 'linkFileToEntity';
+$ophandler['ipv6net']['files']['unlinkFile'] = 'unlinkFile';
+$ophandler['ipv6net']['8021q']['bind'] = 'bindVLANtoIPv6';
+$ophandler['ipv6net']['8021q']['unbind'] = 'unbindVLANfromIPv6';
+
 $page['ipaddress']['parent'] = 'ipv4net';
 $page['ipaddress']['bypass'] = 'ip';
 $page['ipaddress']['bypass_type'] = 'inet4';
 $tab['ipaddress']['default'] = 'Browse';
 $tab['ipaddress']['properties'] = 'Properties';
 $tab['ipaddress']['assignment'] = 'Allocation';
-$tabhandler['ipaddress']['default'] = 'renderIPv4Address';
-$tabhandler['ipaddress']['properties'] = 'renderIPv4AddressProperties';
-$tabhandler['ipaddress']['assignment'] = 'renderIPv4AddressAllocations';
+$tabhandler['ipaddress']['default'] = 'renderIPAddress';
+$tabhandler['ipaddress']['properties'] = 'renderIPAddressProperties';
+$tabhandler['ipaddress']['assignment'] = 'renderIPAddressAllocations';
 $ophandler['ipaddress']['properties']['editAddress'] = 'editAddress';
 $ophandler['ipaddress']['assignment']['delIPv4Allocation'] = 'delIPv4Allocation';
 $ophandler['ipaddress']['assignment']['updIPv4Allocation'] = 'updIPv4Allocation';
 $ophandler['ipaddress']['assignment']['addIPv4Allocation'] = 'addIPv4Allocation';
 
+$page['ipv6address']['parent'] = 'ipv6net';
+$page['ipv6address']['bypass'] = 'ip';
+$page['ipv6address']['bypass_type'] = 'string';
+$tab['ipv6address']['default'] = 'Browse';
+$tab['ipv6address']['properties'] = 'Properties';
+$tab['ipv6address']['assignment'] = 'Allocation';
+$tabhandler['ipv6address']['default'] = 'renderIPAddress';
+$tabhandler['ipv6address']['properties'] = 'renderIPAddressProperties';
+$tabhandler['ipv6address']['assignment'] = 'renderIPAddressAllocations';
+$ophandler['ipv6address']['properties']['editAddress'] = 'editv6Address';
+$ophandler['ipv6address']['assignment']['delIPv6Allocation'] = 'delIPv6Allocation';
+$ophandler['ipv6address']['assignment']['updIPv6Allocation'] = 'updIPv6Allocation';
+$ophandler['ipv6address']['assignment']['addIPv6Allocation'] = 'addIPv6Allocation';
+
 $page['ipv4slb']['title'] = 'IPv4 SLB';
 $page['ipv4slb']['parent'] = 'index';
 $page['ipv4slb']['handler'] = 'renderIPv4SLB';
@@ -448,12 +499,14 @@ $page['reports']['parent'] = 'index';
 $tab['reports']['default'] = 'System';
 $tab['reports']['rackcode'] = 'RackCode';
 $tab['reports']['ipv4'] = 'IPv4';
+$tab['reports']['ipv6'] = 'IPv6';
 $tab['reports']['ports'] = 'Ports';
 $tab['reports']['local'] = getConfigVar ('enterprise');
 $trigger['reports']['local'] = 'trigger_localreports';
 $tabhandler['reports']['default'] = 'renderSystemReports';
 $tabhandler['reports']['rackcode'] = 'renderRackCodeReports';
 $tabhandler['reports']['ipv4'] = 'renderIPv4Reports';
+$tabhandler['reports']['ipv6'] = 'renderIPv6Reports';
 $tabhandler['reports']['ports'] = 'renderPortsReport';
 $tabhandler['reports']['local'] = 'renderLocalReports';
 
@@ -524,10 +577,16 @@ $page['vlan']['bypass'] = 'vlan_ck';
 $page['vlan']['bypass_type'] = 'string';
 $tab['vlan']['default'] = 'View';
 $tab['vlan']['ipv4'] = 'IPv4';
+$tab['vlan']['ipv6'] = 'IPv6';
+$trigger['vlan']['ipv4'] = 'trigger_vlan_ipv4net';
+$trigger['vlan']['ipv6'] = 'trigger_vlan_ipv6net';
 $tabhandler['vlan']['default'] = 'renderVLANInfo';
-$tabhandler['vlan']['ipv4'] = 'renderVLANIPv4';
+$tabhandler['vlan']['ipv4'] = 'renderVLANIPLinks';
+$tabhandler['vlan']['ipv6'] = 'renderVLANIPLinks';
 $ophandler['vlan']['ipv4']['bind'] = 'bindVLANtoIPv4';
 $ophandler['vlan']['ipv4']['unbind'] = 'unbindVLANfromIPv4';
+$ophandler['vlan']['ipv6']['bind'] = 'bindVLANtoIPv6';
+$ophandler['vlan']['ipv6']['unbind'] = 'unbindVLANfromIPv6';
 
 $page['vst']['parent'] = '8021q';
 $page['vst']['bypass'] = 'vst_id';
index a0c15c6..746aeb8 100644 (file)
@@ -396,6 +396,19 @@ function updIPv4Allocation ()
        return buildRedirectURL (__FUNCTION__, $result === FALSE ? 'ERR' : 'OK');
 }
 
+$msgcode['updIPv6Allocation']['OK'] = 12;
+$msgcode['updIv6PAllocation']['ERR'] = 109;
+function updIPv6Allocation ()
+{
+       $ipv6 = assertIPv6Arg ('ip');
+       assertUIntArg ('object_id');
+       assertStringArg ('bond_name', TRUE);
+       assertStringArg ('bond_type');
+
+       $result = updateIPv6Bond ($ipv6, $_REQUEST['object_id'], $_REQUEST['bond_name'], $_REQUEST['bond_type']);
+       return buildRedirectURL (__FUNCTION__, $result === FALSE ? 'ERR' : 'OK');
+}
+
 $msgcode['delIPv4Allocation']['OK'] = 14;
 $msgcode['delIPv4Allocation']['ERR'] = 111;
 function delIPv4Allocation ()
@@ -407,6 +420,16 @@ function delIPv4Allocation ()
        return buildRedirectURL (__FUNCTION__, $result === FALSE ? 'ERR' : 'OK');
 }
 
+$msgcode['delIPv6Allocation']['OK'] = 14;
+$msgcode['delIPv6Allocation']['ERR'] = 111;
+function delIPv6Allocation ()
+{
+       assertUIntArg ('object_id');
+       $ipv6 = assertIPv6Arg ('ip');
+       $result = unbindIPv6FromObject ($ipv6, $_REQUEST['object_id']);
+       return buildRedirectURL (__FUNCTION__, $result === FALSE ? 'ERR' : 'OK');
+}
+
 $msgcode['addIPv4Allocation']['OK'] = 13;
 $msgcode['addIPv4Allocation']['ERR1'] = 170;
 $msgcode['addIPv4Allocation']['ERR2'] = 100;
@@ -437,6 +460,38 @@ function addIPv4Allocation ()
        return buildRedirectURL (__FUNCTION__, 'OK');
 }
 
+$msgcode['addIPv6Allocation']['OK'] = 13;
+$msgcode['addIPv6Allocation']['ERR1'] = 170;
+$msgcode['addIPv6Allocation']['ERR2'] = 100;
+function addIPv6Allocation ()
+{
+       assertUIntArg ('object_id');
+       assertStringArg ('bond_name', TRUE);
+       assertStringArg ('bond_type');
+
+       // Strip masklen.
+       $ipv6 = new IPv6Address;
+       if (! $ipv6->parse (preg_replace ('@/\d+$@', '', $_REQUEST['ip'])))
+               throw new InvalidRequestArgException('ip', $_REQUEST['ip'], 'parameter is not a valid ipv6 address');
+
+       if  (getConfigVar ('IPV4_JAYWALK') != 'yes' and NULL === getIPv6AddressNetworkId ($ipv6))
+               return buildRedirectURL (__FUNCTION__, 'ERR1', array ($ip));
+
+       if (FALSE === bindIPv6ToObject ($ipv6, $_REQUEST['object_id'], $_REQUEST['bond_name'], $_REQUEST['bond_type']))
+               return buildRedirectURL (__FUNCTION__, 'ERR2', array ($error));
+       $address = getIPv6Address ($ipv6);
+       if ($address['reserved'] == 'yes' or strlen ($address['name']))
+       {
+               $release = getConfigVar ('IPV4_AUTO_RELEASE');
+               if ($release >= 1)
+                       $address['reserved'] = 'no';
+               if ($release >= 2)
+                       $address['name'] = '';
+               updateAddress ($ipv6, $address['name'], $address['reserved']);
+       }
+       return buildRedirectURL (__FUNCTION__, 'OK');
+}
+
 $msgcode['addIPv4Prefix']['OK'] = 23;
 $msgcode['addIPv4Prefix']['ERR'] = 100;
 $msgcode['addIPv4Prefix']['ERR1'] = 173;
@@ -458,6 +513,26 @@ function addIPv4Prefix ()
                return buildRedirectURL (__FUNCTION__, 'OK');
 }
 
+$msgcode['addIPv6Prefix']['OK'] = 86;
+$msgcode['addIPv6Prefix']['ERR'] = 100;
+$msgcode['addIPv6Prefix']['ERR1'] = 173;
+$msgcode['addIPv6Prefix']['ERR2'] = 174;
+$msgcode['addIPv6Prefix']['ERR3'] = 175;
+$msgcode['addIPv6Prefix']['ERR4'] = 176;
+function addIPv6Prefix ()
+{
+       assertStringArg ('range');
+       assertStringArg ('name', TRUE);
+
+       $taglist = isset ($_REQUEST['taglist']) ? $_REQUEST['taglist'] : array();
+       global $sic;
+       $error = createIPv6Prefix ($_REQUEST['range'], $sic['name'], $taglist);
+       if ($error != '')
+               return buildRedirectURL (__FUNCTION__, 'ERR', array ($error));
+       else
+               return buildRedirectURL (__FUNCTION__, 'OK');
+}
+
 $msgcode['delIPv4Prefix']['OK'] = 24;
 $msgcode['delIPv4Prefix']['ERR'] = 100;
 function delIPv4Prefix ()
@@ -470,6 +545,18 @@ function delIPv4Prefix ()
                return buildRedirectURL (__FUNCTION__, 'OK');
 }
 
+$msgcode['delIPv6Prefix']['OK'] = 85;
+$msgcode['delIPv6Prefix']['ERR'] = 100;
+function delIPv6Prefix ()
+{
+       assertUIntArg ('id');
+       $error = destroyIPv6Prefix ($_REQUEST['id']);
+       if ($error != '')
+               return buildRedirectURL (__FUNCTION__, 'ERR', array ($error));
+       else
+               return buildRedirectURL (__FUNCTION__, 'OK');
+}
+
 $msgcode['updIPv4Prefix']['OK'] = 25;
 $msgcode['updIPv4Prefix']['ERR'] = 109;
 function updIPv4Prefix ()
@@ -482,6 +569,18 @@ function updIPv4Prefix ()
        return buildRedirectURL (__FUNCTION__, $result !== FALSE ? 'OK' : 'ERR');
 }
 
+$msgcode['updIPv6Prefix']['OK'] = 86;
+$msgcode['updIPv6Prefix']['ERR'] = 109;
+function updIPv6Prefix ()
+{
+       assertUIntArg ('id');
+       assertStringArg ('name', TRUE);
+       assertStringArg ('comment', TRUE);
+       global $sic;
+       $result = updateIPv6Network_real ($sic['id'], $sic['name'], $sic['comment']);
+       return buildRedirectURL (__FUNCTION__, $result !== FALSE ? 'OK' : 'ERR');
+}
+
 $msgcode['editAddress']['OK'] = 27;
 $msgcode['editAddress']['ERR'] = 100;
 function editAddress ()
@@ -500,6 +599,24 @@ function editAddress ()
                return buildRedirectURL (__FUNCTION__, 'OK');
 }
 
+$msgcode['editv6Address']['OK'] = 27;
+$msgcode['editv6Address']['ERR'] = 100;
+function editv6Address ()
+{
+       $ipv6 = assertIPArg ('ip');
+       assertStringArg ('name', TRUE);
+
+       if (isset ($_REQUEST['reserved']))
+               $reserved = $_REQUEST['reserved'];
+       else
+               $reserved = 'off';
+       $error = updateAddress ($ipv6, $_REQUEST['name'], $reserved == 'on' ? 'yes' : 'no');
+       if ($error != '')
+               return buildRedirectURL (__FUNCTION__, 'ERR', array ($error));
+       else
+               return buildRedirectURL (__FUNCTION__, 'OK');
+}
+
 $msgcode['createUser']['OK'] = 40;
 $msgcode['createUser']['ERR'] = 102;
 function createUser ()
@@ -2373,6 +2490,16 @@ function bindVLANtoIPv4 ()
        return buildRedirectURL (__FUNCTION__, $result ? 'OK' : 'ERR');
 }
 
+$msgcode['bindVLANtoIPv6']['OK'] = 48;
+$msgcode['bindVLANtoIPv6']['ERR'] = 110;
+function bindVLANtoIPv6 ()
+{
+       assertUIntArg ('id'); // network id
+       global $sic;
+       $result = commitSupplementVLANIPv6 ($sic['vlan_ck'], $_REQUEST['id']);
+       return buildRedirectURL (__FUNCTION__, $result ? 'OK' : 'ERR');
+}
+
 $msgcode['unbindVLANfromIPv4']['OK'] = 49;
 $msgcode['unbindVLANfromIPv4']['ERR'] = 111;
 function unbindVLANfromIPv4 ()
@@ -2383,6 +2510,16 @@ function unbindVLANfromIPv4 ()
        return buildRedirectURL (__FUNCTION__, $result ? 'OK' : 'ERR');
 }
 
+$msgcode['unbindVLANfromIPv6']['OK'] = 49;
+$msgcode['unbindVLANfromIPv6']['ERR'] = 111;
+function unbindVLANfromIPv6 ()
+{
+       assertUIntArg ('id'); // network id
+       global $sic;
+       $result = commitReduceVLANIPv6 ($sic['vlan_ck'], $sic['id']);
+       return buildRedirectURL (__FUNCTION__, $result ? 'OK' : 'ERR');
+}
+
 $msgcode['process8021QSyncRequest']['OK'] = 63;
 $msgcode['process8021QSyncRequest']['ERR'] = 191;
 function process8021QSyncRequest ()
index f5ce2cd..4d89524 100644 (file)
@@ -124,6 +124,15 @@ function trigger_ipv4 ()
        return considerConfiguredConstraint (spotEntity ('object', $_REQUEST['object_id']), 'IPV4OBJ_LISTSRC') ? 'std' : '';
 }
 
+function trigger_ipv6 ()
+{
+       assertUIntArg ('object_id');
+       if (count (getObjectIPv6Allocations ($_REQUEST['object_id'])))
+               return 'std';
+       // Only hide the tab, if there are no addresses allocated.
+       return considerConfiguredConstraint (spotEntity ('object', $_REQUEST['object_id']), 'IPV4OBJ_LISTSRC') ? 'std' : '';
+}
+
 function trigger_natv4 ()
 {
        assertUIntArg ('object_id');
@@ -210,6 +219,32 @@ function trigger_ipv4net_vlanconfig ()
                return '';
 }
 
+// implement similar logic for IPv6 networks
+function trigger_ipv6net_vlanconfig ()
+{
+       if (!count (getVLANDomainOptions())) // no domains -- no VLANs to bind with
+               return '';
+       $netinfo = spotEntity ('ipv6net', $_REQUEST['id']);
+       if ($netinfo['vlanc'])
+               return 'std';
+       elseif (considerConfiguredConstraint ($netinfo, 'VLANIPV4NET_LISTSRC'))
+               return 'attn';
+       else
+               return '';
+}
+
+function trigger_vlan_ipv4net ()
+{
+       $vlan_info = getVLANInfo ($_REQUEST['vlan_ck']);
+       return count ($vlan_info['ipv4nets']) ? 'std' : 'attn';
+}
+
+function trigger_vlan_ipv6net ()
+{
+       $vlan_info = getVLANInfo ($_REQUEST['vlan_ck']);
+       return count ($vlan_info['ipv6nets']) ? 'std' : 'attn';
+}
+
 function trigger_object_8021qports ()
 {
        global $sic;
index 8a72a22..d05706d 100644 (file)
@@ -106,7 +106,7 @@ CREATE TABLE `File` (
 CREATE TABLE `FileLink` (
   `id` int(10) unsigned NOT NULL auto_increment,
   `file_id` int(10) unsigned NOT NULL,
-  `entity_type` enum('ipv4net','ipv4rspool','ipv4vs','object','rack','user') NOT NULL default 'object',
+  `entity_type` enum('ipv4net','ipv4rspool','ipv4vs','object','rack','user','ipv6net') NOT NULL default 'object',
   `entity_id` int(10) NOT NULL,
   PRIMARY KEY  (`id`),
   KEY `FileLink-file_id` (`file_id`),
@@ -200,6 +200,32 @@ CREATE TABLE `IPv4VS` (
   PRIMARY KEY  (`id`)
 ) ENGINE=InnoDB;
 
+CREATE TABLE `IPv6Address` (
+  `ip` binary(16) NOT NULL,
+  `name` char(255) NOT NULL default '',
+  `reserved` enum('yes','no') default NULL,
+  PRIMARY KEY  (`ip`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `IPv6Allocation` (
+  `object_id` int(10) unsigned NOT NULL default '0',
+  `ip` binary(16) NOT NULL,
+  `name` char(255) NOT NULL default '',
+  `type` enum('regular','shared','virtual','router') default NULL,
+  PRIMARY KEY  (`object_id`,`ip`)
+) ENGINE=InnoDB;
+
+CREATE TABLE `IPv6Network` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `ip` binary(16) NOT NULL,
+  `mask` int(10) unsigned NOT NULL,
+  `last_ip` binary(16) NOT NULL,
+  `name` char(255) default NULL,
+  `comment` text,
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `ip` (`ip`,`mask`)
+) ENGINE=InnoDB;
+
 CREATE TABLE `LDAPCache` (
   `presented_username` char(64) NOT NULL,
   `successful_hash` char(40) NOT NULL,
@@ -384,7 +410,7 @@ CREATE TABLE `Script` (
 ) ENGINE=InnoDB;
 
 CREATE TABLE `TagStorage` (
-  `entity_realm` enum('file','ipv4net','ipv4vs','ipv4rspool','object','rack','user') NOT NULL default 'object',
+  `entity_realm` enum('file','ipv4net','ipv4vs','ipv4rspool','object','rack','user','ipv6net') NOT NULL default 'object',
   `entity_id` int(10) unsigned NOT NULL,
   `tag_id` int(10) unsigned NOT NULL default '0',
   UNIQUE KEY `entity_tag` (`entity_realm`,`entity_id`,`tag_id`),
@@ -447,6 +473,16 @@ CREATE TABLE `VLANIPv4` (
   CONSTRAINT `VLANIPv4-FK-ipv4net_id` FOREIGN KEY (`ipv4net_id`) REFERENCES `IPv4Network` (`id`) ON DELETE CASCADE
 ) ENGINE=InnoDB;
 
+CREATE TABLE `VLANIPv6` (
+  `domain_id` int(10) unsigned NOT NULL,
+  `vlan_id` int(10) unsigned NOT NULL,
+  `ipv6net_id` int(10) unsigned NOT NULL,
+  UNIQUE KEY `network-domain` (`ipv6net_id`,`domain_id`),
+  KEY `VLANIPv6-FK-compound` (`domain_id`,`vlan_id`),
+  CONSTRAINT `VLANIPv6-FK-compound` FOREIGN KEY (`domain_id`, `vlan_id`) REFERENCES `VLANDescription` (`domain_id`, `vlan_id`) ON DELETE CASCADE,
+  CONSTRAINT `VLANIPv6-FK-ipv6net_id` FOREIGN KEY (`ipv6net_id`) REFERENCES `IPv6Network` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB;
+
 CREATE TABLE `VLANSTRule` (
   `vst_id` int(10) unsigned NOT NULL,
   `rule_no` int(10) unsigned NOT NULL,
diff --git a/pix/addressspacev6.png b/pix/addressspacev6.png
new file mode 100644 (file)
index 0000000..e4d11f8
Binary files /dev/null and b/pix/addressspacev6.png differ
index 0d25811..ba3f9bb 100644 (file)
@@ -756,6 +756,48 @@ CREATE TABLE `VLANValidID` (
                        break;
                case '0.19.0':
                        $query[] = 'ALTER TABLE `File` ADD `thumbnail` LONGBLOB NULL AFTER `atime`';
+                       $query[] = "
+CREATE TABLE `IPv6Address` (
+  `ip` binary(16) NOT NULL,
+  `name` char(255) NOT NULL default '',
+  `reserved` enum('yes','no') default NULL,
+  PRIMARY KEY  (`ip`)
+) ENGINE=InnoDB
+";
+                       $query[] = "
+CREATE TABLE `IPv6Allocation` (
+  `object_id` int(10) unsigned NOT NULL default '0',
+  `ip` binary(16) NOT NULL,
+  `name` char(255) NOT NULL default '',
+  `type` enum('regular','shared','virtual','router') default NULL,
+  PRIMARY KEY  (`object_id`,`ip`)
+) ENGINE=InnoDB
+";
+                       $query[] = "
+CREATE TABLE `IPv6Network` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `ip` binary(16) NOT NULL,
+  `mask` int(10) unsigned NOT NULL,
+  `last_ip` binary(16) NOT NULL,
+  `name` char(255) default NULL,
+  `comment` text,
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `ip` (`ip`,`mask`)
+) ENGINE=InnoDB
+";
+                       $query[] = "
+CREATE TABLE `VLANIPv6` (
+  `domain_id` int(10) unsigned NOT NULL,
+  `vlan_id` int(10) unsigned NOT NULL,
+  `ipv6net_id` int(10) unsigned NOT NULL,
+  UNIQUE KEY `network-domain` (`ipv6net_id`,`domain_id`),
+  KEY `VLANIPv6-FK-compound` (`domain_id`,`vlan_id`),
+  CONSTRAINT `VLANIPv6-FK-compound` FOREIGN KEY (`domain_id`, `vlan_id`) REFERENCES `VLANDescription` (`domain_id`, `vlan_id`) ON DELETE CASCADE,
+  CONSTRAINT `VLANIPv6-FK-ipv6net_id` FOREIGN KEY (`ipv6net_id`) REFERENCES `IPv6Network` (`id`) ON DELETE CASCADE
+) ENGINE=InnoDB
+";
+                       $query[] = "ALTER TABLE `TagStorage` CHANGE COLUMN `entity_realm` `entity_realm` ENUM('file','ipv4net','ipv4vs','ipv4rspool','object','rack','user','ipv6net') NOT NULL DEFAULT 'object' FIRST";
+                       $query[] = "ALTER TABLE `FileLink` CHANGE COLUMN `entity_type` `entity_type` ENUM('ipv4net','ipv4rspool','ipv4vs','object','rack','user','ipv6net') NOT NULL DEFAULT 'object' AFTER `file_id`";
                        $query[] = "UPDATE Config SET varvalue = '0.19.0' WHERE varname = 'DB_VERSION'";
                        break;
                default: