TFTP网络协议分析---15

时间:2023-03-09 13:42:49
TFTP网络协议分析---15

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位目的端口号

 

16UDP长度

 

16UDP检验

 

数据(如果有)

 

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;

}