第十五夜:网络编程3-常见的应用层协议

应用层常见协议及其实现库

DNS
利用C语言实现DNS查询和DNS响应包的解析。

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
/*
* 源码出处:https://www.cnblogs.com/qrxqrx/articles/8034781.html
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>

char dns_servers[1][16];//存放DNS服务器的IP
int dns_server_count = 0;

/*
**DNS报文中查询区域的查询类型
*/
#define A 1 //查询类型,表示由域名获得IPv4地址

void ngethostbyname(unsigned char*, int);
void ChangetoDnsNameFormat(unsigned char*, unsigned char*);

/*
**DNS报文首部
**这里使用了位域
*/
struct DNS_HEADER {
unsigned short id; //会话标识
unsigned char rd :1; // 表示期望递归
unsigned char tc :1; // 表示可截断的
unsigned char aa :1; // 表示授权回答
unsigned char opcode :4;
unsigned char qr :1; // 查询/响应标志,0为查询,1为响应
unsigned char rcode :4; //应答码
unsigned char cd :1;
unsigned char ad :1;
unsigned char z :1; //保留值
unsigned char ra :1; // 表示可用递归
unsigned short q_count; // 表示查询问题区域节的数量
unsigned short ans_count; // 表示回答区域的数量
unsigned short auth_count; // 表示授权区域的数量
unsigned short add_count; // 表示附加区域的数量
};

/*
**DNS报文中查询问题区域
*/
struct QUESTION {
unsigned short qtype;//查询类型
unsigned short qclass;//查询类
};
typedef struct {
unsigned char *name;
struct QUESTION *ques;
} QUERY;

/*
**DNS报文中回答区域的常量字段
*/
//编译制导命令
#pragma pack(push, 1)//保存对齐状态,设定为1字节对齐
struct R_DATA {
unsigned short type; //表示资源记录的类型
unsigned short _class; //类
unsigned int ttl; //表示资源记录可以缓存的时间
unsigned short data_len; //数据长度
};
#pragma pack(pop) //恢复对齐状态

/*
**DNS报文中回答区域的资源数据字段
*/
struct RES_RECORD {
unsigned char *name;//资源记录包含的域名
struct R_DATA *resource;//资源数据
unsigned char *rdata;
};

int main(int argc, char *argv[]) {
unsigned char hostname[100];
unsigned char dns_servername[100];
printf("请输入DNS服务器的IP:");
scanf("%s", dns_servername);
strcpy(dns_servers[0], dns_servername);
printf("请输入要查询IP的主机名:");
scanf("%s", hostname);

//由域名获得IPv4地址,A是查询类型
ngethostbyname(hostname, A);

return 0;
}

/*
**实现DNS查询功能
*/
void ngethostbyname(unsigned char *host, int query_type) {
unsigned char buf[65536], *qname, *reader;
int i, j, stop, s;

struct sockaddr_in a;//地址

struct RES_RECORD answers[20], auth[20], addit[20];//回答区域、授权区域、附加区域中的资源数据字段
struct sockaddr_in dest;//地址

struct DNS_HEADER *dns = NULL;
struct QUESTION *qinfo = NULL;

printf("\n所需解析域名:%s", host);

s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); //建立分配UDP套结字

dest.sin_family = AF_INET;//IPv4
dest.sin_port = htons(53);//53号端口
dest.sin_addr.s_addr = inet_addr(dns_servers[0]);//DNS服务器IP

dns = (struct DNS_HEADER *) &buf;
/*设置DNS报文首部*/
dns->id = (unsigned short) htons(getpid());//id设为进程标识符
dns->qr = 0; //查询
dns->opcode = 0; //标准查询
dns->aa = 0; //不授权回答
dns->tc = 0; //不可截断
dns->rd = 1; //期望递归
dns->ra = 0; //不可用递归
dns->z = 0; //必须为0
dns->ad = 0;
dns->cd = 0;
dns->rcode = 0;//没有差错
dns->q_count = htons(1); //1个问题
dns->ans_count = 0;
dns->auth_count = 0;
dns->add_count = 0;

//qname指向查询问题区域的查询名字段
qname = (unsigned char*) &buf[sizeof(struct DNS_HEADER)];

