15.原始套接字
原始套接字(Raw Socket)是一种网络编程接口,它允许开发者访问底层的网络协议,如IP、ICMP、TCP和UDP等。与常规的流式套接字、数据报套接字不同,原始套接字提供了对网络层的直接访问,这意味着开发者可以发送和接收未经操作系统处理的原始数据包。
原始套接字广泛应用于高级网络编程,如网络流量监听、网络数据分析、安全测试等领域。
链路层原始套接字
- 创建过程:通过调用socket()函数并指定协议族为PF_PACKET、套接字类型为SOCK_RAW或SOCK_DGRAM,以及协议类型参数,可以创建一个链路层原始套接字。
- 数据格式:当使用SOCK_RAW时,套接字接收和发送的数据都是从MAC首部开始。在发送时,开发者需要从MAC首部开始构造和封装报文数据。如果选择SOCK_DGRAM,则系统会处理链路层协议头,开发者只需从IP首部(或ARP首部)开始构造即可。
- 注意事项:在使用链路层原始套接字时,需要注意字节序之间的转换,以及如何截取自己想要的数据内容
示例:接收并处理包含TCP协议的IP数据报。
程序首先创建一个链路层原始套接字,然后循环接收数据包。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <net/ethernet.h>
#define MTU 1500
int main()
{
/* 定义变量 */
int sockfd, len;
uint8_t buf[MTU]={};
uint16_t ether_type;
struct iphdr *iph; //IP包头
struct tcphdr *tcph;//TCP包头
struct ether_header *eth;
/* 创建一个链路层原始套接字 */
if( (sockfd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)) ) < 0){
perror("socket");
return 0;
}
printf("sockfd = %d\n", sockfd);
/* 接收(只接收TCP数据协议)并处理IP数据报 */
while(1)
{
/* 接收包含TCP协议的IP数据报 */
len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
eth = (struct ether_header *)buf;
ether_type = htons(eth->ether_type);
switch(ether_type){
case ETHERTYPE_IP:
printf("IP协议\n");
break;
case ETHERTYPE_ARP:
printf("ARP协议\n");
break;
case ETHERTYPE_LOOPBACK:
printf("loop back\n");
break;
default:
printf("其他协议 %x\n",eth->ether_type);
}
if(ether_type != ETHERTYPE_IP)
continue;
/* 打印源IP和目的IP */
iph = (struct iphdr *)(buf+14);
if(iph->protocol != IPPROTO_TCP)
continue;
printf("源IP:%s\n",inet_ntoa(*(struct in_addr *)&iph->saddr) );
printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
/* 打印TCP包头的源端口号和目的端口号 */
tcph = (struct tcphdr *)(buf+14+iph->ihl*4);
printf("%hu--->", ntohs(tcph->source));
printf("%hu\n", ntohs(tcph->dest));
/* 打印TCP数据段的长度 */
printf("TCP首部长度:%d\n", tcph->doff*4);
}
//关闭套接字
close(sockfd);
return 0;
}
使用ping
命令和nc
命令模拟通信,实现捕获
网络层原始套接字
创建原始套接字
/* 创建一个原始套接字 */
if( (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP) ) < 0)
{
perror("socket");
return 0;
}
- AF_INET:表示使用IPv4协议族。
- SOCK_RAW:表示创建一个原始套接字,可以接收所有类型的数据包,包括TCP、UDP等。
- IPPROTO_TCP:表示使用TCP协议。
示例:接收并处理包含TCP协议的IP数据报
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/ether.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <sys/ioctl.h>
#define MTU 1500
int main()
{
/* 定义变量 */
int sockfd = -1, len, datalen, i;
uint8_t buf[MTU]={}, *data;
struct iphdr *iph; //IP包头
struct tcphdr *tcph;//TCP包头
struct winsize size;
/* 创建一个原始套接字 */
if( (sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP) ) < 0)
{
perror("socket");
return 0;
}
printf("sockfd = %d\n", sockfd);
/* 接收(只接收TCP数据协议)并处理IP数据报 */
while(1)
{
/* 接收包含TCP协议的IP数据报 */
len = recvfrom(sockfd, buf, sizeof(buf),0,NULL,NULL);
printf("IP数据报长度 = %d\n", len);
/* 打印源IP和目的IP */
iph = (struct iphdr *)buf;
printf("源IP:%s",inet_ntoa(*(struct in_addr *)&iph->saddr) );
printf("目的IP%s\n",inet_ntoa(*(struct in_addr *)&iph->daddr) );
/* 打印TCP包头的源端口号和目的端口号 */
tcph = (struct tcphdr *)(buf+iph->ihl*4);
printf("%hu--->", ntohs(tcph->source));
printf("%hu\n", ntohs(tcph->dest));
/* 打印TCP数据段的长度 */
printf("TCP首部长度:%d\n", tcph->doff*4);
if(iph->ihl*4+tcph->doff*4 < len) {
data = buf + iph->ihl*4 + tcph->doff*4;
datalen = len - iph->ihl*4 - tcph->doff*4;
ioctl(STDIN_FILENO,TIOCGWINSZ,&size); //terminal 结构体
for(i = 0; i < size.ws_col; i++) //显示一行 =
putchar('=');
putchar('\n');
printf("TCP数据字符:\n");
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
for(i = 0; i < datalen-1; i++) {
printf("%c", data[i]);
}
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
printf("TCP数据16进制:\n");
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
for(i = 0; i < datalen-1; i++){
printf("%x ", data[i]);
}
putchar('\n');
for(i = 0; i < size.ws_col; i++)
putchar('=');
putchar('\n');
}
}
//关闭套接字
close(sockfd);
return 0;
}
- 运行程序
sudo ./tcp
- nc 命令模拟tcp传输
nc 110.242.68.4.1