热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

IP双栈环境下网络应用迁移

IPv4向IPv6迁移有多种途径,在选择具体的迁移方式时,当前环境中运行的应用是否支持IPv6是重要的考量因素之一,同时在编写新的应用时,需要考虑新编写的应用不仅可以适应当前主流的IPv4环境,

IPv4向IPv6迁移有多种途径,在选择具体的迁移方式时,当前环境中运行的应用是否支持IPv6是重要的考量因素之一,同时在编写新的应用时,需要考虑新编写的应用不仅可以适应当前主流的IPv4环境,还要能运行于IPv6环境c socket。当前主要的ICT基础设施都支持IPv6,但目前仍有众多的客户应用是基于IPv4环境开发的,如果在不修改应用以支持IPv6访问的情形下,所有的迁移方式都难言是彻底的。这篇短文拟对IPv4应用向IPv6应用迁移涉及到的相关内容进行简要介绍,双栈改造的方式有多种,本文以ipv4- mapped ipv6为例,期待对需要了解IPv6应用迁移的读者有所帮助。

2. IPv4-mapped IPv6地址

IPv4-mapped IPv6地址格式是::FFFF:IPv4-address,例如::FFFF:192.168.1.1,运行在双协议栈设备上的IPv6应用利用IPv4-mapped IPv6地址可以和基于IPv4的应用互通c socket

基于IPv6的服务端应用在接收到来自IPv4客户端的连接请求时,内部采用IPv4-mapped IPv6地址来表示此连接c socket

基于IPv6的客户端应用连接IPv4的服务端时c socket,采用IPv4-mapped IPv6的地址访问对端.

IPv4-mapped IPv6地址只用在系统内部,不能设置在网卡上作为IPv6的地址,也不会出现在网络数据包的源或目的IP地址字段中c socket

3. IP双栈环境下应用访问

IP双栈主机同时运行IPv4和IPv6协议c socket,配置IPv4和IPv6地址,可以同时和IPv4以及IPv6设备进行通讯:

IP双栈环境下网络应用迁移

图1 IPv4以及IPv6设备同时进行通讯

双栈环境下的不同的网络客户端应用发送数据包的协议路径:

IP双栈环境下网络应用迁移

展开全文

图2 不同的网络客户端应用发送数据包的协议路径

双栈环境下不同的网络服务端应用接受数据包的协议路径:

IP双栈环境下网络应用迁移

图3 不同的网络服务端应用接受数据包的协议路径

4. IPv4客户访问 IPv4服务

这是当前最常见的访问方式,IPv4的客户端在创建socket时采用的IPv4地址族AF_INET,IPv4服务端的socket也采用IPv4地址族AF_INET,客户端和服务端的网络通信都通过IPv4协议栈,客户端和服务端的通讯采用的源和目的IP是IPv4地址c socket

IP双栈环境下网络应用迁移

图4 IPv4客户访问IPv4服务

5. IPv4客户访问 IPv6服务

留存的客户端网络应用在早期创建socket时采用的地址族为IPv4 地址族AF_INET,但服务端为了适应IPV6环境,经过改造,在创建socket时采用的是IPv6地址族AF_INET6,并且在bind调用时不指定具体的IPv6地址或指定IPv4-mapped IPv6地址(实际应用基本不会采用,技术上可行),那么IPv4客户端可以通过连接对端可通达的IPv4地址来访问服务c socket

客户端主机上发送和接受的网络包通过IPv4协议栈,服务端主机从IPv4协议栈接受到数据包,根据IPv4数据包中的传输层协议发送给TCP或UDP模块处理,TCP/UDP模块查找TCP/UDP协议控制块,找到符合条件的的协议控制块(如目的端口相同以及V6only为0等)来进一步接收/处理数据包,同时在协议栈内部用IPv4-mapped IPv6地址来标记此连接c socket

IP双栈环境下网络应用迁移

图5 IPv4客户访问IPv6服务

下文中的所有python的示例都是基于Centos 7.5c socket,采用的python版本是2.7,示例仅为功能演示,错误处理都已忽略:

创建一个 IPv6 地址族的 socket, 绑定服务监听端口 7788 c socket,等待客户的连接请求,服务端接收 IPv4/v6 客户连接请求并输出接收到的数据,并打印相关的 TCP 连接信息

#python

>>> from socket import *

>>> s=socket(AF_INET6,SOCK_STREAM)

>>> s.bind(("",7788))

>>> s.listen(5)

