shell实现netmask掩码和cidr掩码位转换

口算法

netmask转CIDR

例如:255.255.192.0

  • 255.0.0.0、255.255.0.0、255.255.255.0、255.255.255.255 这四种分别对应 8、16、24、32
  • 去掉所有255.的字符和.及其后面的字符,得到了 192。用 256 减去 192,得到了 64,也就是 2 的 6 次方
  • XXX.XXX.192.XXX 24-6=18
    • 192在第一段,用8减
    • 192在第二段,用16减
    • 192在第三段,用24减
    • 192在第四段,用32减

CIDR转netmask

例如:20==>255.255.240.0

根据上面的进行反推,8、16、24、32 可直接转换

  • 24-20=4
    • 小于8则用8减
    • 大于8小于16则用16减
    • 大于16小于24则用24减
    • 大于24小于32则用32减
  • 求得2的N次幂,即:2的4次方得到16,256-16=240
  • 255.255.XXX.0,这里的XXX是240
    • 小于8则是XXX.0.0.0
    • 大于8小于16则255.XXX.0.0
    • 大于16小于24则255.255.XXX.0
    • 大于24小于32则255.255.255.XXX

使用自带命令ipcalc

常用选项

1
2
3
4
5
-b, --broadcast     Display calculated broadcast address
-h, --hostname Show hostname determined via DNS
-m, --netmask Display default netmask for IP (class A, B, or C)
-n, --network Display network address
-p, --prefix Display network prefix

使用示例

1
2
3
4
5
6
[user1@study ~]$ ipcalc -pmnb 222.58.15.18/29
NETMASK=255.255.255.248
PREFIX=29
BROADCAST=222.58.15.23
NETWORK=222.58.15.16
[user1@study ~]$

perl开发的ipcalc

官网:http://jodies.de/ipcalc

1
2
3
4
5
6
7
8
9
10
11
$ ipcalc 192.168.0.1/24 

Address: 192.168.0.1 11000000.10101000.00000000. 00000001
Netmask: 255.255.255.0 = 24 11111111.11111111.11111111. 00000000
Wildcard: 0.0.0.255 00000000.00000000.00000000. 11111111
=>
Network: 192.168.0.0/24 11000000.10101000.00000000. 00000000
HostMin: 192.168.0.1 11000000.10101000.00000000. 00000001
HostMax: 192.168.0.254 11000000.10101000.00000000. 11111110
Broadcast: 192.168.0.255 11000000.10101000.00000000. 11111111
Hosts/Net: 254 Class C, Private Internet

来自openwrt的shell脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

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) ))
}
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}
}
# examples:
mask2cdr 255.255.255.0
cdr2mask 24

思路说明

mask2cdr()

To get the CIDR prefix from a dot-decimal netmask like this one:
要从像下面这样的点分十进制掩码获得CIDR子网掩码

1
255.255.192.0

you first have to convert the four octets to binary and then count the most significant bits (i.e. the number of leading ones):
首先你必须转换成四个八位二进制,而且算出占了1的位数

1
11111111.11111111.11000000.00000000 # 18 ones = /18 in CIDR

This function does that rather creatively. First, we strip off all of the leading 255 octets (i.e. the octets that are all ones in binary) and store the results in variable x:
这个函数确实极具创造性。首先,我们将传入函数的的第一个位置参数(即点分十进制子网掩码)去掉所有开头的255,然后复制给变量x

