TCP/IP实践(一):Internet地址结构

目前Internet的地址涉及到IPv4、IPv6和MAC地址
这里重点介绍一下IPv6地址的构成,以及和相关地址的转换。

IPv6地址和接口标识符

EUI是唯一扩展标识符,EUI-48和EUI-64由IEEE定义。这些都是用于IPv6的地址,它们是通过将接口标识符取反u位来形成的。

如下图所示:
ipv6-addr

1
2
3
4
5
6
7
8
9
10
11
12
13
14
step1: 00:30:48:2A:19:89
转换为EUI-64,在第三子节后加入ff:fe
形成00:30:48:ff:fe:2a:19:89

step2: u位被取反
0x00 -> 0b 0000 0000 -> 0b 0000 0010
即为0x02
02:30:48:ff:fe:2a:19:89

step3: 完成链路本地的IPv6地址
使用保留链路的本地前缀fe80::/10
形成完整地址
fe80::230:48ff:fe2a:1989
(每2个字节为1组,最高位0不写)

获取本机的IPv4/IPv6/MAC地址

具体的实现方法如下:
mac os获取MAC地址的方法和Linux不同
我这里写的是mac os版的
Linux更简单了,参见注释就可以了。

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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//
// main.c
// GetLocalMAC-IP
//
// Created by zhangmin chen on 2018/10/8.
// Copyright © 2018年 zhangmin chen. All rights reserved.
//

#include <sys/types.h>
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <errno.h>

//------------now we try to Get MAC Address------

#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/network/IOEthernetInterface.h>
#include <IOKit/network/IONetworkInterface.h>
#include <IOKit/network/IOEthernetController.h>

static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices);
static kern_return_t GetMACAddress(io_iterator_t intfIterator, UInt8 *MACAddress, UInt8 bufferSize);

// Returns an iterator containing the primary (built-in) Ethernet interface. The caller is responsible for
// releasing the iterator after the caller is done with it.
static kern_return_t FindEthernetInterfaces(io_iterator_t *matchingServices)
{
kern_return_t kernResult;
CFMutableDictionaryRef matchingDict;
CFMutableDictionaryRef propertyMatchDict;

// Ethernet interfaces are instances of class kIOEthernetInterfaceClass.
// IOServiceMatching is a convenience function to create a dictionary with the key kIOProviderClassKey and
// the specified value.
matchingDict = IOServiceMatching(kIOEthernetInterfaceClass);

// Note that another option here would be:
// matchingDict = IOBSDMatching("en0");
// but en0: isn't necessarily the primary interface, especially on systems with multiple Ethernet ports.

if (NULL == matchingDict) {
printf("IOServiceMatching returned a NULL dictionary.\n");
}
else {
// Each IONetworkInterface object has a Boolean property with the key kIOPrimaryInterface. Only the
// primary (built-in) interface has this property set to TRUE.

// IOServiceGetMatchingServices uses the default matching criteria defined by IOService. This considers
// only the following properties plus any family-specific matching in this order of precedence
// (see IOService::passiveMatch):
//
// kIOProviderClassKey (IOServiceMatching)
// kIONameMatchKey (IOServiceNameMatching)
// kIOPropertyMatchKey
// kIOPathMatchKey
// kIOMatchedServiceCountKey
// family-specific matching
// kIOBSDNameKey (IOBSDNameMatching)
// kIOLocationMatchKey

// The IONetworkingFamily does not define any family-specific matching. This means that in
// order to have IOServiceGetMatchingServices consider the kIOPrimaryInterface property, we must
// add that property to a separate dictionary and then add that to our matching dictionary
// specifying kIOPropertyMatchKey.

propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);

if (NULL == propertyMatchDict) {
printf("CFDictionaryCreateMutable returned a NULL dictionary.\n");
}
else {
// Set the value in the dictionary of the property with the given key, or add the key
// to the dictionary if it doesn't exist. This call retains the value object passed in.
CFDictionarySetValue(propertyMatchDict, CFSTR(kIOPrimaryInterface), kCFBooleanTrue);

// Now add the dictionary containing the matching value for kIOPrimaryInterface to our main
// matching dictionary. This call will retain propertyMatchDict, so we can release our reference
// on propertyMatchDict after adding it to matchingDict.
CFDictionarySetValue(matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict);
CFRelease(propertyMatchDict);
}
}

