Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
OpenRC: converting netmask/CIDR without loops!
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Portage & Programming
View previous topic :: View next topic  
Author Message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Sat Jul 30, 2011 4:33 pm    Post subject: OpenRC: converting netmask/CIDR without loops! Reply with quote

Yay!

I've recently come to rewriting Gentoo networks scripts for an embedded solution that I'm still building. I always struggled upon network functions cidr2netmask and netmask2cidr that are found in rc network library ifconfig.sh and net.lo.

These are the beasts:
/lib/rc/net/ifconfig.sh:
_cidr2netmask()
{
        local cidr="$1" netmask="" done=0 i=0 sum=0 cur=128
        local octets= frac=

        local octets=$((${cidr} / 8))
        local frac=$((${cidr} % 8))
        while [ ${octets} -gt 0 ]; do
                netmask="${netmask}.255"
                octets=$((${octets} - 1))
                done=$((${done} + 1))
        done

        if [ ${done} -lt 4 ]; then
                while [ ${i} -lt ${frac} ]; do
                        sum=$((${sum} + ${cur}))
                        cur=$((${cur} / 2))
                        i=$((${i} + 1))
                done
                netmask="${netmask}.${sum}"
                done=$((${done} + 1))

                while [ ${done} -lt 4 ]; do
                        netmask="${netmask}.0"
                        done=$((${done} + 1))
                done
        fi

        echo "${netmask#.*}"
}

