第12章 IPv4和IPv6的互操作性
12.1 概述
未来,因特网会逐渐从IPv4
转换到IPv6
所以应用程序能够对两种协议协同工作是很重要的,我们将讨论IPv4应用
如何与IPv6
进行通信
12.2 IPv4 客户与 IPv6 服务器
- 双栈主机一个很重要的特性就是技能处理
IPv4
又能处理IPv6
客户。这是通过使用IPv4
映射的IPv6
地址来实现的
- 主机使用
IPv6
服务器,收到IPv4
的帧时,会将源IPv4地址
转换为一个映射的IPv6地址
IPv4 地址
可以转换为一个唯一的IPv6 地址
(IPv4地址
补齐到IPv6地址
格式, 方式是在前补::ffff
)
- 然后把数据报相应的字段给替换掉(包括
IP版本标识
字段)
- 补充 IPv6 地址格式
IPv6
中的双冒号:
- 通过使用双冒号
(::)
替换一系列零来指定 IPv6 地址
, 一个IP 地址
中只可使用一次双冒号。
- 例如:
IPv6
地址: ff06:0:0:0:0:0:0:c3
可写作 ff06::c3
。
IPv4
映射的IPv6
地址使用此替代格式。
- 此类型的地址用于将
IPv4
节点表示为IPv6
地址。
- 例如:
IPv4
地址192.1.56.10
映射为IPv6
地址0:0:0:0:0:ffff:192.1.56.10
和 ::ffff:192.1.56.10/96
(短格式)
- 注意
IPv6地址
不能直接转IPv4地址
- 一个
IPv4客户端
和IPv6服务端
通信地步骤如下:
IPv6服务器
启动后创建一个IPv6监听套接字
IPv4客户
调用getsockname
找到服务器地A记录
。服务器既有A也有AAAA记录
,因为它是双栈的
- 客户调用
connect
,发送一个IPv4的SYN
给服务器
- 服务器收到这个
SYN
,把它标志为IPv4
映射为IPv6
,响应一个IPv4 SYN/ACK
。连接建立后, 由accept
返回给服务器的地址就是这个IPv4
映射的IPv6地址
- 当服务器向这个客户端发送数据时,会使用客户端的
IPv4地址
,所以通信使用的全部都是IPv4连接
- 如果服务器不检查这个地址是
IPv6
还是IPv4
映射过来,它永远不会知道客户端的 IP 类型
,客户端也不需要知道服务器的类型
- 大多数双栈主机遵循以下规则:
IPv4 监听套接字
只能接受来自IPv4 客户
的外来连接
- 如果服务器有一个绑定了
IPv6 的监听套接字
,该套接字没设置 IPV6_V6ONLY
套接字选项,它可以接收 IPv4 连接
- 如果服务器有一个
IPv6 监听套接字
,绑定了通配地址,该套接字设置了 IPV6_V6ONLY
套接字选项,它只能接收 IPv6 连接
IPv6
UDP
服务器的情况与之类是, 差别在于数据报的地址格式有所不同.例如IPv6服务器
收到来自某个IPv4客户
的数据报,recvfrom
返回的地址将是该客户端的IPv6地址
(由于IPv4
映射而来)
12.3 IPv6
客户与IPv4
服务器
- 客户机运行在双栈主机上并使用
IPv6套接字描述符
- 过程:
- 一个
IPv4 服务器
在只支持IPv4
的主机上启动一个IPv4 监听套接字
IPv6 客户
启动后调用getaddrinfo
单纯查找IPv6
的地址,因为服务器只支持IPv4
,所以返回给客户端的是一个IPv4
映射的IPv6地址
IPv6 客户
在作为函数参数的 IPv6 套接字地址结构
中设置这个 IPv4 映射的 IPv6 地址
然后调用 connect
。内核检测到这个映射地址后自动发送一个IPv4 SYN
到服务器
- 服务器响应一个
IPv4 SYN/ACK
,连接于是通过使用IPv4 数据报
建立
12.2-12.3 互操作性总结
IPv4客户端
与IPv6服务器(双栈)
套接字
接受数据报
分析:
- 对于
IPv4套接字
它只能接受IPv4数据报
(IPv6地址
不能映射为IPv4地址
)
- 对于
IPv6套接字(带双栈)
它可以接受IPv4数据报
和IPv6数据报
(IPv4地址
可以被唯一映射为IPv6地址
)
- 具体分析: 如果目的地为
IPv6套接字
的IPv4数据报
,那么内核(目的地内核
)把该数据包的源IPv4地址
映射为IPv6地址
作为accept(TCP)
或recvfrom(UDP)
返回的对端IP地址
IPv4服务器
与IPv6客户端(双栈)
套接字
发送数据报
分析:
IPv4数据报
可以接受IPv4套接字
和IPv6套接字
的数据发送(IPv6套接字
的目的地为由IPv4
地址映射的IPv6地址
是内核将其转变为IPv4数据报
)
IPv6数据报
只能接受IPv6套接字
的数据发送
IPv4套接字
不能发送一个IPv6数据报
,因为不可能往IPv4套接字
上设置IPv6地址
,毕竟IPv4套接字
接受的sockaddr_in的in_addr成员
只有4字节
的长度
- 当
IPv6套接字
发送数据时,内核检测到目的IP地址
为由IPv4地址
映射的IPv6地址
,所以此地址
转换为IPv4地址
发送IPv4数据报
- 互操作性总结
IPv6
地址测试宏
- 有一些 IPv6 应用必须知道和它通信的是 IPv6 还是 IPv4 协议,使用 <netinet/in.h> 中的函数可以进行测试
int IN6_IS_ADDR_V4MAPPED(const struct in6_addr *aptr)宏测试IPv6地址是否由IPv4映射而来
源代码可移植性
- 考虑到源码的可移植性,编写代码时应尽量避免
gethostbyname
, gethostbyaddr
等套接字函数,使用 getaddrinfo
, getnameinfo
等函数,使得代码变得与协议无关。
小结
- 双栈主机的
IPv6 服务器
可以和两种客户端进行通讯, 对于IPv4客户端
使用IPv4数据报
进行通信
- 双栈主机的
IPv6 客户端
也可以和两种服务器进行通讯, 对于IPv4服务器
使用IPv4数据报
进行通信
第13章:守护进程和inetd
超级服务器
T:2019/10/28 W:1 14:10:40
13.1 概述
- 守护进程:是在后台运行且不与任何控制终端关联的进程。
Unix
系统通常有很多守护进程在后台运行(通常为20~50个的数量级)执行不同的管理任务.
- 守护进程没有终端,通常是因为他们由开机时的脚本进行启动。守护进程也可能从某个终端由用户在
shell
提示符下键入命令行进行启动,这样的守护进程必须亲自脱离与控制终端的关联,从而避免与 作业控制终端
, 会话管理
,终端产生信号
等发生不希望的交互,也防止
后台的守护进程输出到终端
- 守护进程的启动方式:
- 在系统阶段进行启动,许多守护进程由系统初始化脚本进行启动,脚本通常位于
/etc
等目录,这些脚本启动的守护进程开始就拥有超级用户权限(inetd
,Web
,sendmail
,syslogd
等等)
- 许多网络服务器由
inetd
超级服务器(其本身由第一条启动) 进行启动。Inetd
监听网络请求,每当有一个请求到达,启动相应的实际服务器(Telnet,FTP…)
cron
守护进程(其本身由第一条启动) 按规则定期执行一些程序,这些程序同样作为守护继承运行(单词cron,计时程序
)
at 命令
用于指定将来某个时刻的程序执行,时间到达时,通常使用corn
来进行执行
- 守护进程还可以从用户的终端在前台或者后台进行启动。这么做往往是测试守护进程或者重启关闭的守护进程。
- 因为守护进程没有终端,所以他们的消息使用
syslog
进行处理,即使用 syslog
函数,将消息发送给 syslogd
进程
13.2 syslodg
守护进程
syslogd
守护进程通常由系统初始化脚本进行启动,并在系统工作时间一直运行,启动步骤如下:
- 读取配置文件,在
/etc/syslog.conf
配置文件指定守护进程收取的各种日志消息应如何处理。可能添加到一个文件中,或被写到用户的登录窗口,或被转发给另一个主机上的 syslogd
进程
- 创建
Unix 域数据报套接字
,给它捆绑路径名 /var/run/log
- 创建
UDP 套接字
,捆绑 514 端口
,接收别的主机发送过来的日志
- 打开路径名
/dev/klog
。来自内核的任何出错消息从这个设备输入
syslog
使用 select
来监听上面 2,3,4
步的描述符来接受日志,并按照配置文件进行处理。如果守护进程读取 SIGHUP 信号
,就重新读取配置文件
- 最新的系统不建议开启 514 端口,会遭到攻击
13.3 syslog
- 守护进程没有终端,所以不能把消息
fprintf
到 stderr
上。从守护进程中登记消息的常用技巧是调用 syslog
函数
logger
命令在 shell 脚本
中以向 syslogd
发送消息
#include <syslog.h>
void syslog(int priority, const char * message, ...);
- 参数解析:
priority
:级别(level
)和设施(facility
)两者的组合体
message
:类似 printf
格式串,增加了 %m
规范代表当前 errno
值
- 当
syslog
被应用进程首次调用时,它创建一个 Unix 域数据报套接字
,然后调用 connect
连接到由 syslogd
守护进程创建的 Unix 域数据报套接字的众所周知的路径名
。这个套接字一直打开,直到进程终止关闭. 可以在syslog
使用前调用openlog
,在不需要发送日志时,调用closelog
(注意openlog
并不会立即创建套截止,除非指定NDELAY
选项)
void openlog(const char *ident, int options, int facility);
void closelog(void);
- 参数
ident
是一个由syslog
冠名的每个日志消息之前的字符串,通常是程序的名字
options
有多和常值的逻辑构成
optiions |
说明 |
LOG_CONS |
若无法发送到syslogd守护进程则登记到控制台 |
LOG_NDELAY |
不延迟打开,立即创建套截止 |
LOG_PERROR |
即发送到syslogd守护进程,又发送到标准错误输出 |
LOG_PID |
随每个日志消息登记进程ID |
level |
值 |
说明 |
LOG_EMERG |
0 |
系统不可用(最高优先级) |
LOG_ALERT |
1 |
必须立即采取行动 |
LOG_CRIT |
2 |
临界条件 |
LOG_ERR |
3 |
出错条件 |
LOG_WARNING |
4 |
警告条件 |
LOG_NOTICE |
5 |
正常然而重要的条件(默认值) |
LOG_INFO |
6 |
通告消息 |
LOG_DEBUG |
7 |
调试级消息(最低优先级) |
facility |
说明 |
LOG_AUTH |
安全/授权消息 |
LOG_AUTHPRIV |
安全/授权消息(私用) |
LOG_CRON |
cron守护进程 |
LOG_DAEMON |
西东守护进程 |
LOG_FTP |
FTP守护进程 |
LOG_KERN |
内核消息 |
LOG_LOCAL0 |
本地消息 |
LOG_LOCAL1 |
本地消息 |
LOG_LOCAL2 |
本地消息 |
LOG_LOCAL3 |
本地消息 |
LOG_LOCAL4 |
本地消息 |
LOG_LOCAL5 |
本地消息 |
LOG_LOCAL6 |
本地消息 |
LOG_LOCAL7 |
本地消息 |
LOG_LPR |
行式打印机系统 |
LOG_MAIL |
邮件系统 |
LOG_NEWS |
网络新闻系统 |
LOG_SYSLOG |
由syslogd内部产生的消息 |
LOG_USER |
任意的用户级消息(默认) |
LOG_UUCP |
UUCP系统 |
- 例如,当
rename
函数调用失败时,守护进程执行以下调用:
syslog(LOG_INFO|LOG_LOCAL2, "RENAME(%s,%s): %m", file1, file2);
13.4 daemon_init
- 编写一个守护进程的创建函数,有些系统(如
Linux
)提供 daemon
函数用来创建守护进程,和本程序类似
- 守护进程在没有终端的环境下运行,不会接收
SIGHUP
信号。许多守护进程把这个信号可以当作系统发送的通知,表示配置文件发送了变化,应重新读取配置文件,类似的还有 SIGINT SINGWINCH
信号
#include "unp.h"
#include <syslog.h>
#define MAXFD 64
extern int daemon_proc;
int daemon_init(const char *pname, int facility){
int i;
pid_t pid;
if ( (pid = Fork()) < 0)
return (-1);
else if (pid)
_exit(0);
if (setsid() < 0)
return (-1);