本篇博文以上一篇文章《TCP、IP、ARP、ICMP首部分析》为基础,根据STM32中以太网程序来着重看一下,以太网数据包的解析过程。

我们以一个简单的服务器应用为主线,进行以太网数据包的解析。在贴程序之前我们先看一下对于以太网数据包各部分的宏定义。

/******************** ETH *********************/

/* 目标地址(6),源地址(6),类型/长度(2)*/
#define ETH_HEADER_LEN	14    

/* 类型/长度的定义 */
#define ETHTYPE_ARP_H_V 0x08  /* ETH包类型/长度ARP包标识符高位 */
#define ETHTYPE_ARP_L_V 0x06  /* ETH包类型/长度ARP包标识符低位 */
#define ETHTYPE_IP_H_V  0x08  /* ETH包类型/长度IP包标识符高位 */
#define ETHTYPE_IP_L_V  0x00  /* ETH包类型/长度IP包标识符低位 */

/* Ethernet type field (2bytes) */
#define ETH_TYPE_H_P 12       /* ETH包类型/长度偏移地址 */
#define ETH_TYPE_L_P 13       /* ETH包类型/长度偏移地址 */

/* 目的地址与源地址的位置 */
#define ETH_DST_MAC 0         /* ETH包目的MAC偏移地址 */
#define ETH_SRC_MAC 6         /* ETH包源始MAC偏移地址 */

/******************** ARP *********************/
#define ETH_ARP_OPCODE_REPLY_H_V 0x0   /* ARP包操作类型字节:ARP响应高位 */
#define ETH_ARP_OPCODE_REPLY_L_V 0x02  /* ARP包操作类型字节:ARP响应低位 */

#define ETHTYPE_ARP_L_V 0x06

//#define ETH_ARP_DST_IP_P 0x26     /* arp.dst.ip */

#define ETH_ARP_OPCODE_H_P 0x14   /* ETH包中ARP包类型,ARP头中存储操作类型高位的地址 */
#define ETH_ARP_OPCODE_L_P 0x15   /* ETH包中ARP包类型,ARP头中存储操作类型低位的地址 */

/* arp.src.mac  */
#define ETH_ARP_SRC_MAC_P 0x16    /* ETH包中ARP包类型,ARP头中存储源始MAC的首地址 */
#define ETH_ARP_SRC_IP_P 0x1c     /* ETH包中ARP包类型,ARP头中存储源始IP的首地址 */
#define ETH_ARP_DST_MAC_P 0x20    /* ETH包中ARP包类型,ARP头中存储目的MAC的首地址 */
#define ETH_ARP_DST_IP_P 0x26     /* ETH包中ARP包类型,ARP头中存储目的IP的首地址 */

/******************** IP *********************/
#define IP_HEADER_LEN	20        /* IP包头文件长度 */

#define IP_SRC_P 0x1a             /* ETH包中IP包类型,IP头中存储源始IP地址的首地址 */
#define IP_DST_P 0x1e             /* ETH包中IP包类型,IP头中存储目的IP地址的首地址 */
#define IP_HEADER_LEN_VER_P 0xe   /* ETH包中IP包类型,IP头中存储版本的首地址 */
#define IP_CHECKSUM_P 0x18        /* ETH包中IP包类型,IP头中存储校验和的首地址 */
#define IP_TTL_P 0x16             /* ETH包中IP包类型,IP头中存储生存时间的首地址 */
#define IP_FLAGS_P 0x14           /* ETH包中IP包类型,IP头中存储标志的首地址 */
#define IP_P 0xe                  /* ETH包中IP包类型,IP头的首地址 */
#define IP_TOTLEN_H_P 0x10        /* ETH包中IP包类型,IP头中存储包裹总长高位的地址 */
#define IP_TOTLEN_L_P 0x11        /* ETH包中IP包类型,IP头中存储包裹总长低位的地址 */

#define IP_PROTO_P 0x17  

#define IP_PROTO_ICMP_V 1         /* ETH包中IP包类型,IP头中协议代码1表示ICMP */
#define IP_PROTO_TCP_V 6          /* ETH包中IP包类型,IP头中协议代码6表示TCP */
#define IP_PROTO_UDP_V 17         /* ETH包中IP包类型,IP头中协议代码17表示UDP */