/etc/init.d/net.lo:
_netmask2cidr()
{
        # Some shells cannot handle hex arithmetic, so we massage it slightly
        # Buggy shells include FreeBSD sh, dash and busybox.
        # bash and NetBSD sh don't need this.
        case $1 in
                0x*)
                local hex=${1#0x*} quad=
                while [ -n "${hex}" ]; do
                        local lastbut2=${hex#??*}
                        quad=${quad}${quad:+.}0x${hex%${lastbut2}*}
                        hex=${lastbut2}
                done
                set -- ${quad}
                ;;
        esac

        local i= len=
        local IFS=.
        for i in $1; do
                while [ ${i} != "0" ]; do
                        len=$((${len} + ${i} % 2))
                        i=$((${i} >> 1))
                done
        done

        echo "${len}"
}

Dunno if you, like me, find these overly complicated but I've found a smarter way, IMHO:
Code:
function cidr2netmask()
{
        local maskpat="255 255 255 255"
        local maskdgt="254 252 248 240 224 192 128"
        set -- ${maskpat:0:$(( ($1 / 8) * 4 ))}${maskdgt:$(( (7 - ($1 % 8)) * 4 )):3}
        echo ${1-0}.${2-0}.${3-0}.${4-0}
}

function netmask2cidr()
{
        # Phase 1: Count how many 255 prefixes
        local mask="${1//255./xrandomx}" sfx=${1//255.}
        mask="${mask%%[[:digit:]]*}"
        set -- ${#mask} ${sfx//./ }

        # Phase 2: Convert the first non-255 number, rest is supposed to be zeroes
        mask="0--.128.192.224.240.248.252.254."
        mask="${mask//$2*}"
        echo $(( ${1} + ${#mask}/4 ))
}

Here's the complete script to test these two functions and examples:
cidrnetmask.sh:
#!/bin/sh
# Script: cidrnetmask.sh
# By Vince C <v_cadet AT yahoo.fr>.
# Released under the WTFPL :D .

function is_number()
{
   [ -n "${1}" ] && [ -z "${1//[[:digit:]]}" ]
}

function cidr2netmask()
{
        local maskpat="255 255 255 255"
        local maskdgt="254 252 248 240 224 192 128"
        set -- ${maskpat:0:$(( ($1 / 8) * 4 ))}${maskdgt:$(( (7 - ($1 % 8)) * 4 )):3}
        echo ${1-0}.${2-0}.${3-0}.${4-0}
}

function netmask2cidr()
{
        # Phase 1: Count how many 255 prefixes
        local mask="${1//255./xrandomx}" sfx=${1//255.}
        mask="${mask%%[[:digit:]]*}"
        set -- ${#mask} ${sfx//./ }

        # Phase 2: Convert the first non-255 number, rest is supposed to be zeroes
        mask="0--.128.192.224.240.248.252.254."
        mask="${mask//$2*}"
        echo $(( ${1} + ${#mask}/4 ))
}

# Parse and validate arguments
[ -z "$1" ] && exit 0
[ "${1%/*}" == "$1" ] && mask="$2" || mask="${1#*/}"
[ -z "$mask" ] && echo "No netmask." && exit 0

# Check and convert mask
ip=${1%/*}

# Convert CIDR to netmask and vice versa
echo -n "IP: ${ip}/${mask} --> ${ip}/"
is_number $mask && cidr2netmask $mask || netmask2cidr $mask

Code:
$ cidrnetmask.sh 1.2.3.4/25
IP: 1.2.3.4/25 --> 1.2.3.4/255.255.255.128
Code:
$ cidrnetmask.sh 1.2.3.4/255.255.255.192
IP: 1.2.3.4/255.255.255.192 --> 1.2.3.4/26

The second function can even handle dummy netmasks, like 255.255.128.240:
Code:
$ cidrnetmask.sh 1.2.3.4/255.255.128.240
IP: 1.2.3.4/255.255.128.240 --> 1.2.3.4/17

The first one is not that idiot-proof however.

Advantages: there's no loop, only limited arithmetic, relying on the shell's variable substitution, same result takes less than half the lines. Works well under bash, need to try it under other shells, especially for those special nested expressions but they may be split into more lines, I just wanted the script to be as compact as can be.

Anyone wants to try?
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
truc
Advocate
Advocate


Joined: 25 Jul 2005
Posts: 3199

PostPosted: Sat Jul 30, 2011 9:40 pm    Post subject: Reply with quote

Haven't tested but AFAIK the syntax ${var/pattern/bla} isn't posix
_________________
The End of the Internet!
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Sat Jul 30, 2011 11:14 pm    Post subject: Reply with quote

truc wrote:
Haven't tested but AFAIK the syntax ${var/pattern/bla} isn't posix

How does POSIX allow to replace all occurrences of a pattern in a string? I need it in this script.
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
kimmie
Guru
Guru


Joined: 08 Sep 2004
Posts: 531
Location: Australia

PostPosted: Sun Jul 31, 2011 3:20 am    Post subject: Reply with quote

VinzC wrote:
How does POSIX allow to replace all occurrences of a pattern in a string?

sed 8O
Back to top
View user's profile Send private message
pepoluan
n00b
n00b


Joined: 28 Feb 2011
Posts: 39
Location: Jakarta, Indonesia

PostPosted: Sun Jul 31, 2011 3:28 am    Post subject: Reply with quote

personally, I always use net-misc/sipcalc. then pipe its output via awk, or read it in a 'while' loop.
_________________
Nobody is Perfect.
I am Nobody.
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Sun Jul 31, 2011 8:27 am    Post subject: Reply with quote

VinzC wrote:
How does POSIX allow to replace all occurrences of a pattern in a string?

kimmie wrote:
sed 8O

:lol:

Of course...

pepoluan wrote:
personally, I always use net-misc/sipcalc. then pipe its output via awk, or read it in a 'while' loop.


My constraints are: minimum space, maximum speed (each command has a cost, subshells are even worse) and (hence) maximum simplicity. If I need a package for just one line in one script that makes it not POSIX compliant, I can live without (feels like fetching the «The Big Bertha» for snatching a mosquito, kinda). And if I'm to sacrifice compliance for these reasons, well...
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
truc
Advocate
Advocate


Joined: 25 Jul 2005
Posts: 3199

PostPosted: Sun Jul 31, 2011 11:58 am    Post subject: Reply with quote

I think you'd also have to change this syntax "${parameter:offset:length}" in your script since it's not POSIX either.

As kimmie said, you may have to use external tools instead
_________________
The End of the Internet!
Back to top
View user's profile Send private message
kimmie
Guru
Guru


Joined: 08 Sep 2004
Posts: 531
Location: Australia

PostPosted: Sun Jul 31, 2011 12:57 pm    Post subject: Reply with quote

I can't resist!
Code:
mask2cdr ()
{
   # Works with a[.b[.c[.d]]] or a.[b.[c.[d]]]
   local x y
   x=${1#255.} ; y=${1%$x} ;   set -- $x
   x=${1#255.} ; y=$y${1%$x} ; set -- $x
   x=${1#255.} ; y=$y${1%$x}
   local IFS=.
   set -- 0^^^128^192^224^240^248^252^254^ $x
   x=${1%%$2*}
   echo $(((${#y}<<1)+(${#x}>>2)))
}

Ahhhh! Itch scratched :oops: ... EDIT: Grrrr now it works :?
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Sun Jul 31, 2011 6:24 pm    Post subject: Reply with quote

kimmie wrote:
I can't resist!
Code:
mask2cdr ()
{
   # Works with a[.b[.c[.d]]] or a.[b.[c.[d]]]
   local x y
   x=${1#255.} ; y=${1%$x} ;   set -- $x
   x=${1#255.} ; y=$y${1%$x} ; set -- $x
   x=${1#255.} ; y=$y${1%$x}
   local IFS=.
   set -- 0^^^128^192^224^240^248^252^254^ $x
   x=${1%%$2*}
   echo $(((${#y}<<1)+(${#x}>>2)))
}

Ahhhh! Itch scratched :oops: ... EDIT: Grrrr now it works :?

Wow! Nice! Thanks a *lot* for sharing.
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
truc
Advocate
Advocate


Joined: 25 Jul 2005
Posts: 3199

PostPosted: Mon Aug 01, 2011 6:56 am    Post subject: Reply with quote

Wow kimmie! Nice trick! really

changing the line
Code:
echo $(((${#y}<<1)+(${#x}>>2)))
to
Code:
echo $(((${#y}*2)+(${#x}/4)))

Should make this POSIX compliant I think!
_________________
The End of the Internet!
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Mon Aug 01, 2011 12:03 pm    Post subject: Reply with quote

kimmie's mask2cdr:
mask2cdr ()
{
   # Works with a[.b[.c[.d]]] or a.[b.[c.[d]]]
   local x y
   x=${1#255.} ; y=${1%$x} ;   set -- $x
   x=${1#255.} ; y=$y${1%$x} ; set -- $x
   x=${1#255.} ; y=$y${1%$x}
   local IFS=.
   set -- 0^^^128^192^224^240^248^252^254^ $x
   x=${1%%$2*}
   echo $(((${#y}<<1)+(${#x}>>2)))
}

Hey, let's try this, even shorter:
Code:
mask2cdr ()
{
   # Assumes there's no "255." after a non-255 byte in the mask
   local x=${1##*255.}
   set -- 0^^^128^192^224^240^248^252^254^ $(( (${#1} - ${#x})*2 )) ${x%%.*}
   x=${1%%$3*}
   echo $(( $2 + (${#x}/4) ))
}

It's based on the length of how many "255." prefix the first non-255 byte. They don't need to be counted independently.

I love these kinds of challenges!

Four lines, one variable to compute a CIDR! Thanks guys for your contributions!

EDIT: I've been probably smoking weed with this one though but it works too:
Code:
mask2cdr ()
{
   # Assumes there's no "255." after a non-255 byte in the mask
   set -- 0^^^128^192^224^240^248^252^254^ ${#1} ${1##*255.}
   set -- $(( ($2 - ${#3})*2 )) ${1%%${3%%.*}*}
   echo $(( $1 + (${#2}/4) ))
}

Three lines, no variables... Yay! Is that still POSIX, truc?
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!


Last edited by VinzC on Mon Aug 01, 2011 1:48 pm; edited 3 times in total
Back to top
View user's profile Send private message
kimmie
Guru
Guru


Joined: 08 Sep 2004
Posts: 531
Location: Australia

PostPosted: Mon Aug 01, 2011 12:46 pm    Post subject: Reply with quote

VinzC wrote:
I love these kinds of challenges! Four lines to compute a CIDR! Thanks guys for your contributions!

Yeah!! I think that's squeezed dry now. Good job, very satisying indeed. Thanks for the gauntlets, everyone.

BUT I do have one thing to add on the subject. If I was going to send that code into the world, well, I wouldn't. When you come across something like that in the wild and there's a corner case that doesn't work and you have to debug it instead of going to the pub, that's when you sick Cthulu on to the author and wish retardation on their descendants.

So, for the into-the-wild case, I offer this:
Code:
m2c_sensible_shoes ()
{
   local IFS=.
   set -- $1 0 0 0 0
   declare -i n="($1<<24)+($2<<16)+($3<<8)+$4" i=0
   while (( n & 1<<31 )) ; do (( n <<=1, n &= (1<<32)-1, i++ )) ; done
   echo $(( n ? -1 : i ))
   (( !n ))
}

The advantages are that it's quite clear what's going, and that it returns false and a CDR of -1 for (most) invalid netmasks (not all, if you give it say 511.511.511.511 it'll be quite happy). It's got a loop, but there's no text substitution in it so the shell shouldn't have to reparse.

BTW << is posix AFAIK. Certainly bash --posix will happily run this.

Thoughts?
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Mon Aug 01, 2011 1:24 pm    Post subject: Reply with quote

You're quite right kimmie. Best is to have an indication when things go nasty. Our solution is intended for people who know what they do, I guess. BTW I have again stripped one line from the CIDR script (I have just edited my post above). I'm insane, I know :D .

EDIT

Now my thoughts. I think 32 loops is quite consuming in terms of interpretation. Bash and shells are slow (IMHO) doing arithmetics and loops cost a lot. That's why OpenRC are a little bit more elaborate. If you think of it only the first byte that differs from 255 needs attention.

And finally shells are more targeted at manipulating strings than numbers, hence the clear orientation of my suggestion. In the end, ifconfig and ip (as well as all other [network-related] commands) are passed string arguments. So I think it's a quite valid direction to consider. But it's true, it requires and assumes the mask argument was paid much attention against human mistakes.
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
kimmie
Guru
Guru


Joined: 08 Sep 2004
Posts: 531
Location: Australia

PostPosted: Mon Aug 01, 2011 1:49 pm    Post subject: Reply with quote

It's beeeutifuwel!
Back to top
View user's profile Send private message
VinzC
Advocate
Advocate


Joined: 17 Apr 2004
Posts: 4968
Location: Dark side of the mood

PostPosted: Tue Aug 02, 2011 1:05 am    Post subject: Reply with quote

Hehe, thanks :) . While we're at it, I also took the liberty to rewrite the opposite function:
Code:
function cdr2mask ()
{
   # Number of args to shift, 255..255, first non-255 byte, zeroes
   set -- $(( 5 - ($1 / 8) )) 255 255 255 255 $(( (255 << (8 - ($1 % 8))) & 255 )) 0 0 0
   [ $1 -gt 1 ] && shift $1 || shift
   echo ${1-0}.${2-0}.${3-0}.${4-0}
}

Even works when CIDR is greater than 32!
_________________
Gentoo addict: tomorrow I quit, I promise!... Just one more emerge...
1739!
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Portage & Programming All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum