Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

--- develop ---

* issue: Match the Cacti devices of a Nagios host import by IPv4 and IPv6 address.
The address map now holds every A and AAAA address of a device in one
canonical text form, accepts address literals as device host names without a
lookup, and compares the imported address in the same form. The cache file
keeps its layout; files written by earlier versions are rebuilt once
* dev: Relocate the schema provisioning function `npc_setup_tables()` from
`setup.php` into `includes/database.php` (loaded via `require_once`) to align
with the fleet schema/includes convention; behavior is unchanged
Expand Down
152 changes: 143 additions & 9 deletions controllers/sync.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ function listHostgroups() {
return($this->jsonOutput($output));
}

/**
* The file holding the address map. It is a property so a test can point
* it at a temporary file.
*
* @var string
*/
var $addressCacheFile = '/tmp/npc_address_cache.php';

/**
* The DNS and system lookups, as callables so a test can replace them.
*
* @var callable
*/
var $dnsLookup = 'dns_get_record';
var $nameLookup = 'gethostbyname';

/**
* checkHostExists
*
Expand All @@ -175,19 +191,24 @@ function listHostgroups() {
* found the Cacti host ID is returned.
*
* The first time this method is called a cache of
* id to ip address mappings will be built.
* id to ip address mappings will be built. Every address of a host is
* stored, IPv4 and IPv6, in the canonical text form of
* normalizeAddress(). The file layout is the one earlier versions wrote;
* the added $cacheVersion makes older files rebuild, because they only
* hold the first IPv4 address of each device.
*
* @return int The Cacti host ID
*/
function checkHostExists($ip, $cache_id) {

$cacheFile = '/tmp/npc_address_cache.php';
$cacheFile = $this->addressCacheFile;

$buildCache = 0;
$cacheVersion = 1;

if (file_exists($cacheFile)) {
include($cacheFile);
if ($cacheKey != $cache_id) {
if ($cacheKey != $cache_id || $cacheVersion < 2) {
$buildCache = 1;
}
} else {
Expand All @@ -202,14 +223,17 @@ function checkHostExists($ip, $cache_id) {

$string = "<?php\n\n";
$string .= "\$cacheKey = '" . $cache_id . "';\n";
$string .= "\$cacheVersion = 2;\n";
$string .= "\$hosts = array(\n";

$results = NpcCactiController::getHostnames();
$cacti = new NpcCactiController();
$results = $cacti->getHostnames();

for ($i = 0; $i < count($results); $i++) {
$address = gethostbyname($results[$i]['hostname']);
$string .= "'" . $address . "' => '" . $results[$i]['id'] . "',\n";
$hosts[$address] = $results[$i]['id'];
foreach ($this->resolveAddresses($results[$i]['hostname']) as $address) {
$string .= "'" . $address . "' => '" . $results[$i]['id'] . "',\n";
$hosts[$address] = $results[$i]['id'];
}
}

$string .= ");";
Expand All @@ -219,11 +243,121 @@ function checkHostExists($ip, $cache_id) {
fclose($fh);
}

if (isset($hosts[$ip])) {
return($hosts[$ip]);
foreach ($this->resolveAddresses($ip) as $address) {
if (isset($hosts[$address])) {
return($hosts[$address]);
}
}

return(null);
}

/**
* normalizeAddress
*
* Returns the canonical text form of an IPv4 or IPv6 address, or null when
* the text is not an address. The same address written in different ways
* (2001:DB8::1, 2001:db8:0:0:0:0:0:1, [2001:db8::1], fe80::1%eth0) gives
* the same text, and an IPv4-mapped IPv6 address gives the IPv4 text.
*
* @param string $address
* @return string|null
*/
function normalizeAddress($address) {
$address = trim((string) $address);

if (strlen($address) > 1 && $address[0] == '[' && substr($address, -1) == ']') {
$address = substr($address, 1, -1);
}

/* A scope id names the interface, not the host. */
$address = preg_replace('/%.*$/', '', $address);

if (filter_var($address, FILTER_VALIDATE_IP) === false) {
return(null);
}

$packed = inet_pton($address);

if (strlen($packed) == 16 && strncmp($packed, "\0\0\0\0\0\0\0\0\0\0\xff\xff", 12) == 0) {
$packed = substr($packed, 12);
}

return(inet_ntop($packed));
}

/**
* resolveAddresses
*
* Returns every address, in canonical form, that a host name or address
* stands for. An address is returned as it is without a lookup. A name is
* looked up for A and AAAA records, and for names the resolver does not
* return records for, the system lookup that earlier versions used.
*
* @param string $name host name or address
* @return array canonical addresses, empty when nothing resolves
*/
function resolveAddresses($name) {
$name = trim((string) $name);

$literal = $this->normalizeAddress($name);

if ($literal !== null) {
return(array($literal));
}

if ($name === '') {
return(array());
}

$found = array();

foreach ($this->dnsRecords($name) as $record) {
$address = $this->normalizeAddress(isset($record['ipv6']) ? $record['ipv6'] : (isset($record['ip']) ? $record['ip'] : ''));

if ($address !== null && !in_array($address, $found, true)) {
$found[] = $address;
}
}

if (!count($found)) {
$address = $this->normalizeAddress($this->legacyLookup($name));

if ($address !== null) {
$found[] = $address;
}
}

return($found);
}

/**
* dnsRecords
*
* The A and AAAA records of a name. A failed lookup gives an empty list.
* dns_get_record() reports a failed query as a warning, which the empty
* list already says.
*
* @param string $name
* @return array
*/
function dnsRecords($name) {
$records = @call_user_func($this->dnsLookup, $name, DNS_A | DNS_AAAA);

return(is_array($records) ? $records : array());
}

/**
* legacyLookup
*
* The system resolver lookup earlier versions used. It returns the name
* unchanged when the name does not resolve.
*
* @param string $name
* @return string
*/
function legacyLookup($name) {
return(call_user_func($this->nameLookup, $name));
}

}
1 change: 1 addition & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
<source>
<include>
<file>setup.php</file>
<file>controllers/sync.php</file>
</include>
</source>
</phpunit>
Loading
Loading