900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > 本机发送IP数据包

本机发送IP数据包

时间:2019-01-28 02:28:36

相关推荐

本机发送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)。

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_netkmemcheck_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 socksk;#if defined(CONFIG_IPV6) || defined(CONFIG_IPV6_MODULE)struct ipv6_pinfo*pinet6;#endif/* Socket demultiplex comparisons on incoming packets. *///目标地址__be32inet_daddr;//本机绑定的ip地址__be32inet_rcv_saddr;//目的端口__be16inet_dport;//本地应用程序创建套接字的端口号__u16inet_num;//发送数据包源地址__be32inet_saddr;//数据包存活时间ttl__s16uc_ttl;__u16cmsg_flags;//发送数据包源端口__be16inet_sport;//为分片数据包标识符__u16inet_id;//ip选项struct ip_options*opt;//数据包服务类型tos__u8tos;__u8min_ttl;//组发送存活时间ttl__u8mc_ttl;//发送路由最大mtu__u8pmtudisc;__u8recverr:1,...mc_all:1;//组发送网络设备的索引号intmc_index;//组发送地址__be32mc_addr;struct ip_mc_socklist*mc_list;struct {//标志位unsigned intflags;//产生的分片数据段大小unsigned intfragsize;//ip选项struct ip_options*opt;//路由表缓冲去入口struct dst_entry*dst;//ip数据包大小intlength; /* Total length of all frames *///发送数据包目标地址__be32addr;//存放tcp两端连接信息struct flowifl;} 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);elseiph->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);}//设置分片数据包的表示符IDip_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);elseiph->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);}//设置分片数据包的表示符IDip_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;}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。