在网络通信中,需要用到ip加port,但是ip并不方便记忆,于是我们常用域名来对应一个ip
例如:www.baidu.com 对应 156.36.56.98(随便写的)
com: 一级域名. 表示这是一个企业域名. 同级的还有 "net"(网络提供商), "org"(非盈利组织) 等.
baidu: 二级域名, 公司名.
www: 只是一种习惯用法.
域名解析分为两步
1.查本地 /etc/hosts
互连网信息中心(SRI-NIC)会管理一个 hosts 文件,本地主机只需要定期下载即可,里面就是域名和ip的对应
2.使用DNS技术
如果/etc/hosts找不到,那就去向本地DNS服务器发送请求进行查询,本地DNS服务器通常以守护进程形式存在
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type(0) | Code(0) | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data (可变长度,通常包含时间戳或填充字节) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
ping www.baidu.com
ping命令过程分析:
在终端上写下了这几个字母,随着按下回车,终端模拟器将这几个字母write进主设备,然后驱动程序将主设备数据写进从设备,唤醒从设备等待队列上的进程也就是shell,然后shell读出来,分割字符串,然后创建子进程,进程替换为ping进程,域名通过命令行参数传给了ping进程,然后ping进程去/etc/hosts文件里面查找域名对应ip,如果找不到就向本地DNS守护进程发消息,然后DNS进程开始询问服务器,最后得到对应ip。ping进程接下来的操作如下:
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sock < 0) {
perror("socket"); // 需 root 权限或 CAP_NET_RAW 能力
exit(1);
}
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type(8) | Code(0) | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identifier | Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Payload (可选) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Echo Request
。struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_addr.s_addr = inet_addr("93.184.216.34"); // 目标 IP
// 发送 ICMP 报文
sendto(sock, icmp_packet, sizeof(icmp_packet), 0,
(struct sockaddr*)&dest_addr, sizeof(dest_addr));
char recv_buf[1024];
struct sockaddr_in src_addr;
socklen_t addr_len = sizeof(src_addr);
// 阻塞等待响应(内核通过 Identifier 匹配报文)
recvfrom(sock, recv_buf, sizeof(recv_buf), 0,
(struct sockaddr*)&src_addr, &addr_len);
ping进程在应用层创建好原始套接字,并且创建好ICMP报文,然后用sendto发送,然后经过ip层和数据链路层封装后发出去,当目标主机收到后,经数据链路层和ip层的解包,交给了ICMP协议的接口,检查ICMP头里的类型,发现是请求,那就构建对应的ICMP响应,然后经IP协议和以太网协议的封装又发回去,源主机收到后开始解包,到ICMP协议层时,根据OS维护的hash表,通过响应里的identifier标识符,找到对应的套接字,然后将ICMP报文写进套接字接收缓冲区,并唤醒等待队列上的进程,ping进程recvfrom从接收缓冲区里读出来完整的ICMP报文,然后根据ICMP报文的内容,write写东西到从设备,终端驱动将从设备数据拷到主设备,然后唤醒终端模拟器,终端模拟器再将东西打到终端上,这就是一次发送数据测试,ping进程会不停发送echo request,并设置序列号来区分这些请求3
ping
命令的 ICMP 请求与响应全链路解析ping
进程发送 ICMP Echo Request创建原始套接字
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
CAP_NET_RAW
(/bin/ping
已默认配置)。构造 ICMP Echo Request
Type=8 (Echo Request), Code=0
Identifier=进程PID, Sequence=递增序号
Payload=时间戳或填充数据
发送请求
sendto(sock, icmp_req, sizeof(icmp_req), 0, &dest_addr, addr_len);
IP 层封装
1
(ICMP)。数据链路层封装
0x0800
(IPv4)。eth0
)发送到物理链路。网卡(数据链路层)
IP 层解包
ICMP Time Exceeded
(Type=11)。1,
交给 ICMP 模块。解析 ICMP 报文
Type=8
(Echo Request),Code=0
。Echo Reply
(Type=0),保持相同的 Identifier
和 Sequence
。发送响应
网卡收包
ICMP 协议匹配
Identifier
(如 PID 1234
)查找原始套接字。ping
进程从阻塞中恢复
ping
进程此前因 recvfrom()
阻塞,被内核移至就绪队列。Echo Reply
数据。计算 RTT(往返时间)
ping
进程格式化输出
64 bytes from 93.184.216.34: icmp_seq=1 ttl=53 time=11.3 ms
ttl
:从响应 IP 头部提取。time
:RTT 计算结果。终端显示流程
ping
调用 write()
将结果写入标准输出(文件描述符 1
)。tty
)将数据从 从设备(进程缓冲区)拷贝到 主设备(终端显示器)。xterm
)渲染最终字符。1.ping
超时:内核未收到 Echo Reply
,recvfrom()
超时后停止等待(等待默认 1 秒),靠sendto重发请求,也就是说ICMP协议是没有超时重传的功能的,真正能使其超时重传的是ping进程逻辑
2.ping发送请求到目标主机,目标主机通过硬件中断的协议栈就处理了该请求,根本就没涉及到进程和应用层逻辑
3.ping进程本身处理响应却是寄托协议栈加上进程和应用层逻辑,还使用了套接字