linux内核协议栈 IPv4之发送接口概述

it2024-03-19  83

目录

1 上层协议发送接口

1.1 ip_queue_xmit()

1.2 ip_build_and_send_pkt()

1.3 ip_send_reply()

1.4 ip_append_data()

1.5 ip_push_pending_frames()

2 IP层内部发送过程

2.1 ip_local_out()

2.2 已过 LOCAL_OUT 链 dst_output()

2.2.1 ip_output()

2.3 已过 POST_ROUTING 链 ip_finish_output()

2.3.1 正常发送 ip_finish_output2

2.3.2 分片ip_fragment()


IP层的数据包发送过程应该分两个层次来看:

IP层提供了哪些接口给高层协议(tcp、udp等)使用;IP层内部在收到发送数据请求后做了哪些处理,之后如何将数据包发给网卡的;

1 上层协议发送接口

IP层对外提供了多个发送接口,高层协议会根据需要进行调用,下面对一些常用的接口进行简要的介绍。

1.1 ip_queue_xmit()

int ip_queue_xmit(struct sk_buff *skb, int ipfragok);

该接口主要由tcp使用,tcp的大多数报文的发送也均是由该接口完成。

static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask) { .... //在TCP中该接口实现函数为ip_queue_xmit() err = icsk->icsk_af_ops->queue_xmit(skb, 0); if (likely(err <= 0)) return err; tcp_enter_cwr(sk, 1); return net_xmit_eval(err); }

1.2 ip_build_and_send_pkt()

tcp在三次握手时首次收到 SYN时,回复SYN + ACK使用,因为此时的 tcp 并未完成建链没有新建一个sock用于维护发送队列 sk_write_queue,因此无法调用 tcp_write_xmit 等接口。

static int tcp_v4_send_synack(struct sock *sk, struct request_sock *req, struct dst_entry *dst) { ... if (skb) { struct tcphdr *th = tcp_hdr(skb); ... err = ip_build_and_send_pkt(skb, sk, ireq->loc_addr, ireq->rmt_addr, ireq->opt); ... } ... }

1.3 ip_send_reply()

//RST报文,关闭链接 tcp_v4_send_reset --ip_send_reply //在TIME_WAIT、SYN_RECV状态下回复ACK信息 tcp_v4_send_ack --ip_send_reply

1.4 ip_append_data()

该接口主要由udp使用,udp的大多数报文的发送也是由该接口完成。但是tcp的RST报文和ip_send_reply()最终也是调用的该接口。

icmp_push_reply --ip_append_data ip_send_reply //TCP调用 --ip_append_data raw_sendmsg --ip_append_data udp_sendmsg --ip_append_data

1.5 ip_push_pending_frames()

实际上,ip_append_data()并不会发送报文,它只是将数据封装到skb中,封装完毕后,需要高层协议自己调用ip_push_pending_frames()将数据包发送给IP层。

2 IP层内部发送过程

上面的发送接口处理完毕后,都会调用ip_local_out()进行报文发送。整个 IP 层的整个发送过程如下:

高层协议调用提供的接口将 skb 传给 IP 层;IP 层构造首部,根据需要进行分片处理;过 LOCAL_OUT 点、POST_ROUTING 点;调用邻居子系统接口将数据包输出给网卡。

2.1 ip_local_out()

int ip_local_out(struct sk_buff *skb) { int err; err = __ip_local_out(skb); //大多数情况下,这里为何会返回1??? if (likely(err == 1)) err = dst_output(skb); return err; } EXPORT_SYMBOL_GPL(ip_local_out); int __ip_local_out(struct sk_buff *skb) { struct iphdr *iph = ip_hdr(skb); iph->tot_len = htons(skb->len); //校验和相关处理 ip_send_check(iph); //过LOCAL_OUT点,通过后调用dst_output() return nf_hook(PF_INET, NF_INET_LOCAL_OUT, skb, NULL, skb->dst->dev, dst_output); }

2.2 已过 LOCAL_OUT 链 dst_output()

dst_output() 本身很简单,如下:

/* Output packet to network from transport. */ static inline int dst_output(struct sk_buff *skb) { return skb->dst->output(skb); }

这里实际上会调用路由查询结果中的output(),对于单播报文(无论是本机,还是转发),该指针指向的实际上是ip_output()。

2.2.1 ip_output()

int ip_output(struct sk_buff *skb) { struct net_device *dev = skb->dst->dev; IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS); //设置输出设备,和L2报文类型为以太网 skb->dev = dev; skb->protocol = htons(ETH_P_IP); //过POST_ROUTING点,通过后,调用ip_finish_out() return NF_HOOK_COND(PF_INET, NF_INET_POST_ROUTING, skb, NULL, dev, ip_finish_output, !(IPCB(skb)->flags & IPSKB_REROUTED)); }

2.3 已过 POST_ROUTING 链 ip_finish_output()

该函数进行ip层的最后一段逻辑处理,即将报文发送给网卡,但是从下面的代码可以看出,这个过程并不是直接调用网络设备层的 dev_queue_xmit(),而是通过邻居子系统间接调用。

static int ip_finish_output(struct sk_buff *skb) { ... //如果超过了MTU并且不是GSO场景,那么需要分片,分片后再输出。否则直接输出 if (skb->len > ip_skb_dst_mtu(skb) && !skb_is_gso(skb)) return ip_fragment(skb, ip_finish_output2); else return ip_finish_output2(skb); }

2.3.1 正常发送 ip_finish_output2

static inline int ip_finish_output2(struct sk_buff *skb) { struct dst_entry *dst = skb->dst; struct rtable *rt = (struct rtable *)dst; struct net_device *dev = dst->dev; unsigned int hh_len = LL_RESERVED_SPACE(dev); if (rt->rt_type == RTN_MULTICAST) IP_INC_STATS(IPSTATS_MIB_OUTMCASTPKTS); else if (rt->rt_type == RTN_BROADCAST) IP_INC_STATS(IPSTATS_MIB_OUTBCASTPKTS); //如果skb的头部不足以容纳L2的报头,那么重新调整skb的头部空间,并且释放旧的skb if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) { struct sk_buff *skb2; skb2 = skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev)); if (skb2 == NULL) { kfree_skb(skb); return -ENOMEM; } if (skb->sk) skb_set_owner_w(skb2, skb->sk); kfree_skb(skb); skb = skb2; } //调用邻居子系统的接口输出 if (dst->hh) return neigh_hh_output(dst->hh, skb); else if (dst->neighbour) 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.3.2 分片ip_fragment()

《linux协议栈 IPv4之发送过程中的分段处理ip_fragment()》

最新回复(0)