lv8 嵌入式开发-网络编程开发 19 原始套接字

目录

1 链路层原始套接字用法

1.1 利用原始套接字实现类似wireshark的功能

1.2 利用原始套接字实现ping命令

2 网络层原始套接字用法

2.1 TCP原始套接字用法


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, &ether_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)是在网络编程中使用的两种不同类型的套接字。

  1. 原始套接字(Raw Socket): 原始套接字允许应用程序直接访问网络协议栈,可以发送和接收原始的网络数据包。使用原始套接字,开发者可以自定义协议头部信息,以及对底层网络数据包进行更底层的控制和处理。它提供了更高级别的网络访问权限,但需要更高的权限级别(如管理员或特权用户)来使用。(整个数据包,包括IP的包头、TCP包头)

  2. 标准套接字(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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

4IOT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值