91-接收 IP 数据报

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

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

1. 创建原始套接字

// 创建一个 IPv4 原始套接字
sockfd = socket(AF_INET, SOCK_RAW, protocol);

注意:只有 root 权限的用户才能创建原始套接字

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

2. 实验

  • 程序路径

本文使用的程序托管在 gitos 上:http://git.oschina.net/ivan_allen/unp

如果你已经 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 数据报的各个字段。
    printIp(ip, nr);
  }
}

2.2 运行示例

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

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


这里写图片描述
图1 接收到的 IP 数据报

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


这里写图片描述
图2 简单分析 TCP 首部端口号,印证结果是正确的

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

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

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


这里写图片描述
图3 在 flower 主机上 ping sun 主机,发两个数据包


这里写图片描述
图4 sun 主机收到两个承载 icmp 协议的数据包


这里写图片描述
图5 tcpdump 输出

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

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

3. 总结

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

练习1:打开文件 /usr/include/netinet/in.h(有些同学的机器文件可能是在 usr/local/include/netinet/in.h'),查看该文件定义了哪些协议。

练习2:使用我们的 ip 程序进行测试打印你在 in.h 看到的协议。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值