文章目录
1、对ping命令分析
1.1、基本流程
uboot中存在命令ping,在uboot当中运行该命令,通过wireshark进行抓包,可以直观的看到以下内容。
TI的IP为172.16.89.4
VM的IP为172.16.89.2
- 1、TI发送广播,寻求IP为172.16.89.2的设备
- 2、VM返回信息,告知TI自己的mac地址
- 3、TI发送ICMP包,请求返回信息
- 4、VM接收ICMP包,返回信息
以上完成了一次完整的ping命令,通过对以上步骤的分析,完成uboot对于网络包的解析流程分析。
static int do_ping(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
if (argc < 2)
return -1;
NetPingIP = string_to_ip(argv[1]);
if (NetPingIP == 0)
return CMD_RET_USAGE;
if (NetLoop(PING) < 0) {
printf("ping failed; host %s is not alive\n", argv[1]);
return 1;
}
printf("host %s is alive\n", argv[1]);
return 0;
}
do_ping完成了从命令深入net.c的任务,通过进入NetLoop,完成关于网络的一系列数据处理,一般关于网络传输的命令都是由NetLoop完成。
下面将分析NetLoop如何执行。
int NetLoop(enum proto_t protocol)
{
bd_t *bd = gd->bd;
int ret = -1;
NetRestarted = 0;
NetDevExists = 0;
NetTryCount = 1;
debug_cond(DEBUG_INT_STATE, "--- NetLoop Entry\n");
bootstage_mark_name(BOOTSTAGE_ID_ETH_START, "eth_start");
net_init();
if (eth_is_on_demand_init() || protocol != NETCONS) {
eth_halt();
eth_set_current();
if (eth_init(bd) < 0) {
eth_halt();
return -1;
}
} else
eth_init_state_only(bd);
restart:
net_set_state(NETLOOP_CONTINUE);
/*
* Start the ball rolling with the given start function. From
* here on, this code is a state machine driven by received
* packets and timer events.
*/
debug_cond(DEBUG_INT_STATE, "--- NetLoop Init\n");
NetInitLoop();
//前期进行了一系列的初始化和状态的设置
switch (net_check_prereq(protocol)) {//此处检查当前网络是否准备好
case 1:
/* network not configured */
eth_halt();
return -1;
case 2:
/* network device not configured */
break;
case 0:
NetDevExists = 1;
NetBootFileXferSize = 0;
switch (protocol) {//此处判断要实现的功能
case TFTPGET:
case TFTPPUT:
/* always use ARP to get server ethernet address */
TftpStart(protocol);
break;
...
#if defined(CONFIG_CMD_PING)
case PING:
ping_start();//本次主要研究对象,后面展开讲述。
break;
#endif
···
}
···
}
···
for (;;) {
WATCHDOG_RESET();
#ifdef CONFIG_SHOW_ACTIVITY
show_activity(1);
#endif
···
eth_rx();//读取新的packet
···
if (ctrlc()) {
···
}
ArpTimeoutCheck();//检查是否超时,若未超时则对ARP进行响应
if (timeHandler && ((get_timer(0) - timeStart) > timeDelta)) {
thand_f *x;
···
debug_cond(DEBUG_INT_STATE, "--- NetLoop timeout\n");
x = timeHandler;//此处对arp响应超时进行处理
timeHandler = (thand_f *)0;
(*x)();
}
switch (net_state) {//判断当前状态
case NETLOOP_RESTART:
···
case NETLOOP_SUCCESS:
···
case NETLOOP_FAIL:
···
case NETLOOP_CONTINUE:
continue;
}
}
done:
#ifdef CONFIG_CMD_TFTPPUT
/* Clear out the handlers */
net_set_udp_handler(NULL);//清空UDP协议处理函数
net_set_icmp_handler(NULL);//清空ICMP协议处理函数
#endif
return ret;
}
以上完成了NetLoop到ping_start的任务,完成ping_start之后,再进入到for循环,不断接受数据。
void ping_start(void)
{
printf("Using %s device\n", eth_get_name());//当前使用的设备
NetSetTimeout(10000UL, ping_timeout);//设置超时时间
ping_send();//发送数据
}
以上完成了一些简单的任务,主要任务还是由ping_send来执行。
static int ping_send(void)
{
uchar *pkt;
int eth_hdr_size;
/* XXX always send arp request */
debug_cond(DEBUG_DEV_PKT, "sending ARP for %pI4\n", &NetPingIP);
NetArpWaitPacketIP = NetPingIP;//此处为全局变量,在执行ping命令阶段即已赋值。
eth_hdr_size = NetSetEther(NetTxPacket, NetEtherNullAddr, PROT_IP);//获取大小
pkt = (uchar *)NetTxPacket + eth_hdr_size;//设置pkt
set_icmp_header(pkt, NetPingIP);//为pkt添加头
/* size of the waiting packet */
NetArpWaitTxPacketSize = eth_hdr_size + IP_ICMP_HDR_SIZE;//等待的packet大小
/* and do the ARP request */
NetArpWaitTry = 1;
NetArpWaitTimerStart = get_timer(0);
ArpRequest();//arp响应
return 1; /* waiting 如果没有及时响应,会交由NetLoop执行响应*/
}
1.2、arp响应
关于NetLoop响应问题,当执行完ping_send之后,如果arp没有相应,那么包是没有发出去的,就必须依赖于NetLoop进行循环等待arp响应,这样我们才能够得到IP和mac地址,才能够发送数据,下面我们详细分析,arp响应是如何被捕捉到,并且将包发出去的。
主要在以下三个函数当中,在NetLoop中,有一个ArpTimeoutCheck函数,他在不断地检测是否arp响应超时,在规定时间内还未收到arp回应,则执行ArpRequest,然后执行arp_raw_request,再次将arp数据包发出,即NetSendPacket,等待响应。
以下就是关于超时的检测,其中一个比较重要的变量NetArpWaitPacketIP,这个变量是从何处确定的呢?可以看到当NetArpWaitPacketIP=0时,是会直接返回的。该变量在ping_start时就已经设置,如果未设置,是不会等待arp响应的。
void ArpTimeoutCheck(void)
{
ulong t;
if (!NetArpWaitPacketIP)//是否在等待arp
return;
t = get_timer(0);
/* check for arp timeout */
if ((t - NetArpWaitTimerStart) > ARP_TIMEOUT) {
NetArpWaitTry++;
if (NetArpWaitTry >= ARP_TIMEOUT_COUNT) {
puts("\nARP Retry count exceeded; starting again\n");
NetArpWaitTry = 0;
NetStartAgain();
} else {
NetArpWaitTimerStart = t;
ArpRequest();
}
}
}
以下两个函数暂时不做详细分析。
void arp_raw_request(IPaddr_t sourceIP, const uchar *targetEther,
IPaddr_t targetIP)
{
uchar *pkt;
struct arp_hdr *arp;
int eth_hdr_size;
debug_cond(DEBUG_DEV_PKT, "ARP broadcast %d\n", NetArpWaitTry);
pkt = NetArpTxPacket;
eth_hdr_size = NetSetEther(pkt, NetBcastAddr, PROT_ARP);
pkt += eth_hdr_size;
arp = (struct arp_hdr *) pkt;
arp->ar_hrd = htons(ARP_ETHER);
arp->ar_pro = htons(PROT_IP);
arp->ar_hln = ARP_HLEN;
arp->ar_pln = ARP_PLEN;
arp->ar_op = htons(ARPOP_REQUEST);
memcpy(&arp->ar_sha, NetOurEther, ARP_HLEN); /* source ET addr */
NetWriteIP(&arp->ar_spa, sourceIP); /* source IP addr */
memcpy(&arp->ar_tha, targetEther, ARP_HLEN); /* target ET addr */
NetWriteIP(&arp->ar_tpa, targetIP); /* target IP addr */
NetSendPacket(NetArpTxPacket, eth_hdr_size + ARP_HDR_SIZE);//发送arp包
}
void ArpRequest(void)
{
if ((NetArpWaitPacketIP & NetOurSubnetMask) !=
(NetOurIP & NetOurSubnetMask)) {
if (NetOurGatewayIP == 0) {
puts("## Warning: gatewayip needed but not set\n");
NetArpWaitReplyIP = NetArpWaitPacketIP;
} else {
NetArpWaitReplyIP = NetOurGatewayIP;
}
} else {
NetArpWaitReplyIP = NetArpWaitPacketIP;
}
arp_raw_request(NetOurIP, NetEtherNullAddr, NetArpWaitReplyIP);
}
1.3、arp接收以及发送ICMP
arp的接收在NetLoop当中进行,主要是调用驱动函数的接口来接收,然后驱动当中的recv函数调用了NetReceive。在NetReceive当中,会对当前接收到的类型进行判断,如果是arp,则执行相应的函数,对arp进行处理。
NetReceive(uchar *inpkt, int len)
{
···
switch (eth_proto) {
case PROT_ARP:
ArpReceive(et, ip, len);
break;
}
}
在ArpReceive处理函数中会对发过来的包进行处理,然后将之前的需要发的数据发送出去。
void ArpReceive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
{
···
switch (ntohs(arp->ar_op)) {//对收到的数据进行处理
case ARPOP_REQUEST://如果是获得的请求,则返回我们的IP以及mac
···
NetSendPacket((uchar *)et, eth_hdr_size + ARP_HDR_SIZE);
return;
case ARPOP_REPLY: /* arp reply */
···//如果收到的是响应,则发送我们之前计划发送的packet
memcpy(((struct ethernet_hdr *)NetTxPacket)->et_dest,
&arp->ar_sha, ARP_HLEN);
NetSendPacket(NetTxPacket, NetArpWaitTxPacketSize);
···
return;
default:
···
return;
}
}
上面提到收到arp响应之后,将我们之前做好的packet发送出去,这个packet在ping_start()->ping_send()时就已经准备好了。以上完成arp接收,并且响应,发出ICMP的包,下一步是接收PC返回的ICMP
1.4、接收ICMP
还是之前提到的NetReceive函数,在NetLoop函数中不断的循环接收,也就不断地执行NetReceive函数,直到NetLoop当中状态变量改变,跳出该循环。
NetReceive(uchar *inpkt, int len)
{
···
switch (eth_proto) {
case PROT_IP:
···
···
···
if (ip->ip_p == IPPROTO_ICMP) {
receive_icmp(ip, len, src_ip, et);
return;
}···
break;
}
}
这里只取该部分代码进行分析。
static void receive_icmp(struct ip_udp_hdr *ip, int len,
IPaddr_t src_ip, struct ethernet_hdr *et)
{
struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
switch (icmph->type) {//对icmp包进行分析
case ICMP_REDIRECT:
if (icmph->code != ICMP_REDIR_HOST)
return;
printf(" ICMP Host Redirect to %pI4 ",
&icmph->un.gateway);
break;
default:
#if defined(CONFIG_CMD_PING)
ping_receive(et, ip, len);//此处对ping命令进行处理,是结束循环的关键。
#endif
···
break;
}
}
void ping_receive(struct ethernet_hdr *et, struct ip_udp_hdr *ip, int len)
{
struct icmp_hdr *icmph = (struct icmp_hdr *)&ip->udp_src;
IPaddr_t src_ip;
int eth_hdr_size;
switch (icmph->type) {
case ICMP_ECHO_REPLY://此处是接收到PC的reply,
src_ip = NetReadIP((void *)&ip->ip_src);
if (src_ip == NetPingIP)
net_set_state(NETLOOP_SUCCESS);//设置状态为成功,结束循环
return;
case ICMP_ECHO_REQUEST://此处为接收到PC的请求ICMP,则发送ICMP,等待返回
//此处是PC主动ping通开发板的关键
eth_hdr_size = net_update_ether(et, et->et_src, PROT_IP);
debug_cond(DEBUG_DEV_PKT, "Got ICMP ECHO REQUEST, return "
"%d bytes\n", eth_hdr_size + len);
···
NetSendPacket((uchar *)et, eth_hdr_size + len);
return;
/* default:
return;*/
}
}
以上的分析是完成添加新命令get_netcmd的关键。
2、添加新命令get_netcmd
2.1、为什么添加该命令
当uboot进入命令行之后,我们会发现,PC对uboot发送ping命令是没有响应的,经过第1节的分析,我们也看到了uboot是有相应的响应机制的,只是没有运行起来,所以就需要我们添加一条命令,跳入的NetLoop当中,让uboot循环的去接收数据,然后根据相应的机制返回数据。
添加完该命令后效果如下:
- PC广播寻找172.16.89.4
- TI返回arp
- PC发送ICMP
- TI响应ICMP
另外的,添加该命令可以实现陷入循环接收网络数据的状态,可以接收来自网络的命令,以此来实现PC与UBOOT的通信,发送特定命令,执行我们想要的任务。
2.2、如何开始命令、如何结束命令
如何开始命令比较简单,开始命令有bootcmd进行处理,我们需要提供相应的命令接口即可。
static int get_netcmd(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
if (NetLoop(ARP) < 0) {
return 1;
}
return 0;
}
U_BOOT_CMD(
get_netcmd, 1, 1, get_netcmd,
"get UDP message from network host",
"get tftp ok cmd"
);
可以看到get_netcmd命令非常简单,就是利用NetLoop接口进入到接收模式当中,其中传入的参数为ARP,也就是进入接收ARP的模式,虽然是接收ARP的模式,同时也是可以接收其他类型的数据的,主要依赖于handler函数。
如何结束命令,结束命令有两种方式,一种是接收到我们想要的数据,结束命令,另一种是接收时长到了,结束命令。
关于接收到想要的数据结束命令,从下一小节中进行分析,本小节先分析接收时长到了的情况。
在NetLoop当中添加判断,如果未接到我们想要的数据,并且超过时长,直接设置状态为失败,跳出该循环。这就完成了该命令的超时结束。
if(protocol==ARP&&strcmp(s, "ok")){
timeb=get_timer(0);
if(timeb-timea>GET_NETCMD_TIME){
printf("\n not get net cmd \n");
net_set_state(NETLOOP_FAIL);
}
}
2.3、如何解析收到的数据
关于解析收到的数据,主要用于处理数据,判断是否是我们设定的特殊的命令,如果是特殊命令,则设置相应的环境变量,供bootcmd使用。以下就是解析pkt包的函数,经过一系列的协议处理之后,在该函数中pkt仅剩下了需要的数据,这些数据就可以用来设置环境变量,环境变量用于启动。
static void dummy_handler(uchar *pkt, unsigned dport,
IPaddr_t sip, unsigned sport,
unsigned len)
{
//此处对接受的UDP数据处理,设置环境变量供启动脚本使用
// printf("received UDP (from=%pI4, len=%d)\n",&sip, len);
// printf("pkt=%s",pkt);
if(!strcmp(pkt, "hello\n")){
setenv("tftp_status", "ok");
printf("now run netboot \n");
net_set_state(NETLOOP_SUCCESS);
}else if(!strcmp(pkt, "PRELOADER\n")){
setenv("tftp_preloader","ok");
printf("now load preloader \n");
net_set_state(NETLOOP_SUCCESS);
}else if(!strcmp(pkt, "UBOOT\n")){
setenv("tftp_uboot","ok");
printf("now load uboot \n");
net_set_state(NETLOOP_SUCCESS);
}else if(!strcmp(pkt, "CPU0\n")){
setenv("tftp_cpu0","ok");
printf("now load cpu0 \n");
net_set_state(NETLOOP_SUCCESS);
}else if(!strcmp(pkt, "CPU1\n")){
setenv("tftp_cpu1","ok");
printf("now load cpu1 \n");
net_set_state(NETLOOP_SUCCESS);
}else if(!strcmp(pkt, "FPGA\n")){
setenv("tftp_fpga","ok");
printf("now load fpga \n");
net_set_state(NETLOOP_SUCCESS);
}else{
}
}