>>> s1,c1=s.accept

>>> s1.recv(100)

'aaaaaaaaa'

>>> print c1

('::ffff:192.168.100.165', 33178, 0, 0)

>>> from os import *

>>> system("ss -tn|grep 7788")

ESTAB 0 0 ::ffff:192.168.100.128:7788 ::ffff:192.168.100.165:33178

>>>

创建 IPv4 地址族的客户端 , 连接服务器 192.168.100.128 上的服务端口 7788 并发送数据c socket,同时在系统层面用 ss 命令输出 TCP 的连接信息

#python

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.connect(("192.168.100.128",7788))

>>> s.send(b"aaaaaaaaa")

9

>>> from os import *

>>> system("ss -tn|grep 7788")

ESTAB 0 0 192.168.100.165:33178 192.168.100.128:7788

>>>

6. IPv6客户访问 IPv6服务

新开发的网络客户端和服务端应用socket建议采用IPv6地址族的AF_INET6,此类通讯是向IPv6迁移的最终目标,通讯的源和目的地址都是128bit的IPv6地址c socket

缺省情形下IPv6的服务端可以同时接受来自IPv4和IPv6的客户端的连接请求,在只有IPv6的环境下,可以通过setsockopt设置IPv6_V6ONLY为1或设置sysctl的参数net.IPv6.bindv6Only=1(LINUX环境)使服务程序只接受来自IPv6地址族的连接请求c socket。在IPv6的客户端也可以采用同样设置,客户端只能和IPv6的地址进行通讯。

IP双栈环境下网络应用迁移

图6 IPv6客户访问IPv6服务

7. IPv6客户端访问 IPv4服务端

新开发的网络客户端为了适应IPv6环境,采用IPv6地址族AF_INET6的socket,但留存的IPv4的服务端暂时无法改造,仍然采用IPv4地址族AF_INET,由于客户端采用的是IPv6地址族,IPv6的客户端访问IPv4服务端可以利用服务端的IPv4-mapped IPv6 地址方式和IPv4的服务端通讯c socket

IPv6客户端通过本机的IPv4协议栈,数据包头的源和目的网络层地址都是标准的IPv4地址,IPv4服务端无法区分客户端应用是基于IPv4地址族还是基于IPv6地址族c socket

IP双栈环境下网络应用迁移

图7 IPv6客户端访问IPv4服务端

创建 IPv4 地址族的 socket c socket,绑定服务监听端口 8888 ,在此端口上接收客户连接请求并输出接收到的数据以及连接请求客户端的 IP 地址以及 TCP 端口号信息

#python

>>> from socket import *

>>> s=socket(AF_INET,SOCK_STREAM)

>>> s.bind(("",8888))

>>> s.listen(4)

>>> s1,c1=s.accept

>>> s1.recv(100)

'xxxxxxxxx'

>>> print c1

('192.168.100.128', 49314)

>>> from os import *

>>> system("ss -tn")

State Recv-Q Send-Q Local Address:Port Peer Address:Port

ESTAB 0 0 192.168.100.165:8888 192.168.100.128:49314

>>>

创建 IPv6 地址族的 socket 客户端 , 用 IPV4-mapped 地址连接服务端 192.168.100.165 上的服务监听端口 8888 c socket,连接成功后发送数据给服务端,同时在系统层面采用 ss 命令输出 TCP 的连接信息

#python

>>> from socket import *

>>> s=socket(AF_INET6,SOCK_STREAM)

>>> s.connect(("::ffff:192.168.100.165",8888))

>>> s.send("xxxxxxxxx")

>>> from os import *

>>> system("ss -tn")

State Recv-Q Send-Q Local Address:Port Peer Address:Port

ESTAB 0 0 ::ffff:192.168.100.128:49314 ::ffff:192.168.100.165:8888

8. IPv6 V6ONLY应用

基于IPv6地址族的应用缺省情形下可以和基于IPv4以及IPv6地址族的应用互通,设置IPv6_V6ONLY参数可以改变此行为,此参数在HPUX、Linux环境下缺省为0,Windows环境下缺省为1,如果设置为1,那么应用只能和IPv6地址族的应用互通,在一个只有IPv6的网络环境下,可以设置IPv6_V6ONLY为真c socket。下面针对客户端和服务端的应用设置分别说明.

IPv6 地址族的 socket 设置 V6ONLY 选项为真的客户端访问 IPv4 服务端 ( 失败 )

