《Linux C/C++服务器开发实践》之第6章 原始套接字编程

《Linux C/C++服务器开发实践》之第6章 原始套接字编程

    • [6.1 原始套接字的强大功能](#6.1 原始套接字的强大功能)
    • [6.2 创建原始套接字的方式](#6.2 创建原始套接字的方式)
    • [6.3 原始套接字的基本编程步骤](#6.3 原始套接字的基本编程步骤)
      • [6.3.1 创建原始套接字函数socket](#6.3.1 创建原始套接字函数socket)
      • [6.3.2 接收函数recvfrom](#6.3.2 接收函数recvfrom)
      • [6.3.3 发送函数sendto](#6.3.3 发送函数sendto)
    • [6.4 AF_INET方式捕获报文](#6.4 AF_INET方式捕获报文)
    • [6.5 PF_PACKET方式捕获报文](#6.5 PF_PACKET方式捕获报文)

传输层下的底层应用,链路层收发数据帧。发送自定义的IP报文、UDP报文、TCP报文、ICMP报文、捕获所有经过本机网卡的数据报、伪装本机IP地址、操作IP首部或传输层协议首部等。

6.1 原始套接字的强大功能

(1)收发ICMPv4、ICMPv6和IGMP数据报,只需IP头部预定义好网络层上的协议号。

(2)IP报头某些字段进行设置。

(3)收发不处理或不认识的IPv4数据报。

(4)让网卡处于混杂模式,从而能捕获流经网卡的所有数据报。

6.2 创建原始套接字的方式

接收本机网卡上的数据帧或者数据报。

(1)socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)

发送接收IP数据报,从而分析TCP、UDP和ICMP。

(2)socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))

发送接收以太网数据帧,从而解析链路层以上的协议报文。

(2)socket(AF_INET, SOCK_PACKET, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))

过时。

6.3 原始套接字的基本编程步骤

类似UDP编程。

发送步骤:

(1)定义相关报头,IP报头等。

(2)创建原始套接字。

(3)设置对端IP地址,不涉及端口号(传输层才有)。

(4)组织IP数据报,即填充首部和数据部分。

(5)使用发送函数发送数据报。

(6)关闭释放套接字。

接收步骤:

(1)定义相关报头,IP报头等。

(2)创建原始套接字。

(3)原始套接字绑定到本地一个协议地址上。

(4)使用接收函数接收数据报。

(5)过滤数据报,即判断是否是需要的数据报。

(6)处理数据报。

(7)关闭释放套接字。

6.3.1 创建原始套接字函数socket

(1)socket(AF_INET, SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)

发送接收IP数据报,从而分析TCP、UDP和ICMP。

(2)socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))

发送接收以太网数据帧,从而解析链路层以上的协议报文。

6.3.2 接收函数recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

6.3.3 发送函数sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *des_addr, socklen_t *addrlen);

6.4 AF_INET方式捕获报文

06.01.send.c

cpp 复制代码
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

int main()
{
	int size = sizeof(struct sockaddr_in);
	struct sockaddr_in saddr;
	memset(&saddr, 0, size);

	// 设置服务端的地址信息
	saddr.sin_family = AF_INET;
	//saddr.sin_addr.s_addr = inet_addr("192.168.234.128"); // 该ip为服务端所在的ip
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 该ip为服务端所在的ip
	saddr.sin_port = htons(9999);

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建udp 的套接字
	if (sockfd < 0)
	{
		perror("failed socket");
		return -1;
	}

	// 设置端口复用,就是释放后,能马上再次使用
	char on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	// 发送信息给服务端
	puts("please enter data:");
	char wbuf[] = "send udp";
	//char wbuf[50];
	//scanf("%s", wbuf, sizeof(wbuf));
	int ret = sendto(sockfd, wbuf, sizeof(wbuf), 0, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
	if (ret < 0)
		perror("sendto failed");
	close(sockfd);
	return 0;
}

06.01.rcver.c

cpp 复制代码
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

typedef struct _IP_HEADER // IP头定义,共20个字节
{
	char m_cVersionAndHeaderLen; // 版本信息(前4位),头长度(后4位)
	char m_cTypeOfService;		 // 服务类型8位
	short m_sTotalLenOfPacket;	 // 数据包长度
	short m_sPacketID;			 // 数据包标识
	short m_sSliceinfo;			 // 分片使用
	char m_cTTL;				 // 存活时间
	char m_cTypeOfProtocol;		 // 协议类型
	short m_sCheckSum;			 // 校验和
	unsigned int m_uiSourIp;	 // 源IP地址
	unsigned int m_uiDestIp;	 // 目的IP地址
} IP_HEADER, *PIP_HEADER;

typedef struct _UDP_HEADER // UDP首部定义,共8个字节
{
	unsigned short m_usSourPort; // 源端口号16bit
	unsigned short m_usDestPort; // 目的端口号16bit
	unsigned short m_usLength;	 // 数据包长度16bit
	unsigned short m_usCheckSum; // 校验和16bit
} UDP_HEADER, *PUDP_HEADER;

int main()
{
	// 设置地址信息,ip信息
	int size = sizeof(struct sockaddr_in);
	struct sockaddr_in saddr;
	memset(&saddr, 0, size);
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	saddr.sin_port = htons(8888); // 这里的端口无所谓

	// 创建udp 的套接字
	int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); // 该原始套接字使用UDP协议
	if (sockfd < 0)
	{
		perror("socket failed");
		return -1;
	}

	// 设置端口复用
	char on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	// 绑定地址信息,ip信息
	int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
	if (ret < 0)
	{
		perror("sbind failed");
		return -1;
	}

	struct sockaddr_in raddr;
	int val = sizeof(struct sockaddr);
	char rbuf[500];
	IP_HEADER iph;
	UDP_HEADER udph;
	while (1) // 接收客户端发来的消息
	{
		puts("waiting data");
		ret = recvfrom(sockfd, rbuf, 500, 0, (struct sockaddr *)&raddr, (socklen_t *)&val);
		if (ret < 0)
		{
			perror("recvfrom failed");
			return -1;
		}
		memcpy(&iph, rbuf, 20);		 // 把缓冲区前20个字节拷贝到iph中
		memcpy(&udph, rbuf + 20, 8); // 把ip包头后的8字节拷贝到udph中

		int srcp = ntohs(udph.m_usSourPort);
		struct in_addr ias, iad;
		ias.s_addr = iph.m_uiSourIp;
		iad.s_addr = iph.m_uiDestIp;

		char dip[100];
		strcpy(dip, inet_ntoa(iad));
		printf("(sIp=%s, sPort=%d), \n(dIp=%s, dPort=%d)\n", inet_ntoa(ias), ntohs(udph.m_usSourPort), dip, ntohs(udph.m_usDestPort));
		printf("recv data: %s\n", rbuf + 28);
	}

	close(sockfd); // 关闭原始套接字

	return 0;
}

//sudo ./06.01.rcver
//echo "xxxx" > /dev/udp/localhost/10001

06.02.rcver.c

cpp 复制代码
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

typedef struct _IP_HEADER // IP头定义,共20个字节
{
	char m_cVersionAndHeaderLen; // 版本信息(前4位),头长度(后4位)
	char m_cTypeOfService;		 // 服务类型8位
	short m_sTotalLenOfPacket;	 // 数据包长度
	short m_sPacketID;			 // 数据包标识
	short m_sSliceinfo;			 // 分片使用
	char m_cTTL;				 // 存活时间
	char m_cTypeOfProtocol;		 // 协议类型
	short m_sCheckSum;			 // 校验和
	unsigned int m_uiSourIp;	 // 源IP地址
	unsigned int m_uiDestIp;	 // 目的IP地址
} IP_HEADER, *PIP_HEADER;

typedef struct _UDP_HEADER // UDP头定义,共8个字节
{
	unsigned short m_usSourPort; // 源端口号16bit
	unsigned short m_usDestPort; // 目的端口号16bit
	unsigned short m_usLength;	 // 数据包长度16bit
	unsigned short m_usCheckSum; // 校验和16bit
} UDP_HEADER, *PUDP_HEADER;

int main()
{
	// 设置地址信息,ip信息
	int size = sizeof(struct sockaddr_in);
	struct sockaddr_in saddr;
	memset(&saddr, 0, size);
	saddr.sin_family = AF_INET;
	//saddr.sin_addr.s_addr = inet_addr("192.168.0.153"); // 本机的IP地址,但和发送端设定的目的IP地址不同。
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); 
	saddr.sin_port = htons(8888);

	// 创建udp 的套接字
	int sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 该原始套接字使用ICMP协议
	if (sockfd < 0)
	{
		perror("socket failed");
		return -1;
	}

	// 设置端口复用
	char on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	// 绑定地址信息,ip信息
	int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
	if (ret < 0)
	{
		perror("bind failed");
		return -1;
	}

	struct sockaddr_in raddr;
	int val = sizeof(struct sockaddr);
	char rbuf[500];
	IP_HEADER iph;
	UDP_HEADER udph;
	while (1) // 接收客户端发来的消息
	{
		puts("waiting data");
		ret = recvfrom(sockfd, rbuf, 500, 0, (struct sockaddr *)&raddr, (socklen_t *)&val);
		if (ret < 0)
		{
			printf("recvfrom failed:%d", errno);
			return -1;
		}
		memcpy(&iph, rbuf, 20);
		memcpy(&udph, rbuf + 20, 8);

		int srcp = ntohs(udph.m_usSourPort);
		struct in_addr ias, iad;
		ias.s_addr = iph.m_uiSourIp;
		iad.s_addr = iph.m_uiDestIp;
		char strDip[50] = "";
		strcpy(strDip, inet_ntoa(iad));
		printf("(sIp=%s, sPort=%d), \n(dIp=%s, dPort=%d)\n", inet_ntoa(ias), ntohs(udph.m_usSourPort), strDip, ntohs(udph.m_usDestPort));
		printf("recv data : %s\n", rbuf + 28);
	}

	close(sockfd); // 关闭原始套接字

	return 0;
}

