20201220蔡笃俊《信息安全系统设计与实现》第十三章学习笔记
一、任务内容
- 自学教材第13章,提交学习笔记(10分)
- 知识点归纳以及自己最有收获的内容 (3分)
- 问题与解决思路(2分)
- 实践内容与截图,代码链接(3分)
- ...(知识的结构化,知识的完整性等,提交markdown文档,使用openeuler系统等)(2分)
二、知识点归纳
本章论述了TCP/IP和网络编程,分为两个部分。
第一部分论述了TCP/IP协议及其应用,具体包括TCP/IP栈、IP地址、主机名、DNS、IP数据包和路由器;介绍了TCP/P网络中的UDP和TCP协议、端口号和数据流;阐述了服务器-客户机计算模型和套接字编程接口;通过使用UDP和TCP套接字的示例演示了网络编程。第一个编程项目可实现一对通过互联网执行文件操作的TCP服务器–客户机,可让用户定义其他通信协议来可靠地传输文件内容。
本章的第二部分介绍了Web和CGI编程,解释了HTTP编程模型、Web页面和Web浏览器;展示了如何配置Linux HTTPD服务器来支持用户Web页面、PHP和CGI编程;阐释了客户机和服务器端动态Web页面;演示了如何使用PHP和CGI创建服务器端动态Web页面。
(一)计算机网络知识
1. TCP/IP协议
TCP/IP协议包括ICMP、IP、telnet、udp等协议,是利用IP进行通信时所必须用到的协议群的统称。
2. IP主机和IP地址
主机是支持TCP/IP 协议的计算机或设备。每个主机由一个32位的IP地址来标识。为了方便起见,32位的P地址号通常用点记法表示,例如:134.121.64.1,其中各个字节用点号分开。主机也可以用主机名来表示,如dns1.eec.wsu.edu。IP地址分为两部分,即 NetworkID字段和HostID字段。根据划分,IP地址分为A~E类。例如,一个B类P地址被划分为一个16位NetworkID,其中前2位是10,然后是一个16位的HostID字段。发往P地址的数据包首先被发送到具有相同networkID 的路由器。路由器将通过HostID将数据包转发到网络中的特定主机。每个主机都有一个本地主机名localhost,默认P地址为127.0.0.1。本地主机的链路层是一个回送虚拟设备,它将每个数据包路由回同一个 localhost。
3. IP协议
用于在IP主机之间发送/接收数据包。IP尽最大努力运行。IP主机只向接收主机发送数据包,但它不能保证数据包会被发送到它们的目的地,也不能保证按顺序发送。
4. IP数据包
由IP头、发送方地址和接收方I地址以及数据组成。每个数据包的大小最大为64KB。IP头包含有关数据包的更多信息,例如数据包的总长度、数据包使用TCP还是UDP、生存时间(TTL)计数、错误检测的校验和等。
6. 路由器
是接收和转发数据包的特殊IP主机。一个IP数据包可能会经过许多路由器,或者跳跃到达某个目的地。每个IP包在IP报头中都有一个8位生存时间(TTL)计数,其最大值为255。在每个路由器上,TTL会减小1。如果TTL减小到0,而包仍然没有到达目的地,则会直接丢弃它。这可以防止任何数据包在IP网络中无限循环。
7. UDP
在IP上运行,用于发送/接收数据报。与IP类似,UDP不能保证可靠性,但是快速高效。ping是一个向目标主机发送带时间戳UDP包的应用程序。接收到一个pinging数据包后,目标主机将带有时间戳的UDP包回送给发送者,让发送者可以计算和显示往返时间。如果目标主机不存在或宕机,当TTL减小为0时,路由器将会丢弃pinging UDP数据包。在这种情况下,用户会发现目标主机没有任何响应。用户可以尝试再次ping,或者断定目标主机宕机。
8. TCP
是一种面向连接的协议,用于发送/接收数据流。TCP也可在IP上运行,但它保证了可靠的数据传输。通常,UDP类似于发送邮件的USPS,而TCP类似于电话连接。
9. 端口编号
端口号是分配给应用程序的唯一无符号短整数。要想使用UDP或TCP,应用程序(进程)必须先选择或获取一个端口号。前1024个端口号已被预留。其他端口号可供一般使用。应用程序可以选择一个可用端口号,也可以让操作系统内核分配端口号。
10. 网络和主机字节序
计算机可以使用大端字节序,也可以使用小端字节序。在互联网上,数据始终按网络序排列,这是大端。在小端机器上,例如基于Intel x86的PC,htons()、htonl()、ntohs()、ntohl()等库函数,可在主机序和网络序之间转换数据。例如,PC中的端口号1234按主机字节序(小端)是无符号短整数。必须先通过htons(1234)把它转换成网络序,才能使用。相反,从互联网收到的端口号必须先通过ntohs(port)转换为主机序。
(二)套接字API
在网络编程中,TCP/IP的用户界面是通过一系列C语言库函数和系统调用来实现的,这些函数和系统调用统称为套接字API((Rago1993;Stevens等2004)。为了使用套接字 API,我们需要套接字地址结构,它用于标识服务器和客户机。netdbh和sys/socketh中有套接字地址结构的定义。
1.套接字地址
struct sockaddr_in {
sa_family_t sin_family; // AF_INET for TCP/IP
in port_t sinport; // port number
struct in_addr sin_addr; // IP address
};
struct in_addr{ //internet address
uint32_t s_addr; // IP address in network byte order
};
在套接字地址结构中
- TCP/IP网络的sin family始终设置为AFINET。
- sin_port包含按网络字节顺序排列的端口号。
- sin_addr是按网络字节顺序排列的主机IP地址。
2. 套接字API
- int套接字 (int域 ,int类型,int协议)
- int bind(int sockfd,struct sockaddr *addr,socklen_t addrlen)
- UDP套接字
- UDP套接字使用sendto()/recvfrom0)来发送/接收数据报。
- TCP套接字
- 在创建套接字并将其绑定到服务器地址之后,TCP服务器使用listen()和accept()来接收来自客户机的连接
三、实践过程与截图
socket链接
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 256
#define PORT 7777
void sys_err(char *msg)
{
perror(msg);
exit(-1);
}
int main(int argc , char **argv)
{
int sockFd,n;
char recvLine[MAXLINE];
struct sockaddr_in servAddr;
if (argc != 2)
{
sys_err("usage: a.out <IPaddress>");
}
sockFd=socket(AF_INET,SOCK_STREAM,0);
memset(&servAddr,0,sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(PORT);
if (inet_pton(AF_INET,argv[1],&servAddr.sin_addr) <= 0)
{
sys_err("inet_pton error");
}
connect(sockFd,(struct sockaddr *)&servAddr,sizeof(servAddr));
while((n=read(sockFd,recvLine,MAXLINE)) >0 )
{
recvLine[n] = '\0';
if(fputs(recvLine,stdout) == EOF)
{
sys_err("fputs error");
}
}
if(n <0)
{
sys_err("read error");
}
return 0;
}
socket通信:
服务器端代码:
点击查看代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "aes_options.h" //add
int main()
{
int server_fd;
int client_fd;
int len;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int sin_size;
char buffer[BUFSIZ];
// printf("%d",BUFSIZ);
memset(&server_addr, 0, sizeof(server_addr)); // initialize struct
memset(&server_addr, 0, sizeof(client_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(9000);
if ((server_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) // create server socket
{
perror("socket create failed");
return 1;
}
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0) // bind info on server socket
{
perror("bind failed");
return 1;
}
listen(server_fd, 5); // listen port 9000
sin_size = sizeof(struct sockaddr_in);
if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &sin_size)) < 0)
{
perror("accept failed");
return 1;
}
printf("accept client %s\n", inet_ntoa(client_addr.sin_addr));
len = send(client_fd, "Welcome to my server\n", 21, 0);
while ((len = recv(client_fd, buffer, BUFSIZ, 0)) > 0)
{
char *decryto_string = NULL; // add
decrypt(buffer, &decryto_string, len); // add
printf("%s \n", decryto_string);
if (send(client_fd, decryto_string, len, 0) < 0) // modified
{
perror("send failed");
return 1;
}
}
close(client_fd);
close(server_fd);
return 0;
}
点击查看代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "aes_options.h" //add
int main()
{
int len;
int client_sockfd;
struct sockaddr_in server_addr;
char buffer[BUFSIZ];
char *encrypt_string = NULL;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(9000);
if((client_sockfd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket create failed");
return 1;
}
int username;
int password;
printf("enter the username:");
scanf("%d", &username);
printf("enter the password:");
scanf("%d", &password);
if (username !=2020 || password != 1220)
{
printf("uncorrect name!\n");
return 1;
}
if(connect(client_sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("connect failed");
return 1;
}
printf("connect to server\n");
len = recv(client_sockfd, buffer, BUFSIZ, 0);
buffer[len] = '\0';
printf("%s", buffer);
while(1)
{
printf("enter a data:");
scanf("%s", buffer);
if(!strcmp(buffer,"quit"))
break;
int encrypt_length = encrypt(buffer, &encrypt_string); //add
len = send(client_sockfd, encrypt_string, encrypt_length, 0); //add
len = recv(client_sockfd, buffer, BUFSIZ, 0);
buffer[len] = '\0';
printf("recived:%s \n", buffer);
}
close(client_sockfd);
printf("bye");
return 0;
}
/*char username;
int password;
printf("enter the username:");
scanf("%s", &username);
printf("enter the password:");
scanf("%d", &password);
if (username != "cdj" || password != 20201220)
{
printf("uncorrect name!\n");
return 1;
}
*/
点击查看代码
#include <stdio.h>
#include <openssl/aes.h>
#include <stdlib.h>
#include <string.h>
int encrypt(char *input_string, char **encrypt_string)
{
AES_KEY aes;
unsigned char key[AES_BLOCK_SIZE]; // AES_BLOCK_SIZE = 16
unsigned char iv[AES_BLOCK_SIZE]; // init vector
unsigned int len; // encrypt length (in multiple of AES_BLOCK_SIZE)
unsigned int i;
// set the encryption length
len = 0;
if ((strlen(input_string) + 1) % AES_BLOCK_SIZE == 0)
{
len = strlen(input_string) + 1;
}
else
{
len = ((strlen(input_string) + 1) / AES_BLOCK_SIZE + 1) * AES_BLOCK_SIZE;
}
// Generate AES 128-bit key
for (i = 0; i < 16; ++i)
{
key[i] = 32 + i;
}
// Set encryption key
for (i = 0; i < AES_BLOCK_SIZE; ++i)
{
iv[i] = 0;
}
if (AES_set_encrypt_key(key, 128, &aes) < 0)
{
fprintf(stderr, "Unable to set encryption key in AES\n");
exit(0);
}
// alloc encrypt_string
*encrypt_string = (unsigned char *)calloc(len, sizeof(unsigned char));
if (*encrypt_string == NULL)
{
fprintf(stderr, "Unable to allocate memory for encrypt_string\n");
exit(-1);
}
// encrypt (iv will change)
AES_cbc_encrypt(input_string, *encrypt_string, len, &aes, iv, AES_ENCRYPT);
return len;
}
void decrypt(char *encrypt_string, char **decrypt_string, int len)
{
unsigned char key[AES_BLOCK_SIZE]; // AES_BLOCK_SIZE = 16
unsigned char iv[AES_BLOCK_SIZE]; // init vector
AES_KEY aes;
int i;
// Generate AES 128-bit key
for (i = 0; i < 16; ++i)
{
key[i] = 32 + i;
}
// alloc decrypt_string
*decrypt_string = (unsigned char *)calloc(len, sizeof(unsigned char));
if (*decrypt_string == NULL)
{
fprintf(stderr, "Unable to allocate memory for decrypt_string\n");
exit(-1);
}
// Set decryption key
for (i = 0; i < AES_BLOCK_SIZE; ++i)
{
iv[i] = 0;
}
if (AES_set_decrypt_key(key, 128, &aes) < 0)
{
fprintf(stderr, "Unable to set decryption key in AES\n");
exit(-1);
}
// decrypt
AES_cbc_encrypt(encrypt_string, *decrypt_string, len, &aes, iv,
AES_DECRYPT);
}
点击查看代码
#ifndef _ASE_H_
#define _ASE_H_
int encrypt(char *input_string, char **encrypt_string);
void decrypt(char *encrypt_string, char **decrypt_string, int len);
#endif
四、问题与解决思路
问题:在网络编程过程中,服务器端和客户端的套接字有什么区别?
解决:据网上博客的答案,两种方式差别不大,并没有本质的区别,建议主控机器作为服务器,其它作为客户端。原因在于,监听需要一个固定的端口,相比之下在一台机器上保证一个端口不冲突,远比在众多机器当中保证端口不冲突来得容易。