在IPv6的客户端程序中调用setsockopt来设置IPv6_V6ONLY为真,那么此客户端只能和IPv6的服务端进行通讯,不能通过IPv4-mapped IPv6地址的形式和IPv4的服务端进行通讯c socket

IP双栈环境下网络应用迁移

图8 IPv6 V6ONLY应用

IPv4 客户访问 IPv6 V6ONLY 服务端 ( 失败 )

采用IPv6 地址族AF_INET6的socket 的服务端应用缺省能接受来自IPv4和IPv6客户端的连接请求(windows系统下IPv6地址族的应用V6ONLY缺省为真),在只有IPv6的环境下,调用setsockopt设置IPv6_V6ONLY为1来拒绝来自IPv4 的客户端连接请求,只接受来自IPv6 客户端的连接请求c socket

由于服务端收到IPv4客户端的地址是IPv4的,服务端在查找到对应的TCP/UDP协议控制块后,发现协议控制块被标记为V6ONLY,故拒绝连接请求c socket

IP双栈环境下网络应用迁移

图9 IPv6 V6ONLY应用

V6ONLY服务端示例c socket,创建一个基于IPv6地址族的socket,设置新建socket的IPV6_V6ONLY的选项为真,绑定服务端口9999并启动服务监听

# python

>>> from socket import *

>>> from os import *

>>> s=socket(AF_INET6,SOCK_STREAM)

>>> s.setsockopt(IPPROTO_IPV6,IPV6_V6ONLY,1)

>>> s.bind(('',9999))

>>> s.listen(8)

>>> system("ss -6ltne|grep 9999")

>>>

9. 现有应用改造

在具备源代码的情形下,将一个IPv4的服务端程序改造成支持IPv6的程序,改造后的应用能同时接受来自IPv4和IPv6客户端的服务请求,改造主要涉及到的是有关地址结构的变化c socket

以C源码为例c socket,现有的支持IPv4 地址族的服务端代码:

int sock,sock_new;

unsigned short srv-port;

struct sockaddr_in serv,cli;

socklen_t cli_len=soizeof(cli);

sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

memset((&serv,0,sizeof(serv));

serv.sin_family=AF_INET;

serv.sin_len=sizeof(serv);

serv.sin_port=htons(srv-port));

bind(sock,(struct sockaddr*)&serv,sizeof(serv));

listen(sock,5);

sock_new=accept(sock,(struct sockaddr*)&cli,&len);

改造后的基于IPv6地址族的服务程序c socket,可以同时接受来自IPv4和IPv6 客户端的连接请求:

int sock,sock_new;

unsigned short srv-port;

struct sockaddr_in6 serv,cli;

socklen_t cli_len=soizeof(cli);

sock=socket (AF_INET6,SOCK_STREAM,IPPROTO_TCP);

memset((&serv,0,sizeof(serv));

serv.sin6_family=AF_INET6;

serv.sin6_len=sizeof(serv);

serv.sin6_port=htons(srv-port));

bind(sock,(struct sockaddr*)&serv,sizeof(serv));

listen(sock,5);

sock_new=accept(sock,(struct sockaddr*)&cli,&len);

10. IPv6地址族服务端程序设计

在协议迁移的过渡阶段c socket,为了能同时响应来自IPv4和IPv6的客户端应用请求,服务端程序的设计常见有两种方式:

只创建 1 个 IPv6 地址族的 socket 监听

只建立一个AF_INET6地址族的监听socket来同时响应来自IPv4和IPv6客户端的服务请求c socket。当接受到的请求目的地址属于IPv4地址族时,服务器内部采用IPv4-mapped IPv6地址的方式来表示,此连接请求通过IPv4协议栈处理,当接受到的请求目的地址属于IPv6地址族时,连接请求通过IPv6协议栈处理。

Linux上常用的ftp服务器vsftpd采用此类方式实现c socket,从下面的输出可以看出,针对TCP Port 21只有一条基于AF_INET6地址族的监听项,并且V6ONLY选项是0,也即意味着此TCP port 21可以同时接受来自IPv4和IPv6客户端的连接请求:

#ss -6ltnep|grep :21

LISTEN 0 32 :::21:::* users:(( "vsftpd",pid=1290,fd=3)) ino:16150 sk:12 v6only:0 <->

同时创建 1 个 IPv4 socket 监听和 1 个 IPv6 socket 监听

