Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ip Utils Laravel Package

wikimedia/ip-utils

Wikimedia's PHP library for parsing, validating, and comparing IPv4 and IPv6 addresses, CIDR ranges, and single addresses. Check whether an IP falls within a range, normalize and format addresses, and handle IPv6 with ease—battle-tested in MediaWiki.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer — there is no service provider, facade, or config file to publish:

composer require wikimedia/ip-utils

The entire public surface is static or factory-based, so nothing needs registering in config/app.php. Start by reading two files in vendor/wikimedia/ip-utils/src/:

  • IPUtils.php — the static helper toolbox (isValid, isIPv4, isIPv6, isInRange, isInRanges, sanitizeIP, prettifyIP, toHex, formatHex, combineRanges, getRFC1918Ranges).
  • IPBlock.php — the value object for a CIDR block or start/end range (newFromCIDR, newFromRange, contains, getStart, getEnd, getCIDR).

IP.php, IPv4.php, IPv6.php, IPRange.php, and IPset.php are the lower-level building blocks; you rarely touch them directly.

First use case: validating and normalizing an inbound IP before doing anything else with it.

use Wikimedia\IPUtils;

$ip = IPUtils::sanitizeIP($request->ip());

if ($ip === null || !IPUtils::isValid($ip)) {
    abort(400, 'Bad client address');
}

$ip = IPUtils::prettifyIP($ip); // canonical compressed IPv6 / dotted IPv4

From there, the natural second step is a CIDR check — IPUtils::isInRange($ip, '10.0.0.0/8') — which is the operation you will reach for most often in Laravel apps (admin allowlists, internal-traffic detection, geo/network gating).

Implementation Patterns

Form Request / validation rule. Wrap IPUtils in a reusable rule rather than sprinkling static calls through controllers:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Wikimedia\IPUtils;

class IpAddress implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (!is_string($value) || !IPUtils::isValid(IPUtils::sanitizeIP($value) ?? '')) {
            $fail("The {$attribute} must be a valid IPv4 or IPv6 address.");
        }
    }
}

Apply it to user-supplied fields (webhook source IPs, trusted proxy lists, per-tenant allowlists) — never trust $request->ip() implicitly.

Middleware for CIDR-gated routes. A classic admin-panel or internal-API guard:

public function handle(Request $request, Closure $next): Response
{
    $ranges = config('access.internal_ranges', ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16']);

    if (!IPUtils::isInRanges(IPUtils::sanitizeIP($request->ip()), $ranges)) {
        abort(403);
    }

    return $next($request);
}

isInRanges short-circuits on the first match, so it is the right choice for lists. isInRange is the single-range variant and throws on malformed input, so guard it.

Trusted proxy handling. $request->ip() is only meaningful if TrustProxies is correct. Combine the two: verify the immediate peer ($request->server('REMOTE_ADDR')) is in your load-balancer range with IPUtils::isInRange, and let Laravel's TrustProxies middleware resolve the client address from X-Forwarded-For before your own middleware runs.

Model casts for stored IPs. Store addresses as VARBINARY(16) for indexability and correct ordering. IPUtils::toHex() returns a 32-char hex string, which converts cleanly:

// migration
$table->binary('ip', 16)->nullable()->index();

// cast
protected function ip(): Attribute
{
    return Attribute::make(
        get: fn ($value) => $value === null ? null : IPUtils::formatHex(bin2hex($value)),
        set: fn ($value) => $value === null ? null : hex2bin(IPUtils::toHex($value)),
    );
}

Normalizing through toHex/formatHex also collapses IPv4-mapped IPv6 (::ffff:1.2.3.4) to 1.2.3.4, which prevents duplicate rows for the same client.

Range arithmetic for reports. IPBlock::newFromCIDR('10.0.0.0/24') exposes getStart(), getEnd(), getSize(), and contains(). Use it to summarize login/audit tables by subnet, and IPUtils::combineRanges() to merge overlapping entries before writing to config or displaying rules.

Bulk checks with IPset. When checking one address against thousands of ranges (e.g. a scraped deny list), build an IPset once and reuse it in the request or a queued job rather than looping isInRange.

Config-driven allowlists. Keep CIDRs in config/ as arrays and validate them at boot (an artisan command or a test) with IPUtils::isValid/IPBlock::newFromCIDR so a typo fails loudly in CI rather than silently denying traffic in production.

Gotchas and Tips

  • License is GPL-2.0. That is a real consideration for closed-source Laravel apps. Get sign-off before shipping; a permissive alternative (symfony/http-foundation's IpUtils, or mlocati/ip-lib) may be the safer call if GPL is unacceptable to your legal team.
  • No issue tracker, no Laravel bindings. The GitHub repo is a read-only mirror of Gerrit. File bugs and read history on Wikimedia's Gerrit/Phabricator, not GitHub. There is no facade, no config, no DI — treat the API as a static utility and wrap it behind your own helper or rule so you can swap it later.
  • sanitizeIP() does not validate. It strips whitespace, brackets, and IPv6 zone IDs (fe80::1%eth0) and normalizes ::ffff: forms, but it can return input that is still invalid. Always pair it with isValid().
  • isInRange() throws, isInRanges() returns false. Wrap isInRange in try/catch or pre-validate at the boundary; an uncaught InvalidArgumentException from a user-supplied CIDR is a 500.
  • Normalize before comparing or storing. 2001:DB8::1 and 2001:db8:0:0:0:0:0:1 are the same host. Run everything through sanitizeIP/toHex at the edge so database lookups, cache keys, and rate-limiter keys (RateLimiter::for(...) on IP) are consistent.
  • prettifyIP() is cosmetic. Use it for display and logs; use toHex() for storage and equality. Do not mix them in lookups.
  • Private/loopback helpers are not a security boundary. IPUtils::isPrivate() and isLoopback() only cover RFC 1918 / RFC 4193 / loopback blocks. Link-local, CGNAT (100.64.0.0/10), and cloud metadata addresses (169.254.169.254) are not "private" — add them to your range lists explicitly.
  • IPv6 means more than one address per user. Rate limiting or session binding on the full /128 is trivially bypassed; consider grouping by /64 or /56 using IPBlock::newFromCIDR($ip . '/64')->getCIDR() when you need per-network limits.
  • Extension point is composition, not inheritance. The classes are final-leaning and value-oriented; build your own IpAccessService / IpAllowlist around them instead of subclassing IPBlock. That gives you a single seam to mock in tests and to swap the underlying library.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin