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