/******************** ICMP *********************/
#define ICMP_TYPE_ECHOREPLY_V 0   /* ETH包中IP包类型ICMP包,ICMP头类型代码回射应答 */
#define ICMP_TYPE_ECHOREQUEST_V 8 /* ETH包中IP包类型ICMP包,ICMP头类型代码回射请求 */
//
#define ICMP_TYPE_P 0x22          /* ETH包中IP包类型ICMP包,ICMP头类型代码的首地址 */
#define ICMP_CHECKSUM_P 0x24      /* ETH包中IP包类型ICMP包,ICMP头校验和的首地址 */

/******************** UDP *********************/
#define UDP_HEADER_LEN	8         /* ETH包中IP包类型UDP包,UDP头长度 */
//
#define UDP_SRC_PORT_H_P 0x22     /* ETH包中IP包类型UDP包,UDP头源始端口号高位地址 */
#define UDP_SRC_PORT_L_P 0x23     /* ETH包中IP包类型UDP包,UDP头源始端口号低位地址 */
#define UDP_DST_PORT_H_P 0x24     /* ETH包中IP包类型UDP包,UDP头目的端口号高位地址 */
#define UDP_DST_PORT_L_P 0x25     /* ETH包中IP包类型UDP包,UDP头目的端口号低位地址 */
//
#define UDP_LEN_H_P 0x26          /* ETH包中IP包类型UDP包,UDP头UDP长度高位地址 */
#define UDP_LEN_L_P 0x27          /* ETH包中IP包类型UDP包,UDP头UDP长度低位地址 */
#define UDP_CHECKSUM_H_P 0x28     /* ETH包中IP包类型UDP包,UDP头UDP校验和高位地址 */
#define UDP_CHECKSUM_L_P 0x29     /* ETH包中IP包类型UDP包,UDP头UDP校验和低位地址 */
#define UDP_DATA_P 0x2a           /* ETH包中IP包类型UDP包,UDP包数据区首地址 */

/******************** TCP *********************/
#define TCP_SRC_PORT_H_P 0x22     /* ETH包中IP包类型TCP包,TCP头中存储源始端口高位的地址 */
#define TCP_SRC_PORT_L_P 0x23     /* ETH包中IP包类型TCP包,TCP头中存储源始端口低位的地址 */
#define TCP_DST_PORT_H_P 0x24     /* ETH包中IP包类型TCP包,TCP头中存储目的端口高位的地址 */
#define TCP_DST_PORT_L_P 0x25     /* ETH包中IP包类型TCP包,TCP头中存储目的端口低位的地址 */

/* the tcp seq number is 4 bytes 0x26-0x29 */
#define TCP_SEQ_H_P 0x26          /* ETH包中IP包类型TCP包,TCP头中存储数据序号的首地址 */
#define TCP_SEQACK_H_P 0x2a       /* ETH包中IP包类型TCP包,TCP头中存储确认序号的首地址 */

#define TCP_FLAGS_P 0x2f          /* ETH包中IP包类型TCP包,TCP头中存储标志字节的地址 */
#define TCP_FLAGS_SYN_V 2                                                                    
#define TCP_FLAGS_FIN_V 1
#define TCP_FLAGS_PUSH_V 8
#define TCP_FLAGS_SYNACK_V 0x12   /* ETH包中IP包类型TCP包,TCP头中同步比特确认值 */
#define TCP_FLAGS_ACK_V 0x10      /* ETH包中IP包类型TCP包,TCP头中确认比特值 */
#define TCP_FLAGS_PSHACK_V 0x18   /* ETH包中IP包类型TCP包,TCP头中PSH比特确认值 */