ChangetoDnsNameFormat(qname, host);//修改域名格式
qinfo = (struct QUESTION*) &buf[sizeof(struct DNS_HEADER)
+ (strlen((const char*) qname) + 1)]; //qinfo指向问题查询区域的查询类型字段

qinfo->qtype = htons(query_type); //查询类型为A
qinfo->qclass = htons(1); //查询类为1

//向DNS服务器发送DNS请求报文
printf("\n\n发送报文中...");
if (sendto(s, (char*) buf,sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)+ sizeof(struct QUESTION), 0, (struct sockaddr*) &dest,sizeof(dest)) < 0)
{
perror("发送失败!");
}
printf("发送成功!");

//从DNS服务器接受DNS响应报文
i = sizeof dest;
printf("\n接收报文中...");
if (recvfrom(s, (char*) buf, 65536, 0, (struct sockaddr*) &dest,(socklen_t*) &i) < 0) {
perror("接收失败!");
}
printf("接收成功!");

dns = (struct DNS_HEADER*) buf;

//将reader指向接收报文的回答区域
reader = &buf[sizeof(struct DNS_HEADER) + (strlen((const char*) qname) + 1)
+ sizeof(struct QUESTION)];

printf("\n\n响应报文包含: ");
printf("\n %d个问题", ntohs(dns->q_count));
printf("\n %d个回答", ntohs(dns->ans_count));
printf("\n %d个授权服务", ntohs(dns->auth_count));
printf("\n %d个附加记录\n\n", ntohs(dns->add_count));

/*
**解析接收报文
*/
reader = reader + sizeof(short);//short类型长度为32为,相当于域名字段长度,这时reader指向回答区域的查询类型字段
answers[i].resource = (struct R_DATA*) (reader);
reader = reader + sizeof(struct R_DATA);//指向回答问题区域的资源数据字段
if (ntohs(answers[i].resource->type) == A) //判断资源类型是否为IPv4地址
{
answers[i].rdata = (unsigned char*) malloc(ntohs(answers[i].resource->data_len));//资源数据
for (j = 0; j < ntohs(answers[i].resource->data_len); j++)
{
answers[i].rdata[j] = reader[j];
}
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0';
reader = reader + ntohs(answers[i].resource->data_len);
}

//显示查询结果
if (ntohs(answers[i].resource->type) == A) //判断查询类型IPv4地址
{
long *p;
p = (long*) answers[i].rdata;
a.sin_addr.s_addr = *p;
printf("IPv4地址:%s\n", inet_ntoa(a.sin_addr));
}

return;
}

/*
**从www.baidu.com转换到3www5baidu3com
*/
void ChangetoDnsNameFormat(unsigned char* dns, unsigned char* host) {
int lock = 0, i;
strcat((char*) host, ".");

for (i = 0; i < strlen((char*) host); i++) {
if (host[i] == '.') {
*dns++ = i - lock;
for (; lock < i; lock++) {
*dns++ = host[lock];
}
lock++;
}
}
*dns++ = '\0';
}

HTTP

利用C语言实现HTTP请求,可以采用原生Socket往端口发包,根据HTTP协议自行构造请求的方式。

但更方便的方式是使用libcurl库。自行构造Socket请求的方式既不能保证性能,又无法保证安全。

libcurl库至少是相对完善的第三方库。

详细用法,参考libcurl官方文档以及libcurl官方样例

SMTP

SMTP一例

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
/*
* 源码出处:https://blog.csdn.net/zx824/article/details/7387418
*/
#ifdef WIN32
#include <windows.h>
#include <stdio.h>
#else
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <sys/time.h>
#endif
struct data6{
unsigned int d4:6;
unsigned int d3:6;
unsigned int d2:6;
unsigned int d1:6;
};
// 协议中加密部分使用的是base64方法
char con628(char c6);
void base64(char *dbuf,char *buf128,int len);
void sendemail(char *email,char *body);
int open_socket(struct sockaddr *addr);

int main()
{
char email[] = "[email protected]";
char body[] = "From: \"yyy\"<[email protected]>\r\n"
"To: \"xxx\"<[email protected]>\r\n"
"Subject: Hello\r\n\r\n"
"Hello World, Hello Email!";
sendemail(email, body);
return 0;
}

char con628(char c6)
{
char rtn = '\0';
if (c6 < 26) rtn = c6 + 65;
else if (c6 < 52) rtn = c6 + 71;
else if (c6 < 62) rtn = c6 - 4;
else if (c6 == 62) rtn = 43;
else rtn = 47;
return rtn;
}

