Thursday, November 18, 2021

[SOLVED] Bash check if CIDR address is valid

Issue

I just can't seem to wrap my head around this. I have a regex that check if a string contains a valid CIDR notation address.

(((?:25[012345]|2[0-4]\d|1?\d\d?)\.){3}(?:25[012345]|2[0-4]\d|1?\d\d?))(?:\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\d])

This thing works in Perl, PHP, Javascript, and matches x.x.x.x/8 to y.y.y.y/32.

I've tried to change those \d to [[:digit:]] and to \\d Nothing :(

The test script used to test:

#!/bin/bash

if [ "$1" = "" ]
then
    echo "Usage: $( basename $0) 123.456.789.0/12"
    exit
fi
REGEX1='(((?:25[012345]|2[0-4]\d|1?\d\d?)\.){3}(?:25[012345]|2[0-4]\d|1?\d\d?))(?:\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\d])'
REGEX2='(((?:25[012345]|2[0-4]\\d|1?\\d\\d?)\.){3}(?:25[012345]|2[0-4]\\d|1?\\d\\d?))(?:\\/([1-9]|[1-2][0-9]|3[0-2]))(?![.\\d])'
REGEX3='(((?:25[012345]|2[0-4][[:digit:]]|1?[[:digit:]][[:digit:]]?)\\.){3}(?:25[012345]|2[0-4][[:digit:]]|1?[[:digit:]][[:digit:]]?))(?:\\/([1-9]|[1-2][0-9]|3[0-2]))(?![.[[:digit:]]])'

REGEX=$REGEX3

if [[ $1 =~ $REGEX ]]
then
    echo "$1 OK!"
else
    echo "$1 Not OK! $REGEX"
fi

Any ideas to where to go from here?

Updated. Added working script:

#!/bin/bash

if [ "$1" = "" ]
then
    echo "Usage: $( basename $0) 123.456.789.0/12"
    exit
fi

REGEX='(((25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9][0-9]?))(\/([8-9]|[1-2][0-9]|3[0-2]))([^0-9.]|$)'

if [[ $1 =~ $REGEX ]]
then
    echo "$1 OK!"
else
    echo "$1 Not OK!"
fi

if echo $1 | grep -Pq $REGEX
then
    echo "grep $1 OK!"
else
    echo "grep $1 Not OK!"
fi

Solution

The shortest path to success is GNU grep, which also supports PCRE:

#!/bin/sh

if echo "$CIDR" | grep -qP "$REGEX"
then
  echo "$CIDR OK!"
  exit 0
else
  echo "$CIDR NOT OK!"
  exit 1
fi

grep's -q makes it silent and relies on the exit code to determine success. -P is PCRE.

But I should point out that your regex does not fully match that something is a valid CIDR range; rather, you're matching a valid IP address followed by a slash and a number n ∈ 1-32. An additional requirement to CIDR ranges is that the 32-n lower bits of the address are zero, e.g.:

#!/bin/sh

valid_cidr() {
  CIDR="$1"

  # Parse "a.b.c.d/n" into five separate variables
  IFS="./" read -r ip1 ip2 ip3 ip4 N <<< "$CIDR"

  # Convert IP address from quad notation to integer
  ip=$(($ip1 * 256 ** 3 + $ip2 * 256 ** 2 + $ip3 * 256 + $ip4))

  # Remove upper bits and check that all $N lower bits are 0
  if [ $(($ip % 2**(32-$N))) = 0 ]
  then
    return 0 # CIDR OK!
  else
    return 1 # CIDR NOT OK!
  fi
}

Test this with e.g. 127.0.0.0/24, 127.1.0.0, 127.1.1.0/24.

Or more odd ranges: 10.10.10.8/29, 127.0.0.0/8, 127.3.0.0/10, 192.168.248.0/21.



Answered By - Simon Shine