2010年8月29日星期日

IPv6小结

1. 关于sockaddr_in6
在windowsXP SP3+vc6.0下,该结构体为:

1
2
3
4
5
6
struct sockaddr_in6 {
short sin6_family;    /* AF_INET6 */
u_short sin6_port; /* Transport level port number */
u_long sin6_flowinfo; /* IPv6 flow information */
struct in_addr6 sin6_addr; /* IPv6 address */
};

大小为24个字节;在使用WSAStringToAddress转换函数时,使用该结构体会失败,原因就是结构体大小小于函数所需要的28个字节。

查看了同一台机器上的visual studio 2005的头文件(ws2tcpip.h),上述结构体被命名为old,而同样名字则使用了另一个结构体,大小为28字节:

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
/* Old IPv6 socket address structure (retained for sockaddr_gen definition below) */
 
struct sockaddr_in6_old {
   short   sin6_family;        /* AF_INET6 */
   u_short sin6_port;          /* Transport level port number */
   u_long  sin6_flowinfo;      /* IPv6 flow information */
   struct in6_addr sin6_addr;  /* IPv6 address */
};
 
/* IPv6 socket address structure, RFC 2553 */
 
struct sockaddr_in6 {
   short   sin6_family;        /* AF_INET6 */
   u_short sin6_port;          /* Transport level port number */
   u_long  sin6_flowinfo;      /* IPv6 flow information */
   struct in6_addr sin6_addr;  /* IPv6 address */
   u_long sin6_scope_id;       /* set of interfaces for a scope */
};



关于这个多出来的scope_id变量,具体应用和意义不是很清楚(link-local地址会有所涉及)。

网上搜了下,发现另一个系统建议的同时支持IPv4和IPv6的结构体:


1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct sockaddr_storage {
   short ss_family;               // Address family.
   char __ss_pad1[_SS_PAD1SIZE];  // 6 byte pad, this is to make
                                  // implementation specific pad up to
                                  // alignment field that follows  explicit
                                  // in the data structure.
   __int64 __ss_align;            // Field to force desired structure.
   char __ss_pad2[_SS_PAD2SIZE];  // 112 byte pad to achieve desired size;
                                  // _SS_MAXSIZE value minus size  of
                                  // ss_family, __ss_pad1, and
                                  // __ss_align fields is 112.
};

此结构体大小为128字节,足够大了(大到目前还不知道多出来的空间用于存放什么数据)。
而linux下,sockaddr_in6结构体和windows下最新的结构体一致,sockaddr_storage大小也是128;所以对于windows下vc6.0以下,采用sockaddr_storage结构体,而其他则一律采用sockaddr_in6结构体。


1
2
3
4
5
6
7
8
typedef union tagSockAddr
{
   struct sockaddr_in ip4;
   struct sockaddr_in6 ip6;
#if defined(_WIN32) && _MSC_VER <= 1200
   struct sockaddr_storage ip;
#endif
} USockAddr;
(vc6.0对应的_MSC_VER为1200)

2. 关于IPv6服务器同时支持IPv4和IPv6客户端
按照UNPv3上说,可以在服务器上仅创建一个PF_INET6的socket,bind在IPv6的通配地址,此时该监听socket可以同时支持IPv4和IPv6的客户端接入(当然,前提条件是该服务器同时需具有IPv4地址和IPv6地址),而IPv4客户端接入进来后显示的地址为一个IPv4映射的IPv6地址(可以通过IN6_IS_ADDR_V4MAPPED宏辨别)。

同时,如果不需要这种特性,而强制只是支持IPv6客户端接入,可以通过设置socket属性:IPV6_V6ONLY来修改。

linux上一切ok,然而到了windows上,同样的情况下,windows默认只支持接入IPv6客户端(可恶的默认值),即IPV6_V6ONLY属性值为非0,

而要想在windows上设置该属性,需要vista或者更新的windows版本(完全忽视了windowsXP+vc6.0的用户)。

