0%

IP 组播地址

今天定位一个 IP 组播问题,在抓到该组播报文并分析其以太头时,发现其目的 MAC 地址比较有趣,才学习到原来 IPv4 组播地址与 MAC 组播地址有一定的对应关系,这篇文章将介绍这一知识。

我们知道,D 类 IP 地址被用于 IPv4 组播,其地址范围为 224.0.0.0 — 239.255.255.255。D 类 IP 第一个字节的最高 4 位固定为 1110,后 28 位可以自由使用。而二层组播地址,其也有 24 位固定前缀 01-00-5E,而剩余的 24 位中也只有一半能自由使用,也就是说只有最后 23 位可用于区别不同的组播组。

Octet 0 Octet 1 Octet 2 Octet 3 Octet 4 Octet 5
01 00 5E ?XXXXXXX XXXXXXX XXXXXXX

组播 MAC 地址为什么只有 23 位能使用呢?据说是有一个很有趣的故事:Steve Deering 在研究 IP 组播时想向 IEEE 申请 16 个 OUIs(Organizational Unique Identifiers),每个 OUI 占有 24 位地址空间,这样总共就有 16 * 2^24 个 MAC 组播地址,这样正好就能够和 IPv4 组播地址一一对应。但是每个 OUI 需要花费 $1000,Steve 的经理觉得很贵,因此只买了一个 OUI(24位),并且只给 Steve 一半的地址空间(23 位)用于组播研究。

因此,每一个 IPv4 组播地址有 28 个有效位可以使用,而 MAC 组播地址中只有 23 个有效位可以使用,那么如何完成这两者之间的映射呢?方法就是:将 IPv4 IP 地址中的最后 23 位映射到 MAC 组播地址的后 23 位,这样得到的 MAC 组播地址也就是该 IPv4 组播地址对应的 MAC 地址。这样也造成每 32 个组播 IPv4 地址会映射到同一个 MAC 组播地址

举一个例子,对于 IPv4 组播地址 224.11.1.2 而言,其对应的 MAC 组播地址即为 01:00:5E:0B:01:02:

地址 可读表示 二进制表示
IP 地址 224.11.1.2 1110 0000 0000 1011 0000 0001 0000 0010
MAC 地址 01:00:5E:0B:01:02 0000 0001 0000 0000 0101 1110 0000 1011 0000 0001 0000 0010

而对于 MAC 组播地址 01:00:5E:0B:01:02,其可以对应 32 个组播 IPv4 地址, XXXXX 表示可从 00000 到 11111 :

地址 可读表示 二进制表示
MAC 地址 01:00:5E:0B:01:02 0000 0001 0000 0000 0101 1110 0000 1011 0000 0001 0000 0010
IP 地址 可对应 32 个 IP 地址 1110 XXXX X000 1011 0000 0001 0000 0010

以上就是 IPv4 组播地址与 MAC 组播地址之间的映射关系。当然每次手工计算还挺麻烦的,因此我编写了一个 shell 脚本来完成这一工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/bin/bash

# Copyright (C) fuchencong.com
# Convert IPv4 multicast address to Mac multicast address, and verse.


check_ip_valid()
{
ip_addr=$1

check_addr=$(echo $ip_addr | sed 's/[0-9.]//g')
if [ ! -z "$check_addr" ]; then
return 1
fi

count=0
OLDIFS=$IFS
IFS="."
for value in $ip_addr
do
if [ $value -gt 255 ];
then
return 1;
fi

count=$((count + 1))
done

# total for digits for IPv4 address
if [ $count != 4 ]; then
return 1
fi

IFS=$OLDIFS
return 0
}

check_mac_valid()
{
mac_addr=$1

check_mac=$(echo $mac_addr | sed 's/[A-F0-9:]//g')
if [ ! -z $check_mac ]; then
return 1;
fi

count=0
OLDIFS=$IFS
IFS=":"
for value in $ip_addr
do
value=$(echo "ibase=16; $value" | bc)
if [ $value -gt 255 ];
then
return 1;
fi

count=$((count + 1))
done

# total six bytes for MAC address
if [ $count != 6 ]; then
return 1
fi

IFS=$OLDIFS
return 0
}

ip_to_mac()
{
ip_addr=$1
ip_mask=(0 127 255 255)
mac_addr="01:00:5E"

OLDIFS=$IFS
IFS="."
count=0

for value in $ip_addr
do
# ignore first byte in ip address (25-32bit)
if [ $count -eq 0 ]; then
count=$((count + 1))
continue;
fi

byte=$(($value & ${ip_mask[$count]}))
hex_byte=$(printf "%02X" $byte)
mac_addr="${mac_addr}"":$hex_byte"
count=$((count + 1))
done

IFS=$OLDIFS
echo $mac_addr
}

mac_to_ip()
{
mac_addr=$1
ip_mask=(0 0 0 127 255 255)

ipaddr_4_bit="1110"
ipaddr_23_bit=""

OLDIFS=$IFS
IFS=":"
count=0

# get common 23 bit of mac and ip address
for value in $mac_addr
do
# ignore first 24 bit in mac addr
if [ $count -lt 3 ]; then
count=$((count + 1))
continue;
fi

value=$(echo "ibase=16; $value" | bc)
byte=$(($value & ${ip_mask[$count]}))
# get binary format
binary_byte=$(echo "obase=2; $byte" | bc)
# padding leading zero
binary_byte=$(printf "%08s" $binary_byte | tr ' ' '0')
ipaddr_23_bit=${ipaddr_23_bit}${binary_byte}
count=$((count + 1))
done

IFS=$OLDIFS
# always skip first bit of ipaddr_23_bit
ipaddr_23_bit=${ipaddr_23_bit:1}

# generate 32 multicast ip address
for value in `seq 0 31`
do
ipaddr_5_bit=$(echo "obase=2; $value" | bc)
# padding leading zero
ipaddr_5_bit=$(printf "%05s" $ipaddr_5_bit | tr ' ' '0')

ipaddr_bit=${ipaddr_4_bit}${ipaddr_5_bit}${ipaddr_23_bit}
byte1=$(echo "ibase=2; ${ipaddr_bit:0:8}" | bc)
byte2=$(echo "ibase=2; ${ipaddr_bit:8:8}" | bc)
byte3=$(echo "ibase=2; ${ipaddr_bit:16:8}" | bc)
byte4=$(echo "ibase=2; ${ipaddr_bit:24:8}" | bc)

echo "${byte1}.${byte2}.${byte3}.${byte4}"
done
}

if check_ip_valid $1 ; then
ip_to_mac $1
elif check_mac_valid $1 ; then
mac_to_ip $1
else
echo "Usage: $0 ip address(224.0.0.0) or mac address(01:00:5E:00:00:00)"
exit 1
fi

exit 0

该脚本的运行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ ./mcaddr.sh 224.11.1.2
01:00:5E:0B:01:02
$ ./mcaddr.sh 01:00:5E:0B:01:02
224.11.1.2
224.139.1.2
225.11.1.2
225.139.1.2
226.11.1.2
226.139.1.2
227.11.1.2
227.139.1.2
228.11.1.2
228.139.1.2
229.11.1.2
229.139.1.2
230.11.1.2
230.139.1.2
231.11.1.2
231.139.1.2
232.11.1.2
232.139.1.2
233.11.1.2
233.139.1.2
234.11.1.2
234.139.1.2
235.11.1.2
235.139.1.2
236.11.1.2
236.139.1.2
237.11.1.2
237.139.1.2
238.11.1.2
238.139.1.2
239.11.1.2
239.139.1.2

搞定,收工!