/* TCP数据头中没有可选选项字节 */
#define TCP_HEADER_LEN_PLAIN 20   /* ETH包中IP包类型TCP包,TCP头长度(如果不包含可选选项) */
#define TCP_HEADER_LEN_P 0x2e     /* ETH包中IP包类型TCP包,TCP头中存储偏移的首地址 */
#define TCP_CHECKSUM_H_P 0x32     /* ETH包中IP包类型TCP包,TCP头中存储包校验和地址 */
#define TCP_CHECKSUM_L_P 0x33     /* ETH包中IP包类型TCP包,TCP头中存储包校验和地址 */
#define TCP_OPTIONS_P 0x36        /* ETH包中IP包类型TCP包,TCP头中存储可选选项的首地址 */


上面的宏定义中记录了在各个协议首部一些关键数据的位置及关键代码的参数。对于占用多个字节的参数高位在前

在以太网中数据包的解析主要有4条线路,分别是

ETH  ---> ARP

ETH  ---> IP  ---> TCP  ---> 应用程序

ETH  ---> IP  ---> UDP  ---> 应用程序

ETH  ---> IP  ---> ICMP

下面我们来详细看一下程序,我们将逐行的进行分析。

<span style="color:#ff0000;">int simple_server(void)
{  
    unsigned int plen,dat_p,i1=0,payloadlen=0;
    unsigned char i=0,*buf1 = 0;
    signed char cmd;
    
    /* 将自己设定的mac,ip,wwwport赋值给系统变量 */
    init_ip_arp_udp_tcp(mymac,myip,mywwwport);
    
    printf("\n\r神舟III号MAC地址:0x%x,0x%x,0x%x,0x%x,0x%x,0x%x",mymac[0],mymac[1],mymac[2],mymac[3],mymac[4],mymac[5]);
    printf("\n\r         IP地址:%d.%d.%d.%d",myip[0],myip[1],myip[2],myip[3]);
    printf("\n\r         端口号:%d\n\r\n\r",mywwwport);
    
    while(1)
    {
        /* 判断是否有接收到有效的包 ,ETH包,不包含校验CRC */
        plen = enc28j60PacketReceive(BUFFER_SIZE, buf);
        /* 如果收到有效的包,plen将为非0值。*/
        if(plen==0)
        {   
            /* 没有收到有效的包就退出重新检测 */
            continue; 
        }
        
        /* 查询ETH包头的类型/长度字节,如果收到目的地址为本机IP的ARP包,则发送一个ARP应答包 */
        if(eth_type_is_arp_and_my_ip(buf,plen))
        {
            /* 如果是返回ARP包 */
            make_arp_answer_from_request(buf);
            continue;
        }
        
        /* 如果接收的目的地址不是本机IP的合法IP包,则重新检测 */
        if(eth_type_is_ip_and_my_ip(buf,plen)==0) 
        {
            /* 没有收到有效的包就退出重新检测 */
            continue;
        }

        /* 如果收到ICMP包,并且是ICMP回射请求包 ,则发送一个ICMP回射应答包(判断两个终端之间是否有效连接)*/
        if(buf[IP_PROTO_P]==IP_PROTO_ICMP_V && buf[ICMP_TYPE_P]==ICMP_TYPE_ECHOREQUEST_V)
        {        
            printf("\n\r收到主机[%d.%d.%d.%d]发送的ICMP包",buf[ETH_ARP_SRC_IP_P]
                                                          ,buf[ETH_ARP_SRC_IP_P+1]
                                                          ,buf[ETH_ARP_SRC_IP_P+2]
                                                          ,buf[ETH_ARP_SRC_IP_P+3]);
            make_echo_reply_from_request(buf, plen);
            continue;
        }
        
        /*如果收到一个TCP包,并且端口为80,则进行相应处理 */
        if (buf[IP_PROTO_P]==IP_PROTO_TCP_V&&buf[TCP_DST_PORT_H_P]==0&&buf[TCP_DST_PORT_L_P]==mywwwport)
        {
            printf("\n\r神舟III号接收到TCP包,端口为80。");

            /*如果这是一个TCP连接请求包,发送一个同步请求应答TCP包 */
            if (buf[TCP_FLAGS_P] & TCP_FLAGS_SYN_V)
            {
                printf("包类型为SYN\n\r");
                make_tcp_synack_from_syn(buf);
                continue;
            }

            /*如果这是一个TCP确认包,发送一个同步请求应答TCP包 */
            if (buf[TCP_FLAGS_P] & TCP_FLAGS_ACK_V)
            {
                printf("包类型为ACK\n\r");
                
                /* 初始化数据格式,并获取TCP包,数据区首地址指针 */
                init_len_info(buf); 
                dat_p=get_tcp_data_pointer();

                /* 如果这是一个无数据的TCP包 */
                if (dat_p==0)
                {
                    /* 如果这是一个要求释放连接(终止)的TCP包 */
                    if (buf[TCP_FLAGS_P] & TCP_FLAGS_FIN_V)
                    {   
                        /*发送响应*/
                        printf("终止连接包\n\r");
                        make_tcp_ack_from_any(buf);
                    }

                    /* 等待重新接收数据包 */
                    continue;                                  
                }

                if (strncmp("GET ",(char *)&(buf[dat_p]),4)!=0)
                {
                    /* 如果是Telnet方式登录,返回如下提示信息 */
                    plen=fill_tcp_data_p(buf,0,PSTR("神舟III号\r\n\n\rHTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>200 OK</h1>"));
                    goto SENDTCP;
                }
                if (strncmp("/ ",(char *)&(buf[dat_p+4]),2)==0)
                {
                    /* 如果是通过网页方式登录,输出如下提示信息 */
                    plen=fill_tcp_data_p(buf,plen,PSTR("<p>Usage: "));
                    plen=fill_tcp_data(buf,plen,baseurl);
                    plen=fill_tcp_data_p(buf,plen,PSTR("password</p>"));
                    goto SENDTCP;
                }

                /* 分析网页控制的命令类型 */
                cmd=analyse_get_url((char *)&(buf[dat_p+5]));
    
                if (cmd==-1)
                {
                    plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n<h1>401 Unauthorized</h1>"));
                    goto SENDTCP;
                }

                /* 网页控制点亮LED灯DS1 */
                if (cmd==1)
                {
                    LED1_ON();
                    i=1;
                }

                /* 网页控制熄灭LED灯DS1 */
                if (cmd==0)
                {
                    LED1_OFF();
                    i=0;
                }

                /* 更新网页信息 */
                plen=print_webpage(buf,(i));
                
                SENDTCP:

                /* 发送一个确认TCP包 */
                make_tcp_ack_from_any(buf); 

                /* 发送一个数据TCP包 */
                make_tcp_ack_with_data(buf,plen); 

                continue;
            }
        }
        
        /* UDP包,监听1200端口的UDP包 */
        if (buf[IP_PROTO_P]==IP_PROTO_UDP_V&&buf[UDP_DST_PORT_H_P]==4&&buf[UDP_DST_PORT_L_P]==0xb0)
        {
            /* 获取UDP包数据区指针首地址 */
            payloadlen = buf[UDP_LEN_H_P];
            payloadlen = payloadlen<<8;
            payloadlen = (payloadlen+buf[UDP_LEN_L_P])-UDP_HEADER_LEN;
        
            /* 将UDP包数据区的数据放到buf1变量中 */
            for(i1=0; i1<payloadlen; i1++)
            {         
                buf1[i1]=buf[UDP_DATA_P+i1];
            }
            
            /* 回复一个UDP应答包,包的发送的数据内容与接收到的包数据内容相同 */
            make_udp_reply_from_request(buf,buf1,payloadlen,myudpport);
        }
    }
}</span>


