Radiator Server Documentation — v10.33.3
Table of Contents
  • ipmap
  • Example
  • Configuration Options
  • interval
  • cron
  • duration
  • random
  • Shorthand syntax
  • query
  • lookup
  • mapping
  • @populate
  • Population Behavior
  • Resilience to Source Backend Outages
  • Performance Characteristics
  • REST API
  • IPv6 Support
  • IPv6 example

ipmap

The ipmap backend keeps a fast lookup table of IP networks and individual IP addresses in memory. Populate it from any other backend, such as SQLite or PostgreSQL, then use it to find the most specific network or exact IP address match and return the values saved for it.

Typical use cases include:

  • Classifying RADIUS requests by the network the NAS or client belongs to
  • Dynamic RADIUS client resolution in @pre-client hooks (per-IP shared secrets)
  • Mapping IP addresses to site names, VLAN assignments, or policy identifiers

Example

Dynamic RADIUS client resolution using a @pre-client hook. This populates the ipmap from an SQLite database containing per-client /32 prefixes and their shared secrets, then resolves the secret for each incoming request based on the client IP address:

backends {
    sqlite "SQLITE" {
        filename "/var/lib/radiator/clients.sqlite";

        query "POPULATE_CLIENTS" {
            statement "SELECT network, name, secret FROM clients";

            # The mapping runs for each row
            mapping {
                # Network or IP to insert into the IP map, for example 192.0.2.18 or 192.0.2.10/32
                ipmap.network = network;

                # Additional named values to associate with the prefix. These
                # are optional and can be used for lookup mappings.
                ipmap.name = name;
                ipmap.secret = secret;
            }
        }
    }

    ipmap "RADIUS_CLIENTS" {
        # Re-run the @populate pipeline every 5 minutes to refresh the map
        # with a random delay of up to 30 seconds to avoid thundering herd
        interval {
            duration 5m;
            random 30s;
        }

        @populate {
            backend {
                name "SQLITE";
                query "POPULATE_CLIENTS";
            }
        }

        query "LOOKUP_CLIENT" {
            lookup radius.client.ip;
            mapping {
                radius.client.secret = secret;
            }
        }
    }
}

servers {
    radius "RADIUS" {
        listen {
            protocol udp;
            port 1812;
            ip 0.0.0.0;
        }

        @pre-client {
            backend {
                name "RADIUS_CLIENTS";
                query "LOOKUP_CLIENT";
            }
        }
    }
}

When the ipmap lookup succeeds, radius.client.secret is set from the matched prefix entry, allowing Radiator to validate the RADIUS request authenticator against the dynamically resolved secret. If no prefix matches, the @pre-client hook rejects the request.

Configuration Options

interval

Set an automatic repopulation schedule. When configured, the ipmap periodically re-executes its @populate pipeline to refresh the prefix map contents.

Multiple interval entries can be defined in the same ipmap block. When multiple intervals are present, the server evaluates all of them and picks the closest upcoming scheduled time for the next repopulation. This is useful for defining multiple cron-based schedules (for example, different times of day or specific calendar dates).

The full form uses a block with either a cron or duration parameter and an optional random jitter:

# Cron-based interval with random jitter
interval {
    cron "0 30 * * * * *";
    random 10s;
}

# Duration-based interval with random jitter
interval {
    duration 5m;
    random 30s;
}
cron

A seven-field cron expression string that defines the repopulation schedule. The fields are: second, minute, hour, day-of-month, month, day-of-week, year.

duration

A duration value that specifies the interval between repopulations.

A interval block cannot contain both cron and duration.

random

Optional. A duration value specifying a maximum random jitter added to each scheduled repopulation. The actual delay added is uniformly distributed between zero and the specified value. Use this to prevent multiple Radiator instances from repopulating simultaneously when they share the same schedule.

Shorthand syntax

For simple schedules without jitter, a shorthand form is available:

# Duration shorthand
interval 5m;

# Cron shorthand
interval "0 0 * * * * *";

The shorthand is equivalent to a block with only cron or duration and no random.

If omitted, Radiator still starts one background populate run when the backend is created, but does not schedule any later automatic refreshes. Use the REST API or configure an interval to trigger subsequent repopulations.

