当调用ip_append_data和ip_append_page把传输层数据包复制到内核缓冲区,接下来调用ip_push_pending_frames函数发送数据包。struct sock *sk是ip_push_pending_frames函数唯一的输入参数,sk中包含了指向缓冲区队列sk_write_queue数据结构的指针。
1、取发送缓冲区
ip_push_pending_frames首先从sk_write_queue队列中取除缓冲区的数据,缓冲区队列中第一个数据包一定是sk_buff,保存在局部变量struct sk_buff *skb中,队列中的其他缓冲区数据保存在skb的frag_list链表上,再更新skb的len和data_len数据域指向所有缓冲区的总长度,一旦缓冲区连接到skb就从sk_write_queue队列中清除。
int ip_push_pending_frames(struct sock *sk)
{
struct sk_buff *skb, *tmp_skb;
struct sk_buff **tail_skb;
struct inet_sock *inet = inet_sk(sk);
struct net *net = sock_net(sk);
struct ip_options *opt = NULL;
struct rtable *rt = (struct rtable *)inet->cork.dst;
struct iphdr *iph;
__be16 df = 0;
__u8 ttl;
int err = 0;
//从sk_write_queue队列中取缓冲区的数据
//第一个缓冲区一定是一个sk_buff
if ((skb = __skb_dequeue(&sk->sk_write_queue)) == NULL)
goto out;
//取存放在缓冲区frag_list链上的数据包
tail_skb = &(skb_shinfo(skb)->frag_list);
/* move skb->data to ip header from ext header */
if (skb->data < skb_network_header(skb))
__skb_pull(skb, skb_network_offset(skb));
//释放sk_write_queue缓冲区队列
while ((tmp_skb = __skb_dequeue(&sk->sk_write_queue)) != NULL) {
__skb_pull(tmp_skb, skb_network_header_len(skb));
*tail_skb = tmp_skb;
tail_skb = &(tmp_skb->next);
//更新数据包的长度
skb->len += tmp_skb->len;
//更新缓冲区总长度
skb->data_len += tmp_skb->len;
skb->truesize += tmp_skb->truesize;
tmp_skb->destructor = NULL;
tmp_skb->sk = NULL;
}
...
去sk_write_queue队列中第一个skb包
/* Internal */
#define skb_shinfo(SKB) ((struct skb_shared_info *)(skb_end_pointer(SKB)))
缓冲区列表所在结构体
struct skb_shared_info {
unsigned short nr_frags;
unsigned short gso_size;
/* Warning: this field is not always filled in (UFO)! */
unsigned short gso_segs;
unsigned short gso_type;
__be32 ip6_frag_id;
union skb_shared_tx tx_flags;
//数据包缓冲区链表
struct sk_buff *frag_list;
struct skb_shared_hwtstamps hwtstamps;
/*
* Warning : all fields before dataref are cleared in __alloc_skb()
*/
atomic_t dataref;
skb_frag_t frags[MAX_SKB_FRAGS];
/* Intermediate layers must ensure that destructor_arg
* remains valid until skb destructor */
void * destructor_arg;
};
2、处理IP协议头
接下来向数据包中填充IP协议头,如果套接字控制信息配置了不分割数据包的标志IP_DF,且该标志作用于所有数据包:IP_PMTUDISC_DO,即使数据包长度大于路由最大传输单元PMTU也不分割数据包,就将IP_DF标志设置在IP协议头相应的数据域。然后就是给IP协议头各数据域赋值:版本、协议头长度、如果有IP选项就要调用ip_options_build构建IP选项。
...
//不分割数据包的标志IP_DF
if (inet->pmtudisc >= IP_PMTUDISC_DO ||
(skb->len <= dst_mtu(&rt->u.dst) &&
ip_dont_fragment(sk, &rt->u.dst)))
df = htons(IP_DF);
//初始化ip选项
if (inet->cork.flags & IPCORK_OPT)
opt = inet->cork.opt;
//组发送
if (rt->rt_type == RTN_MULTICAST)
ttl = inet->mc_ttl;
else
ttl = ip_select_ttl(inet, &rt->u.dst);
//IP协议头初始化
iph = (struct iphdr *)skb->data;
iph->version = 4;
iph->ihl = 5;
if (opt) {
iph->ihl += opt->optlen>>2;
//构建IP选项
ip_options_build(skb, opt, inet->cork.addr, rt, 0);
}
iph->tos = inet->tos;
iph->frag_off = df;
ip_select_ident(iph, &rt->u.dst, sk);
iph->ttl = ttl;
iph->protocol = sk->sk_protocol;
iph->saddr = rt->rt_src;
iph->daddr = rt->rt_dst;
skb->priority = sk->sk_priority;
skb->mark = sk->sk_mark;
/*
* Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
* on dst refcount
*/
inet->cork.dst = NULL;
//设置路由
skb_dst_set(skb, &rt->u.dst);
...
3、和网络过滤子系统的交互
最后调用进入netfiler框架OUT链上的钩子处理函数,钩子处理函数结束就调用dst_output继续进入下一个阶段。
//最后进入Netfiler的OUT链上钩子函数
//钩子函数处理结束后调用dst_output继续处理
err = ip_local_out(skb);
if (err) {
if (err > 0)
err = net_xmit_errno(err);
if (err)
goto error;
}
ip_local_out函数:
int ip_local_out(struct sk_buff *skb)
{
int err;
err = __ip_local_out(skb);
if (likely(err == 1))
err = dst_output(skb);
return err;
}
__ip_local_out函数:
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);
}
3、发送数据包的完整过程
以UPD协议微粒子简述传输层到网络层的整个过程,UPD协议通过udp_sendmsg函数调用ip_append_data和ip_push_pend_frams实现数据包的发送,udp_sendmsg首先调用ip_append_data函数将数据包发送到缓冲区,然后判断corkreq标志中有没有设置MSG_MORE,如果没有设置立即调用udp_push_pending_frames将刚缓冲的数据包发送出去,如果ip_append_data缓冲数据包失败就将数据包从sk_write_queue队列中释放:
...
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
//缓冲数据包
err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
sizeof(struct udphdr), &ipc, &rt,
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
//缓存数据包失败删除把skb从sk_write_queue队列中释放
if (err)
udp_flush_pending_frames(sk);
//corkreq标志没有设置MSG_MORE立即调用udp_push_pending_frames发送
//发送刚缓存的数据包
else if (!corkreq)
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
//释放sk
release_sock(sk);
...
udp_push_pending_frames是ip_push_pending_frames的封装,udp_push_pending_frames首先初始化UDP协议头,然后判断是否禁止传输层校验和,如果禁止就调转调用ip_push_pending_frames,如果没有禁止就初始化UDP检验和。
static int udp_push_pending_frames(struct sock *sk)
{
struct udp_sock *up = udp_sk(sk);
struct inet_sock *inet = inet_sk(sk);
struct flowi *fl = &inet->cork.fl;
struct sk_buff *skb;
struct udphdr *uh;
int err = 0;
int is_udplite = IS_UDPLITE(sk);
__wsum csum = 0;
/* Grab the skbuff where UDP header space exists. */
if ((skb = skb_peek(&sk->sk_write_queue)) == NULL)
goto out;
/*
* Create a UDP header
*/
//创建udp协议头
uh = udp_hdr(skb);
uh->source = fl->fl_ip_sport;
uh->dest = fl->fl_ip_dport;
uh->len = htons(up->len);
uh->check = 0;
if (is_udplite) /* UDP-Lite */
csum = udplite_csum_outgoing(sk, skb);
else if (sk->sk_no_check == UDP_CSUM_NOXMIT) { /* UDP csum disabled */
//禁止传输层校验和,直接调到send
skb->ip_summed = CHECKSUM_NONE;
goto send;
} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
//udp校验和
udp4_hwcsum_outgoing(sk, skb, fl->fl4_src, fl->fl4_dst, up->len);
goto send;
} else /* `normal' UDP */
csum = udp_csum_outgoing(sk, skb);
/* add protocol-dependent pseudo-header */
uh->check = csum_tcpudp_magic(fl->fl4_src, fl->fl4_dst, up->len,
sk->sk_protocol, csum);
if (uh->check == 0)
uh->check = CSUM_MANGLED_0;
send:
//发送数据包
err = ip_push_pending_frames(sk);
if (err) {
if (err == -ENOBUFS && !inet->recverr) {
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
err = 0;
}
} else
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_OUTDATAGRAMS, is_udplite);
out:
up->len = 0;
up->pending = 0;
return err;
}