摘自MSDN:
Indicates if a socket created for the AF_INET6 address family is restricted to IPv6 communications only. Sockets created for the AF_INET6 address family may be used for both IPv6 and IPv4 communications. Some applications may want to restrict their use of a socket created for the AF_INET6 address family to IPv6 communications only.
When this value is non-zero (the default on Windows), a socket created for the AF_INET6 address family can be used to send and receive IPv6 packets only.
When this value is zero, a socket created for the AF_INET6 address family can be used to send and receive packets to and from an IPv6 address or an IPv4 address. Note that the ability to interact with an IPv4 address requires the use of IPv4 mapped addresses.
This socket option is supported on Windows Vista or later.

3. 关于WSAStringToAddress和WSAAddressToString函数
windows提供的这两个函数提供了sockaddr结构和字符串之间的转换,linux下类似的为inet_ntop和inet_pton。比较特殊的是windows这两个函数中的字符串格式不仅仅是ip地址,还包含了端口和网络接口信息,如:[fe80::21e:c9ff:fe45:832c%4]:55392,把sockaddr中所有有效的信息都返回了。同样的,在WSAStringToAddress中,可以把类似结构的字符串传入,得到对应的sockaddr结构体。

个人意见:KISS原则;按照这种做法,本来通过一次调用就可以获得的ip地址,现在还要额外做一次分析工作(不知到有没有提供相应的宏),其实这些函数的需求是将不可读的ip地址变为可读,其他信息,直接在需要时从sockaddr结构体中就可获得。

4. 关于link-local地址
实践下来,link-local地址是可以用于局域网内的通信(不过路由器),需注意的是,利用link-local地址进行通信,需指定本地网络接口。例如ping:

windows:ping6 fe80::21c:c0ff:feeb:5cc3%4 (本地网络接口id为4)
linux:ping6 -Ieth0 fe80::21e:c9ff:fe34:5112 (本地网络接口eth0)

而在具体编程中,通过strace上述命令,可以看到实际该接口id是设给了sockaddr_in6.sin6_scope_id = if_nametoindex("eth0");

但为什么link-local地址通信时,需要针对具体网络接口,还不知道原因?而相对的global地址,却不需要。
这个问题被我在迷糊中想到了一个解释(很灵异):之前,如果有多个网络接口的话,那么接口上的ip地址不能为同一网段,否则就会冲突;而现在,IPv6给每个网络接口都默认分配了一个link-local地址,即FE80开头的地址,显然都是同一网段的;此时如果和该网段的另一个地址通信,系统会不知道源地址采用哪一个接口上的地址,因为无论哪个其实都可以和目标进行通信,所以,必须人为的指定具体接口。

5. 关于组播组
介绍两个特别的组播组的用法。FF01::1和FF02::1,分别ping这两个组播组,凡是响应第一个的,都是支持IPv6的局域网内的机子(包括linux和windows);而响应第二个的,则仅是windows主机。原理是所有支持IPv6的主机都会加入FF01::1这个组播组,而windows主机还会加入FF02::1这个组播组。

2010年8月15日星期日

开始IPv6

开始接触IPv6了,在linux的机子上配了下,发现了link-local这个地址,可以直接从MAC地址转换过来(EUI-64),但在局域网内的通信,貌似用这个地址还不行,仅能ping通;后来,自己配了个global地址,还加了下路由,通信ok了。

初步有一个设想:在支持IPv6的机子上,监听某个端口,地址为IPv6的::(unspecified address), 如果当前为IPv6的网络,那一切通信以IPv6进行;反之,如果当前是IPv4的网络,且本机配置了IPv4的地址,那其他IPv4的主机连接时,会以IPv4映射IPv6的地址形式在netstat里显示,如::ffff:172:16:197:80,那tcp的话,之间的通信应该没问题,内核会进行这之间的地址转化,那同样的,udp应该也ok吧。当然,只考虑了单播,组播应该更复杂些,同时兼容IPv4和IPv6,How? 进一步,如果要实现基于链路的PF_PACK接收组播,又该如何兼容呢?

恩,环境也很重要;没环境,只能纸上谈兵了。