目录
1 链路层原始套接字用法
Linux中的原始套接字(Raw Socket)是一种高级套接字类型,允许应用程序直接访问网络协议栈,发送和接收自定义的网络数据包。使用原始套接字,你可以实现各种网络工具、网络协议分析和网络攻防等功能。
下面是使用原始套接字的一般步骤:
1 创建原始套接字:通过调用socket()函数创建一个原始套接字。指定参数AF_PACKET表示使用Packet套接字族,参数SOCK_RAW表示使用原始套接字类型。
int sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
2
绑定套接字到网络设备:使用bind()函数将套接字绑定到特定的网络设备上。你需要指定网络设备的名称或索引。可以使用ifconfig命令或者ioctl()函数来获取设备名称和索引。
struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sa));
sa.sll_family = AF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex("eth0");
bind(sockfd, (struct sockaddr*)&sa, sizeof(sa));
3 构造自定义数据包:使用结构体来构造自定义的网络数据包,如以太网帧(struct ethhdr)和IP数据报头(struct iphdr)。在构造数据包时,你需要设置正确的协议头信息和有效载荷。
struct ethhdr ether_header;
struct iphdr ip_header;
char payload[100];
// 构建以太网帧、IP数据报头和有效载荷
// ...
4 发送和接收数据包:使用sendto()函数发送数据包,指定目标地址和端口。使用recvfrom()函数接收数据包,获取发送者的信息。
// 发送数据包
sendto(sockfd, ðer_header, sizeof(ether_header) + sizeof(ip_header) + payload_length, 0, (struct sockaddr*)&sa, sizeof(sa));
// 接收数据包
struct sockaddr_ll sa_recv;
socklen_t sa_len = sizeof(sa_recv);
recvfrom(sockfd, recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr *)&sa_recv, &sa_len);
5 关闭套接字:当你完成使用原始套接字时,记得使用close()函数关闭套接字。
close(sockfd);
请注意,在使用原始套接字时需要具有足够的权限,通常需要以root用户身份运行程序。此外,使用原始套
接字需要对网络协议有深入的了解,并且要小心操作,以避免对网络造成不良影响。
1.1 利用原始套接字实现类似wireshark的功能
tcp_all.c 接收所用链路层的数据包,但是以TCP为主(实现功能类似wireshark)
#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是一个定义在<netinet/ip.h>中的IP包头结构体,用于表示IPv4协议的包头信息。
struct tcphdr是一个定义在<netinet/tcp.h>中的TCP包头结构体,用于表示TCP协议的包头信息。
struct ether_header是一个定义在<net/ethernet.h>中的以太网帧头结构体,用于表示以太网帧头的信息。
*/
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) //如果是IP包才往下执行
continue;
/* 打印源IP和目的IP */
iph = (struct iphdr *)(buf+14);
if(iph->protocol != IPPROTO_TCP) //如果是ip包并且是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;
}
运行效果:
后期改进,也可以更细的去打印各类协议,详细包内容。
1.2 利用原始套接字实现ping命令
send_ping.c
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <stdlib.h>
#include <linux/icmp.h>
#include <stdio.h>
uint16_t SetChetSum(uint16_t *buf, int size);
void Set_IcmpH(struct icmphdr *icmph, int size);
#define MSG_SIZE 40
int main(int argc, char *argv[])
{
/* 定义变量 */
int fd;
char buf[MSG_SIZE]={};
if(argc < 3)
{
printf("%s <addr> <port>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 创建原始套接字 */
if((fd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0)
{
perror("socket");
return 0;
}
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
if(inet_aton(argv[1], &sin.sin_addr) == 0)
{
printf("Invalid address\n");
exit(EXIT_FAILURE);
}
Set_IcmpH((struct icmphdr *)&buf, MSG_SIZE);
sendto(fd, buf, MSG_SIZE, 0, (struct sockaddr *)&sin, sizeof(sin));
close(fd);
return 0;
}
void Set_IcmpH(struct icmphdr * icmph, int size)
{
static int seq;
icmph->type = ICMP_ECHO; //icmp宏定义
icmph->code = 0; //每个icmp的编号,发一次,简化逻辑
icmph->un.echo.id = getpid();
icmph->un.echo.sequence = seq++;
icmph->checksum = SetChetSum((uint16_t *)icmph, MSG_SIZE);//校验和
}
uint16_t SetChetSum(uint16_t *buf, int size)
{
uint32_t checksum = 0;
while(size > 1)
{
checksum += *buf++;
size -= 16;
}
if(size)
checksum += *(unsigned char *)buf;
checksum = (checksum >> 16) + (checksum & 0xffff);
checksum += (checksum >> 16);
return (uint16_t)(~checksum);
}
实验效果
2 网络层原始套接字用法
原始套接字(Raw Socket)和标准套接字(Standard Socket)是在网络编程中使用的两种不同类型的套接字。
-
原始套接字(Raw Socket): 原始套接字允许应用程序直接访问网络协议栈,可以发送和接收原始的网络数据包。使用原始套接字,开发者可以自定义协议头部信息,以及对底层网络数据包进行更底层的控制和处理。它提供了更高级别的网络访问权限,但需要更高的权限级别(如管理员或特权用户)来使用。(整个数据包,包括IP的包头、TCP包头)
-
标准套接字(Standard Socket): 标准套接字是通过操作系统提供的网络套接字API进行通信的一种方式,使用TCP(Transmission Control Protocol)或UDP(User Datagram Protocol)等传输协议。标准套接字隐藏了底层网络协议的细节,提供了更高层次的抽象和简化的网络编程接口,使得开发人员可以更方便地进行网络通信和应用程序开发。
区别:
- 功能差异:原始套接字允许直接处理和操作底层的网络数据包,可以实现更高级别的网络控制和定制化,而标准套接字则提供了更高层的、抽象化的网络编程接口,适合日常的网络通信需求。
- 权限要求:原始套接字需要更高的权限级别来访问和使用,通常需要管理员或特权用户的权限。而标准套接字可以由一般用户进行操作。
- 使用场景:原始套接字通常用于进行网络嗅探、网络扫描、包分析等高级网络操作,以及特定网络协议的开发和研究。标准套接字则广泛应用于一般的网络通信场景,如客户端和服务器之间的数据传输。
在实际应用中,一般情况下使用标准套接字就能满足大部分的网络通信需求,而原始套接字主要由网络工具和专业开发者使用。
2.1 TCP原始套接字用法
tcp.c (主要用于网络工具的开发)
#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; //首地址是一样的,强转成ip数据报结构体
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); //强转buf中tcp部分
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) { //tcp数据报里面有没有放数据
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;
}
执行效果
IP协议包头结构体
/usr/include/linux/ip.h
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD) //小段模式
__u8 ihl:4, //位域操作,两个合起来占8个字节
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD) //大端模式
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
unsigned char tos; // 服务类型
unsigned short tot_len; // 总长度
unsigned short id; // 标识
unsigned short frag_off; // 分片偏移
unsigned char ttl; // 存活时间
unsigned char protocol; // 协议类型
unsigned short check; // 校验和
unsigned int saddr; // 源IP地址
unsigned int daddr; // 目标IP地址
// 其他字段...
};
/usr/include/linux/in.h
enum {
IPPROTO_IP = 0, /* Dummy protocol for TCP */
#define IPPROTO_IP IPPROTO_IP
IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
#define IPPROTO_ICMP IPPROTO_ICMP
IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
#define IPPROTO_IGMP IPPROTO_IGMP
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
#define IPPROTO_IPIP IPPROTO_IPIP
IPPROTO_TCP = 6, /* Transmission Control Protocol */
#define IPPROTO_TCP IPPROTO_TCP
IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
#define IPPROTO_EGP IPPROTO_EGP
IPPROTO_PUP = 12, /* PUP protocol
...
}
如何找到这些结构体
grep -irn "xxx" /usr/inclue/linux