TFTP网络协议分析
周学伟
文档说明:所有函数都依托与两个出口,发送和接收。
1:作为发送时,要完成基于TFTP协议下的文件传输,但前提是知道木的PC机的MAC地址,因为当发送TFTP请求包时必须提供目的主机的MAC地址。则提供串口srcureCRT控制台,首先进行ARP请求包的发送,收到来自客户端的ARP应答包时,提取出目的主机的MAC地址,然后在发送TFTP请求包,等到目的主机返回数据报文后,文件传输即可开始,此过程,可用wireshark抓包工具进行检测。
2:作为接收时,可在DM9000网卡芯片提供的中断标号初进行等待,当网卡收到相应的数据包时,即可让函数进行处理。
1定义帧头的结构体
typedef unsigned int u32;
typedef unsigned short u16;
typedef unsigned char u8;
typedef struct eth_hdr
{
u8 d_mac[6];
u8 s_mac[6];
u16 type;
}ETH_HDR;
ARP数据包的格式
以太网的目的地址d_mac |
以太网的源地址s_mac |
帧类型 type |
硬件类型 hwtype; |
协议类型 protocol |
硬件地址长度 hwlen |
协议地址长度 protolen |
Opcode 1:请求包 2:应答包 |
发送端的以太网地址smac[6] |
发送端的IP地址 sipaddr[4] |
目的以太网地址 dmac[6]; |
目的IP地址 dipaddr[4]
|
typedef struct arp_hdr
{
ETH_HDR ethhdr;
u16 hwtype;
u16 protocol;
u8 hwlen;
u8 protolen;
u16 opcode;
u8 smac[6];
u8 sipaddr[4];
u8 dmac[6];
u8 dipaddr[4];
}ARP_HDR;
ARP_HDR arpbuf;
IP协议的报文格式
版本 vhl |
报文长度 vhl |
服务级别 tos |
报文长度 len |
标识 ipid |
标志 ipoffset |
生命时间 ttl |
用户协议 proto |
报文校验 ipchksum |
源IP地址 srcipaddr[4] |
目的IP地址 u8 destipaddr[4] |
数据
|
typedef struct ip_hdr
{
ETH_HDR ethhdr;
u8 vhl;
u8 tos;
u16 len;
u16 ipid;
u16 ipoffset;
u8 ttl;
u8 proto;
u16 ipchksum;
u8 srcipaddr[4];
u8 destipaddr[4];
}IP_HDR;
UDP协议格式
16 位源端口号 |
16位目的端口号 |
16位UDP长度 |
16位UDP检验 |
数据(如果有) |
typedef struct udp_hdr
{
IP_HDR iphdr;
u16 sport;
u16 dport;
u16 len;
u16 udpchksum;
}UDP_HDR;
TFTP报文格式
IP首部 |
UDP首部 |
opcode |
文件名 |
0(默认) |
传输模式 |
0(默认) |
|
|
操作码 opcode |
快编码 blocknum |
数据 data[0] |
typedef struct tftp_package
{
u16 opcode;
u16 blocknum;
u8 data[0];
}TFTP_PAK;
#define PROTO_ARP 0x0806
#define PROTO_IP 0x0800
#define PROTO_UDP 0x11
u8 buffer[1500];
u8 host_mac_addr[6] = {0xff,0xff,0xff,0xff,0xff,0xff};
u8 mac_addr[6] = {9,8,7,6,5,4};
u8 ip_addr[4] = {192,168,1,30};
u8 host_ip_addr[4] = {192,168,1,100};
u16 packet_len;
2进入ARP.C中处理
#include "arp.h"
#define HON(n) ((((u16)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))//作为将小端数据变换为大端数据的宏
/*1*****************以下是发送arp请求包****************/
/*************************************************
函数名称:ARP请求包发送函数
调用入口:在主函数中调用的利用secureCRT控制台发送
函数说明:
**************************************************/
void arp_request()
{
memcpy(arpbuf.ethhdr.d_mac,host_mac_addr,6);//以太网的目的地址
memcpy(arpbuf.ethhdr.s_mac,mac_addr,6);//以太网的源地址
arpbuf.ethhdr.type = HON(0x0806);//帧类型,固定为0x0806(采用网络字节序大端格式
arpbuf.hwtype = HON(1);//硬件类型(采用网络字节序大端格式)
arpbuf.protocol = HON(0x0800);//协议类型,固定位0x0800(采用网络字节序大端格式)
arpbuf.hwlen = 6;//硬件地址长度
arpbuf.protolen = 4;//协议地址长度
arpbuf.opcode = HON(1);//opcode判断是请求包还是应答包
memcpy(arpbuf.smac,mac_addr,6);//源Mac地址
memcpy(arpbuf.sipaddr,ip_addr,4);//源IP地址
memcpy(arpbuf.dipaddr,host_ip_addr,4);//目的IP地址
packet_len = 14+28;
dm9000_tx(&arpbuf,packet_len);
}
/*************************************************
函数名称:ARP数据包的解析处理函数
调用入口:net_handle()_网络数据包处理分析函数
函数说明:判断是请求包还是应答包
**************************************************/
u8 arp_process(u8 *buf, u32 len)
{
u32 i;
ARP_HDR *arp_p = (ARP_HDR *)buf;//定义arp_p指针指向接收到的数据缓存区中
if (packet_len<28)//判断是否小于28字节,小于就不做处理
return 0;
switch (HON(arp_p->opcode))
{
case 2://若是ARP响应包,则打印在串口
/***************arp响应包***************/
memcpy(host_ip_addr,arp_p->sipaddr,4);//提取目的主机的IP地址,打印
printf("host ip is : ");
for(i=0;i<4;i++)
printf("%03d ",host_ip_addr[i]);
printf("\n\r");
memcpy(host_mac_addr,arp_p->smac,6);//提取目的PC机的MAC地址,打印
printf("host mac is : ");
for(i=0;i<6;i++)
printf("%02x ",host_mac_addr[i]);
printf("\n\r");
break;
case 1: //若是arp请求包,则发送ARP响应包
/***********发送arp响应包**************/
memcpy(arpbuf.ethhdr.d_mac,arp_p->smac,6);//以太网的目的地址
memcpy(arpbuf.ethhdr.s_mac,mac_addr,6);//以太网的源地址
arpbuf.ethhdr.type = HON(0x0806);//帧类型,固定为0x0806(采用网络字节序大端格式)
arpbuf.hwtype = HON(1);//硬件类型(采用网络字节序大端格式)
arpbuf.protocol = HON(0x0800);//协议类型,固定位0x0800(采用网络字节序大端格式)
arpbuf.hwlen = 6;//硬件地址长度
arpbuf.protolen = 4;//协议地址长度
arpbuf.opcode = HON(2);//opcode判断是请求包还是应答包
memcpy(arpbuf.smac,mac_addr,6);//源Mac地址
memcpy(arpbuf.sipaddr,ip_addr,4);//源IP地址
memcpy(arpbuf.dmac,arp_p->smac,6);//目的MAC地址
memcpy(arpbuf.dipaddr,arp_p->sipaddr,4);//目的IP地址
packet_len = 14+28;//总体包的长度
dm9000_tx(&arpbuf,packet_len);//调用dm9000发送函数,发送应答包
break;
}
}
/*************************************************
函数名称:UDP协议数据包处理函数
调用入口:在ip_process()接收到的IP类型的数据包处理函数
函数说明:UDP协议包下封装的是TFT协议包
**************************************************/
void udp_process(u8* buf, u32 len)
{
UDP_HDR *udphdr = (UDP_HDR *)buf;//定义udphdr 指针指向接收到的数据缓存区中
tftp_process(buf,len,HON(udphdr->sport)); //调用tftp_process()TFTP处理函数
}
/************************************************
函数名称:接收到的IP类型的数据包处理函数
调用接口:在net_handle网络数据包处理分析函数中
函数说明:IP协议包下封装的是UDP协议包
**************************************************/
void ip_process(u8 *buf, u32 len)
{
IP_HDR *p = (IP_HDR *)buf; //定义p指针指向接收到的数据缓存区中
switch(p->proto)//判断协议类型是否为UDP的协议包
{
case PROTO_UDP://如果是则调用udp_processUDP处理函数
udp_process(buf,len);
break;
default:
break;
}
}
/*********************************
函数名称:网络数据包处理分析函数
调用接口:调用者在DM9000的int_issue()函数处
********************************/
void net_handle(u8 *buf, u32 len)
{
ETH_HDR *p = (ETH_HDR *)buf; //定义p指针指向接收到的数据缓存区中
switch (HON(p->type))//判断接收到的数据包的类型
{
case PROTO_ARP://PROTO_ARP = 0x0806
arp_process(buf,len);//如果是ARP包则调用arp_process处理函数
break;
case PROTO_IP://PROTO_IP = 0x0800
ip_process(buf,len);//如果是IP包则调用ip_process处理函数
break;
default:
break;
}
}
3调用TFTP.C处理
#include "string.h"
#include "arp.h"
u8 sendbuf[1024];//发送缓存区
u8* tftp_down_addr = 0x31000000;//数据保存的起始地址
u16 serverport = 0;//源端口号
u16 curblock = 1;//数据的块号
#define HON(n) ((((u16)((n) & 0xff)) << 8) | (((n) & 0xff00) >> 8))//2个字节的数据更改为网络字节序,大端数据
/*************************************************
函数名称:计算校验码长度的函数
调用入口:tftp_send_request()
函数说明:无
**************************************************/
u16 checksum(u8 *ptr, int len)
{
u32 sum = 0;
u16 *p = (u16 *)ptr;
while (len > 1)
{
sum += *p++;
len -= 2;
}
}
if(len == 1)
sum += *(u8 *)p;
while(sum>>16)
sum = (sum&0xffff) + (sum>>16);
return (u16)((~sum)&0xffff);
}
/*************************************************
函数名称:发送tftp请求数据包的函数
调用入口:在主函数中调用的利用secureCRT控制台发送
函数说明:
**************************************************/
void tftp_send_request(const char *filename)
{
u8 *ptftp = &sendbuf[200];//TFTP的内存起始地址指向sendbuf缓存区
u32 tftp_len = 0;//TFTP请求报文的长度
UDP_HDR *udphdr;//定义UDP头的内存指向udphdr
u8 *iphdr;//定义IP头的内存指向iphdr
ptftp[0] = 0x00;
ptftp[1] = 0x01;//写入操作码,网络字节序大端数据
tftp_len += 2 ;//TFTP数据报文的长度变量
sprintf(&ptftp[tftp_len],"%s",filename);//利用sprintf函数写入请求的文件名
tftp_len += strlen(filename);//计算写入的文件名的长度
ptftp[tftp_len] = '\0';//添加文件名的结束符
tftp_len += 1;//长度加1
sprintf(&ptftp[tftp_len],"%s","octet");//利用sprintf函数写入传输数据的模式
tftp_len += strlen("octect");//计算写入的传输数据的模式的长度
ptftp[tftp_len] = '\0';//添加文件名的结束符
tftp_len += 1;//长度加1
udphdr = ptftp-sizeof(UDP_HDR);//利用sizeof函数计算UDP头的长度
iphdr = ptftp-sizeof(UDP_HDR)+ sizeof(ETH_HDR);//利用sizeof函数计算IP头的长度
/***************以下是UDP帧头信息****************/
udphdr->sport = HON(48915);//UDP头的16位源端口号
udphdr->dport = HON(69);//UDP头的16位目的端口号
udphdr->len = HON(tftp_len+sizeof(UDP_HDR)-sizeof(IP_HDR));//UDP头的16位长度
udphdr->udpchksum = 0x00;//UDP头的16位校验码
/***************以下IP帧头信息*******************/
udphdr->iphdr.vhl = 0x45;//版本和报文长度
udphdr->iphdr.tos = 0x00;//服务级别
udphdr->iphdr.len = HON(tftp_len+sizeof(UDP_HDR)-sizeof(ETH_HDR));//报文长度(采用网络字节序大端格式)
udphdr->iphdr.ipid = HON(0x00);//标识(采用网络字节序)
udphdr->iphdr.ipoffset = HON(0x4000);//标志(采用网络字节序大端格式)
udphdr->iphdr.ttl = 0xff;//报文的生存时间
udphdr->iphdr.proto = 17;//用户协议类型
memcpy(udphdr->iphdr.srcipaddr,ip_addr,4);//源iP地址
memcpy(udphdr->iphdr.destipaddr,host_ip_addr,4);//目的IP地址
udphdr->iphdr.ipchksum = 0;//首先将报文校验设置为0
udphdr->iphdr.ipchksum = checksum(iphdr,20);//利用checksum函数计算校验码的长度
memcpy(udphdr->iphdr.ethhdr.s_mac,mac_addr,6);//源MAC地址
memcpy(udphdr->iphdr.ethhdr.d_mac,host_mac_addr,6);//目的MAC地址
udphdr->iphdr.ethhdr.type = HON(PROTO_IP);//IP的协议类型(采用网络字节序,大端格式)
dm9000_tx((u32 *)udphdr,sizeof(UDP_HDR)+tftp_len);//利用dm9000_tx发送TFTP请求报文
}
/*************************************************
函数名称:接收tftp请求数据包确认的函数
调用入口:tftp_process()TFTP协议数据包处理函数
函数说明:处理完成后利用dm9000_tx()函数发送响应报文
**************************************************/
void tftp_send_ack(u16 blocknum)
{
u8 *ptftp = &sendbuf[200];//ptftp指针指向sendbuf缓存区中
u32 tftp_len = 0;//TFTP请求报文的长度
UDP_HDR *udphdr;//定义UDP头的内存指向udphdr
u8 *iphdr;//定义IP头的内存指向iphdr
ptftp[0] = 0x00;//写入操作码,网络字节序大端数据
ptftp[1] = 0x04;
tftp_len += 2 ;//TFTP数据报文的长度变量
ptftp[2] = (blocknum&0xff00)>>8;//提取客户端发送的数据报文中的块号
ptftp[3] = (blocknum&0xff);//因为采用的是网路字节序大端格式,则要变换为小端个数
tftp_len += 2 ;//TFTP数据报文的长度变量加2个季节
udphdr = ptftp-sizeof(UDP_HDR);//利用sizeof函数计算UDP头的长度
iphdr = ptftp-sizeof(UDP_HDR)+ sizeof(ETH_HDR);//利用sizeof函数计算IP头的长度
/****************以下是UDP帧头信息***************/
udphdr->sport = HON(48915);;//UDP头的16位源端口号(采用网络字节序大端格式)
udphdr->dport = HON(serverport);//UDP头的16位目的端口号(采用网络字节序大端格式)
udphdr->len = HON(tftp_len+sizeof(UDP_HDR)-sizeof(IP_HDR));//UDP头的16位长度
udphdr->udpchksum = 0x00;//UDP头的16位校验码
/******************以下是IP帧头信息******************/
udphdr->iphdr.vhl = 0x45;//版本和报文长度
udphdr->iphdr.tos = 0x00;//服务级别
udphdr->iphdr.len = HON(tftp_len+sizeof(UDP_HDR)-sizeof(ETH_HDR));//报文长度(采用网络字节序大端格式)
udphdr->iphdr.ipid = HON(0x00);//标识(采用网络字节序)
udphdr->iphdr.ipoffset = HON(0x4000);//标志(采用网络字节序大端格式)
udphdr->iphdr.ttl = 0xff;//报文的生存时间
udphdr->iphdr.proto = 17;//用户协议类型
memcpy(udphdr->iphdr.srcipaddr,ip_addr,4);//源iP地址
memcpy(udphdr->iphdr.destipaddr,host_ip_addr,4);//目的IP地址
udphdr->iphdr.ipchksum = 0;//首先将报文校验设置为0
udphdr->iphdr.ipchksum = checksum(iphdr,20);//利用checksum函数计算校验码的长度
memcpy(udphdr->iphdr.ethhdr.s_mac,mac_addr,6);//源MAC地址
memcpy(udphdr->iphdr.ethhdr.d_mac,host_mac_addr,6);//目的MAC地址
udphdr->iphdr.ethhdr.type = HON(PROTO_IP);//IP的协议类型(采用网络字节序,大端格式)
dm9000_tx((u32 *)udphdr,sizeof(UDP_HDR)+tftp_len);//利用dm9000_tx发送TFTP请求报文
}
/***************************************************
函数名称:TFTP协议数据包处理函数
调用接口:在arp.C中的udp_process()UDP协议数据包处理函数
函数说明:len表示整个接收到的数据包长度,包括:
----------------------------------------------
| IP帧头 || UDP帧头 || TFTP帧头 || tftp数据 ||
----------------------------------------------
****************************************************/
void tftp_process(u8 *buf, u32 len, u16 port)
{
u32 i;
u32 tftp_len;//TFTP协议包长度
serverport = port;//源端口号
TFTP_PAK *ptftp = buf + sizeof(UDP_HDR);//定义ptftp 指针指向接收到的数据缓存区中
tftp_len = len - sizeof(UDP_HDR);//TFTP协议包长度
if(HON(ptftp->opcode) == 3)//当OPCODE=3时,表示该网络包是TFTP的数据报文
{
if (HON(ptftp->blocknum) == curblock)//按照块编号判断是否是最后一组数据报文
{
for (i = 0;i<(tftp_len-4);i++)//复制数据到指定的内存中去
{
*(tftp_down_addr) = *(ptftp->data+i);
}
tftp_send_ack(HON(ptftp->blocknum));//客户端发送接收数据的应答包
curblock++; //更新标志块的编号
if ((tftp_len-4)<512)
curblock = 1;//判断是否为最后一个数据包,若是则更新内存块的编号
tftp_down_addr = 0x31000000; //更新内存地址
}
}
}
4 部分MAIN函数
switch (num)
{
case 1:
tftp_send_request("start.o");
Break;
case 2:
arp_request();
break;
default:
printf("Error: wrong selection!\n\r");
break;
}
5 dm9000中的处理函数
/************************
接收数据函数
在外部中断处理函数中调用
***********************/
void int_issue()
{
packet_len = dm9000_rx(&buffer[0]); //计算接收到的数据包长度
net_handle(&buffer[0],packet_len);//调用此函数进行数据包的opcode类型分析
SRCPND = (1<<4);//清楚中断标志位
INTPND = (1<<4);
EINTPEND |= 1<<7;
}