// IOServiceGetMatchingServices retains the returned iterator, so release the iterator when we're done with it.
// IOServiceGetMatchingServices also consumes a reference on the matching dictionary so we don't need to release
// the dictionary explicitly.
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, matchingServices);
if (KERN_SUCCESS != kernResult) {
printf("IOServiceGetMatchingServices returned 0x%08x\n", kernResult);
}

return kernResult;
}

// Given an iterator across a set of Ethernet interfaces, return the MAC address of the last one.
// If no interfaces are found the MAC address is set to an empty string.
// In this sample the iterator should contain just the primary interface.
static kern_return_t GetMACAddress(io_iterator_t intfIterator, UInt8 *MACAddress, UInt8 bufferSize)
{
io_object_t intfService;
io_object_t controllerService;
kern_return_t kernResult = KERN_FAILURE;

// Make sure the caller provided enough buffer space. Protect against buffer overflow problems.
if (bufferSize < kIOEthernetAddressSize) {
return kernResult;
}

// Initialize the returned address
bzero(MACAddress, bufferSize);

// IOIteratorNext retains the returned object, so release it when we're done with it.
while ((intfService = IOIteratorNext(intfIterator)))
{
CFTypeRef MACAddressAsCFData;

// IONetworkControllers can't be found directly by the IOServiceGetMatchingServices call,
// since they are hardware nubs and do not participate in driver matching. In other words,
// registerService() is never called on them. So we've found the IONetworkInterface and will
// get its parent controller by asking for it specifically.

// IORegistryEntryGetParentEntry retains the returned object, so release it when we're done with it.
kernResult = IORegistryEntryGetParentEntry(intfService,
kIOServicePlane,
&controllerService);

if (KERN_SUCCESS != kernResult) {
printf("IORegistryEntryGetParentEntry returned 0x%08x\n", kernResult);
}
else {
// Retrieve the MAC address property from the I/O Registry in the form of a CFData
MACAddressAsCFData = IORegistryEntryCreateCFProperty(controllerService,
CFSTR(kIOMACAddress),
kCFAllocatorDefault,
0);
if (MACAddressAsCFData) {
CFShow(MACAddressAsCFData); // for display purposes only; output goes to stderr

// Get the raw bytes of the MAC address from the CFData
CFDataGetBytes(MACAddressAsCFData, CFRangeMake(0, kIOEthernetAddressSize), MACAddress);
CFRelease(MACAddressAsCFData);
}

// Done with the parent Ethernet controller object so we release it.
(void) IOObjectRelease(controllerService);
}

// Done with the Ethernet interface object so we release it.
(void) IOObjectRelease(intfService);
}

return kernResult;
}


//print Hex data
void pHx(unsigned char* p, int len) {
printf("Hex: ");
for(int i = 0; i < len; i++) {
printf("%02X:", p[i]);
}
printf("\b\n");
}

// get local mac
// For Linux

/*
* for linux
*
* char* getMac(char* mac, char* dv) {
struct ifreq ifr;
int sock;
if(!mac || dv)
return mac;

if( (sock = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
perror("socket ");
return mac;
}

strcpy(ifr.ifr_name, dv);
if(ioctl(sock, SIOCGIFHWADDR, &ifr) < 0) {
//SIOCGIFHWADDR
perror("ioctl ");
return mac;
}

pHx( (unsigned char*)ifr.ifr_ifru.ifru_addr.sa_data, sizeof(ifr.ifr_ifru.ifru_addr.sa_data) );
//eth length of MAC is 48bits
sprintf(mac, "%02X:%02X:%02X:%02X:%02X:%02X",
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[0],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[1],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[2],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[3],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[4],
(unsigned char)ifr.ifr_ifru.ifru_addr.sa_data[5]);

return mac;
}
*/


int main(int argc, const char* argv[]) {
char hostname[128];
struct hostent* hostent1;
// int i;

gethostname(hostname, sizeof(hostname));
hostent1 = gethostbyname(hostname);

printf("Hostname: %s\n", hostent1->h_name);
printf("\n");

// char mac[30];
struct ifaddrs* ifap0 = NULL, *ifap = NULL;
void* addPtr = NULL;

getifaddrs(&ifap0);
ifap = ifap0;

while(ifap != NULL) {
if(ifap->ifa_addr->sa_family == AF_INET) {
// is a valid IPv4 address
addPtr = & ((struct sockaddr_in *)ifap->ifa_addr)->sin_addr;

char addressBuffer[INET_ADDRSTRLEN];
inet_ntop(AF_INET, addPtr, addressBuffer, INET_ADDRSTRLEN);
if(strcmp(addressBuffer, "127.0.0.1") != 0) {
printf("%s IPv4: %s\n", ifap->ifa_name, addressBuffer);
}
} else if(ifap->ifa_addr->sa_family == AF_INET6) {
// is a valid IPv6 address
addPtr = & ((struct sockaddr_in *)ifap->ifa_addr)->sin_addr;

char addressBuffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, addPtr, addressBuffer, INET6_ADDRSTRLEN);
if(strcmp(addressBuffer, "::") != 0) {
printf("%s IPv6: %s\n", ifap->ifa_name, addressBuffer);
}
}

ifap = ifap->ifa_next;
}