以下提到所有关于行的信息都是针对上面标红的程序。


3-5行定义了要用的到参数,它们是plen代表数据包长度,dat_p代表解析出的数据,payloadlen是协议长度,buf1解析出的实验数据长度,cmd是应用程序中的命令。

8行将我们认为设置的mymac,myip,mywwwport赋值给应用中的变量mac,ip,wwwport。

10-12行实现参数打印。

17行通过读取ENC28J60的寄存器判断是否接收到数据。如果有接收到数据则将数据放入buf中,同时返回数据长度plen。(buf为定义的全局变量用于存储数据)

19-23行用于判断plen的数据为0说明没有数据,程序通过continue继续循环。如果不为0则继续往下执行。

26行用于判断以太网数据包是否为本机IP的ARP包,它内部的操作函数为

unsigned char eth_type_is_arp_and_my_ip(unsigned char *buf,unsigned  int len)
{
	unsigned char i=0;

    /* 包长度不够,直接返回 */
	if (len<41)
	{
	    return(0);
	}

    /* 如果类型不是ARP包,直接返回 */
	if(buf[ETH_TYPE_H_P] != ETHTYPE_ARP_H_V || buf[ETH_TYPE_L_P] != ETHTYPE_ARP_L_V)
	{
	    return(0);
	}

    /* 如果ARP包的IP地址与本机IP不一致,直接返回 */
	while(i<4)
	{
	    if(buf[ETH_ARP_DST_IP_P+i] != ipaddr[i])
		{
	        return(0);
	    }
	    i++;
	}

    printf("\n\r收到主机[%d.%d.%d.%d]发送的ARP包"
                    ,buf[ETH_ARP_SRC_IP_P]
                    ,buf[ETH_ARP_SRC_IP_P+1]
                    ,buf[ETH_ARP_SRC_IP_P+2]
                    ,buf[ETH_ARP_SRC_IP_P+3]);
    
	return(1);
}


