<?php
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/../includes/functions.php';

$page_title   = 'IP Subnet Calculator – IPv4 and IPv6';
$page_desc    = 'Free IP Subnet Calculator for IPv4 and IPv6: calculate network address, subnet mask, host range and broadcast address on ipcheck.tools.';
$page_current = 'subnet';

$query      = trim(strip_tags($_GET['ip'] ?? ''));
$get_prefix = isset($_GET['prefix']) && $_GET['prefix'] !== '' ? (int)$_GET['prefix'] : null;
$result     = [];
$error      = '';
$is_ipv6    = false;

function ipv6_to_bin(string $ip): string {
    $packed = inet_pton($ip);
    if ($packed === false) return '';
    $bin = '';
    for ($i = 0; $i < strlen($packed); $i++) {
        $bin .= sprintf('%08b', ord($packed[$i]));
    }
    return $bin;
}

function bin_to_ipv6(string $bin): string {
    $hex = '';
    for ($i = 0; $i < 128; $i += 4) {
        $hex .= dechex(bindec(substr($bin, $i, 4)));
    }
    $groups = str_split($hex, 4);
    $ip = implode(':', $groups);
    return inet_ntop(inet_pton($ip)) ?: $ip;
}

function ipv6_subnet_calc(string $ip, int $prefix): array {
    $ip_bin   = ipv6_to_bin($ip);
    if (!$ip_bin) return [];
    $net_bin  = substr($ip_bin, 0, $prefix) . str_repeat('0', 128 - $prefix);
    $bc_bin   = substr($ip_bin, 0, $prefix) . str_repeat('1', 128 - $prefix);
    $mask_bin = str_repeat('1', $prefix) . str_repeat('0', 128 - $prefix);
    $host_bits = 128 - $prefix;
    $hosts = $host_bits > 60 ? '~' . number_format(pow(2, min($host_bits, 63)), 0, ',', '.') . ' (huge)' : number_format(pow(2, $host_bits), 0, ',', '.');
    $type = 'Global Unicast';
    if ($ip === '::1')                                   $type = 'Loopback';
    elseif (str_starts_with($ip_bin, '11111110100'))     $type = 'Link-Local (fe80::/10)';
    elseif (str_starts_with($ip_bin, '11111100'))        $type = 'Unique Local (fc00::/7)';
    elseif (str_starts_with($ip_bin, '11111111'))        $type = 'Multicast (ff00::/8)';
    return [
        'ip' => $ip, 'prefix' => $prefix, 'cidr' => $ip . '/' . $prefix,
        'network' => bin_to_ipv6($net_bin), 'broadcast' => bin_to_ipv6($bc_bin),
        'first_host' => bin_to_ipv6(substr($net_bin, 0, 127) . '1'),
        'last_host'  => bin_to_ipv6(substr($bc_bin, 0, 127) . '0'),
        'mask' => bin_to_ipv6($mask_bin), 'hosts' => $hosts,
        'host_bits' => $host_bits, 'type' => $type,
    ];
}

