/dev/null'); $js = json_decode((string)$raw, true); if (!is_array($js) || empty($js['Peer'])) exit(0); // The hostnames this mesh is made of. Anything else on the tailnet is somebody's laptop. $vars = vv_conf_vars(); $mesh = []; foreach ($vars as $k => $v) { if (preg_match('/^HOST\d+$/', $k) && trim((string)$v) !== '') $mesh[strtolower(trim($v))] = true; } if (!$mesh) exit(0); $now = time(); $lines = []; foreach ($js['Peer'] as $peer) { $host = strtolower(trim((string)($peer['HostName'] ?? ''))); if ($host === '') continue; // Same unambiguous-prefix rule the rest of the partnership layer uses: the tailnet name and // the conf hostname are different strings and nothing keeps them in step. $match = null; if (isset($mesh[$host])) { $match = $host; } else { $cand = []; foreach (array_keys($mesh) as $m) { if (str_starts_with($host, $m) || str_starts_with($m, $host)) $cand[] = $m; } if (count($cand) === 1) $match = $cand[0]; } if ($match === null) continue; $tx = (int)($peer['TxBytes'] ?? 0); $rx = (int)($peer['RxBytes'] ?? 0); if ($tx === 0 && $rx === 0) continue; $lines[] = $now . '|' . $match . '|' . $tx . '|' . $rx; } if (!$lines) exit(0); if ($show) { echo implode("\n", $lines) . "\n"; exit(0); } @mkdir(dirname(VV_MESH_DB), 0755, true); @file_put_contents(VV_MESH_DB, implode("\n", $lines) . "\n", FILE_APPEND | LOCK_EX); // Trim in place. Read-filter-rewrite rather than append-only truncation, because the cut is by // age and the file is not ordered by peer. $cutoff = $now - (VV_MESH_KEEP_DAYS * 86400); $all = @file(VV_MESH_DB, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: []; if (count($all) > 200) { $keep = []; foreach ($all as $l) { $ts = (int)strtok($l, '|'); if ($ts >= $cutoff) $keep[] = $l; } if (count($keep) !== count($all)) { $tmp = VV_MESH_DB . '.tmp'; if (@file_put_contents($tmp, implode("\n", $keep) . "\n") !== false) @rename($tmp, VV_MESH_DB); } }