目录
一、tftp概述
二、TFTP的通信过程
三、TFTP的协议报文分析
四、抓包工具wireshark
1、IP过滤
2、协议过滤
3、端口过滤
4、mac地址过滤
5、逻辑与与逻辑或
五、tftp示例
六、UDP广播
1、广播的概述
2、UDP广播的特点
3、UDP广播地址
4、广播与单播的对比
编辑 5、广播编程
6、udp多播(组播)
1、多播的概述
2、多播地址
3、多播地址结构体
一、tftp概述
tftp:简单文件传输协议、基于UDP协议。
数据传输模式:
octet:二进制模式 netascii:文本模式
二、TFTP的通信过程
TFTP通信过程总结(无选项)
- 服务器在69号端口等待客户端的请求。
- 服务器若批准此请求,则使用临时端口与客户端进行通信。
- 每个数据包的编号都有变化(从1开始)。
- 每个数据包都要得到ACK的确认如果出现超时,则需要重新发送最后的包(数据或 ACK)。
- 数据的长度以512Byte传输 6、小于512Byte的数据意味着传输结束 。
三、TFTP的协议报文分析
六种报文:
| 差错码 | |
| 1 | File not found. |
| 2 | Access violation |
| 3 | Disk full or allocation exceeded |
| 4 | illegal TFTP operation |
| 5 | Unknown tlansfer ID |
| 6 | File already exists |
| 7 | No such user |
| 8 | Unsupported option(s) requested |
TFTP带选项的读写报文:
通信过程:
tsize选项:
当读操作时,tsize选项的参数必须为“0”,服务器会返回待读取的文件的大小
当写操作时,tsize选项参数应为待写入文件的大小,服务器会回显该选项blksize选项:
修改传输文件时使用的数据块的大小(范围:8~65464)
timeout选项:
修改默认的数据传输超时时间(单位:秒)
四、抓包工具wireshark
过滤规则:
1、IP过滤
ip.src==192.168.44.130只显示源IP为192.168.44.130的数据报文
ip.dst==192.168.44.130只显示目的IP为192.168.44.130的数据报文
ip.addr==192.168.44.130只显示示源IP或目的IP为192.168.44.130的数据报文
2、协议过滤
tcp udp arp tftp
3、端口过滤
udp.srcport==8000只显示源端口为8000的udp报文
udp.dstport==8000只显示目的端口为8000的udp报文
udp.port==8000只显示目的或源端口为8000的udp报文
tcp.srcport==8000只显示源端口为8000的tcp报文
tcp.dstport==8000只显示目的端口为8000的tcp报文
tcp.port==8000只显示目的或源端口为8000的tcp报文
4、mac地址过滤
eth.src==aa:bb:cc:dd:ee:ff只显示源mac为aa:bb:cc:dd:ee:ff的数据报文
eth.dst==aa:bb:cc:dd:ee:ff只显示目的mac为aa:bb:cc:dd:ee:ff的数据报文 eth.addr==aa:bb:cc:dd:ee:ff只显示源mac或目的mac为aa:bb:cc:dd:ee:ff的数据报文
5、逻辑与与逻辑或
and ==> && 逻辑与 同时满足
or ==> || 逻辑或 只要有一个满足就行
五、tftp示例
编写TFTP客户端(并不带选项) 从服务器下载文件
流程:
sockfd = 创建UDP套接字
构建读请求报文、并发送到服务器的69号端口
fd=open以写的方式打开本地文件
while(1)
{
unsigned char buf[516]="";
int len = recvfrom(sockfd,buf,sizeof(buf),0, &dst_addr,sizeof(ds_addr));
write(fd,buf+4,len‐4);
if(len < 516)
break;
}
close(fd);
close(sockfd);
构建下载a.txt的报文:
unsigned char read_cmd[128]="";
int s_len = sprintf(read_cmd,"%c%c%s%c%s%c",0,1,"a.txt", 0,"octet", 0);
sendto(sockfd,read_cmd,s_len,0,dst_addr‐‐>69 )
tftp客户端(下载文件):
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> static int sockfd; void sig_dispose(int sig) { if (SIGINT == sig) { close(sockfd); puts("\nclose!"); exit(0); } } /*********************************************************************** * 函数名称: int main(int argc, char *argv[]) 函数功能: tftp客户端程序,可以从tftp服务器下载程序 ************************************************************************/ int main(int argc, char *argv[]) { unsigned short p_num = 0; unsigned char cmd = 0; char cmd_buf[512] = ""; char recv_buf[516] = ""; struct sockaddr_in client_addr; socklen_t cliaddr_len = sizeof(client_addr); signal(SIGINT, sig_dispose); if (argc < 3) { // 如果参数个数小于3,则认为输入命令错误 printf("cmd example:./tftpc 192.168.8.8 hello.txt\n"); return 0; } sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket error"); exit(-1); } struct sockaddr_in dest_addr; bzero(&dest_addr, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(69); inet_pton(AF_INET, argv[1], &dest_addr.sin_addr.s_addr); // 构造下载请求,argv[2]为文件名 int len = sprintf(cmd_buf, "%c%c%s%c%s%c", 0, 1, argv[2], 0, "octet", 0); sendto(sockfd, cmd_buf, len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); int fd = open(argv[2], O_WRONLY | O_CREAT, 0666); if (fd < 0) { perror("open error"); close(sockfd); exit(-1); } do { // 接收服务器发送的内容 len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr *)&client_addr, &cliaddr_len); cmd = recv_buf[1]; if (cmd == 3) // 是否为数据包 { // 接收的包编号是否为上次包编号+1 if ((unsigned short)(p_num + 1) == ntohs(*(unsigned short *)(recv_buf + 2))) { write(fd, recv_buf + 4, len - 4); p_num = ntohs(*(unsigned short *)(recv_buf + 2)); printf("recv:%d\n", p_num); // 十进制方式打印包编号 } recv_buf[1] = 4; sendto(sockfd, recv_buf, 4, 0, (struct sockaddr *)&client_addr, sizeof(client_addr)); } else if (cmd == 5) // 是否为错误应答 { close(sockfd); close(fd); unlink(argv[2]); // 删除文件 printf("error:%s\n", recv_buf + 4); exit(-1); } } while (len == 516); // 如果收到的数据小于516则认为出错 close(fd); return 0; }六、UDP广播
1、广播的概述
广播:由一台主机向该主机所在子网内的所有主机发送数据的方式
广播只能用UDP或原始IP实现,不能用TCP。
以下几个协议都用到广播
- 地址解析协议(ARP)
- 动态主机配置协议(DHCP)
- 网络时间协议(NTP)
2、UDP广播的特点
- 处于同一子网的所有主机都必须处理数据
- UDP数据包会沿协议栈向上一直到UDP层
- 运行音视频等较高速率工作的应用,会带来很大的负担
- 局限于局域网内使用
3、UDP广播地址
定向广播地址:主机ID全1
1、例:对于192.168.44.0/24,其定向广播地址为192.168.44.255
2、通常路由器不转发该广播
受限广播地址:255.255.255.255
路由器从不转发该广播
4、广播与单播的对比
单播:
广播:
5、广播编程
socket函数创建的套接字 一般不支持广播。
setsockopt函数 可以让 套接字 支持广播。
int setsockopt(int sockfd, int level,int optname,const void *optval, socklen_t optlen);
参数说明:
成功执行返回0,否则返回-1
示例:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char const *argv[]) { // 创建一个udp套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 使用setsockopt函数设置sockfd支持广播 int yes = 1; setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int)); // 发送广播数据 struct sockaddr_in dst_addr; bzero(&dst_addr, sizeof(dst_addr)); // memset(&dst_addr,0,sizeof(dst_addr)); dst_addr.sin_family = AF_INET; dst_addr.sin_port = htons(8000); inet_pton(AF_INET, "192.168.131.255", &dst_addr.sin_addr.s_addr); sendto(sockfd, "broadcast msg", strlen("broadcast msg"), 0, (struct sockaddr *)&dst_addr, sizeof(dst_addr)); close(sockfd); return 0; }6、udp多播(组播)
1、多播的概述
多播:数据的收发仅仅在同一分组中进行
多播的特点:
- 多播地址标示一组接口
- 多播可以用于广域网使用
- 在IPv4中,多播是可选的
2、多播地址
多播IP地址范围:224.0.0.1 ~ 239.255.255.254
多播mac地址映射: 前5位固定,后23位映射
通信过程:
基于mac地址的不完备硬件过滤+基于IP地址的完备软件过滤
3、多播地址结构体
setsockopt函数支持多播:
int setsockopt(int sockfd, int level,int optname, const void *optval, socklen_t optlen);
参数说明:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char const *argv[]) { // 创建一个udp套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // bind绑定固定端口 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(8000); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr)); // 使用setsockopt函数加入224.0.0.2多播组中 struct ip_mreq mreq; // inet_pton(AF_INET,"224.0.0.2",&mreq.imr_multiaddr.s_addr); mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.3"); // 多播组IP mreq.imr_interface.s_addr = inet_addr("192.168.44.131"); // 加入多播组的IP setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(struct ip_mreq)); // 接收多播组的消息 while (1) { unsigned char buf[1500] = ""; recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); printf("%s\n", buf); } close(sockfd); return 0; }