网络层——ip_build_header 函数
前篇(跳跃有点大,记得理清思路找到被调用位置,参见connect(一))介绍的 tcp_connect 函数内部调用了 build_header函数,实则是ip层的 ip_build_header 函数,该函数的主要功能是创建合适的 mac和ip头部
/* * This routine builds the appropriate hardware/IP headers for * the routine. It assumes that if *dev != NULL then the * protocol knows what it's doing, otherwise it uses the * routing/ARP tables to select a device struct. */ //创建合适的 mac/ip 首部 int ip_build_header(struct sk_buff *skb, unsigned long saddr, unsigned long daddr, struct device **dev, int type, struct options *opt, int len, int tos, int ttl) { static struct options optmem; struct iphdr *iph;//ip首部 struct rtable *rt;//ip路由表 unsigned char *buff; unsigned long raddr; int tmp; unsigned long src; buff = skb->data;//得到数据部分(首部和有效负载) /* * See if we need to look up the device. */ #ifdef CONFIG_INET_MULTICAST //多播处理 if(MULTICAST(daddr) && *dev==NULL && skb->sk && *skb->sk->ip_mc_name) *dev=dev_get(skb->sk->ip_mc_name); #endif //对dev初始化,并且获得下一站ip地址 if (*dev == NULL) { if(skb->localroute)//路由表查询 //该函数完成对本地链路上主机或者网络地址的路由查询工作 //查询就是对链表中每个元素进行检查,检查的根据就是对表项中目的地址和实际要发送数据包中的目的地址进行网络号(和子网络号)的比较 rt = ip_rt_local(daddr, &optmem, &src); else rt = ip_rt_route(daddr, &optmem, &src);//这个函数和上面那个类似 if (rt == NULL) { ip_statistics.IpOutNoRoutes++; return(-ENETUNREACH); } *dev = rt->rt_dev;//路由路径出站的接口设备 /* * If the frame is from us and going off machine it MUST MUST MUST * have the output device ip address and never the loopback */ if (LOOPBACK(saddr) && !LOOPBACK(daddr))//回路检查 saddr = src;/*rt->rt_dev->pa_addr;*/ raddr = rt->rt_gateway;//下一站ip地址,网关或路由器地址 opt = &optmem; } else//已经指定了发送接口设备,仍需要进行路由表查询,寻找下一站ip地址 { /* * We still need the address of the first hop. */ if(skb->localroute) rt = ip_rt_local(daddr, &optmem, &src); else rt = ip_rt_route(daddr, &optmem, &src); /* * If the frame is from us and going off machine it MUST MUST MUST * have the output device ip address and never the loopback */ if (LOOPBACK(saddr) && !LOOPBACK(daddr))//回路检查 saddr = src;/*rt->rt_dev->pa_addr;*/ raddr = (rt == NULL) ? 0 : rt->rt_gateway;//下一站地址 } /* * No source addr so make it our addr */ //如果没有指定本地地址,就设置源端地址为本地接口地址 if (saddr == 0) saddr = src; /* * No gateway so aim at the real destination */ // if (raddr == 0) raddr = daddr; /* * Now build the MAC header. */ //创建 MAC 头,返回MAC头部大小tmp tmp = ip_send(skb, raddr, len, *dev, saddr); //MAC header | IP header | TCP header | payload buff += tmp;//buff指针偏移tmp,移到ip首部首地址 len -= tmp; /* * Book keeping */ skb->dev = *dev;//接口设备 skb->saddr = saddr;//源端ip地址 if (skb->sk) skb->sk->saddr = saddr;//本地地址 /* * Now build the IP header. */ /* * If we are using IPPROTO_RAW, then we don't need an IP header, since * one is being supplied to us by the user */ if(type == IPPROTO_RAW) return (tmp); //获取ip首部,及初始化 iph = (struct iphdr *)buff;//获取ip首部 iph->version = 4; iph->tos = tos; iph->frag_off = 0; iph->ttl = ttl; iph->daddr = daddr;//ip地址 iph->saddr = saddr; iph->protocol = type; iph->ihl = 5; skb->ip_hdr = iph; /* Setup the IP options. */ #ifdef Not_Yet_Avail build_options(iph, opt); #endif //普通的ip首部长为20个字节长 return(20 + tmp); /* IP header plus MAC header size */ }内部调用了一个ip_send函数,用于创建填充MAC头部(这函数名取得。。)
/* * Take an skb, and fill in the MAC header. */ static int ip_send(struct sk_buff *skb, unsigned long daddr, int len, struct device *dev, unsigned long saddr) { int mac = 0; skb->dev = dev;//指定设备接口 skb->arp = 1; if (dev->hard_header) { /* * Build a hardware header. Source address is our mac, destination unknown * (rebuild header will sort this out) */ //创建mac 头部,调用下层函数 eth_header(eth.c) mac = dev->hard_header(skb->data, dev, ETH_P_IP, NULL, NULL, len, skb); if (mac < 0)//返回负值,表示创建未成功 { mac = -mac; skb->arp = 0;//设置arp为0,表示六安路曾首部中缺少下一站主机硬件地址 skb->raddr = daddr; /* next routing address 数据包下一站ip地址*/ } } return mac;//返回mac头部长度 }6、链路层——eth_header 函数
承接上面函数,完成创建MAC首部工作
/* * Create the Ethernet MAC header for an arbitrary protocol layer * * saddr=NULL means use device source address如果传值源地址为空,则使用设备的地址作为源地址 * daddr=NULL means leave destination address (eg unresolved arpARP地址解析获得目的地址) */ //创建一个mac 头(链路层),并返回头部长度 int eth_header(unsigned char *buff, struct device *dev, unsigned short type, void *daddr, void *saddr, unsigned len, struct sk_buff *skb) { struct ethhdr *eth = (struct ethhdr *)buff;//获得以太网头 /* * Set the protocol type. For a packet of type ETH_P_802_3 we put the length * in here instead. It is up to the 802.2 layer to carry protocol information. */ //设置协议类型 if(type!=ETH_P_802_3) eth->h_proto = htons(type); else eth->h_proto = htons(len); /* * Set the source hardware address. */ //源端地址设置 if(saddr) memcpy(eth->h_source,saddr,dev->addr_len); else//传参为空,则使用设备的地址作为源地址 memcpy(eth->h_source,dev->dev_addr,dev->addr_len); /* * Anyway, the loopback-device should never use this function... */ //如果是一个回路网络,设置目的地址为空,不然信息会无终止传输,引起广播风暴 if (dev->flags & IFF_LOOPBACK) { memset(eth->h_dest, 0, dev->addr_len); return(dev->hard_header_len); } if(daddr)//设置目的地址,传参为NULL,即这里不会去设置目的地址 { memcpy(eth->h_dest,daddr,dev->addr_len); return dev->hard_header_len; } return -dev->hard_header_len;//返回负值,表示创建未成功 }
至此,connect 函数基本上算是分析完了,中间涉及到数据包的发送与接收我们另外剖析。
参考书籍:《Linux内核网络栈源代码情景分析》、Linux kernel 1.2.13