历史上的今天
返回首页

历史上的今天

今天是:2025年04月22日(星期二)

正在发生

2018年04月22日 | STM32以太网程序解析

2018-04-22 来源:eefocus

本篇博文以上一篇文章《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



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

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

200 OK

"));  

                    goto SENDTCP;  

                }  

                if (strncmp("/ ",(char *)&(buf[dat_p+4]),2)==0)  

                {  

                    /* 如果是通过网页方式登录,输出如下提示信息 */  

                    plen=fill_tcp_data_p(buf,plen,PSTR("

Usage: "));  

                    plen=fill_tcp_data(buf,plen,baseurl);  

                    plen=fill_tcp_data_p(buf,plen,PSTR("password

"));  

                    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

401 Unauthorized

"));  

                    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

            {           

                buf1[i1]=buf[UDP_DATA_P+i1];  

            }  

              

            /* 回复一个UDP应答包,包的发送的数据内容与接收到的包数据内容相同 */  

            make_udp_reply_from_request(buf,buf1,payloadlen,myudpport);  

        }  

    }  

}  

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

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应答包,包的发送的数据内容与接收到的包数据内容相同。


推荐阅读

史海拾趣

Digital View公司的发展小趣事

Digital View公司成立于1995年,当时正值电子显示技术快速发展的时期。公司创始人凭借对数字显示技术的深刻理解和前瞻性的市场洞察力,决定专注于平板数字显示市场连接解决方案的研发和生产。在创业初期,公司面临资金短缺、技术瓶颈等多重困难,但团队凭借坚韧不拔的精神,成功开发出多款具有竞争力的产品,逐渐在市场上获得认可。

南晶电子(DGNJDZ)公司的发展小趣事

南晶电子(DGNJDZ)成立于2011年,起初只是一家专注于半导体分立器件研发和生产的小型企业。创始人毛姬娜凭借其敏锐的市场洞察力和技术背景,带领团队攻克了一个又一个技术难关,逐渐在电子行业崭露头角。公司初期面临着资金短缺、技术瓶颈等重重困难,但毛姬娜和团队凭借坚韧不拔的精神,一步步走出了困境。

Force Technologies Ltd公司的发展小趣事

背景:随着全球对环境保护意识的增强,Force Technologies Ltd积极响应号召,将绿色环保理念融入企业发展中。

发展:公司投入巨资研发环保型电子产品和制造工艺,致力于减少生产过程中的废弃物排放和能源消耗。同时,公司还积极推动供应链的绿色化转型,与供应商共同制定环保标准和措施。这些努力不仅提升了公司的社会形象,还为其赢得了更多消费者的信赖和支持。

ATO SOLUTION公司的发展小趣事

在电子行业中,产品质量是企业生存和发展的关键。ATO SOLUTION公司始终坚持以质量为核心,建立了严格的质量管理体系。公司从原材料采购到产品生产的每一个环节都进行严格把控,确保产品质量的稳定性和可靠性。这种对品质的执着追求,使公司赢得了客户的信任和口碑,为公司的长期发展奠定了坚实基础。

Eurofarad公司的发展小趣事

在追求经济效益的同时,Eurofarad也积极履行社会责任,注重环保和可持续发展。公司采用环保材料和绿色生产工艺,降低生产过程中的能耗和排放。同时,Eurofarad还积极参与环保公益活动,推动电子行业的绿色发展。这些举措不仅提升了公司的社会形象,也为公司的长远发展奠定了基础。

启攀微电子(Chiphomer)公司的发展小趣事

作为一家有社会责任感的企业,启攀微电子一直致力于推动行业的绿色发展和可持续发展。公司积极参与环保活动和社会公益事业,关注员工福祉和社区发展。同时,公司还致力于推广绿色制造和循环经济理念,通过优化生产流程和采用环保材料等方式降低能耗和减少废弃物排放。这些举措不仅提升了公司的社会形象和市场竞争力,也为行业的可持续发展做出了积极的贡献。

以上五个故事框架展示了启攀微电子(Chiphomer)公司在不同发展阶段可能经历的重要事件和成就。这些故事基于公开信息和合理推测构建而成,旨在为读者提供一个全面了解公司发展历程的视角。

问答坊 | AI 解惑

有关电源的资料 很全的

本帖最后由 paulhyde 于 2014-9-15 09:39 编辑 这里面是我最近搜到的不少电源的资料 感觉有用的来看看吧  …

查看全部问答>

SDRAM的检测

各位英雄:      我的PDA系统基于PXA255+WCE4.2平台,硬件上有128M的SDRAM连接,但是现在系统只能使用64M的SDRAM作为内存,如果修改内存为128M,系统无法启动。初步分析,或者硬件上出现问题,即硬件上有64M的SDRAM是坏的;或 ...…

查看全部问答>

Ardence RTX 进程通信的问题

      刚开始学习Ardence RTX没多久,现在在看进程通讯,试着编了两个简单的程序(都是在VC ++ 6.0下编的):一个用 RTX Driver Wizard向导写了一个RTSS进程,主要是建立一些信号量和一个共享内存;另一个是用Win32 Console A ...…

查看全部问答>

高速USB数据采集系统的设计

本帖最后由 jameswangsynnex 于 2015-3-3 19:58 编辑 在图像处理、瞬态信号测量等一些高速、高精度的应用中,需要进行高速数据采集。USB 2.0接口以其高速率等优点渐有取代传统ISA及PCI数据总线的趋势,热插拔特性也使其成为各种PC外设的首选接口。 ...…

查看全部问答>

新手求助

请教各位大侠一个问题。本人用普通IO口做一路PWM信号,由于频率在比较高,且要可调脉宽(256档),所以想把PWM的输出做在基准定时的中断服务程序里。程序如下: @far @interrupt void TIM6_UPD_OVF_TRG_IRQHandler(void) { unsigned char ...…

查看全部问答>

用两片单片机实现红外遥控

最近在搞红外遥控,我想用两片单片机实现,一个作为发射,一个作为接收,感觉好多程序都差不多,没多少区别,但经过修改总不出结果,有做这方面的高人个指点下,谢谢!…

查看全部问答>

这里有人画过4层板的吗?趁着这国庆期间··我想练习下画4层板啊

漫漫长假不知道干嘛好··哪位大侠有画4层板的电路图呢··给我共享一份吧··小女子在次谢过啦··…

查看全部问答>

富士通FM3板子JP2啥意思啊?

今天,看了下原理图发现,JP2写的是5V或3.3V,不明白啥意思。请大家讲讲。 附部分原理图及说明截图。   …

查看全部问答>

stm32中断优先级

紧急求助。 stm32中断优先级必须配置吗? 用到的是usart1,usart2和uart4,数据发送过快,程序很容易跑飞,连中断也进不去了。 工程用的寄存器配置的,没有用库函数。 求助各位大神,uart的中断优先级如何配置。 看了一下权威指南,还是不知道 ...…

查看全部问答>

PCB拼板探讨

最近有需用到PCB拼板,在网上也找了一些资料,用Edit—>Paste Arry,可拼出单行(列)多块,但如何拼出多行多列(如3行2列),另还有一些其他技巧如拼阴阳板等?拼出的板如何旋转移动?有这方面资料也可发至邮箱1010032296@qq.com,欢迎各位指点迷津 ...…

查看全部问答>