首先判断以太网数据包的ETH首部(总第12和13字节)类型长度是否为ARP协议代码(0x0806),然后判断ARP首部中总第0x26字节至第0x29字节存储的目的IP时候与本机相符,如果没有问题,则返回1,否则返回0

29行,在判断是arp包,且为目的IP为本机IP的情况下,设置返回一个ARP响应包,该行函数的内部操作函数为

void make_arp_answer_from_request(unsigned char *buf)
{
    unsigned char i=0;
    
    /* 填写ETH包头的目的MAC地址以及源MAC地址 */
    make_eth(buf); 
    
    /* 将ARP包的操作类型字节改为ARP响应 */
    buf[ETH_ARP_OPCODE_H_P]=ETH_ARP_OPCODE_REPLY_H_V;   
    buf[ETH_ARP_OPCODE_L_P]=ETH_ARP_OPCODE_REPLY_L_V;
    
    /* 填写ARP包的目的MAC地址以及源MAC地址 */
    while(i<6)
    {
        buf[ETH_ARP_DST_MAC_P+i]=buf[ETH_ARP_SRC_MAC_P+i];
        buf[ETH_ARP_SRC_MAC_P+i]=macaddr[i];
        i++;
    }
          
    i=0;

    /* 填写ARP包的目的IP地址以及源IP地址 */
    while(i<4)
    {
        buf[ETH_ARP_DST_IP_P+i]=buf[ETH_ARP_SRC_IP_P+i];
        buf[ETH_ARP_SRC_IP_P+i]=ipaddr[i];
        i++;
    }
    
    printf("\n\r神舟III号[%d.%d.%d.%d]发送ARP相应",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3]);
    
    /* 发送ARP相应包 ,ARP包有42个字节 */
    enc28j60PacketSend(42,buf); 
}


首先是填写ETH首部,因为是返回ARP响应,因此通过make_eth函数将buf中的源mac数据填入到目的mac位置处,将本服务器的mac填入源mac位置处,实现源mac与目的mac位置的对调。然后修改ARP首部中操作类型(总第0x14和0x15字节)为ARP响应(0x0002)(原本是0x0001,ARP请求)。接着将ARP首部中的MAC和源IP分别与目的MAC和目的IP对调。最后通过ENC28J60函数发送ARP数据包。完成一次ARP的通信。

30行表明在完成ARP响应后,通过continue重新开始接收数据包(后面的if函数就不执行,因为已经识别为ARP包了,其它的if肯定不成立)

如果不是ARP包则26行至31行不成立,程序继续往下执行。

34行判断以太网包是否为合法的合法的IP包,该行函数的内部操作函数为

