网络子系统48_ip协议数据帧的发送

简介:
//ip协议与l4协议接口,l4通过此接口向下l3传递数据帧
//函数主要任务:
//	1.通过路由子系统路由封包
//	2.填充l3报头
//	3.ip分片
//	4.计算校验和
//	5.衔接邻居子系统,向下层传送封包。
1.1 int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_options *opt = inet->opt;//选项与sock相关
	struct rtable *rt;
	struct iphdr *iph;

	//检查skb是否已经被路由
	rt = (struct rtable *) skb->dst;
	if (rt != NULL)
		goto packet_routed;
	//检测ip的可用性
	//dst->obsolete 
	//		0 	默认值,该结构有效可以被使用
	//		2 	表示该结构将被删除因而不能被使用
	//	   -1   表示该结构被IPsec和IPv6使用但不被IPv4使用 	
	rt = (struct rtable *)__sk_dst_check(sk, 0);
	if (rt == NULL) {//没有可用的路由信息,重新路由封包
		u32 daddr;

		daddr = inet->daddr;//sock中指定的目标ip地址
		if(opt && opt->srr)//源路由选项
			daddr = opt->faddr;//将第一跳地址作为目标地址

		{
			struct flowi fl = { .oif = sk->sk_bound_dev_if,//出口设备
					    .nl_u = { .ip4_u =
						      { .daddr = daddr,//对目标地址进行路由,为sock中指定的目的地址,或者在源路由选项时,使用的第一跳地址
							.saddr = inet->saddr,//源地址
							.tos = RT_CONN_FLAGS(sk) } },//sock中指定的tos
					    .proto = sk->sk_protocol,//l4协议
					    .uli_u = { .ports =
						       { .sport = inet->sport,
							 .dport = inet->dport } } };//源端口,目的端口

			if (ip_route_output_flow(&rt, &fl, sk, 0))//对出口skb进行路由
				goto no_route;
		}
		__sk_dst_set(sk, &rt->u.dst);
		tcp_v4_setup_caps(sk, &rt->u.dst);//根据网卡对tso的支持,更新sk的tso
	}
	skb->dst = dst_clone(&rt->u.dst);//增加dst的引用计数

packet_routed:
	if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)//处理严路由选项,下一跳地址必须为网管地址
		goto no_route;

	iph = (struct iphdr *) skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));//移动skb->data向上,准备填写ip报头
	*((__u16 *)iph)	= htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));//设置报头长度为20字节,选项的长度,在ip_options_build中增加
	iph->tot_len = htons(skb->len);//总长度,skb中所有数据的长度,包括报头,选项,有效载荷
	if (ip_dont_fragment(sk, &rt->u.dst) && !ipfragok)//检查禁止了分片
		iph->frag_off = htons(IP_DF);//设置报头指示禁止分片
	else
		iph->frag_off = 0;//分片中的偏移量
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);//生存周期
	iph->protocol = sk->sk_protocol;//指示ip上层的l4协议类型
	iph->saddr    = rt->rt_src;//源地址,目的地址
	iph->daddr    = rt->rt_dst;
	skb->nh.iph   = iph;

	if (opt && opt->optlen) {//sock中设置了选项
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->daddr, rt, 0);//处理选项
	}

	ip_select_ident_more(iph, &rt->u.dst, sk, skb_shinfo(skb)->tso_segs);//为ip选择id

	ip_send_check(iph);//ip报头校验和

	skb->priority = sk->sk_priority;//skb的优先级为sock的优先级,在dev_queue_xmit中用于在规则队列中排队

	return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
		       dst_output);//调用dst->output,由ip_route_output_flow设置

no_route:
	IP_INC_STATS(IPSTATS_MIB_OUTNOROUTES);
	kfree_skb(skb);
	return -EHOSTUNREACH;
}

//调用路径ip_queue_xmit->ip_output或ip_forward_finish->ip_output
1.2 int ip_output(struct sk_buff *skb)
{
	IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);

	if (skb->len > dst_pmtu(skb->dst) && !skb_shinfo(skb)->tso_size)
		return ip_fragment(skb, ip_finish_output);//ip分片
	else
		return ip_finish_output(skb);
}

