7 n__ssrf_8c
gullradriel edited this page 2026-10-29 20:54:40 +01:00

src/n_ssrf.c

SSRF address guard (see n_ssrf.h). More...

Functions

Name
int zero_run(const uint8_t * ip, int lo, int hi)
int v6_blocked(const uint8_t ip[16])
int v4_blocked(const uint8_t ip[4])
int n_ssrf_ipv6_allowed(const uint8_t ip[16])
classify an IPv6 address as safe-to-connect or not
int n_ssrf_ipv4_allowed(const uint8_t ip[4])
classify an IPv4 address as safe-to-connect or not

Detailed Description

SSRF address guard (see n_ssrf.h).

Author: Castagnier Mickael

Version: 1.0

Pure byte classification.

Functions Documentation

function zero_run

static int zero_run(
    const uint8_t * ip,
    int lo,
    int hi
)

function v6_blocked

static int v6_blocked(
    const uint8_t ip[16]
)

function v4_blocked

static int v4_blocked(
    const uint8_t ip[4]
)

function n_ssrf_ipv6_allowed

int n_ssrf_ipv6_allowed(
    const uint8_t ip[16]
)

classify an IPv6 address as safe-to-connect or not

Parameters:

  • ip 16 bytes, network order

Return: 1 if ip is a public routable address; 0 otherwise. Rejects unspecified (::), loopback (::1), ULA (fc00::/7), link-local (fe80::/10), multicast (ff00::/8), documentation (2001:db8::/32) and discard (100::/64). IPv4-mapped (::ffff:0:0/96), NAT64 (64:ff9b::/96) and 6to4 (2002::/16) are unwrapped and their embedded IPv4 re-checked, so a private v4 cannot be smuggled in. NULL is rejected (0).

function n_ssrf_ipv4_allowed

int n_ssrf_ipv4_allowed(
    const uint8_t ip[4]
)

classify an IPv4 address as safe-to-connect or not

Parameters:

  • ip 4 bytes, network order

Return: 1 if ip is a public routable unicast address; 0 if it is unspecified / private / loopback / CGNAT / link-local / documentation / benchmark / multicast / reserved (RFC 1918, 6598, 5735, 5737, 3927, 2544, ...). NULL is rejected (0).

Source code

/*
 * Nilorea Library
 * Copyright (C) 2005-2026 Castagnier Mickael
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * permissions and limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "nilorea/n_ssrf.h"

/* 1 if the IPv4 address is NOT safe to connect to (private/reserved/etc.). */
static int v4_blocked(const uint8_t ip[4]) {
    uint8_t a = ip[0], b = ip[1], c = ip[2];
    if (a == 0)
        return 1; /* 0.0.0.0/8  "this host"                */
    if (a == 10)
        return 1; /* 10.0.0.0/8  private (RFC1918)         */
    if (a == 127)
        return 1; /* 127.0.0.0/8  loopback                 */
    if (a == 100 && (b & 0xC0) == 0x40)
        return 1; /* 100.64.0.0/10  CGNAT (RFC6598)        */
    if (a == 169 && b == 254)
        return 1; /* 169.254.0.0/16  link-local            */
    if (a == 172 && (b & 0xF0) == 0x10)
        return 1; /* 172.16.0.0/12  private                */
    if (a == 192 && b == 168)
        return 1; /* 192.168.0.0/16  private               */
    if (a == 192 && b == 0 && c == 0)
        return 1; /* 192.0.0.0/24  IETF protocol           */
    if (a == 192 && b == 0 && c == 2)
        return 1; /* 192.0.2.0/24  TEST-NET-1              */
    if (a == 198 && (b & 0xFE) == 18)
        return 1; /* 198.18.0.0/15  benchmarking           */
    if (a == 198 && b == 51 && c == 100)
        return 1; /* 198.51.100.0/24  TEST-NET-2          */
    if (a == 203 && b == 0 && c == 113)
        return 1; /* 203.0.113.0/24  TEST-NET-3          */
    if (a >= 224)
        return 1; /* 224/4 multicast + 240/4 reserved + 255.. */
    return 0;
}

int n_ssrf_ipv4_allowed(const uint8_t ip[4]) {
    return ip && !v4_blocked(ip);
}

/* Are bytes [lo, hi) of \p ip all zero? */
static int zero_run(const uint8_t* ip, int lo, int hi) {
    int i;
    for (i = lo; i < hi; i++)
        if (ip[i])
            return 0;
    return 1;
}

static int v6_blocked(const uint8_t ip[16]) {
    if (zero_run(ip, 0, 16)) /* ::  unspecified                          */
        return 1;
    if (zero_run(ip, 0, 15) && ip[15] == 1) /* ::1  loopback             */
        return 1;
    /* ::ffff:0:0/96  IPv4-mapped -> classify the embedded IPv4 */
    if (zero_run(ip, 0, 10) && ip[10] == 0xff && ip[11] == 0xff)
        return v4_blocked(ip + 12);
    /* 64:ff9b::/96  NAT64 -> classify the embedded IPv4 */
    if (ip[0] == 0x00 && ip[1] == 0x64 && ip[2] == 0xff && ip[3] == 0x9b &&
        zero_run(ip, 4, 12))
        return v4_blocked(ip + 12);
    /* 2002::/16  6to4 -> classify the embedded IPv4 (bytes 2..5) */
    if (ip[0] == 0x20 && ip[1] == 0x02)
        return v4_blocked(ip + 2);
    if ((ip[0] & 0xFE) == 0xFC) /* fc00::/7  unique-local (ULA)          */
        return 1;
    if (ip[0] == 0xFE && (ip[1] & 0xC0) == 0x80) /* fe80::/10 link-local */
        return 1;
    if (ip[0] == 0xFF) /* ff00::/8  multicast                            */
        return 1;
    if (ip[0] == 0x20 && ip[1] == 0x01 && ip[2] == 0x0D && ip[3] == 0xB8)
        return 1; /* 2001:db8::/32  documentation                       */
    if (ip[0] == 0x01 && ip[1] == 0x00 && zero_run(ip, 2, 8))
        return 1; /* 100::/64  discard-only                             */
    return 0;
}

int n_ssrf_ipv6_allowed(const uint8_t ip[16]) {
    return ip && !v6_blocked(ip);
}