//sudo ./06.02.rcver
//echo "xxxx" > /dev/udp/localhost/10001

6.5 PF_PACKET方式捕获报文

06.03.rcver.c

cpp 复制代码
#include <stdio.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h> //for inet_ntoa
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if_ether.h>
#include <net/if.h>
#include <unistd.h> //for close
#include <string.h>

typedef struct _IP_HEADER // IP头定义,共20个字节
{
	char m_cVersionAndHeaderLen; // 版本信息(前4位),头长度(后4位)
	char m_cTypeOfService;		 // 服务类型8位
	short m_sTotalLenOfPacket;	 // 数据包长度
	short m_sPacketID;			 // 数据包标识
	short m_sSliceinfo;			 // 分片使用
	char m_cTTL;				 // 存活时间
	char m_cTypeOfProtocol;		 // 协议类型
	short m_sCheckSum;			 // 校验和
	unsigned int m_uiSourIp;	 // 源IP地址
	unsigned int m_uiDestIp;	 // 目的IP地址
} IP_HEADER, *PIP_HEADER;

typedef struct _UDP_HEADER // UDP头定义,共8个字节
{
	unsigned short m_usSourPort; // 源端口号16bit
	unsigned short m_usDestPort; // 目的端口号16bit
	unsigned short m_usLength;	 // 数据包长度16bit
	unsigned short m_usCheckSum; // 校验和16bit
} UDP_HEADER, *PUDP_HEADER;