unsigned char eth_type_is_ip_and_my_ip(unsigned char *buf,unsigned  int len)
{
    unsigned char i=0;
    
    /* 包长度不够,直接返回 */
    if (len<42)
    {
        return(0);
    }
    
    /* 如果包类型不是IP包,直接返回 */
    if(buf[ETH_TYPE_H_P]!=ETHTYPE_IP_H_V || buf[ETH_TYPE_L_P]!=ETHTYPE_IP_L_V)
    {    
       return(0);
    }
    
    /*************************************
    如果长度参数不正确,直接返回 
    IP包头中 4位的版本,4位头长
    1000     1001
    IPv4    5个4字节
    *************************************/   
    if (buf[IP_HEADER_LEN_VER_P]!=0x45)   
    {
        /* must be IP V4 and 20 byte header */
        return(0);
    }
    
    /* 如果IP包的IP地址与本机IP不一致,直接返回 */   
    while(i<4)
    {
        if(buf[IP_DST_P+i]!=ipaddr[i])
        {
            return(0);
        }
        i++;
    }
    return(1);
}


函数首先先判断包的长度是否小于42,如果小于42则肯定不对,直接返回错误。关于42这个数字的选取,它是把ICMP包也考虑进去了,(42=14+20+8),如果单纯考虑TCP包应该是60.


然后判断以太网数据包的ETH首部(总第12和13字节)类型长度是否为IP协议代码(0x0800),接着判断IP首部中总第0x0E字节是否为0x45(版本为0100代表IPv4,头长为5,以4字节为单位,所以IP首位长度为20个字节),最后判断IP首部中(总第0x1e字节至0x21字节)代表的目的IP是否与本机IP相符。如果没有问题,则返回1,否则返回0。

如果判断为不合法的IP包,则通过37行的continue,重新开始接收数据。如果判断为合法的IP包则继续往下执行,接下来有可能是ICMP,TCP或者UDP协议

41行通过if语句判断是否为ICMP包,并且是ICMP回射请求包,判断的方法是查看IP首部总第0x17字节的协议代码是否为1(1代表ICMP),并且总第0x22字节是否为8(8代表回射请求,0代表回射应答),如果两者都不正确,或者只有后者正确则说明不是ICMP,如果只有前者正确说明,是ICMP但是不是回射请求包,如果两者都正确说明是ICMP回射请求包,if条件成立,我们需要返回一个ICMP回射应答包

47行是发送回射应答包的处理程序,它的内部函数为

void make_echo_reply_from_request(unsigned char *buf,unsigned  int len)
{
	/* 填写包的目的MAC地址以及源MAC地址	*/
	make_eth(buf);

	/* 填写包的目的IP地址以及源IP地址 */
	make_ip(buf);

    /* 填写ICMP相应包类型,类型为回射应答 */
	buf[ICMP_TYPE_P]=ICMP_TYPE_ECHOREPLY_V;	  

    /*******************************************************************
    we changed only the icmp.type field from request(=8) to reply(=0).
	we can therefore easily correct the checksum:
    *******************************************************************/
	if (buf[ICMP_CHECKSUM_P] > (0xff-0x08))
	{
	    buf[ICMP_CHECKSUM_P+1]++;
	}
	buf[ICMP_CHECKSUM_P]+=0x08;

    printf("\n\r神舟III号[%d.%d.%d.%d]发送ICMP包响应",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3]);

    /* 发送ICMP响应包 */
	enc28j60PacketSend(len,buf);
}


同样的调用make_eth函数实现源mac与目的mac的对调。调用make_ip函数实现源ip与目的ip的对调。不过在make_ip中还增加了fill_ip_hdr_checksum函数,它内部的操作是

void fill_ip_hdr_checksum(unsigned char *buf)
{
    unsigned  int ck;

    /* 清空两个字节的校验和 */
    buf[IP_CHECKSUM_P]=0;
    buf[IP_CHECKSUM_P+1]=0;

    /* 不允许数据包分段,,此为最后数据包,偏移地址为0 */
    buf[IP_FLAGS_P]=0x40; 
    buf[IP_FLAGS_P+1]=0;  

    /* 生存时间为64ms */
    buf[IP_TTL_P]=64; 
    
    /* 计算校验和,并填充 */
    ck=checksum(&buf[IP_P], IP_HEADER_LEN,0);
    buf[IP_CHECKSUM_P]=ck>>8;
    buf[IP_CHECKSUM_P+1]=ck& 0xff;
}