// base64的实现
void base64(char *dbuf, char *buf128, int len)
{
struct data6 *ddd = NULL;
int i = 0;
char buf[256] = {0};
char *tmp = NULL;
char cc = '\0';
memset(buf, 0, 256);
strcpy(buf, buf128);
for(i = 1; i <= len/3; i++)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
dbuf[(i-1)*4+3] = con628((unsigned int)ddd->d4);
}
if(len%3 == 1)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = '=';
dbuf[(i-1)*4+3] = '=';
}
if(len%3 == 2)
{
tmp = buf+(i-1)*3;
cc = tmp[2];
tmp[2] = tmp[0];
tmp[0] = cc;
ddd = (struct data6 *)tmp;
dbuf[(i-1)*4+0] = con628((unsigned int)ddd->d1);
dbuf[(i-1)*4+1] = con628((unsigned int)ddd->d2);
dbuf[(i-1)*4+2] = con628((unsigned int)ddd->d3);
dbuf[(i-1)*4+3] = '=';
}
return;
}
// 发送邮件
void sendemail(char *email, char *body)
{
int sockfd = 0;
struct sockaddr_in their_addr = {0};
char buf[1500] = {0};
char rbuf[1500] = {0};
char login[128] = {0};
char pass[128] = {0};
#ifdef WIN32
WSADATA WSAData;
WSAStartup(MAKEWORD(2, 2), &WSAData);
#endif
memset(&their_addr, 0, sizeof(their_addr));
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(25);
their_addr.sin_addr.s_addr = inet_addr("123.58.177.49");//qq smtp 服务器
// 连接邮件服务器,如果连接后没有响应,则2 秒后重新连接
sockfd = open_socket((struct sockaddr *)&their_addr);
memset(rbuf,0,1500);
while(recv(sockfd, rbuf, 1500, 0) == 0)
{
printf("reconnect...\n");
//Sleep(2);
//close(sockfd);
sockfd = open_socket((struct sockaddr *)&their_addr);

memset(rbuf,0,1500);
}

printf("%s\n", rbuf);

// EHLO
memset(buf, 0, 1500);
sprintf(buf, "EHLO abcdefg-PC\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// AUTH LOGIN
memset(buf, 0, 1500);
sprintf(buf, "AUTH LOGIN\r\n");
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// USER
memset(buf, 0, 1500);
sprintf(buf,"[email protected]");
memset(login, 0, 128);
base64(login, buf, strlen(buf));
sprintf(buf, "%s\r\n", login);
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// PASSWORD
sprintf(buf, "password");
memset(pass, 0, 128);
base64(pass, buf, strlen(buf));
sprintf(buf, "%s\r\n", pass);
send(sockfd, buf, strlen(buf), 0);
printf("%s\n", buf);

memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// MAIL FROM
memset(buf, 0, 1500);
sprintf(buf, "MAIL FROM: <[email protected]>\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// RCPT TO 第一个收件人
sprintf(buf, "RCPT TO:<%s>\r\n", email);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// DATA 准备开始发送邮件内容
sprintf(buf, "DATA\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// 发送邮件内容,\r\n.\r\n内容结束标记
sprintf(buf, "%s\r\n.\r\n", body);
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// QUIT
sprintf(buf, "QUIT\r\n");
send(sockfd, buf, strlen(buf), 0);
memset(rbuf, 0, 1500);
recv(sockfd, rbuf, 1500, 0);
printf("%s\n", rbuf);

// VC2005 需要使用
//closesocket(sockfd);
close(sockfd);
#ifdef WIN32
WSACleanup();
#endif
return;

}
// 打开TCP Socket连接
int open_socket(struct sockaddr *addr)
{
int sockfd = 0;
sockfd=socket(PF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
fprintf(stderr, "Open sockfd(TCP) error!\n");
exit(-1);
}
if(connect(sockfd, addr, sizeof(struct sockaddr)) < 0)
{
fprintf(stderr, "Connect sockfd(TCP) error!\n");
exit(-1);
}
return sockfd;
}

其他协议,后续学到再补充。

第十六夜:编写一个Linux的服务 第十四夜:网络编程2-Socket通信
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×