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

传输层下的底层应用,链路层收发数据帧。发送自定义的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

#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

#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

#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

#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

#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;
}

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值