为了避免在内核中采用IPv4-mapped IPv6地址,在同一个服务端程序中分别创建AF_INET和AF_INET6两个地址族的socket,同时监听在相同的端口上,IPv4地址族AF_INET的监听socket负责接受来自IPv4客户端的连接,并且设置IPv6地址族socket的IPv6_V6ONLY属性为真,从而只负责接受来自IPv6客户端的连接,由于有两个处于监听状态的socket, 调用select来处理来自不同socket地址族的连接请求c socket

Linux系统中sshd的是采用此方式实现的c socket,从strace跟踪sshd相关系统调用的输出或查看openssh的源码都可以验证:

socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3

setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0

bind(3, {sa_family=AF_INET, sin_port=htons(22), sin_addr=inet_addr("0.0.0.0")}, 16) = 0

listen(3, 128) = 0

socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 4

setsockopt(4, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0

setsockopt(4, SOL_IPv6, IPv6_V6ONLY, [1], 4) = 0

bind(4, {sa_family=AF_INET6, sin6_port=htons(22), inet_pton(AF_INET6, "::", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = 0

listen(4, 128) = 0

select(5, [3 4], NULL, NULL, NULL)

ss指令输出可以直观的表明其机制:

# ss -ltnep|grep 22

LISTEN 0 128 *:22*:* users:(( "sshd",pid=3236,fd=3)) ino:88651 sk:1f <->

LISTEN 0 128 :::22 :::* users:(( "sshd",pid=3236,fd=4)) ino:88653 sk:20 v6only:1<->

11. IPv6客户端程序设计

IPv6客户端通过指定地址或域名来访问IPv4以及IPv6的服务端,如果服务提供方是IPv4,那么需要用IPv4-mapped IPv6地址的形式去访问c socket。针对域名访问,需要采用DNS实现名称来解析,在IPv4环境下的解析库函数调用有两个:gethostbyname(正向解析)、gethostbyaddr(反向解析),此类解析用于IPv4环境。IPv6环境下则采用getaddrinfo和getnameinfo来解析IPv4和IPv6地址信息,getaddrinfo根据域名/服务名称以及相关hints等信息返回一组A和AAAA记录,每条返回的记录附加有用于创建socket所需的AF_xxx以及SOCK_xxx等信息,客户端程序通常利用循环语句针对getaddrinfo返回地址列表的每一项尝试创建socket以及connect对端,直到发现针对一条地址记录的socket和connect调用都返回成功为止。

客户端只需要知道服务端的域名以及服务端口,采用getaddrinfo调用返回的地址列表信息,就可以创建合适的地址族的socket和服务端连接c socket

Python 的 getaddrinfo 使用示例 :

解析地址的列表
#python

>>> from socket import *

>>>from os import *

>>> from pprint import pprint

>>> list=getaddrinfo("")

>>> pprint(list)

[(2, 1, 6, '', ('23.7.213.221', 80)),

(2, 2, 17, '', ('23.7.213.221', 80)),

(10, 1, 6, '', ('2600:1417:a000:195::1463', 80, 0, 0)),

(10, 2, 17, '', ('2600:1417:a000:195::1463', 80, 0, 0)),

(10, 1, 6, '', ('2600:1417:a000:1b4::1463', 80, 0, 0)),

(10, 2, 17, '', ('2600:1417:a000:1b4::1463', 80, 0, 0))]

>>> list[0][0:3]

(2, 1, 6)

>>> list[0][4]

('23.7.213.221', 80)

连接DNS解析返回列表中的第一项的IPv4地址

c socket

,成功后在系统层面输出TCP连接的四元组信息:
>>> s=socket(*list[0][0:3])

>>> s.connect(list[0][4])

>>> system("ss -tn|grep 80")

ESTAB 0 0 192.168.100.165:39838 23.7.213.221:80 0

尝试用DNS解析返回列表中的第三项IPv6的地址信息来连接地址,连接失败:

>>> s1=socket(*list[2][0:3])
>>> s1.connect(list[2][4])

Traceback (most recent call last):

File "", line 1, in

File "/usr/lib64/python2.7/socket.py", line 224, in meth

return getattr(self._sock,name)(*args)

socket.error: [Errno 101] Network is unreachable

>>>

C 语言的 getaddrinfo 使用示例:

main(int argc, char **argv)

struct addrinfo *res, *ainfo;

struct addrinfo hints;

int error;

struct sockaddr_in6 peeraddr6;

struct sockaddr_in6 addr6;

char connect_addr[INET6_ADDRSTRLEN];

if (argc != 2) {

fprintf(stderr, "Usage: %s \n", argv[0]);

exit(1);

memset ((char *)&hints, 0, sizeof(hints));

hints.ai_socktype = SOCK_STREAM;

error = getaddrinfo(argv[1], ");

if (error != 0)

exit(1);

for (ainfo = res; ainfo != NULL; ainfo = ainfo->ai_next) {

s = socket (ainfo->ai_family,ainfo->ai_socktype,ainfo->ai_protocol);

if (s == -1)

continue;

if (connect(s, ainfo->ai_addr, ainfo->ai_addrlen) == -1)

continue;

else

break;

java,Python等语言对这些细节都做了很好的封装,如python中的socket.create_connection就可以实现以上C代码的所有功能

c socket

12. 结束语
本文对基于不同IP地址族的应用的访问方式以及具体实现的方法进行了简要说明,同时也介绍了如何改造现有IPv4应用以适应新的IPv6环境,介绍了同时适用于IPv4以及IPv6环境的应用的方法

c socket

。IPv6应用迁移是未来应用迁移发展的方向,希望本文内容对IPv6应用迁移感兴趣的读者有所帮助。



推荐阅读
  • 我的读书清单(持续更新)201705311.《一千零一夜》2006(四五年级)2.《中华上下五千年》2008(初一)3.《鲁滨孙漂流记》2008(初二)4.《钢铁是怎样炼成的》20 ... [详细]
  • Web动态服务器Python基本实现
    Web动态服务器Python基本实现 ... [详细]
  • linux网络子系统分析(二)—— 协议栈分层框架的建立
    目录一、综述二、INET的初始化2.1INET接口注册2.2抽象实体的建立2.3代码细节分析2.3.1socket参数三、其他协议3.1PF_PACKET3.2P ... [详细]
  • 二维码的实现与应用
    本文介绍了二维码的基本概念、分类及其优缺点,并详细描述了如何使用Java编程语言结合第三方库(如ZXing和qrcode.jar)来实现二维码的生成与解析。 ... [详细]
  • JUnit下的测试和suite
    nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd ... [详细]
  • 本文深入探讨了Go语言中的接口型函数,通过实例分析其灵活性和强大功能,帮助开发者更好地理解和运用这一特性。 ... [详细]
  • AI炼金术:KNN分类器的构建与应用
    本文介绍了如何使用Python及其相关库(如NumPy、scikit-learn和matplotlib)构建KNN分类器模型。通过详细的数据准备、模型训练及新样本预测的过程,展示KNN算法的实际操作步骤。 ... [详细]
  • 本文介绍了如何通过C#语言调用动态链接库(DLL)中的函数来实现IC卡的基本操作,包括初始化设备、设置密码模式、获取设备状态等,并详细展示了将TextBox中的数据写入IC卡的具体实现方法。 ... [详细]
  • OBS Studio自动化实践:利用脚本批量生成录制场景
    本文探讨了如何利用OBS Studio进行高效录屏,并通过脚本实现场景的自动生成。适合对自动化办公感兴趣的读者。 ... [详细]
  • 问题场景用Java进行web开发过程当中,当遇到很多很多个字段的实体时,最苦恼的莫过于编辑字段的查看和修改界面,发现2个页面存在很多重复信息,能不能写一遍?有没有轮子用都不如自己造。解决方式笔者根据自 ... [详细]
  • spring boot使用jetty无法启动 ... [详细]
  • 在OpenCV 3.1.0中实现SIFT与SURF特征检测
    本文介绍如何在OpenCV 3.1.0版本中通过Python 2.7环境使用SIFT和SURF算法进行图像特征点检测。由于这些高级功能在OpenCV 3.0.0及更高版本中被移至额外的contrib模块,因此需要特别处理才能正常使用。 ... [详细]
  • Jenkins API当前未直接提供获取任务构建队列长度的功能,因此需要通过解析HTML页面来间接实现这一需求。 ... [详细]
  • 本文介绍了一种方法,通过使用Python的ctypes库来调用C++代码。具体实例为实现一个简单的加法器,并详细说明了从编写C++代码到编译及最终在Python中调用的全过程。 ... [详细]
  • 高级缩放示例.就像谷歌地图一样.它仅缩放图块,但不缩放整个图像.因此,缩放的瓷砖占据了恒定的记忆,并且不会为大型缩放图像调整大小的图像.对于简化的缩放示例lookhere.在Win ... [详细]
author-avatar
肉斯情-
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有