query

Define a named lookup query. Each query specifies an expression to evaluate for the IP address and an optional mapping to apply when a match is found. The query always returns a single entry. If no match is found, the query rejects.

query "CHECK_NAS_IP_ADDRESS" {
    lookup radius.request.attr.NAS-IP-Address;
    mapping {
        vars.device_name = name;
        vars.client_secret = secret;
    }
}
lookup

Required. An expression that resolves to the IP address used for the longest-prefix match. The expression result is interpreted as an IP address. Accepted value types include IP addresses, IP networks (the network address is used), and strings in standard IP address or CIDR notation.

mapping

Optional. Attribute mappings applied when a matching prefix is found. Within the mapping block, each named value assigned during population is available as a source identifier.

These identifiers can be mapped to any writable target attribute, such as vars.*, radius.reply.attr.*, or radius.client.*. If a field was not set on the matched entry, the identifier evaluates to none.

When no matching prefix is found, the query result is reject and aaa.reason is set to a descriptive message including the IP address that failed to match.

@populate

Define the pipeline executed to populate the IP prefix map. The populate block can contain one or more backend calls whose result mappings write to ipmap.* target fields:

@populate {
    backend {
        name "SOURCE_BACKEND";
        query "POPULATE_QUERY";
    }
}

Each map entry has one required IP network or individual IP address and zero or more named values. Plain IP addresses are stored as host entries, equivalent to /32 for IPv4 or /128 for IPv6. Set the network or address with ipmap.network, and set values with arbitrary ipmap.* fields such as ipmap.name, ipmap.secret, ipmap.site, or ipmap.vlan. When a lookup matches an entry, the query mapping can read those fields by their names, for example name, secret, site, and vlan.

The source backend query mapping must assign ipmap.network. Named value fields are optional:

FieldRequiredDescription
ipmap.networkyesIP network prefix in CIDR notation (e.g. 192.0.2.0/24 or 10.0.0.1/32)
ipmap.<name>noNamed value to associate with the prefix. Use any field name except network

An entry is committed to the map once ipmap.network has been assigned. Assignment order does not matter for value fields, but ipmap.network starts each entry and must be set for every entry. A network-only entry can be used when the lookup result only needs to confirm that an IP address matches a configured prefix.

Example populate mapping from a SQL source:

query "POPULATE_IPMAP" {
    statement "SELECT network, name, secret FROM devices";

    mapping {
        ipmap.network = network;
        ipmap.name = name;
        ipmap.secret = secret;
    }
}

Multiple backend calls within a single @populate block are supported. Each call appends entries to the same prefix map:

@populate {
    backend {
        name "BACKEND_A";
        query "POPULATE_SITE_A";
    }
    backend {
        name "BACKEND_B";
        query "POPULATE_SITE_B";
    }
}

For static or small deployments, populate the map directly using modify actions instead of querying an external backend:

ipmap "IPMAP" {
    query "CHECK_NAS" {
        lookup radius.request.attr.NAS-IP-Address;
        mapping {
            vars.network_name = name;
        }
    }

    @populate {
        modify {
            ipmap.network = "192.0.2.0/24";
            ipmap.name = "internal-network";
            ipmap.site = "datacenter-a";
        }

        modify {
            ipmap.network = "198.51.100.0/24";
            ipmap.name = "guest-network";
            ipmap.site = "datacenter-b";
        }
    }
}

Each modify block assigns one entry. Use multiple modify blocks to add all required prefixes.

Population Behavior

Radiator always starts one populate run automatically when the backend is created. This startup population runs in the background.

Lookups do not wait for the startup populate to finish. Until the first successful snapshot is published, queries run against an empty map and reject if no prefix matches.

This keeps startup memory usage predictable because the server does not retain request waiters while a large populate pipeline runs. For startup-critical uses such as @pre-client client resolution, expect connecting clients to retry until the first snapshot has been published.

After the first populate has succeeded, the map remains static unless:

  • An interval is configured for automatic periodic repopulation
  • A manual repopulation is triggered via the REST API

Only startup, scheduled, and manual populate requests trigger repopulation. Lookups are pure reads and never trigger a populate themselves.

