github仓库:https://github.com/brucewayne9064/LinuxCpp_server

Raw socket:在传输层下面使用的套接字,可以在链路层收发原始数据帧,在用户空间完成MAC上各个层次的实现

一、原始套接字的功能

  • 收发ICMPv4、ICMPv6、IGMP数据报
  • 设置IP报头
  • 收发内核不处理的IPv4数据报
  • 让网卡处于混杂模式,从而捕获流经网卡的所有数据报(制作网络嗅探器)

二、创建原始套接字的方式

创建原始套接字的方式有以下几种:

  • 使用socket()函数,第一个参数设置为PF_PACKET,第二个参数设置为SOCK_RAW,第三个参数设置为htons(protocol),其中protocol是指定可以接收或发送的数据包类型,如ETH_P_IP、ETH_P_ARP、ETH_P_ALL等。这种方式可以创建链路层的原始套接字,可以接收或发送本机网卡上所有的数据帧。
  • 使用socket()函数,第一个参数设置为AF_INET,第二个参数设置为SOCK_RAW,第三个参数设置为protocol,其中protocol是指定IP协议类型,如IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMP等。这种方式可以创建IP层的原始套接字,可以接收或发送本机收发的IP包。
  • 使用socket()函数,第一个参数设置为AF_INET6,第二个参数设置为SOCK_RAW,第三个参数设置为protocol,其中protocol是指定IPv6协议类型,如IPPROTO_TCP、IPPROTO_UDP、IPPROTO_ICMPV6等。这种方式可以创建IPv6层的原始套接字,可以接收或发送本机收发的IPv6包。

原始套接字的使用需要注意以下几点:

  • 原始套接字需要root权限或CAP_NET_RAW能力才能创建。
  • 原始套接字发送数据时需要自己构造协议头部,如以太网头、IP头、TCP头等。
  • 原始套接字接收数据时需要自己解析协议头部,如以太网头、IP头、TCP头等。
  • 原始套接字可以用于实现高级网络编程,如抓包、分析、欺骗等。

三、原始套接字的基本编程步骤

1
2
3
+----------+-----------+----------+----------+
| 链路层报头 | 网络层报头 | 传输层报头 | 应用层数据 |
+----------+-----------+----------+----------+

其中,应用层数据是用户数据部分,传输层报头包括TCP报头或UDP报头,网络层报头包括IP报头,链路层报头包括以太网帧头。

  • 如果您使用流套接字(SOCK_STREAM)或数据报套接字(SOCK_DGRAM),您只能看到用户数据部分,而无法查看任何报头。

  • 如果您使用原始套接字 sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP),则表示您可以查看接收到的数据的IP报头内容和UDP报头内容。

  • 如果您使用原始套接字 sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)),则您可以查看接收到的数据的链路层报头内容,包括以太网帧头、网络层报头(如IP报头)和传输层报头(如TCP报头)。

四、AF_INET方式捕获报文

在接收端程序中,saddr.sin_port = htons(8888); 这一行代码指定了监听套接字绑定的端口号。这里的端口号是指定监听套接字监听哪个端口号。如果使用了原始套接字(SOCK_RAW),这意味着你可以接收到所有目标地址为本机地址的UDP数据包,而不管目标端口号是什么。因此,在这种情况下,绑定的端口号并不重要。

当你使用非原始套接字(例如SOCK_DGRAM)时,监听套接字的端口号必须和发送数据时使用的目标端口号一样。这样,监听套接字才能接收到发送到指定端口号的数据包。

例如,如果你在发送端使用sendto函数发送数据,并指定目标端口号为8888,那么在接收端,你需要创建一个非原始套接字,并将其绑定到端口号8888,才能接收到发送的数据包。

五、PF_PACKET方式捕获报文