本机发送IP数据包

本地发送IP数据包是指数据包包括:传输层产生的数据包、裸IP、SCTP、IGMP,TCP和网络层的接口函数是ip_queue_xmit,UDP和网络层接口函数是ip_push_pending_frames,数据包对外发送在内核要做以下几件事情。

a、查找下一个站点

IP层需要知道完成数据包输出功能的是那个网络设备,以及下一个站点的路由信息,寻找路由的任务由ip_route_output_flow函数完成。

b、初始化IP头

初始化ip协议头,处理IP选项、数据包分片、处理校验和。

c、调用网络过滤子系统做安全检查,不合法就丢掉

d、更新统计信息

linux在传输层实现了多种不同传输层协议,不同的协议不同组织数据包不同,向网络层发送数据包的方式也不同,如图是传输层与网络层接口函数关系。

传输层与网络层接口函数

                                                                        

从上图看从传输层到网络层,发送数据包的方式主要分为两种

1)、数据包传给网络层时已经对数据包的分片做了大量的预处理,留给IP层的工作很多少了,比如(TCP、SCTP)。

扫描二维码关注公众号,回复: 4337294 查看本文章

2)、UDP和裸IP将数据包分片的工作都留给了IP层。

函数说明:

ip_queue_xmit:传输层TCP协议调用,将数据包从传输层发送到网络层,创建协议头和IP选项,然后调用dst_output发送。

ip_append_data:传输层中UDP协议调用,缓存传输层到网络层的请求发送的数据包缓冲区。

ip_append_page:传输层UDP协议调用,缓存传输层到网络层的请求发送数据面。

ip_push_pending_frames:将ip_append_data和ip_append_page创建的数据包发送队列发送出去。

dst_output:数据包发送函数,当数据包的目标地址是其他主机初始化为ip_output。

ip_build_and_send_pkt:用于TCP发送同步回答消息时创建IP协议头和选项并发送。

ip_send_reply:用户TCP发送回答消息和复位是创建IP协议头和选项并发送。

对于裸IP和IGMP他们自己构造IP协议头,所以直接调用dst_output,TCP协议头管理连接并向对端发送回答消息和复位消息时调用ip_send_reply,ip_send_reply又调用ip_append_data和ip_push_pending_frames来回答消息数据包。

一、关键数据结构体

1.1.struct sock

linux内核实现支持各种协议栈,struct sock接头体是通用套接字数据结构,这个结构体数据很庞大,定义在include/net/sock.h中

struct sock {
	/*
	 * Now struct inet_timewait_sock also uses sock_common, so please just
	 * don't add nothing before this first member (__sk_common) --acme
	 */
	 //套接字在网络中最小描述, 
	 //内核管理套接最重要的信息结构体
	struct sock_common	__sk_common;
	//定义sock_common元素的别名方便访问
#define sk_node			__sk_common.skc_node
#define sk_nulls_node		__sk_common.skc_nulls_node
#define sk_refcnt		__sk_common.skc_refcnt
#define sk_tx_queue_mapping	__sk_common.skc_tx_queue_mapping

#define sk_copy_start		__sk_common.skc_hash
#define sk_hash			__sk_common.skc_hash
#define sk_family		__sk_common.skc_family
#define sk_state		__sk_common.skc_state
#define sk_reuse		__sk_common.skc_reuse
#define sk_bound_dev_if		__sk_common.skc_bound_dev_if
#define sk_bind_node		__sk_common.skc_bind_node
#define sk_prot			__sk_common.skc_prot
#define sk_net			__sk_common.skc_net
	kmemcheck_bitfield_begin(flags);
	//tcp关闭套接字会发送RST数据包
...
}

1.2.struct inet_sock结构体

struct inet_sock继承了通用套接字属性(struct sock),是linux内核实现TCP/IP协议栈PF_INET协议族的套接字数据结构,struct inet_sock包含了发送数据包大部分控制信息:数据包目的ip、发送网络设备、ip选项。struct sock是struct inet_sock的第一个元素,所以他们的基础地址是一样的。