函数先清空校验和,然后设置IP首部标志总第0x14字节中不分段位为1,表示不允许分段,段偏移量为0,设置生存时间为64跳。最后计算校验和,填充校验和。校验和计算函数的计算方法我们这里不做介绍。

make_ip函数执行完成后,将ICMP相应的包类型修改为回射应答。然后填写IP校验和,最后通过ENC28J60将数据发送出去。

48行处理完ICMP回射应答后,通过continue语句重新接收数据包做处理。

如果不是ICMP包41行if函数不成立,程序继续往下执行。

52行if语句判断是否TCP包,判断的依据为查看IP首部(总第0x17字节)的协议代码是否为6(6代表TCP),并且TCP首部目的端口(总第0x24字节和0x25字节)是否与服务器的port相同,如果不正确则说明不是TCP包,程序继续往下执行。如果两者都正确说明是TCP包,if条件成立,执行if里面的函数

57行if语句判断TCP包类型,判断方法是对TCP首部中的6个标识位进行识别(总第0x2F字节的后6位)判断SYN位是否置位,如果没有置位则if条件不成立继续往下执行,如果SYN位置位说明TCP包是一个连接请求TCP包。

60行对连接请求TCP包做应答,该行函数的内部操作是

void make_tcp_synack_from_syn(unsigned char *buf)
{
    unsigned  int ck;
    
    /* 填写包的目的MAC地址以及源MAC地址	*/
    make_eth(buf);
    
    /* 计算包长度=IP头(20)+TCP头(24)*/
    buf[IP_TOTLEN_H_P]=0;
    buf[IP_TOTLEN_L_P]=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4;
    
    /* 填写包的目的IP地址以及源IP地址 */
    make_ip(buf);

    buf[TCP_FLAGS_P]=TCP_FLAGS_SYNACK_V;
    
    /* 填写TCP头 */
    make_tcphead(buf,1,1,0);
    
    /* 计算校验和 长度=8 (开始于ip.src) + TCP头长(20) + 可选选项(4字节,mss) */
    ck=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN+4,2);
    buf[TCP_CHECKSUM_H_P]=ck>>8;
    buf[TCP_CHECKSUM_L_P]=ck& 0xff;
    
    printf("\n\r神舟III号[%d.%d.%d.%d]发送SYN包响应",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3]);
    
    enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+4+ETH_HEADER_LEN,buf);
}


首先是make_eth,然后计算包裹长度填入IP首部的包裹长度(总第0x10字节和0x11字节),接着make_ip,接着修改TCP首部中6个标志为中的ACK与SYN位置位,表示同步确认。接着调用make_tcphead函数,该函数的内部操作:"首先将TCP首部的源端口与目的端口互相对调,然后设置数据序号与确认序号,接着设置选项字节,最后更新偏移位。"接着计算校验和填充TCP首部中的校验和,最后通过ENC28J60将数据发送出去。

61行,发送同步请求应答TCP包后,continue语句重新接收数据包做处理。

65行通过if语句判断TCP包类型,判断方法是对TCP首部中的6个标识位进行识别(总第0x2F字节的后6位)判断ACK位是否置位,如果没有置位则if条件不成立继续往下执行,如果ACK位置位说明TCP包是一个TCP确认包。接下来就解析这个包数据

70行初始化数据格式,并获取TCP包,数据区长度。数据区长度保存在全局变量info_data_len中

71行用于获取数据区首地址在buf中的位置。其结果返回到dat_p变量。

74行判断是否为一个无数据的TCP包,如果有数据则不进入if内部,程序继续往下进行,如果是无数据的TCP包,则在if内部做继续判断

77行在上一个if内部继续做判断通过6个bit的标志位判断是否为要求释放连接(终止)的TCP包。如果不是则,continue继续等待重新接收数据包,如果是执行81行函数

81行函数对要求释放连接(终止)的TCP包做应答,它内部到操作函数是

