天天看点

91-接收 IP 数据报

本文我们需要用到一种新的类型的套接字 —— Raw Sockets,原始套接字。如此重要的知识点,没有出现在大标题中,实在很抱歉。

它出现的理由很简单,我们可以自己构造一个完整的 IP 数据报,通过原始套接字发送出去。也可以从原始套接字中读取一个完整的 IP 数据报。

1. 创建原始套接字

// 创建一个 IPv4 原始套接字
sockfd = socket(AF_INET, SOCK_RAW, protocol);      
注意:只有 root 权限的用户才能创建原始套接字

第三个参数 protocol 在上一节讲解 IP 协议的时候讲过,它是批 IP 数据报中的 8 位协议字段。

2. 实验

  • 程序路径

如果你已经 clone 过这个代码了,请使用 ​

​git pull​

​​ 更新一下。本节程序所使用的程序路径是 ​

​unp/program/icmp/ip​

​.

  • 程序说明

程序 ip 启动后,从网卡读取指定的 protocol 协议的 ip 数据报,并将各个字段打印在屏幕上,最后会打印数据部分。

程序 ip 可以从传入一个参数,表示想接收什么协议类型的 ip 数据报,默认情况下 protocol 的值是 IPPROTO_ICMP,也就是 1 (最好记住它!),表示只接收承载 ICMP 协议的 ip 数据报。

比如你可以执行:

$ ./ip 6      

表示只接收承载 TCP 协议的 ip 数据报。

2.1 关键代码

#include "common.h"

int main(int argc, char* argv[]) {
  struct sockaddr_in from;
  // struct ip 结构体定义在 include/ip.h 中,上一篇文章已经讲过了。
  struct ip *ip;
  socklen_t len;
  int nr, ret, sockfd, protocol;

  // IPPROTO_ICMP 被定义为 1
  protocol = IPPROTO_ICMP;

  if (argc > 1) {
    // 由命令行传入,表示进程想接收什么协议的数据//
    // 也就是说,希望接收 ip 首部中的协议号为 protocol 的协议
    protocol = atoi(argv[1]);
  }

  // 创建原始套接字
  sockfd = socket(AF_INET, SOCK_RAW, protocol);
  if (sockfd < 0) ERR_EXIT("socket");

  for (;;) {
    len = sizeof(from);
    // 使用 recvfrom 接收 ip 数据报,它会将网卡上所有符合要求的 ip 数据报读取到 buf 中。recvfrom 的最后两个参数可以是 NULL.
    nr = recvfrom(sockfd, buf, BUF_SIZE, 0, (struct sockaddr*)&from, &len);
    if (nr < 0) {
      ERR_EXIT("recvfrom");
    }

    // 打印 from 接收到的地址,它肯定和 ip 数据报首部中的源 ip 地址相同。
    WARNING("from: %s\n", inet_ntoa(from.sin_addr));

    // 打印 ip 首部和数据部分
    // buf 是 ip 首部开始第一个字节的地址
    ip = (struct ip*)buf;
    // 函数 printIp 打印 ip 数据报的各个字段。      

2.2 运行示例

2.2.1 接收承载 TCP 协议的数据报

在运行的时候,需要以 root 权限运行,因此这里加上了 sudo 命令。打印结果时,注意观察 ip 首部的 protocol 字段。

91-接收 IP 数据报

图1 接收到的 IP 数据报

因为承载的是 TCP 协议,我们可以看绿色部分,按照 TCP 首部可以分析出源端口号是 0x3703,即 14083。目的端口号是 0x0016,即 22 号端口(SSH 服务的端口)。

91-接收 IP 数据报

图2 简单分析 TCP 首部端口号,印证结果是正确的

2.2.2 接收承载 ICMP 协议的数据报

相信大家都使用过 ping 命令,ping 命令的原理就是利用 icmp 协议。因此可以使用 ping 命令来“制造”icmp 数据包。

打印结果时,注意观察 ip 首部的 protocol 字段。

91-接收 IP 数据报

图3 在 flower 主机上 ping sun 主机,发两个数据包

91-接收 IP 数据报

图4 sun 主机收到两个承载 icmp 协议的数据包

91-接收 IP 数据报

图5 tcpdump 输出

注意 tcpdump 输出的那个 id 不是 ip 首部字段中的 id,而是 icmp 首部中的 id.

根据图 4 中的结果分析,可以看到第一个 icmp 包的 id 号为 0x3dfa,即 15866,与 tcpdump 一致。

3. 总结

  • 掌握创建原始套接字的方法
  • 掌握读取 ip 数据报的方法

继续阅读