struct inet_sock {
	/* sk and pinet6 has to be the first two members of inet_sock */
	//通用套接字结构
	struct sock		sk;
#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)
	struct ipv6_pinfo	*pinet6;
#endif
	/* Socket demultiplex comparisons on incoming packets. */
	//目标地址
	__be32			inet_daddr;
	//本机绑定的ip地址
	__be32			inet_rcv_saddr;
	//目的端口
	__be16			inet_dport;
	//本地应用程序创建套接字的端口号
	__u16			inet_num;
	//发送数据包源地址
	__be32			inet_saddr;
	//数据包存活时间ttl
	__s16			uc_ttl;
	__u16			cmsg_flags;
	//发送数据包源端口
	__be16			inet_sport;
	//为分片数据包标识符
	__u16			inet_id;
	//ip选项
	struct ip_options	*opt;
	//数据包服务类型tos
	__u8			tos;
	__u8			min_ttl;
	//组发送存活时间ttl
	__u8			mc_ttl;
	//发送路由最大mtu
	__u8			pmtudisc;
	__u8			recverr:1,
...
				mc_all:1;
	//组发送网络设备的索引号
	int			mc_index;
	//组发送地址
	__be32			mc_addr;
	struct ip_mc_socklist	*mc_list;
	struct {
		//标志位
		unsigned int		flags;
		//产生的分片数据段大小
		unsigned int		fragsize;
		//ip选项
		struct ip_options	*opt;
		//路由表缓冲去入口
		struct dst_entry	*dst;
		//ip数据包大小
		int			length; /* Total length of all frames */
		//发送数据包目标地址
		__be32			addr;
		//存放tcp两端连接信息
		struct flowi		fl;
	} cork;
};

1.3、struct inet_sock struct sock关键函数

a、sk_dst_set 和__sk_dst_set函数

支持TCP协议的套机字,需要管理TCP连接,保存目标地址使用的路由,

b、sk_dst_check和__sk_dst_check

检查达到目标地址路由是否有效。

c、skb_set_owner_w

指定一个数据包所属的套接字,也就是设定skb->sk数据域。

d、sock_alloc_send_skb和sock_wmalloc函数

分片sk_buff所需要的内存空间,sock_alloc_send_skb分配单个缓冲区或一系列分片数据包的第一个缓冲区,sock_wmalloc管理其余的子分片。

二、ip_queue_ximit函数

ip_queue_xmit是传输层TCP协议和网络层的接口,ip_queue_xmit对数据包做一系列的IP层的初始化工作,本机产生的每个数据包都属于某个套接字,包含套接字数据结构的指针skb->sk。ip_queue_ximit主要做以下工作

1、设置路由

先检查skb->rtable是否为空,如果为空就要为数据包分配路由,如果没有设置路由就调用ip_route_ouput_flow设置skb的路由,然后调用__sk_dst_check检查目标路由是否可达。

...
	rcu_read_lock();
	//获取路由
	rt = skb_rtable(skb);
	if (rt != NULL)
		goto packet_routed;

	/* Make sure we can route this packet. */
	//检查路由是否可达
	rt = (struct rtable *)__sk_dst_check(sk, 0);
	if (rt == NULL) {
		__be32 daddr;

		/* Use correct destination address if we have options. */
		//设置了源路由选项,重新查询新路由和daddr取自源路由IP地址列表
		daddr = inet->inet_daddr;
		if(opt && opt->srr)
			daddr = opt->faddr;

		{
...
			//路由不可达,重新选路由
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
		}
		//发送数据包的输出网络设备信息
		sk_setup_caps(sk, &rt->u.dst);
	}
...

2、构建IP协议头

到此时skb只包含了IP数据包的常规负载数据、协议头、从传输层传来的负载数据。TCP为Socket Buffer分配内存是按找最大可能需要的来向系统申请内存,主要是考虑了下层协议头需要空间,这样做的可以避免IP层或更低协议在空间不够的情况下做缓冲区复制或重分配提高效率。

...
/* OK, we know where to send it, allocate and build IP header. */
	//查看是否有足够空间存放IP协议头
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
	skb_reset_network_header(skb);
	iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !skb->local_df)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	//初始化IP协议头的iph结构体各数据域
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	//协议
	iph->protocol = sk->sk_protocol;
	//原地址
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;
	/* Transport layer set skb->h.foo itself. */
	//构建IP选项
	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->inet_daddr, rt, 0);
	}
	//设置分片数据包的表示符ID
	ip_select_ident_more(iph, &rt->u.dst, sk,
			     (skb_shinfo(skb)->gso_segs ?: 1) - 1);
	//优先级设置
	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;
...

3、函数结束处理

