A policy is a named set of matching rules. Each policy has a Mode, a list of individual IPAddresses, and a list of CIDR IPNetworks. Policies are defined once and referenced by name — either from a route's metadata or as the global policy.
Policy Modes
The Mode decides how a match is interpreted.
| Mode | Behaviour | ||
|---|---|---|---|
AllowList | Blocks all requests except those whose IP matches an entry. Use for "only these clients may reach this route." | ||
BlockList | Allows all requests except those whose IP matches an entry. Use for "everyone except these known-bad clients." | ||
Disabled | Filtering is off. Every request passes through. Handy for temporarily switching a policy off without deleting it. |
AllowList is the default when Mode is omitted.
A newly constructed
IPFilterPolicywith no addresses or networks and the defaultAllowListmode blocks everything — an empty allowlist matches no one. This is a deliberate fail-closed default.
Matching individual addresses
List individual IPv4 or IPv6 addresses under IPAddresses:
{
"PolicyName": "Admins",
"Mode": "AllowList",
"IPAddresses": ["203.0.113.7", "203.0.113.8", "2001:db8::1"]
}
Addresses are parsed once and held in a HashSet<IPAddress>, so lookups are O(1) regardless of list size.
Matching CIDR networks
List CIDR ranges under IPNetworks. Each entry is a prefix and a length separated by /:
{
"PolicyName": "Intranet",
"Mode": "AllowList",
"IPNetworks": ["192.168.0.0/24", "10.0.0.0/8", "2001:db8::/32"]
}
Networks are stored in a trie (see IPNetworkCollection), so a client IP is matched against every configured range in a single bit-wise walk rather than one comparison per range.
Each network string must be exactly
prefix/length. A malformed entry (missing or extra/) throwsArgumentExceptionthe first time the policy's networks are evaluated.
Combining addresses and networks
A single policy may declare both lists. A request matches the policy if its IP is in IPAddresses or falls within any entry in IPNetworks:
{
"PolicyName": "Trusted",
"Mode": "AllowList",
"IPAddresses": ["203.0.113.7"],
"IPNetworks": ["192.168.0.0/24"]
}
How that "match" translates to allow/block depends on the mode:
- In
AllowListmode, matching → allowed, everything else → blocked. - In
BlockListmode, matching → blocked, everything else → allowed.
IPv4 and IPv6
The middleware normalises IPv4-mapped IPv6 addresses (e.g. ::ffff:192.168.0.1) back to their IPv4 form before matching, so a policy listing 192.168.0.1 matches a client that arrives over a dual-stack socket. List genuine IPv6 clients using their IPv6 address or network.
Lazy parsing
Address and network parsing is deferred until the first request evaluates a policy, and the parsed results are cached for the lifetime of that policy instance. When configuration reloads, the provider builds fresh policy instances, so the next request re-parses the updated values.