int main()
{
	int sock;
	if ((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0)
	{ // htons(ETH_P_ALL)
		perror("socket");
		return -1;
	}

	char buffer[2048];
	IP_HEADER iph;
	UDP_HEADER udph;
	long long cn = 1;
	while (1)
	{
		int n = recvfrom(sock, buffer, 2048, 0, NULL, NULL);
		/* Check to see if the packet contains at least
		 * complete Ethernet (14), IP (20) and TCP/UDP
		 * (8) headers.
		 */
		if (n < 42)
		{
			perror("recvfrom():");
			printf("Incomplete packet (errno is %d)\n", errno);
			close(sock);
			return -1;
		}

		unsigned char *ethhead = (unsigned char *)buffer;
		/*
		printf("Source MAC address: "
			"%02x:%02x:%02x:%02x:%02x:%02x\n",
			ethhead[0], ethhead[1], ethhead[2],
			ethhead[3], ethhead[4], ethhead[5]);
		printf("Destination MAC address: "
			"%02x:%02x:%02x:%02x:%02x:%02x\n",
			ethhead[6], ethhead[7], ethhead[8],
			ethhead[9], ethhead[10], ethhead[11]);
			*/
		unsigned char *iphead = ethhead + 14; /* Skip Ethernet header */
		if (*iphead == 0x45)
		{ /* Double check for IPv4
		   * and no options present */
			// printf("Layer-4 protocol %d,", iphead[9]);
			memcpy(&iph, iphead, 20);
			if (iphead[12] == iphead[16] && iphead[13] == iphead[17] && iphead[14] == iphead[18] && iphead[15] == iphead[19])
				continue;

			if (iphead[12] == 127)
				continue;

			printf("-----cn=%lld-----\n", cn++);
			printf("%d bytes read\n", n);
			/*这样也可以得到IP和端口
			printf("Source host %d.%d.%d.%d\n",iphead[12], iphead[13],
				iphead[14], iphead[15]);
			printf("Dest host %d.%d.%d.%d\n",iphead[16], iphead[17],
				iphead[18], iphead[19]);
					*/

			struct in_addr ias, iad;
			ias.s_addr = iph.m_uiSourIp;
			iad.s_addr = iph.m_uiDestIp;
			char dip[100];
			strcpy(dip, inet_ntoa(iad));
			printf("sIp=%s,   dIp=%s, \n", inet_ntoa(ias), dip);

			// printf("Layer-4 protocol %d,", iphead[9]);//如果需要,可以打印下协议号
			if (IPPROTO_ICMP == iphead[9])
				puts("Receive ICMP package.");
			if (IPPROTO_UDP == iphead[9])
			{
				memcpy(&udph, iphead + 20, 8); // 加20是越过IP首部
				printf("Source, Dest ports %d,%d\n", udph.m_usSourPort, udph.m_usDestPort);
				printf("Receive UDP package, data:%s\n", iphead + 28); // 越过ip首部和udp首部
			}

			if (IPPROTO_TCP == iphead[9])
				puts("Receive TCP package.");
		}
	}

	return 0;
}

06.03.winSend.c

cpp 复制代码
#include <stdio.h>
#include <winsock2.h>

// #define _WINSOCK_DEPRECATED_NO_WARNINGS
// #pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);		   // 制作Winsock库的版本号
	int err = WSAStartup(wVersionRequested, &wsaData); // 初始化Winsock库
	if (err != 0)
		return 1;

	int size = sizeof(struct sockaddr_in);
	struct sockaddr_in saddr;
	memset(&saddr, 0, size);

	// 设置地址信息,ip信息
	saddr.sin_family = AF_INET;
	saddr.sin_addr.s_addr = inet_addr("192.168.35.128"); // 该ip为服务端所在的ip
	saddr.sin_port = htons(9999);

	int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建udp 的套接字
	if (sockfd < 0)
	{
		perror("failed socket");
		return -1;
	}

	// 设置端口复用
	char on = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	// 发送信息给服务端
	puts("please enter data:");
	// char wbuf[50];
	// scanf_s("%s", wbuf, sizeof(wbuf));
	char wbuf[] = "send udp";
	int ret = sendto(sockfd, wbuf, sizeof(wbuf), 0, (struct sockaddr *)&saddr, sizeof(struct sockaddr));
	if (ret < 0)
		perror("sendto failed");
	closesocket(sockfd);

	WSACleanup(); // 释放套接字库
	return 0;
}
相关推荐
zbdx不知名菜鸡2 小时前
linux基础 超级笔记
linux·运维·服务器
wellnw2 小时前
【IPv6】IPv6地址格式及地址分类(组播、单播、任播)整理
运维·服务器·网络
wanhengwangluo3 小时前
高防服务器的优劣势有哪些?
运维·服务器
总是学不会.3 小时前
SpringBoot项目:前后端打包与部署(使用 Maven)
java·服务器·前端·后端·maven
shelby_loo4 小时前
在Ubuntu下通过Docker部署NAS服务器
服务器·ubuntu·docker
极客小张4 小时前
基于STM32的智能家居语音控制系统:集成LD3320、ESP8266设计流程
c语言·stm32·物联网·算法·毕业设计·课程设计·语言识别
卑微求AC5 小时前
继电器原理及应用
c语言·开发语言·51单片机·嵌入式
꧁༺❀氯ྀൢ躅ྀൢ❀༻꧂5 小时前
算法与程序课程设计——观光铁路
c语言·c++·算法·课程设计·dijkstra 算法·spfa算法
李的阿洁5 小时前
OSPF的不规则区域
运维·服务器·网络
网安老伯5 小时前
【2024版】最新kali linux入门及常用简单工具介绍(非常详细)零基础入门到精通,收藏这一篇就够了_kalilinux
linux·运维·服务器·开发语言·web安全·网络安全·xss