During repopulation, the entire map is replaced atomically. Concurrent lookups continue to use the previous snapshot until the new map is published.

Resilience to Source Backend Outages

Once populated, the ipmap serves all lookups entirely from its in-memory snapshot. Query execution does not contact the source backend at all - it is a pure local memory lookup. This means the ipmap continues to respond correctly even if the source backend (database, LDAP server, etc.) becomes unavailable after population.

Before the first successful startup populate, the snapshot is empty and lookups reject when no prefix matches.

If a scheduled or manual repopulation fails because the source backend is unreachable, the ipmap retains its last successfully populated snapshot and continues serving lookups from it. This makes the ipmap suitable for use cases where lookup availability must not depend on the health of an external data source, such as dynamic RADIUS client resolution in @pre-client hooks.

Performance Characteristics

The ipmap is designed for high-throughput, low-latency lookups:

  • No network I/O - Lookups are pure in-memory operations. There are no network round-trips, database connections, or disk access involved in query execution.
  • Concurrent reads without blocking - Multiple requests perform lookups simultaneously without waiting for each other, regardless of load.
  • Fast lookup regardless of map size - Lookup time depends only on the IP address length, not on the number of entries. A map with millions of prefixes performs the same as one with a handful of entries.
  • Non-blocking repopulation - Lookups continue uninterrupted while the map is being repopulated. The updated map becomes visible atomically once repopulation completes.

These properties make the ipmap well suited for the critical path of every request, including @pre-client hooks.

REST API

Trigger manual repopulation by sending a POST request to the management API:

POST /api/v1/backends/{backend_name}/ipmap/populate

This endpoint requires write authorization. It triggers repopulation asynchronously and returns HTTP 200 immediately. The response confirms that the request was submitted to the management API, not that repopulation has completed.

Check logs to confirm when the new snapshot has been published or whether repopulation failed and retried.

IPv6 Support

The ipmap backend supports both IPv4 and IPv6 prefixes in the same map. Use standard IPv6 CIDR notation for ipmap.network values (e.g. 2001:db8::/32, 2001:db8:abcd::/48, fe80::1/128). Longest-prefix matching works identically for both address families - the most specific matching prefix wins regardless of whether the address is IPv4 or IPv6.

IPv6 prefix lengths range from /0 to /128. A /128 prefix represents a single host address, analogous to /32 in IPv4.

IPv6 example

Classify requests based on a Framed-IPv6-Address attribute using nested prefixes of varying specificity:

backends {
    ipmap "IPMAP" {
        query "LOOKUP_IPV6" {
            lookup radius.request.attr.Framed-IPv6-Address;
            mapping {
                vars.ipv6_name = name;
            }
        }

        @populate {
            modify {
                ipmap.network = "2001:db8::/32";
                ipmap.name = "broad-32";
            }

            modify {
                ipmap.network = "2001:db8:abcd::/48";
                ipmap.name = "subnet-48";
            }

            modify {
                ipmap.network = "2001:db8:abcd:1234::/64";
                ipmap.name = "subnet-64";
            }

            modify {
                ipmap.network = "2001:db8:abcd:1234::42/128";
                ipmap.name = "host-128";
            }
        }
    }
}

With this configuration:

  • A lookup for 2001:db8:abcd:1234::42 matches the /128 host entry and returns host-128
  • A lookup for 2001:db8:abcd:1234::1 matches the /64 subnet and returns subnet-64
  • A lookup for 2001:db8:abcd:ffff::1 matches the /48 subnet and returns subnet-48
  • A lookup for 2001:db8:1::1 matches the broad /32 prefix and returns broad-32
  • A lookup for 2001:db9::1 matches no entry and the query returns reject
Navigation
  • @init

  • @verification

  • aaa

  • backends

    • file

    • http

    • ipmap

    • jsonfile

    • ldap

    • mysql

    • postgresql

    • radius

    • radius-dns-sd

    • sqlite

    • system

  • caches

  • captures

  • certificates

  • clients

  • conditions

  • dictionary

  • hmac-otp

  • include

  • ip-accept

  • license

  • logging

  • management

  • proxy-protocol

  • scripts

  • servers

  • statistics

  • stats

  • ui