苹果在去年的WWDC2015中宣布在今年的6月1日之后发布的应用必须支持IPv6-Only的网络环境。
苹果官方给出的解决方法是:
-
IP address literals embedded in protocols. Many communications protocols, such as Session Initiation Protocol (SIP), File Transfer Protocol (FTP), WebSockets, and Peer-to-Peer Protocol (P2PP), include IP address literals in protocol messages. For example, the
FTP
parameter commandsDATA PORT
andPASSIVE
exchange information that includes IP address literals. Similarly, IP address literals may appear in the values of SIP header fields, such asTo
,From
,Contact
,Record-Route
, andVia
. See Use High-Level Networking Frameworks and Don’t Use IP Address Literals. -
IP address literals embedded in configuration files. Configuration files often include IP address literals. See Don’t Use IP Address Literals.
-
Network preflighting. Many apps attempt to proactively check for an Internet connection or an active Wi-Fi connection by passing IP address literals to network reachability APIs. See Connect Without Preflight.
-
Using low-level networking APIs. Some apps work directly with sockets and other raw network APIs such as
gethostbyname
,gethostbyname2
, andinet_aton
. These APIs are prone to misuse or they only support IPv4—for example, resolving hostnames for theAF_INET
address family, rather than theAF_UNSPEC
address family. See Use High-Level Networking Frameworks. -
Using small address family storage containers. Some apps and networking libraries use address storage containers—such as
uint32_t
,in_addr
, andsockaddr_in
—that are 32 bits or smaller. See Use Appropriately Sized Storage Containers. [1]
而在我们的工程中需要使用底层C++的socket建立长连接来进行信息交互,因此我们需要对我们的工程中的socket连接进行IPv6 Only网络下的适配工作。
首先介绍下IPv6:
从很久远的计算机网络课堂中我们得知IPv6是为了弥补IPv4网络环境下地址数量有限而诞生的容量更大的网络地址协议。目前在中国,当我们用网络设备连接上有线网络、大部分无线网络(部分无线路由局域网内使用IPv6)、4G、3G等网络时,设备被分配的地址均为IPv4地址(教育网部分使用IPv6),但是随着不同国家不同的运营商和企业逐渐部署IPv6 DNS64/NAT64网络之后,设备被分配的地址会变成IPv6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容 [3]。
他们访问IPv4地址时的过程是这样的:
客户端向服务器端请求域名解析,首先通过DNS64 Server查询IPv6的地址,如果查询不到,再向DNS Server查询IPv4地址,通过DNS64 Server将查询到的IPv4地址合成为一个IPv6的地址,最终将一个IPv6的地址返回给客户端 [3]。如下图所示:
而在我们的底层socket连接中,并不支持使用域名来通过DNS Server来兼容IPv4和IPv6网络来建立连接,而需要我们以P2P的方式直接使用服务器的IP Address来直接建立连接。那么我们需要在IPv6-Only的网络下手动把IPv4的地址合成为IPv6的地址,来访问服务器。此外对于服务端我们需要的让服务器能够兼容IPv4和IPv6的客服端发来的连接请求,我不太清楚服务端的代码,具体方法请参考参考文献[2]。
在代码中我们建立socket连接的地方,我们需要手动来判断当前网络来生成对应IP格式。这里在iOS 9.2版本之后,苹果宣布 'getaddrinfo' 方法将可以支持按照当前网络环境得到IPv4或者IPv6的地址。这需要我们载入"#include <netdb.h>"和"#include <arpa/inet.h>"。具体的代码如下:
// clear previous sockets
if(mySocket>=0){
close(mySocket);
mySocket=-1;
}
struct addrinfo hints, *res, *ressave;
bzero(&hints, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
char szPort[10]={0};
sprintf(szPort, "%d",nPort);
int ret;
// get IPv4 or IPv6 address according to the App running network (IPv4 or IPv6)
if (0 != (ret = getaddrinfo(szIP, szPort, &hints, &res)))
{
return -1;
}
// set the port of IP address, because sometime the port value will be set to '0'
if (res->ai_family == AF_INET6) {
{
sockaddr_in6 *paddrinfo=(sockaddr_in6 *)res->ai_addr;
paddrinfo->sin6_port = htons(nPort);
} else if (res->ai_family == AF_INET) {
sockaddr_in6 *paddrinfo=(sockaddr_in *)res->ai_addr;
paddrinfo->sin6_port = htons(nPort);
}
ressave = res;
while (NULL != res)
{
//init socket
if (-1 == (mySocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol)))
{
res = res->ai_next;
continue;
}
//connect to the server
if (-1 == connect(mySocket, res->ai_addr, res->ai_addrlen))
{
close(mySocket);
mySocket=-1;
res = res->ai_next;
continue;
}
break;
}
freeaddrinfo(ressave);
建立完socket连接后,就可以按照原先的网络进行正常的发送/接收数据了。
相关参考文献:
1.Supporting IPv6 DNS64/NAT64 Networks
2.Application Aspects of IPv6 Transition