【Linux 内核网络协议栈源码剖析】connect 函数剖析(二)

网络层——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


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核网络协议栈是指在操作系统内核中实现的一系列网络协议和功能。它负责处理网络数据包的收发、路由选择、协议解析等一系列操作,以实现网络通信和数据传输。 Linux内核网络协议栈包括以下几个层次: 1. 网络接口层(Network Interface Layer):负责处理物理网络接口的驱动程序和硬件设备的通信。它提供了对底层网络设备的抽象,如以太网、Wi-Fi、蓝牙等。 2. 网络层(Network Layer):负责处理IP协议相关的操作,包括IP地址分配、路由选择和IP数据包的转发等。其中主要的协议有IPv4和IPv6。 3. 传输层(Transport Layer):负责处理端到端的数据传输,主要通过TCP(传输控制协议)和UDP(用户数据报协议)来实现。TCP提供可靠的、面向连接的数据传输,而UDP提供无连接的、不可靠的数据传输。 4. 应用层(Application Layer):负责处理特定应用程序的数据传输和协议,如HTTP、FTP、DNS等。应用层协议依赖于传输层和网络层的支持,通过这些协议实现应用程序之间的通信。 此外,Linux内核还提供了一些额外的功能,如网络地址转换(NAT)、防火墙(iptables)、网络隧道(Tunneling)等,以满足网络通信和安全的需求。 总之,Linux内核网络协议栈是一个复杂而庞大的系统,通过不同层次的协议和功能实现了网络通信的各个方面。它为应用程序提供了丰富的网络功能,使得Linux成为一个强大的网络操作系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值