1
local x=${1##*255.}

This step uses parameter expansion, which the entire script relies on pretty heavily. If we continue with our example netmask of 255.255.192.0, we now have the following values:
这一步我们使用参数扩展,整个脚本对此有很强的依赖性。如果我们根据刚刚的例子继续看,会得到如下值

1
2
$1: 255.255.192.0 
$x: 192.0

Next we set three variables: $1, $2, and $3. These are called positional parameters; they are much like ordinary named variables but are typically set when you pass arguments to a script or function. We can set the values directly using set –, for example:
接下来我们设置了三个变量:$1,$2和$3,他们都是位置参数,和普通变量大同小异,但是当你将参数传给一个脚本或者函数时他们是很典型的设置。我们可以直接使用set --来设定变量的值,列如:

1
set -- foo bar # $1 = foo, $2 = bar

I prefer using named variables over positional parameters since it makes scripts easier to read and debug, but the end result is the same. We set $1 to:
相比较位置参数,我更喜欢使用定义好的变量,因为这样使得脚本更容易阅读和调试bug,但是最终的结果都是相同的,我们把位置参数$1设置为:

1
0^^^128^192^224^240^248^252^254^

This is really just a table to convert certain decimal values to binary and count the number of 1 bits. We’ll come back to this later.
这其实只是一个用来转换十进制到二进制,统计1bits数量的一个表。我们稍后将会回顾这里

We set $2 to
我们将位置参数$2设置为:

1
$(( (${#1} - ${#x})*2 ))

This looks complex, but it is really just counting the number of 1 bits we stripped off in the first command. It breaks down to this:
这个看起来有些复杂,但是其实只是计算我们从第一个命令中去掉(二进制数中八个位都占了1的位)的数量。它划分成了这样:

1
2
(number of chars in $1 - number of chars in $x) * 2
# 如果传递给函数的子网掩码是255.255.255.192的话,那么其实就是(15-3)*2=24

which in our case works out to
在我们的情况下,其实就是这样:

1
(13 - 5) * 2 = 16

We stripped off two octets so we get 16. Makes sense.
我们去除了两个(在二进制都占了1的位数=8)8位因此得到了16

We set $3 to:
我们将位置参数$3设置为:

1
${x%%.*}

which is the value of $x with everything after the first . stripped off. In our case, this is 192.
从刚开始到现在$x的值,在我们的情况下已经赋值为192

We need to convert this number to binary and count the number of 1 bits in it, so let’s go back to our “conversion table.” We can divide the table into equal chunks of four characters each:
我们需要将这个数字转换为二进制并计算占了1的位的数量,让我们回到我们的“转换表”。我们可以把表分成每个块的字符数等于四的块:

1
0^^^ 128^ 192^ 224^ 240^ 248^ 252^ 254^

In binary, the above numbers are:
转换为二进制就是

1
2
00000000 10000000 11000000 11100000 11110000 11111000 11111100 11111110
# 0 ones 1 one 2 ones 3 ones ...

If we count from the left, each four-character block in the table corresponds to an additional 1 bit in binary. We’re trying to convert 192, so let’s first lop off the rightmost part of the table, from 192 on, and store it in x:
如果我们从左边数,表中的每个四字符的块对应一个额外 占了1的位 的二进制数。我们正试图转换 192 ,所以让我们先剔除掉从192开头的了最右边的表,并赋值给x :

1
x=${1%%$3*}

The value of $x is now
$x的值现在就是

1
0^^^128^

which contains two four-character blocks, or two 1 bits in binary.
包含了两个4字符的块或者说是两个占了1位的二进制

Now we just need to add up the 1 bits from our leading 255 octets (16 total, stored in variable $2) and the 1 bits from the previous step (2 total):
现在我们只需要把之前算的两个(二进制下都为1的)八位组,(16个,赋值给位变量 $ 2 ),加上从第二步的表中算出$x在二进制中占了1的位的数2

1
echo $(( $2 + (${#x}/4) ))

where
其中

1
${#x}/4

is the number of characters in $x divided by four, i.e. the number of four-character blocks in $x.
是$x的字符数除以四(每四个字符是一个块)

Output:
输出结果

1
18

cdr2mask()

Let’s keep running with our previous example, which had a CIDR prefix of 18.

We use set – to set positional parameters $1 through $9:

1
2
3
4
5
6
7
8
9
$1: $(( 5 - ($1 / 8) ))  # 5 - (18 / 8) = 3 [integer math]
$2: 255
$3: 255
$4: 255
$5: 255
$6: $(( (255 << (8 - ($1 % 8))) & 255 )) # (255 << (8 - (18 % 8))) & 255 = 192
$7: 0
$8: 0
$9: 0

Let’s examine the formulas used to set $1 and $6 a little closer. $1 is set to:

1
$(( 5 - ($1 / 8) ))

The maximum and minimum possible values for a CIDR prefix are 32 for netmask

1
11111111.11111111.11111111.11111111

and 0 for netmask

1
00000000.00000000.00000000.00000000

The above formula uses integer division, so the possible results range from 1 to 5:

1
2
5 - (32 / 8) = 1
5 - ( 0 / 8) = 5

$6 is set to:

1
$(( (255 << (8 - ($1 % 8))) & 255 ))

Let’s break this down for our example CIDR prefix of 18. First we take the modulus and do some subtraction:

1
8 - (18 % 8) = 6

Next we bitwise shift 255 by this value:

1
255 << 6

This is the same as pushing six 0 bits onto the end of 255 in binary:

1
11111111000000

Finally, we bitwise AND this value with 255:

1
2
11111111000000 &
00000011111111 # 255

which gives

1
00000011000000

or simply

1
11000000

Look familiar? This is the third octet in our netmask in binary:

1
2
11111111.11111111.11000000.00000000
^------^

In decimal, the value is 192.

Next we shift the positional parameters based on the value of $1:

1
[ $1 -gt 1 ] && shift $1 || shift

In our case, the value of $1 is 3, so we shift the positional parameters 3 to the left. The previous value of $4 becomes the new value of $1, the previous value of $5 becomes the value of $2, and so on:

1
2
3
4
5
6
$1: 255
$2: 255
$3: 192
$4: 0
$5: 0
$6: 0

These values should look familiar: they are the decimal octets from our netmask (with a couple of extra zeros tacked on at the end). To get the netmask, we simply print out the first four with dots in between them:

1
echo ${1-0}.${2-0}.${3-0}.${4-0}

The -0 after each parameter says to use 0 as the default value if the parameter is not set.

Output:

1
255.255.192.0
有钱任性,请我吃包辣条
0%