C++后端开发入门-UDP与原始套接字编程

UDP

当服务端接收缓冲区满了,后来收到的包会被丢弃,且服务端接收包后进行处理时也不收包,缓解措施可以单开一个进程,并增加延时。

UDP所用套接字函数在TCP篇都提到过,下面是例子,服务端为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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>
#include <stdio.h>
char rbuf[50];
int main() {
int sockfd, size, ret;
char on = 1;
struct sockaddr_in saddr;
struct sockaddr_in raddr;
size = sizeof(struct sockaddr_in); //设置地址信息,ip信息
memset(&saddr, 0, size);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建udp 的套接字
if (sockfd < 0) {
puts("socket failed");
return -1;
};
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口复用
ret = bind(sockfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)); //绑定地址信息,ip信息
if (ret < 0) {
puts("sbind failed");
return -1;
};
int val = sizeof(struct sockaddr);
while (1) { //循环接收客户端发来的消息
puts("waiting data");
ret = recvfrom(sockfd, rbuf, 50, 0, (struct sockaddr*)&raddr, (socklen_t*)&val);
if (ret < 0)
perror("recvfrom failed");
printf("recv data :%s\n", rbuf);
memset(rbuf, 0, 50);
};
close(sockfd); //关闭udp套接字,这里不可达的。
getchar();
return 0;
};

客户端为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <cstdio>
#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>
char wbuf[50];
int main() {
int sockfd, size, ret;
char on = 1;
struct sockaddr_in saddr;
size = sizeof(struct sockaddr_in);
memset(&saddr, 0, size);
saddr.sin_family = AF_INET; //设置地址信息,ip信息
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = inet_addr("192.168.0.153");//该ip为服务端所在的虚拟机ip
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建udp 的套接字
if (sockfd < 0) {
perror("failed socket");
return -1;
};
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口复用
while (1) {//循环发送信息给服务端
puts("please enter data:");
scanf("%s", wbuf, sizeof(wbuf));
ret = sendto(sockfd, wbuf, sizeof(wbuf), 0, (struct sockaddr*)&saddr, sizeof(struct sockaddr));
if (ret < 0)
perror("sendto failed");
memset(wbuf, 0, sizeof(wbuf));
};
close(sockfd);
getchar();
return 0;
};

原始套接字编程

原始套接字指在传输层下面,即链路层收发数据帧。利用原始套接字可以开发自定义IP/TCP/UDP/ICMP报文、伪装本机IP/MAC地址、捕获所有经过本机网卡的数据报、收发没经过内核协议栈的数据报、操作IP首部或传输层协议等,还可以制作网络嗅探工具、实现拒绝服务攻击、IP地址欺骗等,需要在管理员权限下使用。

原始套接字的创建:

1
2
socket(AF_INET,SOCK_RAW,IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP); //发送接收IP数据报 用于分析TCP/UDP/ICMP等
socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL)); //发送接收以太网数据帧 用于解析链路层以上的IP/ARP数据报等协议

AF_INET方式

捕获UDP报文

发送端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <cstdio>
#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>
char wbuf[50];
int main() {
int sockfd, size, ret;
char on = 1;
struct sockaddr_in saddr;
size = sizeof(struct sockaddr_in);
memset(&saddr, 0, size);
saddr.sin_family = AF_INET; //设置服务端的地址信息
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = inet_addr("192.168.0.118");//该ip为服务端所在的ip
sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建udp 的套接字
if (sockfd < 0) {
perror("failed socket");
return -1;
};
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口复用,就是释放后,能马上再次使用
puts("please enter data:"); //发送信息给服务端
scanf("%s", wbuf, sizeof(wbuf));
ret = sendto(sockfd, wbuf, sizeof(wbuf), 0, (struct sockaddr*)&saddr, sizeof(struct sockaddr));
if (ret < 0)
perror("sendto failed");
close(sockfd);
getchar();
return 0;
};

接收端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <cstdio>
#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>
char rbuf[500];
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 sockfd, size, ret;
char on = 1;
struct sockaddr_in saddr;
struct sockaddr_in raddr;
IP_HEADER iph;
UDP_HEADER udph;
size = sizeof(struct sockaddr_in); //设置地址信息,ip信息
memset(&saddr, 0, size);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888); //这里的端口无所谓
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);//创建udp 的套接字 该原始套接字使用UDP协议
if (sockfd < 0) {
perror("socket failed");
return -1;
};
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口复用
ret = bind(sockfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)); //绑定地址信息,ip信息
if (ret < 0) {
perror("sbind failed");
return -1;
};
int val = sizeof(struct sockaddr);
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);//关闭原始套接字
getchar();
return 0;
};

捕获PING包

服务端可用系统自带ping命令,接收端为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <cstdio>
#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>
char rbuf[500];
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 sockfd, size, ret;
char on = 1;
struct sockaddr_in saddr;
struct sockaddr_in raddr;
IP_HEADER iph;
UDP_HEADER udph;
size = sizeof(struct sockaddr_in); //设置地址信息,ip信息
memset(&saddr, 0, size);
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = inet_addr("192.168.0.153");//本机的IP地址,但和发送端设定的目的IP地址不同。
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);//创建udp 的套接字 该原始套接字使用ICMP协议
if (sockfd < 0) {
perror("socket failed");
return -1;
};
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); //设置端口复用
ret = bind(sockfd, (struct sockaddr*)&saddr, sizeof(struct sockaddr)); //绑定地址信息,ip信息
if (ret < 0) {
perror("bind failed");
getchar();
return -1;
};
int val = sizeof(struct sockaddr);
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;
};

PF_PACKET方式

接下来抓取ICMP和UDP报文,接收端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#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 argc, char** argv) {
int sock, n;
char buffer[2048];
unsigned char* iphead, * ethhead;
struct sockaddr_in saddr;
struct sockaddr_in raddr;
IP_HEADER iph;
UDP_HEADER udph;
if ((sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))) < 0) { //htons(ETH_P_ALL)
perror("socket");
return -1;
};
long long cn = 1;
while (1) {
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;
};
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]);
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=%ld-----\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;
};