Splunk Search

Classify IPv4 and IPv6 addresses as Internal vs External

msquicc
Path Finder

How can I reliably classify IPv4 and IPv6 addresses as internal vs external?  

Requirements:

  • Handle both IPv4 and IPv6

  • Validate the IP format (don’t just assume “not in RFC1918 = external”)

  • Treat private, loopback, link-local, etc. as internal, and only mark truly public IPs as external

I’ve seen a bunch of posts on this topic, but most either miss some cases (CGNAT, IPv6 ULA/link-local) or overcomplicate things with regex.

I'm curious to see what others have come up with.  Ideally, maybe this could turn into a nice suggestion for Splunk ideas.  I do think it would be great to have this baked into the product.  I'm going to post my suggestion that uses cidrmatch wrapped in macro, but please let me know if you find any issues with it, have suggestions, or a better solution!  

Labels (1)
0 Karma
1 Solution

msquicc
Path Finder

Here's my solution that I've been piecing together for a while now.  I'm using cidrmatch() to determine internal vs. external, as well as validate IP address format.  I wrap the whole thing in a macro.  


Example usage in a query:

| eval src_ip_class = `ipClass(src_ip)`


Macro Name: 

ipClass(1)


Macro Arguments: 

IP


Macro Definition:

case(
    ``` Empty / null ```
    isnull($IP$) OR $IP$="", "Empty",

    ``` Internal IPv4 (RFC1918, CGNAT, loopback, link-local) ```
    cidrmatch("10.0.0.0/8",      $IP$)
    OR cidrmatch("172.16.0.0/12", $IP$)
    OR cidrmatch("192.168.0.0/16",$IP$)
    OR cidrmatch("100.64.0.0/10", $IP$)   ``` CGNAT ```
    OR cidrmatch("127.0.0.0/8",   $IP$)   ``` loopback ```
    OR cidrmatch("169.254.0.0/16",$IP$),  ``` link-local ```
        "Internal IPv4",

    ``` Internal IPv6 (ULA, link-local, loopback) ```
    cidrmatch("fc00::/7",  $IP$)          ``` Unique local ```
    OR cidrmatch("fe80::/10",$IP$)        ``` Link-local ```
    OR cidrmatch("::1/128",$IP$),         ``` Loopback ```
        "Internal IPv6",

    ``` Any remaining valid IPv4 ```
    cidrmatch("0.0.0.0/0", $IP$), "External IPv4",

    ``` Any remaining valid IPv6 ```
    cidrmatch("::/0", $IP$), "External IPv6",

    ``` Everything else ```
    1==1, "Invalid"
)

 

Run Anywhere Example

| makeresults count=1
| eval src_ip="10.42.17.8, 172.20.55.13, 192.168.100.77, 100.88.12.200, 127.0.0.1, 169.254.33.10, 8.8.8.8, fd12:3456:789a:1::25, fe80::a4b3:22ff:fe19:7c01, ::1, 2600:1407:5800::5ce, , 999.999.1.2, ham-sandwich, fe80:::1"
| eval src_ip = trim(split(src_ip,","))
| mvexpand src_ip
``` ^ Create sample data ^ ```

```Solution example below```
| eval src_ip_class = `ipClass(src_ip)`
| table src_ip src_ip_class
| sort 0 src_ip_class

View solution in original post

0 Karma

msquicc
Path Finder

Here's my solution that I've been piecing together for a while now.  I'm using cidrmatch() to determine internal vs. external, as well as validate IP address format.  I wrap the whole thing in a macro.  


Example usage in a query:

| eval src_ip_class = `ipClass(src_ip)`


Macro Name: 

ipClass(1)


Macro Arguments: 

IP


Macro Definition:

case(
    ``` Empty / null ```
    isnull($IP$) OR $IP$="", "Empty",

    ``` Internal IPv4 (RFC1918, CGNAT, loopback, link-local) ```
    cidrmatch("10.0.0.0/8",      $IP$)
    OR cidrmatch("172.16.0.0/12", $IP$)
    OR cidrmatch("192.168.0.0/16",$IP$)
    OR cidrmatch("100.64.0.0/10", $IP$)   ``` CGNAT ```
    OR cidrmatch("127.0.0.0/8",   $IP$)   ``` loopback ```
    OR cidrmatch("169.254.0.0/16",$IP$),  ``` link-local ```
        "Internal IPv4",

    ``` Internal IPv6 (ULA, link-local, loopback) ```
    cidrmatch("fc00::/7",  $IP$)          ``` Unique local ```
    OR cidrmatch("fe80::/10",$IP$)        ``` Link-local ```
    OR cidrmatch("::1/128",$IP$),         ``` Loopback ```
        "Internal IPv6",

    ``` Any remaining valid IPv4 ```
    cidrmatch("0.0.0.0/0", $IP$), "External IPv4",

    ``` Any remaining valid IPv6 ```
    cidrmatch("::/0", $IP$), "External IPv6",

    ``` Everything else ```
    1==1, "Invalid"
)

 

Run Anywhere Example

| makeresults count=1
| eval src_ip="10.42.17.8, 172.20.55.13, 192.168.100.77, 100.88.12.200, 127.0.0.1, 169.254.33.10, 8.8.8.8, fd12:3456:789a:1::25, fe80::a4b3:22ff:fe19:7c01, ::1, 2600:1407:5800::5ce, , 999.999.1.2, ham-sandwich, fe80:::1"
| eval src_ip = trim(split(src_ip,","))
| mvexpand src_ip
``` ^ Create sample data ^ ```

```Solution example below```
| eval src_ip_class = `ipClass(src_ip)`
| table src_ip src_ip_class
| sort 0 src_ip_class
0 Karma
Get Updates on the Splunk Community!

[Puzzles] Solve, Learn, Repeat: Dynamic formatting from XML events

This challenge was first posted on Slack #puzzles channelFor a previous puzzle, I needed a set of fixed-length ...

Enter the Agentic Era with Splunk AI Assistant for SPL 1.4

  🚀 Your data just got a serious AI upgrade — are you ready? Say hello to the Agentic Era with the ...

Stronger Security with Federated Search for S3, GCP SQL & Australian Threat ...

Splunk Lantern is a Splunk customer success center that provides advice from Splunk experts on valuable data ...