if(ifap0) {
freeifaddrs(ifap0); ifap0 = NULL;
}

printf("\n\n");
printf("--------Now we try to get MAC address----------------\n");
printf("\n");

kern_return_t kernResult = KERN_SUCCESS;
io_iterator_t intfIterator;
UInt8 MACAddress[kIOEthernetAddressSize];

kernResult = FindEthernetInterfaces(&intfIterator);

if(KERN_SUCCESS != kernResult) {
printf("FindEthernetInterfaces returned 0x%08x\n", kernResult);
} else {
kernResult = GetMACAddress(intfIterator, MACAddress, sizeof(MACAddress));

if(KERN_SUCCESS != kernResult) {
printf("GetMACAddress return 0x%08x\n", kernResult);
} else {
printf("This system's built-in MAC address is %02x:%02x:%02x:%02x:%02x:%02x.\n",
MACAddress[0], MACAddress[1], MACAddress[2], MACAddress[3], MACAddress[4], MACAddress[5]);

}
}

(void) IOObjectRelease(intfIterator);
return kernResult;

}

IPv4组播地址

常见的组播地址:

1
2
3
224.0.0.0 - 224.0.0.255
本地网络控制,不转发,相当于是组播的范围边界
发送到这些地址的数据报不被转发
1
2
224.0.1.0 - 224.0.1.255
例:NTP(网络时间协议)组播组(224.0.1.1)
1
2
3
224.0.2.0 - 224.0.255.255
Ad hoc块1
保留一些地址
1
2
3
4
224.2.0.0 - 224.2.255.255
SDP/SAP

会话描述工具
1
2
3
4
5
232.0.0.0 - 232.255.255.255
源特定组播(SSM)

例:某些应用使用SSM块实现SSM
结合自己的单源地址形成一个SSM信道
1
2
3
4
5
6
233.0.0.0 - 233.251.255.255
GLOP块,组播地址基于主机的AS号
将AS号放入IPv4地址的第2、3字节中

例: 233.__.__.255
__填充的是16bits的AS number
1
2
3
4
5
6
7
8
9
10
234.0.0.0 - 234.255.255.255
235.0.0.0 - 238.255.255.255

基于单播前缀的IPv4组播地址保留
什么意思呢?就是给单播地址分配一个相关的UBM地址
例: 192.0.2.0/24 --> 234.192.0.2

看下图,如果我们知道组播地址
234.128.32.0/24
去掉前缀234,然后左移8位,128.32.0.0/16分配给加州大学伯克利分校

broadcast

IPv6组播

01

1
2
3
4
5
6
7
8
9
一个组织分配了单播前缀
3ffe:ffff:1::/48

前缀长度: 48 = 0b110000 = 0011 0000
第2字段的值为:
0000 0000 0011 0000 -> 0x0030 -> 30

ff3x:30:3ff3:fff:1::/96
(96 = 128-32)

02

1
2
3
4
5
6
7
8
可以根据IID(接口标识符)形成自己的组播地址

IID = 02-11-22-33-44-55-66-77

使用前缀形式ff3x:0011/32
组播地址为:
ff3x:0011:0211:2233:4455:6677:gggg:gggg
(gggg:gggg为32位组组播组ID的16进制表示

RP的单播IPv6地址嵌入IPv6组播地址

03

1
2
3
4
5
6
7
8
9
10
11
1、取出64位前缀
2、取出RIID字段
3、获取RP地址

组播地址
ff75:940:2001:db8:dead:beef:f00d:face
前缀 - 2001:db8:dead:beef
RIID - 0x9
前缀长度 - 0x40(64)

RP地址 - 2001:db8:dead:beef::9

单播地址分配

单个供应商/无网络/单个地址

这里介绍一些特殊的地址

1、IPv4回送地址:127.0.0.0/8
主机和IP堆栈内部的地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回之,不进行任何网络传输。

2、IPv6组播地址: ff02::1
ff02表示同一链路/子网中所有的NTP服务器