最后调用ip_local_out,再调用__ip_local_out,然后调用ip_send_check计算校验和,最后调用网络过滤子系统OUT链上的钩子处理函数,钩子处理函数结束调用dst_output接续。

int __ip_local_out(struct sk_buff *skb)
{
	struct iphdr *iph = ip_hdr(skb);

	iph->tot_len = htons(skb->len);
	//ip校验和
	ip_send_check(iph);
	//调用OUT链上的钩子处理函数
	return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, skb, NULL,
		       skb_dst(skb)->dev, dst_output);
}

以下是ip_queue_xmit完整代码:

int ip_queue_xmit(struct sk_buff *skb)
{
	struct sock *sk = skb->sk;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_options *opt = inet->opt;
	struct rtable *rt;
	struct iphdr *iph;
	int res;

	/* Skip all of this if the packet is already routed,
	 * f.e. by something like SCTP.
	 */
	rcu_read_lock();
	//获取路由
	rt = skb_rtable(skb);
	if (rt != NULL)
		goto packet_routed;

	/* Make sure we can route this packet. */
	//检查路由是否可达
	rt = (struct rtable *)__sk_dst_check(sk, 0);
	if (rt == NULL) {
		__be32 daddr;

		/* Use correct destination address if we have options. */
		//设置了源路由选项,重新查询新路由和daddr取自源路由IP地址列表
		daddr = inet->inet_daddr;
		if(opt && opt->srr)
			daddr = opt->faddr;

		{
			struct flowi fl = { .oif = sk->sk_bound_dev_if,
					    .mark = sk->sk_mark,
					    .nl_u = { .ip4_u =
						      { .daddr = daddr,
							.saddr = inet->inet_saddr,
							.tos = RT_CONN_FLAGS(sk) } },
					    .proto = sk->sk_protocol,
					    .flags = inet_sk_flowi_flags(sk),
					    .uli_u = { .ports =
						       { .sport = inet->inet_sport,
							 .dport = inet->inet_dport } } };

			/* If this fails, retransmit mechanism of transport layer will
			 * keep trying until route appears or the connection times
			 * itself out.
			 */
			security_sk_classify_flow(sk, &fl);
			//路由不可达,重新选路由
			if (ip_route_output_flow(sock_net(sk), &rt, &fl, sk, 0))
				goto no_route;
		}
		//发送数据包的输出网络设备信息
		sk_setup_caps(sk, &rt->u.dst);
	}
	skb_dst_set_noref(skb, &rt->u.dst);

packet_routed:
	if (opt && opt->is_strictroute && rt->rt_dst != rt->rt_gateway)
		goto no_route;

	/* OK, we know where to send it, allocate and build IP header. */
	//查看是否有足够空间存放IP协议头
	skb_push(skb, sizeof(struct iphdr) + (opt ? opt->optlen : 0));
	skb_reset_network_header(skb);
	iph = ip_hdr(skb);
	*((__be16 *)iph) = htons((4 << 12) | (5 << 8) | (inet->tos & 0xff));
	if (ip_dont_fragment(sk, &rt->u.dst) && !skb->local_df)
		iph->frag_off = htons(IP_DF);
	else
		iph->frag_off = 0;
	//初始化IP协议头的iph结构体各数据域
	iph->ttl      = ip_select_ttl(inet, &rt->u.dst);
	//协议
	iph->protocol = sk->sk_protocol;
	//原地址
	iph->saddr    = rt->rt_src;
	iph->daddr    = rt->rt_dst;
	/* Transport layer set skb->h.foo itself. */
	//构建IP选项
	if (opt && opt->optlen) {
		iph->ihl += opt->optlen >> 2;
		ip_options_build(skb, opt, inet->inet_daddr, rt, 0);
	}
	//设置分片数据包的表示符ID
	ip_select_ident_more(iph, &rt->u.dst, sk,
			     (skb_shinfo(skb)->gso_segs ?: 1) - 1);
	//优先级设置
	skb->priority = sk->sk_priority;
	skb->mark = sk->sk_mark;

	res = ip_local_out(skb);
	rcu_read_unlock();
	return res;

no_route:
	rcu_read_unlock();
	IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTNOROUTES);
	kfree_skb(skb);
	return -EHOSTUNREACH;
}

猜你喜欢

转载自blog.csdn.net/City_of_skey/article/details/84189737