文章目录
- 1. ARP协议
- 1.1 ARP协议的定义
- 1.2 ARP协议的工作过程
- 1.3 ARP协议的数据结构
- 1.4 ARP欺骗
- 1.5 ARP协议的局限性
- 1.6 ARP协议与Posix API的关系
- 2. ARP协议例子
- 3. ICMP协议
- 3.1 ICMP协议的定义
- 3.2 ICMP协议的类型
- 3.3 ICMP协议的工作原理
- 3.4 ICMP协议的应用
- 3.5 ICMP协议的局限性和安全性
- 3.6 ICMP协议与Posix API的关系
- 4. ICMP协议例子
- 5. 用户态网络协议栈
- 5.1 用户态网络协议栈的定义
- 5.2 用户态网络协议栈的优点
- 5.3 用户态网络协议栈的缺点
- 5.4 用户态网络协议栈的例子
- 5.5 用户态网络协议栈与Posix API的关
- 6. TCP协议演示
- 6.1 TCP协议的基础知识
- 6.2 TCP的三次握手
- 6.3 TCP的四次挥手
- 6.4 TCP协议与Posix API的关系
- 7. TCP协议栈实现
- 7.1 TCP协议栈的层级结构
- 7.2 TCP协议栈的主要模块
- 7.3 TCP协议栈的数据流
- 7.4 TCP协议栈与Posix API的关系
1. ARP协议
1.1 ARP协议的定义
ARP协议,即地址解析协议(Address Resolution Protocol),是一个在局域网中实现IP地址到MAC地址转换的协议。ARP是工作在OSI模型的数据链路层和网络层之间的一种协议。
1.2 ARP协议的工作过程
ARP的工作过程主要包括四个步骤:ARP请求、ARP应答、ARP缓存、ARP刷新。
- ARP请求:一台主机向局域网内广播一个ARP请求,请求包含发送主机的IP地址和MAC地址,以及目标主机的IP地址,但不包含目标主机的MAC地址。
- ARP应答:所有收到ARP请求的主机都会检查自己的IP地址与ARP请求中的目标IP地址是否相同。如果相同,则该主机会发送ARP应答,该应答包含自己的IP地址和MAC地址。
- ARP缓存:发送ARP请求的主机会保存收到的ARP应答中的IP地址和MAC地址映射信息,这个保存过程叫做ARP缓存。这样,下次需要发送数据时,就可以直接从ARP缓存中查找目标MAC地址,而不用再次发出ARP请求。
- ARP刷新:由于网络状况可能发生变化,ARP缓存的有效期通常不会很长,一段时间后,ARP缓存中的映射关系需要被刷新。
1.3 ARP协议的数据结构
ARP报文主要由硬件类型、协议类型、硬件地址长度、协议地址长度、操作字段、发送者硬件地址、发送者协议地址、目标硬件地址、目标协议地址等部分组成。特别是操作字段,它用来标识ARP报文的类型,如果值为1,则表示这是一个ARP请求;如果值为2,则表示这是一个ARP应答。
1.4 ARP欺骗
ARP欺骗(ARP Spoofing)是一种网络攻击手段,攻击者通过发送伪造的ARP应答,使得其他主机的ARP缓存中保存了错误的IP地址和MAC地址映射信息,从而实现了对网络数据的劫持或者篡改。
1.5 ARP协议的局限性
ARP协议虽然简洁易用,但也有一些局限性。例如,ARP协议只能用于IPv4网络,对于IPv6网络,ARP协议已经被NDP(Neighbor Discovery Protocol)所取代。此外,由于ARP协议的安全性较低,容易受到ARP欺骗的攻击,所以在一些需要高安全性的网络中,可能需要采用其他方式来获取MAC地址。
1.6 ARP协议与Posix API的关系
在Posix API中,并没有直接操作ARP协议的接口,通常ARP协议的操作是由操作系统的网络栈自动完成的。但是,通过使用raw socket,我们可以直接操作数据链路层的数据,从而可以发送和接收ARP报文。同时,我们也可以通过ioctl函数获取和设置ARP缓存的内容。
2. ARP协议例子
下面是一个简单的ARP请求和响应的例子:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/if_ether.h>
#include <net/if.h>
#include <netpacket/packet.h>
#define ARP_REQUEST 1
#define ARP_REPLY 2
typedef struct arp_header {
uint16_t hardware_type;
uint16_t protocol_type;
uint8_t hardware_len;
uint8_t protocol_len;
uint16_t opcode;
uint8_t sender_mac[6];
uint8_t sender_ip[4];
uint8_t target_mac[6];
uint8_t target_ip[4];
} arp_header_t;
void fill_arp_request(arp_header_t *arp) {
arp->hardware_type = htons(1);
arp->protocol_type = htons(ETH_P_IP);
arp->hardware_len = 6;
arp->protocol_len = 4;
arp->opcode = htons(ARP_REQUEST);
//填写你的MAC地址和IP地址
memcpy(arp->sender_mac, "\xaa\xbb\xcc\xdd\xee\xff", 6);
memcpy(arp->sender_ip, "\xc0\xa8\x01\x0a", 4); // 192.168.1.10
//填写目标MAC地址和IP地址
memcpy(arp->target_mac, "\x00\x00\x00\x00\x00\x00", 6);
memcpy(arp->target_ip, "\xc0\xa8\x01\x14", 4); // 192.168.1.20
}
int main() {
int sd;
char buffer[60];
struct sockaddr_ll socket_address;
//建立原始套接字
sd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP));
if (sd == -1) {
perror("socket creation failed");
return -1;
}
//清空缓冲区和套接字地址
memset(buffer, 0x00, 60);
memset(&socket_address, 0x00, sizeof(socket_address));
//填充Ethernet帧头部
struct ethhdr *eth = (struct ethhdr *)buffer;
eth->h_proto = htons(ETH_P_ARP);
//填写你的MAC地址
memcpy(eth->h_source, "\xaa\xbb\xcc\xdd\xee\xff", 6);
//填写目标MAC地址
memcpy(eth->h_dest, "\xff\xff\xff\xff\xff\xff", 6);
//填充ARP请求
arp_header_t *arp = (arp_header_t *)(buffer + 14);
fill_arp_request(arp);
//设置目标地址
socket_address.sll_family = AF_PACKET;
socket_address.sll_protocol = htons(ETH_P_ARP);
socket_address.sll_ifindex = if_nametoindex("eth0");
//发送ARP请求
if (sendto(sd, buffer, 42, 0, (struct sockaddr*)&socket_address, sizeof(socket_address)) == -1) {
perror("send failed");
return -1;
}
return 0;
}
上面的程序首先创建了一个原始套接字,然后填充了一个Ethernet帧和ARP请求,最后将其发送到网络中。这是一个非常简单的ARP请求,用于查询给定IP地址(192.168.1.20)对应的MAC地址。
在ARP头部中,hardware_type
字段指的是硬件类型,protocol_type
字段指的是协议类型,hardware_len
字段和protocol_len
字段分别代表硬件地址和协议地址的长度。opcode
字段指示ARP消息的类型,可以是ARP请求或ARP回复。
3. ICMP协议
3.1 ICMP协议的定义
Internet控制消息协议(ICMP,Internet Control Message Protocol)是TCP/IP协议族的一部分,它是网络层的一个重要协议,主要用于网络设备之间发送控制消息。ICMP常用于处理网络通信中的错误情况,并进行相关的诊断。
3.2 ICMP协议的类型
ICMP消息有很多种类型,包括但不限于:目标不可达(Type 3)、源抑制(Type 4)、重定向(Type 5)、回显请求和回显应答(Type 8 和 Type 0)、时间超过(Type 11)、参数问题(Type 12)、时间戳请求和时间戳应答(Type 13 和 Type 14)等。
3.3 ICMP协议的工作原理
以“回显请求”和“回显应答”(也就是我们常说的ping命令)为例:
- 发送端发送一个带有特定标识符和序列号的“回显请求”ICMP消息到目标IP地址。
- 如果目标IP地址的主机可达,它会回送一个带有相同标识符和序列号的“回显应答”ICMP消息。
- 发送端在收到“回显应答”消息后,会计算发送请求和接收应答之间的时间差,这个时间就是我们常说的网络延迟。
3.4 ICMP协议的应用
ICMP协议主要用于网络诊断。常见的应用如下:
- ping命令:用于测试目标主机是否可达,以及网络延迟。
- traceroute命令:用于跟踪数据包从源主机到目标主机的路由路径。
- 路由器和网关:可以利用ICMP发送错误消息,如“目标不可达”、“重定向”等。
3.5 ICMP协议的局限性和安全性
ICMP协议提供了一种处理和报告网络问题的方法,但它也有一些局限性和安全问题。
- 局限性:ICMP不能提供关于网络问题的详细信息,例如,它不能诊断是哪个应用程序或哪个服务导致的网络问题。
- 安全问题:由于ICMP可以用于发现网络的结构和活动,因此它可能会被用于进行网络侦查和拒绝服务(DoS)攻击。
3.6 ICMP协议与Posix API的关系
在Posix API中,我们可以通过使用raw socket和setsockopt函数来发送和接收ICMP消息。例如,我们可以创建一个raw socket,然后使用setsockopt函数来设置ICMP的类型和代码,最后使用sendto函数来发送ICMP消息。在接收ICMP消息时,我们可以使用recvfrom函数来获取ICMP消息的内容和来源地址。
4. ICMP协议例子
如下是一个简单的ICMP echo请求(即ping)的例子。这个程序会向指定的IP地址发送ICMP echo请求,并接收并打印来自那个IP地址的ICMP echo回应:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <arpa/inet.h>
#define PACKET_SIZE 4096
unsigned short checksum(void *b, int len) {
unsigned short *buf = b;
unsigned int sum = 0;
unsigned short result;
for (sum = 0; len > 1; len -= 2)
sum += *buf++;
if (len == 1)
sum += *(unsigned char*)buf;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
result = ~sum;
return result;
}
void ping(struct sockaddr_in *addr) {
const int val = 255;
int i, sockfd;
struct icmp sendicmp;
struct sockaddr_in from;
char recvbuf[PACKET_SIZE] = {0};
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (sockfd < 0) {
printf("socket() failed\n");
return;
}
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &val, sizeof(val));
sendicmp.type = ICMP_ECHO;
sendicmp.code = 0;
sendicmp.checksum = 0;
sendicmp.un.echo.id = getpid();
sendicmp.un.echo.sequence = 1;
sendicmp.checksum = checksum(&sendicmp, sizeof(sendicmp));
if (sendto(sockfd, &sendicmp, sizeof(sendicmp), 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) < 0) {
printf("sendto() failed\n");
return;
}
struct sockaddr_in from_addr;
socklen_t from_len = sizeof(from_addr);
if (recvfrom(sockfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*)&from_addr, &from_len) < 0) {
printf("recvfrom() failed\n");
return;
}
struct ip *iph = (struct ip *)recvbuf;
struct icmp *icmph = (struct icmp *)(recvbuf + (iph->ip_hl<<2));
if (icmph->icmp_type == ICMP_ECHOREPLY) {
printf("Received ICMP echo reply from %s\n", inet_ntoa(from.sin_addr));
} else {
printf("Received non-echo reply packet\n");
}
}
int main() {
struct sockaddr_in addr_ping, *addr;
addr = &addr_ping;
memset(addr, 0, sizeof(struct sockaddr_in));
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr("8.8.8.8"); // Google DNS server
ping(addr);
return 0;
}
上述程序的主体部分是ping函数,该函数发送一个ICMP echo请求并接收回应。ping函数首先创建一个原始套接字,并设置接收缓冲区大小。
然后,创建一个ICMP echo请求消息,包括类型(ICMP_ECHO)、代码(0)、校验和、ID(当前进程的PID)和序列号。计算校验和并将其写入到消息中。
使用sendto函数将ICMP消息发送到指定的目标地址。然后,使用recvfrom函数接收回应。如果接收到的ICMP消息的类型是ICMP_ECHOREPLY,说明接收到了一个ICMP echo回应。打印出源地址,并返回。
主函数中设定目标地址为"8.8.8.8",这是Google的公共DNS服务器,通常可用于ping测试。
5. 用户态网络协议栈
5.1 用户态网络协议栈的定义
用户态网络协议栈是指运行在用户空间的网络协议栈。传统的网络协议栈,如TCP/IP协议栈,通常运行在内核空间,而用户态网络协议栈则是将网络协议栈的实现从内核空间转移到了用户空间。
5.2 用户态网络协议栈的优点
- 易于开发和调试:用户态网络协议栈可以直接利用用户空间的各种开发工具和库,可以更方便地进行开发和调试。
- 可扩展性:用户态网络协议栈可以更灵活地扩展和修改,可以更好地适应各种特定的应用需求。
- 高性能:用户态网络协议栈可以绕过内核的系统调用和上下文切换,直接访问网络硬件,从而提高网络处理的效率。
5.3 用户态网络协议栈的缺点
- 需要专门的驱动支持:由于用户态网络协议栈直接访问网络硬件,因此需要网络设备驱动提供相应的支持。
- 安全性问题:用户态网络协议栈直接访问网络硬件,如果没有恰当的隔离和权限控制,可能会带来安全问题。
- 兼容性问题:用户态网络协议栈可能需要修改应用程序的代码,以适应其API,这可能会带来一些兼容性问题。
5.4 用户态网络协议栈的例子
目前,已经有一些用户态网络协议栈的实现,如DPDK (Data Plane Development Kit),Seastar等。这些用户态网络协议栈通常提供了一套完整的API,可以让应用程序直接进行网络编程,而无需依赖内核的网络服务。
5.5 用户态网络协议栈与Posix API的关
用户态网络协议栈通常需要使用其自己的API来进行网络编程,这些API可能与Posix API有所不同。然而,一些用户态网络协议栈,如Seastar,提供了与Posix API相兼容的接口,从而使得已有的基于Posix API的应用程序可以更容易地迁移到用户态网络协议栈上。
6. TCP协议演示
6.1 TCP协议的基础知识
传输控制协议(TCP,Transmission Control Protocol)是一个面向连接的、可靠的、基于字节流的传输层通信协议,它在互联网协议(IP)之上提供了一个稳定且可靠的数据传输服务。
TCP协议包含以下主要特性:
- 面向连接:在数据传输之前,必须先在发送端和接收端之间建立一个连接。
- 可靠传输:通过序号、确认应答、重传、流量控制等机制,确保数据可靠地从发送端传输到接收端。
- 流量控制和拥塞控制:通过滑动窗口、慢启动、拥塞避免、快重传和快恢复等机制,控制数据的传输速率,防止网络拥塞。
6.2 TCP的三次握手
TCP连接的建立过程通常被称为“三次握手”:
- 第一次握手:客户端发送一个SYN包(序号为X)到服务器,请求建立连接。
- 第二次握手:服务器收到SYN包后,回复一个SYN-ACK包(序号为Y,确认号为X+1)到客户端,确认收到SYN包。
- 第三次握手:客户端收到SYN-ACK包后,回复一个ACK包(确认号为Y+1)到服务器,确认收到SYN-ACK包。
6.3 TCP的四次挥手
TCP连接的关闭过程通常被称为“四次挥手”:
- 第一次挥手:主动关闭的一方(假设为客户端)发送一个FIN包(序号为M)到对方,表示不再发送数据。
- 第二次挥手:被动关闭的一方(假设为服务器)收到FIN包后,回复一个ACK包(确认号为M+1)到客户端,确认收到FIN包。
- 第三次挥手:被动关闭的一方在所有数据发送完毕后,发送一个FIN包(序号为N)到对方,表示不再发送数据。
- 第四次挥手:主动关闭的一方收到FIN包后,回复一个ACK包(确认号为N+1)到被动关闭的一方,确认收到FIN包。
6.4 TCP协议与Posix API的关系
在Posix API中,我们可以使用如下几个主要的函数来实现TCP通信:
- socket函数:创建一个新的套接字。
- bind函数:将套接字绑定到一个特定的地址和端口上。
- listen函数:在套接字上监听连接请求。
- connect函数:向一个特定的地址和端口发起连接请求。
- accept函数:接受一个连接请求,并返回一个新的套接字来处理这个连接。
- send和recv函数:在已建立的连接上发送和接收数据。
- close函数:关闭一个套接字和相关的连接。
这些函数的使用方法以及它们如何与TCP协议交互是Posix API网络编程的核心内容。
7. TCP协议栈实现
7.1 TCP协议栈的层级结构
在实现TCP协议栈时,我们通常会根据协议的层级结构来组织代码,每一层协议都对应一个或多个模块。比如:
- 应用层:这一层包含了HTTP、FTP、SMTP等应用协议的处理模块。
- 传输层:这一层主要实现了TCP和UDP协议。
- 网络层:这一层主要实现了IP协议,包括IP地址处理、路由选择、IP数据包的封装和解封装等功能。
- 数据链路层和物理层:这两层通常由操作系统和网络设备驱动来实现,应用程序一般不直接处理这两层的协议。
7.2 TCP协议栈的主要模块
在TCP协议栈中,主要有以下几个模块:
- 套接字管理模块:负责管理套接字的创建、绑定、监听、连接、关闭等操作。
- 连接管理模块:负责TCP连接的建立、维护和关闭,包括三次握手、四次挥手、状态转换等功能。
- 数据传输模块:负责数据的发送和接收,包括数据的封装和解封装、序号和确认应答的处理、重传机制、滑动窗口等功能。
- 拥塞控制模块:负责控制数据的发送速率,防止网络拥塞,包括慢启动、拥塞避免、快重传和快恢复等机制。
7.3 TCP协议栈的数据流
在TCP协议栈中,数据的流动路径如下:
- 应用程序通过调用send函数,将数据发送给传输层。
- 传输层接收到数据后,根据目标IP地址和端口号,将数据封装成一个或多个TCP数据包,并将它们发送到网络层。
- 网络层接收到TCP数据包后,根据目标IP地址,将数据包封装成一个或多个IP数据包,并将它们发送到数据链路层。
- 数据链路层接收到IP数据包后,将其发送到物理网络中。
接收数据的流程与发送数据的流程相反,从物理网络开始,经过数据链路层、网络层、传输层,最后到达应用程序。
7.4 TCP协议栈与Posix API的关系
在TCP协议栈中,Posix API作为应用程序和传输层之间的接口。应用程序通过调用Posix API来发送和接收数据,而TCP协议栈则负责实现这些API,并根据TCP协议的规则来处理数据的传输。