声明:由于CMX micronet的协议栈是需要付费的,此代码来源于网上,用来学习交流,用于其它的商业活动请联系
最近在弄毕业设计,拿的是同事做好的一个51的串口转网口的板子,cpu c8051f340 ,比较高档的一个51单片机,silicon lab公司的,改进的cip-51内核,所有的外设配置都比较灵活,感觉不错。网卡也是 silicon labs cp2200,官方有两者应用的公板出售,并且提供协议栈,但是对协议栈封装成了库,看不到原代码,比较不爽,同事给我的代码用的不是库了,而是实实在在的代码,后来网上一查,这个协议栈是基于cmx micronet改过来的,此前对网络的这些底层实现基本为0,研究了几天,看明白了点点。
目前的代友码主要实现了arp icmp ip tcp udp http,目前是一个webserver,通过html提交数据向串口发出数据。
主要的程序目录如下:
c8051f340.h------此单片机的一些sfr及地址的定义
cp220x.h---------cp220x的寄存器定义
cp220x.c---------将cp220x的寄存器映射到51的地址空间
net.h------------定义了一些各种协议的报文头格式及一些标志位,还有些预处理
arp.h------------arp协议的实现的所需函数的声明
arp.c------------arp协议实现所需函数的具体实现
icmp.h-----------icmp协议的实现的所需函数的声明
icmp.c-----------icmp协议实现所需函数的具体实现
tcp.h------------tcp协议的实现的所需函数的声明
tcp.c------------tcp协议实现所需函数的具体实现
udp.h------------udp协议的实现的所需函数的声明
udp.c------------udp协议实现所需函数的具体实现
http.h-----------http协议的实现的所需函数的声明
http.c-----------http协议实现所需函数的具体实现
cksum.h----------校验和所需函数的声明
cksum.c----------校验和所需函数的实现
先看net.h文件
注:我只是加了些中文注释
//-----------------------------------------------------------------------------
// NET.H
//一些网络相关的包,报文头,常量的数据结构
//-----------------------------------------------------------------------------
typedef unsigned char UCHAR;//1 byte
typedef unsigned int UINT;//2 bytes
typedef unsigned long ULONG;//4 bytes
typedef unsigned long LONG; void eth_send(UCHAR xdata * outbuf, UCHAR * hwaddr, UINT ptype, UINT len); // 常量定义
#define TRUE 1 #define ON 1 #define OK 1 #define FALSE 0 #define OFF 0 #define MATCH 0 #define RET 0x0D #define LF 0x0A #define SPACE 0x20
// 端口定义
#define ECHO_PORT 7 #define DAYTIME_PORT 13 #define CHARGEN_PORT 19 #define TIME_PORT 37 #define HTTP_PORT 80
// 事件标志
#define EVENT_ETH_ARRIVED 0x0001 //有待处理的以太网帧事件
#define EVENT_AGE_ARP_CACHE 0x0002 //更新ARP缓存事件 60s更新一次
#define EVENT_TCP_RETRANSMIT 0x0004 //TCP重传事件
#define EVENT_TCP_INACTIVITY 0x0008 //关断不活动连接事件 (0.5s)
#define EVENT_ARP_RETRANSMIT 0x0010 //ARP请求数据包重传事件
#define EVENT_READ_ANALOG 0x0020 #define EVENT_RS232_ARRIVED 0x0040 //串口数据到达事件
// 以太网络帧的类型
#define IP_PACKET 0x0800 #define ARP_PACKET 0x0806 #define RARP_PACKET 0x8035
// IP数据段中所采用的协议类型
#define ICMP_TYPE 1 #define IGMP_TYPE 2 #define TCP_TYPE 6 #define UDP_TYPE 17
//ARP消息类型
#define ARP_REQUEST 1 #define ARP_RESPONSE 2 #define RARP_REQUEST 3 #define RARP_RESPONSE 4
// 在ARP报文中的硬件类型
#define DIX_ETHERNET 1 #define IEEE_ETHERNET 6
//ARP缓存
typedef struct { ULONG ipaddr; UCHAR hwaddr[6]; UCHAR timer; } ARP_CACHE;
//ARP请求后的答复
typedef struct { UCHAR xdata * buf; ULONG ipaddr; UCHAR proto_id; UINT len; UCHAR timer; } WAIT;
//ARP报文头 28字节
typedef struct { UINT hardware_type;//硬件类型2byte
UINT protocol_type;//协议类型2byte
UCHAR hwaddr_len;//MAC地址长度1byte
UCHAR ipaddr_len;//IP地址长度1byte
UINT message_type;//操作类型 arp应答、arp请求、rarp应答、rarp请求
UCHAR source_hwaddr[6];//源MAC地址
ULONG source_ipaddr;//源IP地址
UCHAR dest_hwaddr[6];//目的MAC地址
ULONG dest_ipaddr;//目的IP地址
} ARP_HEADER;
//以太网帧头部 14字节
typedef struct { UCHAR dest_hwaddr[6]; //目的mac地址
UCHAR source_hwaddr[6]; //源mac地址
UINT frame_type; //协议类型
} ETH_HEADER;
//IP报文头 20字节
typedef struct { UCHAR ver_len; //版本(4bit)与此报文头的长度(4bit)
UCHAR type_of_service; //服务类型 8bit,用数值表示出报文的重要程度,此数大的报文优先
UINT total_length; //总长度 16bit
UINT identifier; //标识 16bit(假如多于一个报文,用来标识出报文位置)
UINT fragment_info; //标志(3bit)片偏移(13bit)
UCHAR time_to_live; //生存时间(8bit)
UCHAR protocol_id; //处理此报文的上层协议(8bit)
UINT header_cksum; //首部检验和(16bit)
ULONG source_ipaddr; //源IP(32bit)
ULONG dest_ipaddr; // 目的IP(32bit)
} IP_HEADER;
//ICMP PING回显请求报文结构
typedef struct { UCHAR msg_type;//类型8bit
UCHAR msg_code;//代码 8bit
UINT checksum;//校验和16bit
UINT identifier;//标识符16bit
UINT sequence;//序号16bit
UCHAR echo_data;//选项数据,必须回显
} PING_HEADER;
//ICMP差错报文结构(目的不可达报文)
typedef struct { UCHAR msg_type; //类型8bit
UCHAR msg_code; //代码 8bit
UINT checksum; //校验和16bit
ULONG msg_data; //消息数据32bit
UCHAR echo_data; //回显数据8bit
} ICMP_ERR_HEADER;
//UDP数据包结构
typedef struct { UINT source_port; UINT dest_port; UINT length; UINT checksum; UCHAR msg_data; } UDP_HEADER;
//TCP报文头 21字节(不算选项的话21字节)
typedef struct { UINT source_port;//源端口号 16bit
UINT dest_port; //目的端口号 16bit
ULONG sequence; //序号从TCP发端向TCP收端发送的数据字节流 32bit
ULONG ack_number; //32bit 确认序号
UINT flags; //首部长度及一些标志位
UINT window; //窗口大小 16bit
UINT checksum;//校验和 16bit
UINT urgent_ptr;//紧急指针16bit
UCHAR options;//选项
} TCP_HEADER;
//TCP连接的数据结构
typedef struct { ULONG ipaddr; UINT port; ULONG his_sequence; ULONG my_sequence; ULONG old_sequence; ULONG his_ack; UCHAR timer; UCHAR inactivity; UCHAR state; char query[20]; } CONNECTION;
|
再看一下arp协议所实现 的函数
//-----------------------------------------------------------------------------
//模块处理ARP消息和APR应求并且管理ARP缓存(ARP协议实现)
//-----------------------------------------------------------------------------
#include <string.h> #include <stdio.h> #include "C8051f340.h" #include "net.h" #include "ip.h" #include "arp.h"
extern WAIT xdata wait; extern UCHAR xdata my_hwaddr[]; extern UCHAR code broadcast_hwaddr[]; extern ULONG code my_ipaddr; extern ULONG code my_subnet; extern ULONG code gateway_ipaddr; extern UCHAR idata debug; ARP_CACHE xdata arp_cache[CACHESIZE];// 定义arp缓存为10个ARP_CACHE单位
UCHAR waiting_for_arp;
extern char xdata outbuf1[];
void init_arp(void) { memset(arp_cache, 0, sizeof(arp_cache)); //初始化arp缓存全部为0
memset(&wait, 0, sizeof(wait)); //初台化wait空间
waiting_for_arp = FALSE; //无arp请求
}
//------------------------------------------------------------------------
// This is called every 60 seconds to age the ARP cache
// If an entry times out then it is deleted from the cache
// See "TCP/IP Illustrated, Volume 1" Sect 4.3
// 函数作用:更新ARP缓存
//------------------------------------------------------------------------
void age_arp_cache(void) { UCHAR i; for (i=0; i < CACHESIZE; i++)//查询arp缓存表 { if ((arp_cache[i].ipaddr != 0) && (arp_cache[i].timer)) { arp_cache[i].timer--; if (arp_cache[i].timer == 0) { // Timed out so clear out cache entry
// Do not need to zero hwaddr //时间到则进行清零 arp_cache[i].ipaddr = 0; } } } }
//------------------------------------------------------------------------
// This allocates memory for the entire outgoing message,
// including eth and ip headers, then builds an outgoing
// ARP response message
// See "TCP/IP Illustrated, Volume 1" Sect 4.4
// ARP请求广播
//------------------------------------------------------------------------
void arp_send(UCHAR * hwaddr, ULONG ipaddr, UCHAR msg_type) { UCHAR xdata * outbuf; ARP_HEADER xdata * arp; // Allocate memory for entire outgoing message including
// eth header. Always 42 bytes
// outbuf = (UCHAR xdata *)malloc(42);
outbuf = outbuf1; // Allow 14 bytes for the ethernet header//前14个字节为以太网帧头部
arp = (ARP_HEADER xdata *)(outbuf + 14); arp->hardware_type = DIX_ETHERNET; arp->protocol_type = IP_PACKET; arp->hwaddr_len = 6; arp->ipaddr_len = 4; arp->message_type = (UINT)msg_type; // My hardware address and IP addresses
memcpy(arp->source_hwaddr, my_hwaddr, 6); arp->source_ipaddr = my_ipaddr; // Destination hwaddr and dest IP addr
if (msg_type == ARP_REQUEST) memset(arp->dest_hwaddr, 0, 6); else memcpy(arp->dest_hwaddr, hwaddr, 6); arp->dest_ipaddr = ipaddr; // If request then the message is a brodcast, if a response then
// send to specified hwaddr
// ARP payload size is always 28 bytes
if (msg_type == ARP_REQUEST) eth_send(outbuf, broadcast_hwaddr, ARP_PACKET, 28); else eth_send(outbuf, hwaddr, ARP_PACKET, 28); }
//------------------------------------------------------------------------
// This re-sends an ARP request if there was no response to
// the first one. It is called every 0.5 seconds. If there
// is no response after 2 re-tries, the datagram that IP was
// trying to send is deleted
//ARP重新发送
//-----------------------------------------------------------------------
void arp_retransmit(void) { static UCHAR idata retries = 0; if ((waiting_for_arp) && (wait.timer)) { wait.timer--; if (wait.timer == 0) { retries++; if (retries <= 2) { arp_send(NULL, wait.ipaddr, ARP_REQUEST); wait.timer = ARP_TIMEOUT; } else { wait.timer = 0; waiting_for_arp = 0; // free(wait.buf);
} } } }
//------------------------------------------------------------------------
// Find the ethernet hardware address for the given ip address
// If destination IP is on my subnet then we want the eth
// address of destination, otherwise we want eth addr of gateway.
// Look in ARP cache first. If not found there, send ARP request.
// Return pointer to the hardware address or NULL if not found
// See "TCP/IP Illustrated, Volume 1" Sect 4.5
//ARP将IP地址解析为硬件地址的过程,返回mac 地址存储的首地址
//------------------------------------------------------------------------
UCHAR xdata * arp_resolve(ULONG dest_ipaddr) { UCHAR i; // If destination IP is not on my subnet then we really want eth addr
// of gateway, not destination IP
if ((dest_ipaddr ^ my_ipaddr) & my_subnet) { if (gateway_ipaddr == 0) { return (NULL); } else dest_ipaddr = gateway_ipaddr; } // See if IP addr of interest is in ARP cache
for (i=0; i < CACHESIZE; i++) { if (arp_cache[i].ipaddr == dest_ipaddr) return (&arp_cache[i].hwaddr[0]); }
// Not in cache so broadcast ARP request
arp_send(NULL, dest_ipaddr, ARP_REQUEST); // Set a flag to indicate that an IP datagram is waiting
// to be sent
waiting_for_arp = TRUE; // Null means that we have sent an ARP request
return (NULL); }
//------------------------------------------------------------------------
// This handles incoming ARP messages
// See "TCP/IP Illustrated, Volume 1" Sect 4.4
// Todo: Resolve problem of trying to add to a full cache
// 接收ARP回复
//------------------------------------------------------------------------
void arp_rcve(UCHAR xdata * inbuf) { UCHAR idata i, cached, oldest; UINT idata minimum; ARP_HEADER xdata * arp; arp = (ARP_HEADER xdata *)(inbuf + 14); //去除以太网帧的头信息
cached = FALSE; // Print message
// Validate incoming frame //表明是无效的帧
if ((arp->hardware_type != DIX_ETHERNET) || (arp->protocol_type != IP_PACKET)) return;
// Search ARP cache for senders IP address
// If found, update entry and restart timer
for (i=0; i < CACHESIZE; i++) { if (arp_cache[i].ipaddr == arp->source_ipaddr) { memcpy(&arp_cache[i].hwaddr[0], &arp->source_hwaddr[0], 6); arp_cache[i].timer = CACHETIME; cached = TRUE; break; } } if (arp->dest_ipaddr != my_ipaddr) return; // At this point we know the the frame is addressed to me
// If not already in cache then add entry and start timer
if (cached == FALSE) { // Find first blank space and add entry
// Blank entries are indicated by ip addr = 0
for (i=0; i < CACHESIZE; i++) { if (arp_cache[i].ipaddr == 0) { arp_cache[i].ipaddr = arp->source_ipaddr; memcpy(&arp_cache[i].hwaddr[0], &arp->source_hwaddr[0], 6); arp_cache[i].timer = CACHETIME; break; } }
// If no blank entries in arp cache then sort cache
// to find oldest entry and replace it
if (i == CACHESIZE) { // Oldest entry is the one with lowest timer value
minimum = 0xFFFF; for (i=0; i < CACHESIZE; i++) { if (arp_cache[i].timer < minimum) { minimum = arp_cache[i].timer; oldest = i; } } // "oldest" is now index of oldest entry, so replace it
arp_cache[oldest].ipaddr = arp->source_ipaddr; memcpy(&arp_cache[oldest].hwaddr[0], &arp->source_hwaddr[0], 6); arp_cache[oldest].timer = CACHETIME; } }
// If we are waiting for an arp response and the arp response
// that just came in is addressed to me and is from the host
// we are waiting for, then send the message-in-waiting
if (arp->message_type == ARP_RESPONSE) { if ((waiting_for_arp) && (wait.ipaddr == arp->source_ipaddr)) { waiting_for_arp = FALSE; ip_send(wait.buf, wait.ipaddr, wait.proto_id, wait.len); } } else if (arp->message_type == ARP_REQUEST) { // Send ARP response
arp_send(arp->source_hwaddr, arp->source_ipaddr, ARP_RESPONSE); } }
|
//-----------------------------------------------------------------------------
// ARP.H
//每条ARP缓存记录的生命周期为20分钟,2分钟内未用则删除。
//缓存容量满时,删除最老的记录
//-----------------------------------------------------------------------------
// Allow up to 10 entries in ARP cache
#define CACHESIZE 10 //ARP缓存大小
// ARP cache entry expiration time = 20 minutes
#define CACHETIME 20 // 60 second intervals
// Allow 2 seconds to receive an ARP reply
#define ARP_TIMEOUT 4 // 0.5 second intervals
void init_arp(void); UCHAR xdata * arp_resolve(ULONG); void arp_rcve(UCHAR xdata *); void age_arp_cache(void); void arp_retransmit(void);
|
程序注释得比较清晰了,具体过程需要参考tcp/ip 详解
再就是ip协议了,这个虽然经过精简后只有两个函数,看起来还是很抽像的
//-----------------------------------------------------------------------------
// 实现IP协议
// 参考 RFC 791, 1122, RFC 815
//-----------------------------------------------------------------------------
#include <string.h> #include "C8051f340.h" #include "net.h" #include "cksum.h" #include "arp.h" #include "icmp.h" #include "tcp.h" #include "ip.h"
extern UCHAR idata debug; extern ULONG code my_ipaddr; WAIT xdata wait;
//------------------------------------------------------------------------
// This handles outgoing IP datagrams. It adds the 20 byte IP header
// and checksum then forwards the IP datagram to the Ethernet layer
// for sending. See "TCP/IP Illustrated, Volume 1" Sect 3.2
// 处理向外发送的IP报文
//------------------------------------------------------------------------
void ip_send(UCHAR xdata * outbuf, ULONG ipaddr, UCHAR proto_id, UINT len) { IP_HEADER xdata * ip; UCHAR xdata * hwaddr; static UINT ip_ident = 0; ip = (IP_HEADER xdata *)(outbuf + 14); //14byte是以太网帧的报文头
ip->ver_len = 0x45; // IPv4 with 20 byte header
ip->type_of_service = 0; ip->total_length = 20 + len; ip->identifier = ip_ident++; // sequential identifier
ip->fragment_info = 0; // not fragmented 不分段
ip->time_to_live = 32; // max hops
ip->protocol_id = proto_id; // type of payload
ip->header_cksum = 0; ip->source_ipaddr = my_ipaddr; // Outgoing IP address
ip->dest_ipaddr = ipaddr;
// Compute and insert complement of checksum of ip header
// Outgoing ip header length is always 20 bytes
ip->header_cksum = ~cksum(outbuf + 14, 20); // Use ARP to get hardware address to send this to
hwaddr = arp_resolve(ip->dest_ipaddr); // Null means that the ARP resolver did not find the IP address
// in its cache so had to send an ARP request
if (hwaddr == NULL) { // Fill in the destination information so ehrn the ARP response
// arrives we can identify it and know what to do when we get it
wait.buf = outbuf; wait.ipaddr = ip->dest_ipaddr; wait.proto_id = proto_id; wait.len = len; wait.timer = ARP_TIMEOUT; return; } eth_send(outbuf, hwaddr, IP_PACKET, 20 + len); }
//------------------------------------------------------------------------
// This handles incoming IP datagrams from the Ethernet layer
// See "TCP/IP Illustrated, Volume 1" Sect 3.2
//------------------------------------------------------------------------
void ip_rcve(UCHAR xdata * inbuf) { IP_HEADER xdata * ip; UINT idata header_len, payload_len; ip = (IP_HEADER xdata *)(inbuf + 14); // Make sure it is addressed to my IP address
if (ip->dest_ipaddr != my_ipaddr) return;
// Validate checksum of ip header
header_len = 4 * (0x0F & ip->ver_len); payload_len = ip->total_length - header_len; if (cksum(inbuf + 14, header_len) != 0xFFFF) { return; } // Make sure incoming message is IP version 4
if ((ip->ver_len >> 4) != 0x04) { return; }
// Make sure incoming message is not fragmented because
// we cannot handle fragmented messages
//确保收进来的IP报文头是没有分段的,分段报文无法处理
if ((ip->fragment_info & 0x3FFF) != 0) { return; }
// At this point we have received a valid IP datagram addressed
// to me. We do not use header options, and do not forward
// messages, so in the unlikely event there are header options,
// delete them and shift the data down. The advantage is that
// layers such as UDP and TCP know where their data starts
// 如果报文头大于20的话,则需进行处理
if (header_len > 20) { // Use memmove because of overlap
memmove(inbuf + 34, inbuf + 14 + header_len, payload_len);
// Adjust info to reflect the move
header_len = 20; ip->ver_len = 0x45; ip->total_length = 20 + payload_len; } // Look at protocol ID byte and call the appropriate
// function to handle the received message. See
// "TCP/IP Illustrated, Volume 1" Sect 1.7 and RFC 791
// for values for various protocols
switch (ip->protocol_id) { case ICMP_TYPE: icmp_rcve(inbuf, payload_len); break;
case IGMP_TYPE: ; // We cannot handle IGMP messages
break; case UDP_TYPE: ; break;
case TCP_TYPE: tcp_rcve(inbuf, payload_len); break;
default: ; break; } }
|
这个我也只是大致看明白了,其中有几个函数调用是网卡的驱动函数,应该能看出来。
再就是icmp协议了,这个同样对其精简了许多了,只处理了二种报文,这个代码我注释得比较详细,
//-----------------------------------------------------------------------------
// 处理ICMP消息(ICMP协议的实现)
// 具体细节参考 RFC 792, 896, 950, 1122, 1191
//-----------------------------------------------------------------------------
#include <string.h> #include "C8051f340.h" #include "net.h" #include "cksum.h" #include "ip.h" #include "icmp.h"
extern UCHAR idata debug; extern char xdata outbuf1[];
//------------------------------------------------------------------------
// 建立一个ping回显应答消息,为发整个需发送的信息分配内存,包括以太帧头与IP头
// 见TCP/IP详解 卷1 7.2
//------------------------------------------------------------------------
void ping_send(UCHAR xdata * inbuf, ULONG ipaddr, UINT len) { PING_HEADER xdata * ping_in; PING_HEADER xdata * ping_out; UCHAR xdata * outbuf;
//为接受到的ping回复加上以太网帧头(14byte)与IP头(20byte)
ping_in = (PING_HEADER xdata *)(inbuf + 34); outbuf = outbuf1;
// 为应答ping的消息加上以太网帧头(14byte)与IP头(20byte)
ping_out = (PING_HEADER xdata *)(outbuf + 34); ping_out->msg_type = 0; ping_out->msg_code = 0; ping_out->checksum = 0; ping_out->identifier = ping_in->identifier;//标识原值信息返回
ping_out->sequence = ping_in->sequence;//序号信息原值返回
//回显数据原样返回
memcpy(&ping_out->echo_data, &ping_in->echo_data, len - 8); //校验ICMP的报文头及插入的额外的数据
ping_out->checksum = ~cksum(outbuf + 34, len);
//通过IP数据报发磅
ip_send(outbuf, ipaddr, ICMP_TYPE, len); }
//------------------------------------------------------------------------
//目的不可达报文 类型3 代码3 表示目的不可达报文是由端口不可达
//处理方法是返回我们收到的原始的IP数据报文头,20byte以后的数据全部不收
//详见TCP/IP详解 卷1 6.5
//------------------------------------------------------------------------
void dest_unreach_send(UCHAR xdata * inbuf, ULONG ipaddr) { UCHAR xdata * outbuf; ICMP_ERR_HEADER xdata * icmp; outbuf = outbuf1; icmp = (ICMP_ERR_HEADER xdata *)(outbuf + 34); // 填充ICMP出错报文的头
icmp->msg_type = 3; // 目的不可达
icmp->msg_code = 3; // 端口不可达
icmp->checksum = 0; // 填充ICMP错误信息数据
icmp->msg_data = 0; // 拷贝20字节的原始IP地址头
// 及ICMP_ERR_HEADER的前8字节,
//echo_data数据的地址用来存放前边提到的28字节
memcpy(&icmp->echo_data, inbuf + 14, 28); //校验36字节,ICMP_ERR_HEADER头的前8字节
//及echo_data数据的地址用来存放前边提到的28字节
icmp->checksum = ~cksum(outbuf + 34, 36); // 通过IP数据报发送
ip_send(outbuf, ipaddr, ICMP_TYPE, 36); }
//------------------------------------------------------------------------
// 处理收到ICMP请求
// 详见TCP/IP详解 卷1 6.2
//------------------------------------------------------------------------
void icmp_rcve(UCHAR xdata * inbuf, UINT len) { IP_HEADER * ip; UCHAR idata msg_type; UINT idata temp; // 前边是以太网帧头的14个字节
ip = (IP_HEADER *)(inbuf + 14); // IP 头在前边已将地址确定
// 对有效的ICMP消息数据进行校验
temp = cksum(inbuf + 34, len); if (temp != 0xFFFF) { return; } // 通过ICMP消息类型做出相应的处理
msg_type = *(inbuf + 34); switch(msg_type) { case 3: ; break;
case 8://请求回显(ping请求)//则用ping发送回显
ping_send(inbuf, ip->source_ipaddr, len); break;
default: ; break; } }
|
还有过TCP协议的实现还没有彻底研究明白,研究后与http协议一起贴出来.
阅读(2539) | 评论(2) | 转发(1) |