//调用路径ip_output->ip_finish_output
1.3 int ip_finish_output(struct sk_buff *skb)
{
	struct net_device *dev = skb->dst->dev;

	skb->dev = dev;//出口设备
	skb->protocol = htons(ETH_P_IP);//skb准备向l2层传送,在skb->protocol中告诉l2层,上层l3的协议类型

	return NF_HOOK(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
		       ip_finish_output2);//skb被路由之后的netfilter拦截点
}
//调用路径ip_output->ip_finish_output->ip_finish_output2
//函数主要任务:	
//	1.保证skb头部足够空间容纳l2帧头
//	2.通过l2帧头缓存,拷贝l2帧头,完成向下层的传输
//	3.没有可供使用的l2帧头缓存,则衔接邻居子系统,交由邻居子系统处理skb
1.4 static inline int ip_finish_output2(struct sk_buff *skb)
{
	struct dst_entry *dst = skb->dst;
	struct hh_cache *hh = dst->hh;
	struct net_device *dev = dst->dev;
	int hh_len = LL_RESERVED_SPACE(dev);

	if (unlikely(skb_headroom(skb) < hh_len && dev->hard_header)) {//skb头空间不足l2头,设备驱动提供了在skb中填充l2头的功能
		struct sk_buff *skb2;

		skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));//重新分配skb头来容纳l2头
		if (skb2 == NULL) {
			kfree_skb(skb);
			return -ENOMEM;
		}
		if (skb->sk)
			skb_set_owner_w(skb2, skb->sk);
		kfree_skb(skb);
		skb = skb2;
	}

	if (hh) {//l2帧头缓存
		int hh_alen;

		read_lock_bh(&hh->hh_lock);
		hh_alen = HH_DATA_ALIGN(hh->hh_len);
  		memcpy(skb->data - hh_alen, hh->hh_data, hh_alen);//拷贝帧头缓存中的l2帧头到skb中
		read_unlock_bh(&hh->hh_lock);
	    skb_push(skb, hh->hh_len);
		return hh->hh_output(skb);//通过l2帧头缓存的output函数,完成skb向下的传递
	} else if (dst->neighbour)//没有l2帧头缓存,则需要邻居子系统,获取数据帧的l2地址
		return dst->neighbour->output(skb);

	if (net_ratelimit())
		printk(KERN_DEBUG "ip_finish_output2: No header cache and no neighbour!\n");
	kfree_skb(skb);
	return -EINVAL;
}

目录
相关文章
|
2天前
|
网络协议 安全 Go
Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
【10月更文挑战第28天】Go语言进行网络编程可以通过**使用TCP/IP协议栈、并发模型、HTTP协议等**方式
22 13
|
1天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
2天前
|
网络协议 算法 网络性能优化
计算机网络常见面试题(一):TCP/IP五层模型、TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议
计算机网络常见面试题(一):TCP/IP五层模型、应用层常见的协议、TCP与UDP的区别,TCP三次握手、四次挥手,TCP传输可靠性保障、ARQ协议、ARP协议
|
3天前
|
网络协议 网络安全 数据安全/隐私保护
计算机网络概念:网关,DHCP,IP寻址,ARP欺骗,路由,DDOS等
【10月更文挑战第27天】计算机主机网关的作用类似于小区传达室的李大爷,负责将内部网络的请求转发到外部网络。当小区内的小不点想与外面的小明通话时,必须通过李大爷(网关)进行联系。网关不仅帮助内部设备与外部通信,还负责路由选择,确保数据包高效传输。此外,网关还参与路由表的维护和更新,确保网络路径的准确性。
13 2
|
5天前
|
网络协议 安全 NoSQL
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练等具体操作详解步骤;精典图示举例说明、注意点及常见报错问题所对应的解决方法IKUN和I原们你这要是学不会我直接退出江湖;好吧!!!
网络空间安全之一个WH的超前沿全栈技术深入学习之路(8-2):scapy 定制 ARP 协议 、使用 nmap 进行僵尸扫描-实战演练、就怕你学成黑客啦!
|
29天前
|
安全 网络协议 算法
HTTPS网络通信协议揭秘:WEB网站安全的关键技术
HTTPS网络通信协议揭秘:WEB网站安全的关键技术
133 4
HTTPS网络通信协议揭秘:WEB网站安全的关键技术
|
24天前
|
网络协议 网络虚拟化 网络架构
【网络实验】/主机/路由器/交换机/网关/路由协议/RIP+OSPF/DHCP(上)
【网络实验】/主机/路由器/交换机/网关/路由协议/RIP+OSPF/DHCP(上)
53 1
|
12天前
|
存储 缓存 Ubuntu
配置网络接口的“IP”命令10个
【10月更文挑战第18天】配置网络接口的“IP”命令10个
36 0
|
21天前
|
运维 安全 网络协议
Python 网络编程:端口检测与IP解析
本文介绍了使用Python进行网络编程的两个重要技能:检查端口状态和根据IP地址解析主机名。通过`socket`库实现端口扫描和主机名解析的功能,并提供了详细的示例代码。文章最后还展示了如何整合这两部分代码,实现一个简单的命令行端口扫描器,适用于网络故障排查和安全审计。
|
24天前
|
网络协议 数据安全/隐私保护 网络虚拟化
【网络实验】/主机/路由器/交换机/网关/路由协议/RIP+OSPF/DHCP(下)
【网络实验】/主机/路由器/交换机/网关/路由协议/RIP+OSPF/DHCP(下)
41 0