if ($query) {
    $raw_ip = preg_replace('/\/\d+$/', '', $query);
    $raw_ip = trim($raw_ip);

    if (filter_var($raw_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
        $is_ipv6 = true;
        $ip = $raw_ip;
        if ($get_prefix !== null) $prefix = $get_prefix;
        elseif (str_contains($query, '/')) { [, $pfx] = explode('/', $query, 2); $prefix = (int)$pfx; }
        else $prefix = 64;
        $prefix = max(0, min(128, $prefix));
        $result = ipv6_subnet_calc($ip, $prefix);
        if (!$result) $error = 'Invalid IPv6 address.';

    } elseif (filter_var($raw_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
        $ip = $raw_ip;
        if ($get_prefix !== null) $prefix = $get_prefix;
        elseif (str_contains($query, '/')) { [, $pfx] = explode('/', $query, 2); $prefix = (int)$pfx; }
        else $prefix = 24;
        $prefix = max(0, min(32, $prefix));

        if ($prefix < 0 || $prefix > 32) {
            $error = 'Invalid prefix. Allowed range: /0 to /32.';
        } else {
            $mask_long = $prefix > 0 ? (0xFFFFFFFF << (32 - $prefix)) & 0xFFFFFFFF : 0;
            $ip_long   = ip2long($ip);
            $net_long  = $ip_long & $mask_long;
            $bc_long   = $net_long | (~$mask_long & 0xFFFFFFFF);
            $hosts_total  = $prefix <= 30 ? pow(2, 32 - $prefix) : ($prefix === 31 ? 2 : 1);
            $hosts_usable = $prefix <= 30 ? $hosts_total - 2 : ($prefix === 31 ? 2 : 1);
            $wildcard     = long2ip(~$mask_long & 0xFFFFFFFF);
            $first = (int)explode('.', $ip)[0];
            $class = match(true) {
                $first < 128 => 'A', $first < 192 => 'B', $first < 224 => 'C',
                $first < 240 => 'D (Multicast)', default => 'E (Reserved)',
            };
            $ip_bin   = implode('.', array_map(fn($o) => sprintf('%08b', $o), explode('.', $ip)));
            $mask_bin = implode('.', array_map(fn($o) => sprintf('%08b', $o), explode('.', long2ip($mask_long))));
            $subnets = [];
            if ($prefix < 29) {
                $sub_size = pow(2, 32 - ($prefix + 3));
                for ($i = 0; $i < 8; $i++) {
                    $sub_net   = $net_long + ($i * $sub_size);
                    $sub_bc    = $sub_net + $sub_size - 1;
                    $subnets[] = [
                        'network'    => long2ip($sub_net) . '/' . ($prefix + 3),
                        'first_host' => long2ip($sub_net + 1),
                        'last_host'  => long2ip($sub_bc - 1),
                        'broadcast'  => long2ip($sub_bc),
                    ];
                }
            }
            $result = [
                'ip' => $ip, 'prefix' => $prefix, 'cidr' => $ip . '/' . $prefix,
                'network' => long2ip($net_long), 'broadcast' => long2ip($bc_long),
                'first_host' => long2ip($net_long + 1), 'last_host' => long2ip($bc_long - 1),
                'netmask' => long2ip($mask_long), 'wildcard' => $wildcard,
                'hosts_total'  => number_format($hosts_total, 0, ',', '.'),
                'hosts_usable' => number_format(max(0, $hosts_usable), 0, ',', '.'),
                'class' => $class, 'ip_bin' => $ip_bin, 'mask_bin' => $mask_bin,
                'is_private' => is_private_ip($ip), 'subnets' => $subnets,
            ];
        }
    } else {
        $error = 'Invalid IP address. Please enter an IPv4 (e.g. 192.168.1.0) or IPv6 (e.g. 2001:db8::) address.';
    }
}

$current_prefix = $result['prefix'] ?? $get_prefix ?? ($is_ipv6 ? 64 : 24);

require_once __DIR__ . '/header.php';
?>

<div class="hero">
  <div class="hero-label">Tool</div>
  <div class="hero-title">IP Subnet Calculator</div>
  <div class="hero-sub">IPv4 and IPv6 – Calculate network address, subnet mask and host range</div>
  <form method="GET" action="/en/subnet.php">
    <div class="input-group" style="max-width:660px;">
      <input class="input-text" type="text" name="ip"
             value="<?= h(preg_replace('/\/\d+$/', '', $query)) ?>"
             placeholder="e.g. 192.168.1.0 or 2001:db8::"
             required />
      <select name="prefix" class="input-select" style="width:140px; flex-shrink:0;">
        <?php
        $max_prefix = $is_ipv6 ? 128 : 32;
        for ($p = 0; $p <= $max_prefix; $p++) {
            $selected = $current_prefix === $p ? 'selected' : '';
            echo '<option value="' . $p . '" ' . $selected . '>/ ' . $p . '</option>';
        }
        ?>
      </select>
      <button type="submit" class="btn btn-primary">
        <i class="ti ti-calculator"></i> Calculate
      </button>
    </div>
  </form>
  <div style="display:flex; gap:8px; justify-content:center; margin-top:14px;">
    <a href="/en/subnet.php?ip=192.168.1.0&prefix=24" class="copy-btn" style="margin-top:0; font-size:11px;">IPv4</a>
    <a href="/en/subnet.php?ip=2001:db8::&prefix=64" class="copy-btn" style="margin-top:0; font-size:11px;">IPv6</a>
  </div>
</div>

<div class="wrap">

  <div class="ad-slot"><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-8287576653347400" data-ad-slot="2715725452" data-ad-format="auto" data-full-width-responsive="true"></ins><script>(adsbygoogle = window.adsbygoogle || []).push({});</script></div>

  <?php if ($error): ?>
    <div class="alert alert-danger"><i class="ti ti-alert-circle"></i><span><?= h($error) ?></span></div>

  <?php elseif ($result && $is_ipv6): ?>
    <div class="stat-grid">
      <div class="stat"><div class="stat-val" style="font-size:12px; color:var(--purple-dim);">/<?= $result['prefix'] ?></div><div class="stat-key">Prefix Length</div></div>
      <div class="stat"><div class="stat-val" style="font-size:11px;"><?= $result['hosts'] ?></div><div class="stat-key">Addresses</div></div>
      <div class="stat"><div class="stat-val" style="font-size:12px; color:var(--blue-dim);"><?= $result['host_bits'] ?></div><div class="stat-key">Host Bits</div></div>
      <div class="stat"><div class="stat-val" style="font-size:11px; color:var(--green);"><?= h($result['type']) ?></div><div class="stat-key">Address Type</div></div>
    </div>
    <div class="grid2">
      <div class="card card-purple">
        <div class="card-title"><i class="ti ti-network"></i> Network Information</div>
        <div class="data-row"><span class="dk">CIDR</span><span class="dv mono" style="color:var(--purple-dim); font-size:11px;"><?= h($result['cidr']) ?></span></div>
        <div class="data-row"><span class="dk">Network address</span><span class="dv mono" style="font-size:11px;"><?= h($result['network']) ?></span></div>
        <div class="data-row"><span class="dk">First host</span><span class="dv mono" style="font-size:11px;"><?= h($result['first_host']) ?></span></div>
        <div class="data-row"><span class="dk">Last host</span><span class="dv mono" style="font-size:11px;"><?= h($result['last_host']) ?></span></div>
        <div class="data-row"><span class="dk">Total addresses</span><span class="dv mono"><?= h($result['hosts']) ?></span></div>
      </div>
      <div class="card">
        <div class="card-title"><i class="ti ti-info-circle"></i> Details</div>
        <div class="data-row"><span class="dk">Subnet mask</span><span class="dv mono" style="font-size:10px;"><?= h($result['mask']) ?></span></div>
        <div class="data-row"><span class="dk">Prefix length</span><span class="dv mono">/<?= $result['prefix'] ?></span></div>
        <div class="data-row"><span class="dk">Host bits</span><span class="dv mono"><?= $result['host_bits'] ?></span></div>
        <div class="data-row"><span class="dk">Type</span><span class="dv" style="font-size:12px;"><?= h($result['type']) ?></span></div>
      </div>
    </div>

  <?php elseif ($result && !$is_ipv6): ?>
    <div class="stat-grid">
      <div class="stat"><div class="stat-val" style="font-size:12px; color:var(--blue-dim);">/<?= $result['prefix'] ?></div><div class="stat-key">Prefix Length</div></div>
      <div class="stat"><div class="stat-val" style="font-size:13px;"><?= $result['hosts_usable'] ?></div><div class="stat-key">Usable Hosts</div></div>
      <div class="stat"><div class="stat-val" style="font-size:13px;">Class <?= $result['class'] ?></div><div class="stat-key">IP Class</div></div>
      <div class="stat"><div class="stat-val" style="color:<?= $result['is_private'] ? 'var(--orange)' : 'var(--green)' ?>; font-size:13px;"><?= $result['is_private'] ? 'Private' : 'Public' ?></div><div class="stat-key">IP Type</div></div>
    </div>
    <div class="grid2">
      <div class="card card-blue">
        <div class="card-title"><i class="ti ti-network"></i> Network Information</div>
        <div class="data-row"><span class="dk">CIDR notation</span><span class="dv mono blue"><?= h($result['cidr']) ?></span></div>
        <div class="data-row"><span class="dk">Network address</span><span class="dv mono"><?= h($result['network']) ?></span></div>
        <div class="data-row"><span class="dk">Broadcast</span><span class="dv mono"><?= h($result['broadcast']) ?></span></div>
        <div class="data-row"><span class="dk">First host</span><span class="dv mono"><?= h($result['first_host']) ?></span></div>
        <div class="data-row"><span class="dk">Last host</span><span class="dv mono"><?= h($result['last_host']) ?></span></div>
        <div class="data-row"><span class="dk">Total hosts</span><span class="dv mono"><?= h($result['hosts_total']) ?></span></div>
        <div class="data-row"><span class="dk">Usable hosts</span><span class="dv mono"><?= h($result['hosts_usable']) ?></span></div>
      </div>
      <div class="card">
        <div class="card-title"><i class="ti ti-math-function"></i> Subnet Details</div>
        <div class="data-row"><span class="dk">Subnet mask</span><span class="dv mono"><?= h($result['netmask']) ?></span></div>
        <div class="data-row"><span class="dk">Wildcard mask</span><span class="dv mono"><?= h($result['wildcard']) ?></span></div>
        <div class="data-row"><span class="dk">Prefix length</span><span class="dv mono">/<?= h($result['prefix']) ?></span></div>
        <div class="data-row"><span class="dk">IP class</span><span class="dv">Class <?= h($result['class']) ?></span></div>
        <div class="data-row"><span class="dk">Type</span><span class="dv"><?= $result['is_private'] ? '<span class="badge badge-orange">Private / RFC1918</span>' : '<span class="badge badge-green">Public</span>' ?></span></div>
      </div>
    </div>
    <div class="card">
      <div class="card-title"><i class="ti ti-binary"></i> Binary representation</div>
      <div class="data-row"><span class="dk">IP address</span><span class="dv mono" style="font-size:11px; color:var(--blue-dim);"><?= h($result['ip_bin']) ?></span></div>
      <div class="data-row"><span class="dk">Subnet mask</span><span class="dv mono" style="font-size:11px;"><?= h($result['mask_bin']) ?></span></div>
    </div>
    <?php if ($result['subnets']): ?>
    <div class="card">
      <div class="card-title"><i class="ti ti-topology-star"></i> Possible subnets /<?= $result['prefix'] + 3 ?> (first 8)</div>
      <table class="result-table">
        <thead><tr><th>#</th><th>Network</th><th>First Host</th><th>Last Host</th><th>Broadcast</th></tr></thead>
        <tbody>
          <?php foreach ($result['subnets'] as $i => $sub): ?>
          <tr>
            <td style="color:var(--text4);"><?= $i + 1 ?></td>
            <td class="mono"><?= h($sub['network']) ?></td>
            <td class="mono"><?= h($sub['first_host']) ?></td>
            <td class="mono"><?= h($sub['last_host']) ?></td>
            <td class="mono"><?= h($sub['broadcast']) ?></td>
          </tr>
          <?php endforeach; ?>
        </tbody>
      </table>
    </div>
    <?php endif; ?>

  <?php else: ?>
    <div class="alert alert-info">
      <i class="ti ti-info-circle"></i>
      <span>Enter an IPv4 or IPv6 address with prefix, e.g. <strong>192.168.1.0/24</strong> or <strong>2001:db8::/32</strong>.</span>
    </div>
    <div class="grid2">
      <div class="card">
        <div class="card-title"><i class="ti ti-bookmark"></i> IPv4 Examples</div>
        <?php foreach ([['192.168.1.0/24','Home network'],['10.0.0.0/8','Class A private'],['172.16.0.0/12','Class B private'],['192.168.1.0/28','Small subnet']] as [$cidr,$desc]): ?>
        <div class="data-row">
          <span class="dk"><a href="/en/subnet.php?ip=<?= urlencode(explode('/',$cidr)[0]) ?>&prefix=<?= explode('/',$cidr)[1] ?>" style="font-family:var(--font-mono); font-size:12px;"><?= $cidr ?></a></span>
          <span class="dv"><?= $desc ?></span>
        </div>
        <?php endforeach; ?>
      </div>
      <div class="card">
        <div class="card-title"><i class="ti ti-bookmark"></i> IPv6 Examples</div>
        <?php foreach ([['2001:db8::/32','Documentation'],['fc00::/7','Unique Local'],['fe80::/10','Link-Local'],['2001:db8::/64','Typical LAN']] as [$cidr,$desc]): ?>
        <div class="data-row">
          <span class="dk"><a href="/en/subnet.php?ip=<?= urlencode(explode('/',$cidr)[0]) ?>&prefix=<?= explode('/',$cidr)[1] ?>" style="font-family:var(--font-mono); font-size:12px;"><?= $cidr ?></a></span>
          <span class="dv"><?= $desc ?></span>
        </div>
        <?php endforeach; ?>
      </div>
    </div>
  <?php endif; ?>

  <div class="ad-slot"><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-8287576653347400" data-ad-slot="2715725452" data-ad-format="auto" data-full-width-responsive="true"></ins><script>(adsbygoogle = window.adsbygoogle || []).push({});</script></div>

  <div class="tools-row">
    <a href="/en/"               class="tool-btn"><i class="ti ti-home"></i><span>My IP</span></a>
    <a href="/en/subnet.php"     class="tool-btn current"><i class="ti ti-calculator"></i><span>Subnet</span></a>
    <a href="/en/ip-lookup.php"  class="tool-btn"><i class="ti ti-world-search"></i><span>IP Lookup</span></a>
    <a href="/en/ipv4.php"       class="tool-btn"><i class="ti ti-network"></i><span>IPv4 Detail</span></a>
    <a href="/en/ipv6.php"       class="tool-btn"><i class="ti ti-network"></i><span>IPv6 Detail</span></a>
  </div>

  <div class="seo-box">
    <h2>IPv4 and IPv6 Subnet Calculator</h2>
    <p>The subnet calculator supports both <strong>IPv4</strong> (CIDR /0–/32) and <strong>IPv6</strong> (CIDR /0–/128). Simply enter the IP address – the tool automatically detects whether it is IPv4 or IPv6. For IPv4, the network address, broadcast, first and last host, subnet mask, wildcard mask and possible subnets are calculated. For IPv6, the network address, prefix length and address type are shown.</p>
  </div>

<?php require_once __DIR__ . '/footer.php'; ?>