void make_tcp_ack_from_any(unsigned char *buf)
{
    unsigned  int j;

    /* 填写包的目的MAC地址以及源MAC地址	*/
    make_eth(buf);
    
    /* 填充包的类型为ack响应包 */
    buf[TCP_FLAGS_P]=TCP_FLAGS_ACK_V;

    /* 判断接收的TCP包内是否有数据 */
    if (info_data_len==0)
    {
        make_tcphead(buf,1,0,1); 
    }
    else
    {
        make_tcphead(buf,info_data_len,0,1); 
    }
    
    /* 设置IP包头中总长度:长度=IP头(20)+TCP头(20(不包括选项字节))*/ 
    j=IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN;
    buf[IP_TOTLEN_H_P]=j>>8;
    buf[IP_TOTLEN_L_P]=j& 0xff;

   /* 填写包的目的IP地址以及源IP地址 */
    make_ip(buf);

    /* 计算校验和 长度=8 (开始于ip.src) + TCP头长(20) + 数据长度 */
    j=checksum(&buf[IP_SRC_P], 8+TCP_HEADER_LEN_PLAIN,2);
    buf[TCP_CHECKSUM_H_P]=j>>8;
    buf[TCP_CHECKSUM_L_P]=j& 0xff;
    
    printf("\n\r神舟III号[%d.%d.%d.%d]发送ACK包响应",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3]);
    
    enc28j60PacketSend(IP_HEADER_LEN+TCP_HEADER_LEN_PLAIN+ETH_HEADER_LEN,buf);
}


首先make_eth,然后修改TCP中标志位,使TCP包变为应答包,根据TCP包中是否有数据make_tcphead,接着设置IP首部中包裹长度,接着make_ip,接着填充校验和,最后通过ENC28J60函数将数据发送出去。

如果是有数据的TCP包则接下来解析数据。

88行到93行为判断网络为Telnet方式登录的处理程序。首先判断的依据是真正用户数据(TCP数据)的前四个字节不是“GET ”,如果是Telnet登录方式则通过91行的函数填充数据。然后通过92行语句跳转至131行至135行,发送TCP确认包及TCP数据包。

94行到 101行为判断通过网页方式登录的处理程序。首先判断的依据是真正用户数据(TCP数据)的第4个字节和第5个字节为“/ ”,如果是这种方式则通过97行至99行填充TCP要发送的数据。同100行语句跳转至131行至135行,发送TCP确认包及TCP数据包。

如果不是以上两者之一的登录方式,则程序继续往下执行,分析TCP数据

104行分析网页控制命令,函数内部操作为

signed char analyse_get_url(char *str)
{
    unsigned char i=0;
    
    /* 密码错误则返回-1 */
    if (verify_password(str)==0)
    {
        return(-1);
    }

    /* 密码不要长于9个,寻找第一个'/' */
    while(*str && i<10 && *str >',' && *str<'{')
    {
        if (*str=='/')
        {
            str++;
            break;
        }
        i++;
        str++;
    }

    if (*str < 0x3a && *str > 0x2f)
    {
        /* ascii码表中的数字 */
        return(*str-0x30);
    }

    return(-2);
}


该函数的实参是&(buf[dat_p+5]),即从实际用户数据的第5个字节开始分析,首先通过verify_password函数验证密码是否正确,验证的方式是通过strncmp函数将数据与密码做依次对比。接下来通过while语句寻找第一个“/”,然后返回“/”后面的那个数字作为命令。

当cmd命令为负数时实际上是没有通过密码验证的。当cmd为正数时是正确的命令根据不同的命令做不同的操作。具体看106行至124行。

127行用于更新网页信息,整个函数实际上都是在填充TCP数据。这个我也没太研究这里就不说了。。

如果也不是TCP包,我们继续往下执行程序看是否为UDP包

142行if语句判断是否UDP包,判断的依据为查看IP首部(总第0x17字节)的协议代码是否为17(17代表UDP),并且UDP首部目的端口(总第0x24字节和0x25字节)是否为1200.如果不正确则说明不是UDP包,程序继续往下执行。如果是UDP包,则执行if里面的函数

145行至147行用于获取UDP数据长度。

150行至153行,将UDP数据导入到buf1数组中

156行用于回复一个UDP应答包,包的发送的数据内容与接收到的包数据内容相同。