网站首页 > 基础教程 正文
本文索引:
1. 网络协议整体框架
2. 以太网帧/IP/UDP/TCP帧格式
3. ARP/RARP
4. ICMP/IGMP
5. PING
6. trace
7. BOOTP/DHCP
8. DNS
一、网络协议整体框架:
1. 定义:
网络协议的作用是使不同类型的网络能够相互通信。它提供了一个通用的框架,允许各种设备和应用程序在不同的网络环境中进行数据交换。 其核心在于其分层结构,每一层都负责特定的任务,从而简化了网络的设计和管理。 共有多种不同的分层方法。 本文重点说明下TCP/IP四层协议体系结构。
2. 内容:
TCP/IP协议栈并非单一协议,而是一组协议的集合,构成网络通信的基础。它是一个分层模型,通常被描述为四层模型,虽然实际实现可能更复杂。包括:链路层、网络层、传输层和应用层;分层结构如下:
1)链路层 (Network Access Layer / Link Layer):
a. 作用: 负责在物理网络上传输数据。它处理物理介质的细节,例如电缆或无线信号。
b. 内容: 这一层与具体的物理网络技术相关,例如:
---Ethernet: 局域网技术
---Wi-Fi: 无线局域网技术
---PPP (Point-to-Point Protocol): 点到点连接技术。
2) 网络层 (Internet Layer):
a.作用: 负责在网络之间路由数据包。它使用IP地址来寻址,并决定数据包如何从源主机到达目标主机。
b. 内容:
主要协议有
---IP (Internet Protocol): 定义了数据包的格式和寻址方式,负责数据包在网络中的路由。 IPv4和IPv6是两种主要的IP版本。
---ICMP (Internet Control Message Protocol): 用于网络诊断,例如ping命令
---ARP (Address Resolution Protocol): 将IP地址转换为MAC地址。
---RARP (Reverse Address Resolution Protocol): 将MAC地址转换为IP地址。
3) 传输层 (Transport Layer)
a. 作用: 提供端到端的可靠数据传输。它负责将数据分割成数据包,并确保这些数据包按顺序到达目的地。
b. 内容: 主要包含两个协议:
---TCP (Transmission Control Protocol): 面向连接、可靠的传输协议,提供流量控制、拥塞控制和错误校验。确保数据包的顺序和完整性
---UDP (User Datagram Protocol): 无连接、不可靠的传输协议,速度快但没有错误校验和顺序保证。常用于实时应用,例如视频流和在线游戏。
4) 应用层 (Application Layer):
a. 作用: 提供网络应用程序使用的接口。 它负责与用户交互,并决定数据如何被处理和传输。
b. 内容: 包含各种网络应用协议,例如:
---HTTP (Hypertext Transfer Protocol): 用于网页浏览。
---HTTPS (Hypertext Transfer Protocol Secure): 安全版本的HTTP,使用SSL/TLS加密。
---FTP (File Transfer Protocol): 用于文件传输。
---SMTP (Simple Mail Transfer Protocol): 用于电子邮件发送。
---POP3 (Post Office Protocol version 3) / IMAP (Internet Message Access Protocol): 用于电子邮件接收。
---DNS (Domain Name System): 将域名转换为IP地址。
---DHCP (Dynamic Host Configuration Protocol): 自动分配IP地址。
二、以太网帧/MAC帧/IP/UDP/TCP包格式
1.以太网帧/MAC帧:
1) 作用:
以太网帧定义了如何在以太网网络中传输数据。以太网帧主要由数据链路层(Data Link Layer)和物理层(Physical Layer)负责,它封装了从上层协议传来的数据,并在局域网中传输。具体作用如下:
a. 数据封装与传输: 以太网帧将网络层(如IP层)传来的数据进行封装,形成数据帧,并通过网络媒介(如电缆或无线信号)进行传输。
b. 寻址与路由: 以太网帧包含了源和目标的MAC地址,用于在局域网内找到正确的接收设备。
c. 错误检测: 以太网帧还包含了错误检查字段(CRC),用于检测数据在传输过程中是否发生了错误。
2) 以太网帧格式:
各字段的具体说明如下:
字段 | 长度 | 说明 |
前导码(Preamble) | 7字节 | 用于同步发送方和接收方的时钟,确保数据的正确接收。前导码由固定的“10101010...”组成。 |
帧起始定界符(SFD) | 1字节 | 标识帧的开始,紧接着前导码后面的一字节10101011。 |
目标MAC地址 | 6字节 | 目标设备的MAC地址 |
源MAC地址 | 6字节 | 发送设备的MAC地址 |
类型字段(Type) | 2字节 | 表示上层协议类型。常见的有: |
数据和填充 | 46-1500字节 | 以太网帧的主要部分,包含了上层协议的数据。数据部分的最小长度为46字节,最大长度为1500字节。如果数据部分小于46字节,则需要通过填充(Padding)来填补,直到达到最小长度。 |
CRC校验 | 4字节 | 用于错误检测 |
3) 以太网、802.3和802.2的关系
---以太网协议:是一个广义的术语,指代局域网中使用的技术,是数据链路层的一种方式。包括媒体访问控制(MAC层)和逻辑链路控制(LLC层);
---IEEE 802.3: 是以太网的具体标准,定义了物理层和MAC子层的实现。主要定义了局域网的物理层和数据链路层的MAC(媒体访问控制)子层。它描述了如何在物理介质上进行数据传输,包括帧的格式、传输速率、介质类型等。
---IEEE 802.2:定义了数据链路层的逻辑链路控制(LLC)子层。它位于MAC子层之上,提供了对上层协议的支持,使得不同的网络协议可以在同一物理网络上共存。
几者间的关系即:
4)使用场景:
MAC帧用于在局域网内的设备之间传输数据。每个设备都有一个唯一的MAC地址,MAC帧使用这些地址来确保数据到达正确的目的地。
网络协议栈中的数据在传输前被封装成MAC帧,并在接收时被解封装。MAC帧提供了数据链路层的封装格式。
5)示例代码:
下面是一个使用C语言构建和发送以太网MAC帧的示例代码。这个示例使用原始套接字(Raw Socket)来创建和发送MAC帧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/packet.h>
#include <net/if.h>
#include <sys/ioctl.h>
#define ETH_FRAME_LEN 1514 // 最大以太网帧长度
int main (int arc, char **argv) {
int sockfd;
struct sockaddr_ll sa_ll;
unsigned char frame[ETH_FRAME_LEN];
unsigned char dest_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; // 广播地址
unsigned char src_mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; // 源MAC地址
const char *data = "Hello, Ethernet!";
// 创建原始套接字
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sockfd < 0) {
perror("Socket creation failed");
return 1;
}
// 获取网络接口索引(比如eth0的索引)
struct ifreq ifr;
strncpy((char *)ifr.ifr_name, "eth0", IFNAMSIZ); // "eth0"可以修改为实际的网络接口
if (ioctl(sockfd, SIOCGIFINDEX, &ifr) < 0) {
perror("ioctl failed");
close(sockfd);
return 1;
}
int ifindex = ifr.ifr_ifindex;
// 填充以太网帧
memcpy(frame, dest_mac, 6); // 目的MAC地址
memcpy(frame + 6, src_mac, 6); // 源MAC地址
frame[12] = 0x08; // Type字段(IPv4)
frame[13] = 0x00;
memcpy(frame + 14, data, strlen(data)); // 数据负载
// 填充 sockaddr_ll 结构
memset(&sa_ll, 0, sizeof(struct sockaddr_ll));
sa_ll.sll_protocol = htons(ETH_P_IP); // 设置协议为IPv4
sa_ll.sll_ifindex = ifindex; // 设置接口索引
sa_ll.sll_halen = ETH_ALEN; // 硬件地址长度(6字节)
memcpy(sa_ll.sll_addr, dest_mac, 6); // 设置目的MAC地址
// 发送数据
if (sendto(sockfd, frame, 14 + strlen(data), 0, (struct sockaddr*)&sa_ll, sizeof(struct sockaddr_ll)) < 0) {
perror("Send failed");
close(sockfd);
return 1;
}
printf("Frame sent successfully\n");
close(sockfd);
return 0;
}
代码说明:
a. socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)):
创建一个原始套接字,使用 AF_PACKET 地址族,SOCK_RAW 类型,协议为 ETH_P_ALL(表示所有以太网协议类型)。
b. struct ifreq ifr:
使用 ioctl 获取指定网络接口(如 eth0)的接口索引 (ifindex),这个索引需要填充到 sockaddr_ll 结构中。
c. 填充 MAC 地址和数据帧:
设置帧的目的MAC地址、源MAC地址、类型字段(例如IPv4类型 0x0800)以及数据负载。
d. sendto() 函数:
使用 sendto 发送以太网帧,传递给 sockaddr_ll 结构体,其中包括接口索引、协议类型和目标MAC地址。
struct sockaddr_ll 定义在 <net/packet.h> 中,通常用于原始套接字(raw socket)来发送和接收数据链路层的帧。其定义如下:
struct sockaddr_ll {
unsigned short sll_protocol; // 协议类型,通常是ETH_P_ALL或特定的以太网类型
int sll_ifindex; // 网络接口的索引
unsigned short sll_hatype; // 硬件类型(一般是ETH_P_ETHER表示以太网)
unsigned char sll_pkttype; // 包类型(例如,广播、单播、组播)
unsigned char sll_halen; // 硬件地址长度
unsigned char sll_addr[8]; // 目标硬件地址(如MAC地址)
};
---sll_protocol:
这是协议类型字段,表示该帧的协议类型。通常它会设置为ETH_P_ALL(表示所有协议类型的以太网帧),或者特定协议的值,如ETH_P_IP(表示IPv4协议)。 ETH_P_ALL 是 0x0003,表示所有协议的数据帧。
---sll_ifindex:
这是网络接口的索引,表示发送帧的网络接口。这个值通过命令 ifconfig 或 ip link show 可以获得。例如,eth0 或 eth1 等网络接口的索引值。
---sll_hatype:
这个字段表示硬件类型,通常设置为 ETH_P_ETHER,表示标准的以太网类型。该字段可以用于更低级别的协议,但在大多数情况下它是设置为以太网类型(ETH_P_ETHER)。
--- sll_pkttype:
表示包的类型。常见的包类型有:
.PACKET_HOST:指向主机的数据包。
.PACKET_BROADCAST:广播数据包。
.PACKET_MULTICAST:多播数据包。
.PACKET_OTHERHOST:其他主机的数据包。
.PACKET_OUTGOING:发送的数据包。
---sll_halen: 这个字段表示硬件地址的长度(通常是6字节,即MAC地址的长度)。
---sll_addr:
存储目标硬件地址(如MAC地址)的数组,通常是6字节。对于以太网帧,MAC地址的长度是6字节。如果该字段设置为广播地址(ff:ff:ff:ff:ff:ff),表示数据包将发送到所有设备。
2.IP帧数据:
1) 作用:
在网络协议栈中,IP帧主要作用是在网络层提供无连接、不可靠的数据报服务,实现主机间的端到端数据传输。 它并不负责数据的可靠传输,也不保证数据包的顺序到达,这些工作留给更高层协议(如TCP)来完成。
作用体现在以下几个方面:
- 寻址: IP数据报包含源IP地址和目的IP地址,这使得它能够在互联网上路由,找到正确的目的地。IP地址是互联网上设备的逻辑地址,用于标识网络上的主机或设备。
- 分片与重组: 当IP数据报过大无法通过某个网络链路传输时,IP层会将它分割成多个更小的片段(fragment),并在到达目的地后重新组装成完整的IP数据报。这保证了数据包能够在不同网络环境下传输。
- 路由: IP数据报在网络中传输时,会经过多个路由器。每个路由器都会根据IP数据报的目的IP地址,选择最佳路径转发数据报,最终将数据报传递到目的地。 IP路由协议(如RIP、OSPF、BGP)负责维护路由信息。
- 差错检测: 虽然IP层本身不保证可靠传输,但IP数据报包含校验和(checksum),用于检测数据报在传输过程中是否发生错误。如果检测到错误,数据报会被丢弃。但是,IP层不会重传丢失的数据包。
- 服务质量 (QoS): 在一些网络应用中,需要对数据包进行优先级划分,保证某些类型的流量优先传输。IP层可以通过一些机制(如DiffServ)来实现服务质量控制。
- 隧道技术: IP隧道技术允许在IP网络中创建虚拟的IP网络,用于安全传输数据或跨越不同的网络类型。IP数据报可以封装在IP隧道中进行传输。
- 封装和解封装: IP数据报是网络层的数据单元,它封装了来自上层协议(如TCP、UDP)的数据,并在到达目的地后解封装,将数据传递给上层协议处理。
2)IP帧格式(IPv4和IPv6):
a. IPv4 数据报格式
IPv4数据报分为多个字段,以下是每个字段的说明:
---版本(Version, 4 bits): 指定IP的版本号,IPv4使用值4。
---首部长度(IHL, Internet Header Length, 4 bits):
使用32位字作为单位,IP头的长度(不包括数据部分)。最短是5(表示20字节),如果有选项字段则更长。
---服务类型(Type of Service, 8 bits):
现在被称为区分服务(Differentiated Services),用于标识数据包的优先级和QoS(服务质量)。
---总长度(Total Length, 16 bits):
整个IP数据报的长度(以字节为单位),包括首部和数据。
---标识(Identification, 16 bits):
唯一标识数据报。用于分片情况下,接收方可以通过此字段和标识数据片属于哪个数据报。
---标志(Flags, 3 bits):
控制分片:
- 第一个位保留位,必须为0。
- 第二个位为DF(Don't Fragment),如果设为1,数据报不能分片。
- 第三个位为MF(More Fragments),如果设为1,表示后面还有更多分片。
---片偏移(Fragment Offset, 13 bits):
数据报分片时,标识每个分片在原数据报中的位置(以8字节为单位)。
---生存时间(Time to Live, TTL, 8 bits):
数据报在网络中能经过的最大跳数(路由器数量)。每通过一个路由器,此值减1,为0时数据报被丢弃。
---协议(Protocol, 8 bits):
指定数据部分使用的协议类型(如6表示TCP,17表示UDP)。
---首部校验和(Header Checksum, 16 bits):
仅对IP头进行校验,确保传输过程中没有损坏。
---源IP地址(Source Address, 32 bits): 发送方的IP地址。
---目的IP地址(Destination Address, 32 bits):接收方的IP地址。
---选项(Options, 可变长): 可选字段,提供控制和调试功能,如记录路径、时间戳等。
---数据(Payload, 可变长):实际传输的数据,由协议字段指明的上层协议来解释和处理。
b. IPv6 数据报格式
IPv6是为了解决IPv4地址枯竭和一些协议局限性而设计的,以下是IPv6数据报的基本格式:
---版本(Version, 4 bits): IPv6的版本号为6。
---流量类别(Traffic Class, 8 bits): 类似于IPv4的服务类型字段,用于标识数据包的优先级。
---流标签(Flow Label, 20 bits): 用于标识数据包流,提高对特定流的处理效率。
---有效负载长度(Payload Length, 16 bits):不包括IP头的负载长度。
---下一个头部(Next Header, 8 bits):类似于IPv4的协议字段,在标准IPv6数据报内,指定紧随其后的扩展报头或数据的类型(如TCP或UDP)。
---跳限制(Hop Limit, 8 bits): 类似于IPv4的TTL,限制数据包在网络中的寿命。
---源地址(Source Address, 128 bits):发送方的IP地址。
---目的地址(Destination Address, 128 bits):接收方的IP地址。
---数据(Payload, 可变长):包含传输数据,实现中的上层协议处理。
3)使用场景:
选择IPv4还是IPv6取决于具体的应用场景和需求。 对于小型网络、现有基础设施或需要与旧系统兼容的场景,IPv4仍然是首选。 对于大型网络、物联网、移动网络以及需要高安全性和更大地址空间的场景,IPv6是更好的选择。 在许多情况下,IPv4和IPv6的双协议栈部署是过渡阶段的最佳解决方案。
4) 代码示例:
以下是一个简单的示例,展示如何手动封装IP帧并发送数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <sys/socket.h>
// 计算校验和
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;
}
int main (int argc, char **argv) {
int sockfd;
char buffer[4096];
struct sockaddr_in dest_addr;
struct iphdr *iph = (struct iphdr *)buffer;
// 创建原始套接字
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置目的地址
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(0); // 端口不需要设置
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目的IP地址
// 填充IP头部
iph->ihl = 5; // IP头部长度
iph->version = 4; // IPv4
iph->tos = 0; // 服务类型
iph->tot_len = sizeof(struct iphdr) + strlen("Hello"); // 总长度
iph->id = htonl(54321); // ID
iph->frag_off = 0; // 分段偏移
iph->ttl = 255; // 生存时间
iph->protocol = IPPROTO_ICMP; // 协议类型(这里使用ICMP作为示例)
iph->check = 0; // 校验和
iph->saddr = inet_addr("192.168.1.100"); // 源IP地址
iph->daddr = dest_addr.sin_addr.s_addr; // 目的IP地址
// 计算IP校验和
iph->check = checksum((unsigned short *)iph, sizeof(struct iphdr));
// 填充数据
strcpy(buffer + sizeof(struct iphdr), "Hello");
// 发送数据包
if (sendto(sockfd, buffer, iph->tot_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("Send failed");
} else {
printf("Packet sent\n");
}
close(sockfd);
return 0;
}
补充说明:
---原始套接字:使用SOCK_RAW创建原始套接字,以便手动构建IP头部。
---IP头部:构造IP头部,设置版本、头部长度、服务类型、总长度、ID、生存时间、协议类型、源IP和目的IP等。
---校验和:计算IP校验和,以确保数据完整性。
---数据发送:使用sendto函数发送构造好的数据包。
---IPv6和IPv4的区别:IPv4使用 AF_INET 创建TCP和UDP套接字,IPv6使用 AF_INET6 创建TCP和UDP套接字,其它接口使用都是一样的。
3.UDP帧数据:
1) 作用:
a. 无连接传输: UDP是无连接的协议,发送数据之前不需要建立连接。这使得UDP在传输数据时延迟较低,适合实时应用。
b. 简单性: UDP的协议头部较小(8字节),相较于TCP的头部(20字节及以上),UDP的开销更小,适合需要快速传输的场景。
c. 不保证可靠性: UDP不提供数据包的重传、顺序控制或完整性检查。数据包可能会丢失、重复或乱序到达,适合对可靠性要求不高的应用。
d. 广播和多播支持: UDP支持广播(向网络上的所有主机发送数据)和多播(向特定组的主机发送数据),适合需要同时向多个接收者发送数据的场景。
2)帧格式:
UDP头部为固定的8字节(64位),包含以下四个字段:
a. 源端口(Source Port,2字节):
指示发送方的应用程序的端口号。对于不需要响应的通信,这是可选的。如果不使用,可以设置为0。
b. 目的端口(Destination Port,2字节):
指示接收方的应用程序的端口号,此字段在数据报到达目的地时用于识别目标应用程序。
c. 长度(Length,2字节):
表示整个UDP数据报的长度(包括头部和数据)。该值的最小值为8(仅头部长度),最大值为65,535字节。
d. 校验和(Checksum,2字节):
校验和用于错误检测,对UDP头部和数据部分进行检验。计算时还包括一个伪头部(来自IP层信息),该字段在IPV4中是可选的,但在IPv6中是强制校验的。
3)使用场景:
适合对速度要求高、对可靠性要求低的应用,具有较低的延迟和开销。
a.实时应用: 如视频会议、在线游戏、语音通话等,这些应用对延迟敏感,能够容忍一定的数据丢失。
b. DNS查询: DNS使用UDP进行域名解析,因为请求和响应数据包较小,且不需要建立连接。
c.简单的请求-响应协议: 如SNMP(简单网络管理协议)和TFTP(简单文件传输协议)。
4)代码示例:
以下是一个简单的示例,展示如何手动封装UDP帧并发送。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/udp.h>
#include <netinet/ip.h>
#include <sys/socket.h>
// UDP伪头部结构体,用于校验和计算
struct pseudo_header {
u_int32_t source_address;
u_int32_t dest_address;
u_int8_t placeholder;
u_int8_t protocol;
u_int16_t udp_length;
};
// 计算校验和
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;
}
int main() {
int sockfd;
char buffer[4096];
struct sockaddr_in dest_addr;
struct udphdr *udph = (struct udphdr *)(buffer + sizeof(struct iphdr));
struct iphdr *iph = (struct iphdr *)buffer;
struct pseudo_header psh;
// 创建原始套接字
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置目的地址
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080);
dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
// 填充IP头部
iph->ihl = 5; // IP头部长度
iph->version = 4; // IPv4
iph->tos = 0; // 服务类型
iph->tot_len = sizeof(struct iphdr) + sizeof(struct udphdr) + strlen("Hello");
iph->id = htonl(54321); // ID
iph->frag_off = 0; // 分段偏移
iph->ttl = 255; // 生存时间
iph->protocol = IPPROTO_UDP; // 协议
iph->check = 0; // 校验和
iph->saddr = inet_addr("127.0.0.1"); // 源IP
iph->daddr = dest_addr.sin_addr.s_addr; // 目的IP
// 填充UDP头部
udph->source = htons(12345); // 源端口
udph->dest = htons(8080); // 目的端口
udph->len = htons(sizeof(struct udphdr) + strlen("Hello")); // UDP长度
udph->check = 0; // 校验和
// 填充伪头部
psh.source_address = iph->saddr;
psh.dest_address = iph->daddr;
psh.placeholder = 0;
psh.protocol = IPPROTO_UDP;
psh.udp_length = udph->len;
int psize = sizeof(struct pseudo_header) + sizeof(struct udphdr) + strlen("Hello");
char *pseudogram = malloc(psize);
memcpy(pseudogram, (char *)&psh, sizeof(struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header), udph, sizeof(struct udphdr) + strlen("Hello"));
// 计算UDP校验和
udph->check = checksum(pseudogram, psize);
// 填充数据
strcpy(buffer + sizeof(struct iphdr) + sizeof(struct udphdr), "Hello");
// 发送数据包
if (sendto(sockfd, buffer, iph->tot_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("Send failed");
} else {
printf("Packet sent\n");
}
close(sockfd);
free(pseudogram);
return 0;
}
说明:
- 原始套接字:使用SOCK_RAW创建原始套接字,以便手动构建IP和UDP头部。
- IP头部:构造IP头部,设置版本、头部长度、协议等。
- UDP头部:构造UDP头部,设置源和目的端口、长度等。
- 伪头部:用于计算UDP校验和,包含源IP、目的IP、协议和UDP长度。
- 校验和:计算UDP校验和,以确保数据完整性。
- 数据发送:使用sendto函数发送构造好的数据包。
--------------------------------------------------------------------
4.TCP帧数据:
1) 作用:
a.面向连接: TCP是面向连接的协议,在发送数据之前需要建立连接,确保双方都准备好进行数据传输。
b. 可靠性: TCP提供可靠的数据传输,确保数据包按顺序到达,并且在丢失时会进行重传。它使用序列号、确认应答和重传机制来实现这一点。
c.流量控制: TCP使用流量控制机制(如滑动窗口)来防止发送方发送过多数据,导致接收方处理不过来。
d.拥塞控制: TCP具有拥塞控制机制,能够根据网络状况动态调整数据传输速率,避免网络拥塞。
e. 数据完整性: TCP提供数据完整性检查,确保数据在传输过程中没有被损坏。
2) 帧格式:
TCP帧(或称为TCP段)由TCP头部和数据部分组成。以下是TCP帧的详细格式解析。
---源端口(Source Port)(16 bits):发送方的端口号。
---目的端口(Destination Port)(16 bits):接收方的端口号。
---序列号(Sequence Number)(32 bits):用于标识发送的数据字节流的序列号。对于第一个数据段,序列号是初始序列号(ISN),后续段的序列号是前一个段的序列号加上数据字节数。
---确认号(Acknowledgment Number)(32 bits):表示期望接收的下一个字节的序列号。只有在ACK标志位被设置时,这个字段才有效。
---数据偏移(Data Offset)(4 bits):TCP头部的长度,以32位字(4字节)为单位。这个字段指示TCP头部的结束位置,通常是5(表示20字节)到15(表示60字节)。
---保留(Reserved)(3 bits):保留供将来使用,通常设置为0。
---控制标志(Flags)(9 bits)
包含多个控制位:
- URG(Urgent Pointer):紧急指针标志。
- ACK(Acknowledgment):确认标志。
- PSH(Push):推送标志,表示接收方应立即将数据传递给应用层。
- RST(Reset):重置连接。
- SYN(Synchronize):同步序列号,用于建立连接。
- FIN(Finish):结束连接。
---窗口大小(Window Size)(16 bits):表示接收方的缓冲区大小,告知发送方可以发送多少字节的数据。
---校验和(Checksum)(16 bits):用于错误检测,确保数据在传输过程中未被损坏。校验和是对TCP头部和数据部分的计算结果。
---紧急指针(Urgent Pointer)(16 bits):仅在URG标志位被设置时有效,指示紧急数据的结束位置。
---选项(Options)(可变长度):可选字段,允许TCP进行一些扩展功能,如最大报文段大小(MSS)、时间戳等。
---数据(Data)(可变长度):实际传输的数据部分,长度可变,取决于应用层发送的数据。
以下为使用tcpdump抓的TCP数据包相关信息,可对比上面的TCP头帧格式来看。
3)使用场景:
适合对可靠性和数据完整性要求高的应用,提供可靠的、顺序的、面向连接的数据传输。
a. 文件传输: 如FTP(文件传输协议)和HTTP(超文本传输协议),这些应用需要确保数据的完整性和顺序。
b.电子邮件: 如SMTP(简单邮件传输协议)和IMAP(互联网消息访问协议),需要可靠的邮件传输。
c. 远程登录: 如SSH(安全外壳协议)和Telnet,确保命令和响应的可靠传输。
4)代码示例:
以下是一个简单的示例, 展示手动封装TCP帧涉及到构建TCP头部和IP头部,并通过套接字发送。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
// 计算校验和
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;
}
int main() {
int sockfd;
char buffer[4096];
struct sockaddr_in dest_addr;
struct iphdr *iph = (struct iphdr *)buffer;
struct tcphdr *tcph = (struct tcphdr *)(buffer + sizeof(struct iphdr));
/***1. 创建原始套接字 ***/
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sockfd < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
/*** 2. 设置目的地址 ***/
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(8080); // 目的端口
dest_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 目的IP地址
/***3.填充TCP头部 ***/
tcph->source = htons(12345); // 源端口
tcph->dest = htons(8080); // 目的端口
tcph->seq = 0; // 序列号
tcph->ack_seq = 0; // 确认号
tcph->doff = 5; // TCP头部长度
tcph->fin = 0; // FIN标志
tcph->syn = 1; // SYN标志
tcph->rst = 0; // RST标志
tcph->psh = 0; // PSH标志
tcph->ack = 0; // ACK标志
tcph->urg = 0; // URG标志
tcph->window = htons(5840); // 窗口大小
tcph->check = 0; // 校验和
tcph->urg_ptr = 0; // 紧急指针
/***4. 填充IP头部 ***/
iph->ihl = 5; // IP头部长度
iph->version = 4; //ipv4版本
IPv4 iph->tos = 0; // 服务类型
iph->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr); // 总长度 iph->id = htonl(54321); // ID
iph->frag_off = 0; // 分段偏移
iph->ttl = 255; // 生存时间
iph->protocol = IPPROTO_TCP; // 协议类型
iph->check = 0; // 校验和
iph->saddr = inet_addr("192.168.1.100"); // 源IP地址
iph->daddr = dest_addr.sin_addr.s_addr; // 目的IP地址
/***5.计算IP校验和 ***/
iph->check = checksum((unsigned short *)iph, sizeof(struct iphdr));
/***6.计算TCP校验和**/
int psize = sizeof(struct pseudo_header) + sizeof(struct tcphdr);
char *pseudogram = malloc(psize);
struct pseudo_header {
u_int32_t source_address;
u_int32_t dest_address;
u_int8_t placeholder;
u_int8_t protocol;
u_int16_t tcp_length;
};
struct pseudo_header psh;
psh.source_address = iph->saddr;
psh.dest_address = iph->daddr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = htons(sizeof(struct tcphdr));
memcpy(pseudogram, (char *)&psh, sizeof(struct pseudo_header));
memcpy(pseudogram + sizeof(struct pseudo_header), tcph, sizeof(struct tcphdr));
// 计算TCP校验和
tcph->check = checksum((unsigned short *)pseudogram, psize);
// 发送数据包
if (sendto(sockfd, buffer, iph->tot_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)) < 0) {
perror("Send failed");
} else {
printf("Packet sent\n");
}
close(sockfd);
free(pseudogram);
return 0;
}
说明:
- 原始套接字:使用SOCK_RAW创建原始套接字,以便手动构建IP和TCP头部。
- TCP头部:构造TCP头部,设置源端口、目的端口、序列号、确认号、标志位、窗口大小等。
- IP头部:构造IP头部,设置版本、头部长度、服务类型、总长度、ID、生存时间、协议类型、源IP和目的IP等。
- 伪头部:用于计算TCP校验和,包含源IP、目的IP、协议和TCP长度。
- 校验和:计算IP和TCP的校验和,以确保数据完整性。
- 数据发送:使用sendto函数发送构造好的数据包。
<<常用网络协议整理笔记(二)>>待续......
- 上一篇: 一文了解常见的网络协议
- 下一篇: 必备知识!这7种常见路由协议谁还不知道
猜你喜欢
- 2025-01-11 网络小白必看!计算机网络基础讲解——网络协议
- 2025-01-11 网络基础知识:ARP、ICMP、IP协议
- 2025-01-11 了解不同类型的网络通信协议
- 2025-01-11 常用网络协议整理笔记(二)---ARP/RARP协议
- 2025-01-11 一图看懂8种流行的网络协议栈
- 2025-01-11 Socks5与HTTP:网络协议比较与应用场景解析
- 2025-01-11 简单!三分钟教你速通通信协议
- 2025-01-11 网络路由器常用协议介绍
- 2025-01-11 计算机网络协议
- 2025-01-11 推荐一款运维必备工具,网络通讯协议全览图,收藏起来备用...
- 01-11网络小白必看!计算机网络基础讲解——网络协议
- 01-11网络基础知识:ARP、ICMP、IP协议
- 01-11了解不同类型的网络通信协议
- 01-11常用网络协议整理笔记(二)---ARP/RARP协议
- 01-11一图看懂8种流行的网络协议栈
- 01-11Socks5与HTTP:网络协议比较与应用场景解析
- 01-11简单!三分钟教你速通通信协议
- 01-11网络路由器常用协议介绍
- 最近发表
- 标签列表
-
- gitpush (61)
- pythonif (68)
- location.href (57)
- tail-f (57)
- pythonifelse (59)
- deletesql (62)
- c++模板 (62)
- css3动画 (57)
- c#event (59)
- linuxgzip (68)
- 字符串连接 (73)
- nginx配置文件详解 (61)
- html标签 (69)
- c++初始化列表 (64)
- exec命令 (59)
- canvasfilltext (58)
- mysqlinnodbmyisam区别 (63)
- arraylistadd (66)
- node教程 (59)
- console.table (62)
- c++time_t (58)
- phpcookie (58)
- mysqldatesub函数 (63)
- window10java环境变量设置 (66)
- c++虚函数和纯虚函数的区别 (66)