從udp_sendmsg到ip_output發包過程:
udp_sendmsg->udp_send_skb->ip_send_skb->ip_local_out->ip_local_out_sk->__ip_local_out->__ip_local_out_sk->dst_output_sk->ip_output;在dst_output_sk函數中調用了skb_dst(skb)->output(sk, skb);
一、udp_sendmsg
UDP socket在傳輸層調用的發送函數爲udp_sendmsg,這個函數內容好多。
首先獲取發送的目的地址和目的端口,然後處理控制信息,接着選路,最後生成skb,將數據發送出去。發送報文時的skb就是在這個函數中生成的。
這個函數中有兩個變量需要重點關注:corkreq、up->pending,這兩個變量決定了udp_sendmsg函數的流程走向。
corkreq:表示是否使用緩衝機制,是否阻塞,意思就是把多個短的數據合成一個長的數據發送。
up->pending:表示當前sock還有數據沒有發送到ip層,一般在設置了CORK標記的場景下才會設置這個標記。
轉載自:
http://arthurchiao.art/blog/tuning-stack-tx-zh/#chap_5.1
http://blog.chinaunix.net/uid-14528823-id-4468600.html
int udp_sendmsg(struct sock *sk, struct msghdr *msg, size_t len)
{
struct inet_sock *inet = inet_sk(sk);
struct udp_sock *up = udp_sk(sk);
struct flowi4 fl4_stack;
struct flowi4 *fl4;
int ulen = len;
struct ipcm_cookie ipc;
struct rtable *rt = NULL;
int free = 0;
int connected = 0;
__be32 daddr, faddr, saddr;
__be16 dport;
u8 tos;
/*獲取pcflag標誌確定該套接字是普通的UDP套接字還是輕量級套接字*/
int err, is_udplite = IS_UDPLITE(sk);
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
struct sk_buff *skb;
struct ip_options_data opt_copy;
/*UDP數據報最長爲64KB*/
if (len > 0xFFFF)
return -EMSGSIZE;
/*
* Check the flags.
*/
/*UDP不支持發送帶外數據,如果發送標誌中設置了MSG_OOB,則返回*/
if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
return -EOPNOTSUPP;
ipc.opt = NULL;
ipc.tx_flags = 0;
ipc.ttl = 0;
ipc.tos = -1;
getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
fl4 = &inet->cork.fl.u.ip4;
/*當前的sock有等待發送的數據,直接將數據追加*/
if (up->pending) {
/*
* There are pending frames.
* The socket lock must be held while it's corked.
*/
lock_sock(sk);
if (likely(up->pending)) {
if (unlikely(up->pending != AF_INET)) {
release_sock(sk);
return -EINVAL;
}
goto do_append_data;
}
release_sock(sk);
}
/*UDP數據報長度,包括UDP data + UDP header*/
ulen += sizeof(struct udphdr);
/*
* Get and verify the address.
*/
/*獲取目的IP地址和端口:目的地址和端口有兩個可能的來源:
1. 如果之前socket已經建立,那socket本身就存儲了目標地址;
2. 地址通過msghdr傳入,通常爲調用sendto發送UDP數據*/
if (msg->msg_name) {
/*地址信息保存到usin中*/
DECLARE_SOCKADDR(struct sockaddr_in *, usin, msg->msg_name);
if (msg->msg_namelen < sizeof(*usin))
return -EINVAL;
if (usin->sin_family != AF_INET) {
if (usin->sin_family != AF_UNSPEC)
return -EAFNOSUPPORT;
}
daddr = usin->sin_addr.s_addr;
dport = usin->sin_port;
if (dport == 0)
return -EINVAL;
} else {
/*msg沒有目的地址的情況:通常爲先調用了connect,然後調用send發送UDP數據,
UDP套接字調用connetc之後,UDP傳輸控制塊狀態爲TCP_ESTABLISHED*/
if (sk->sk_state != TCP_ESTABLISHED)
/*即沒有指明目的地址,又沒有建立connect連接,則返錯。*/
return -EDESTADDRREQ;
daddr = inet->inet_daddr;
dport = inet->inet_dport;
/* Open fast path for connected socket.
Route will not be used, if at least one option is set.
*/
connected = 1;
}
/*獲取存儲在 socket 上的源地址、發送網絡設備索引(device index)和時間戳選項*/
ipc.addr = inet->inet_saddr;
ipc.oif = sk->sk_bound_dev_if;
sock_tx_timestamp(sk, &ipc.tx_flags);
/*msg中控制信息處理*/
if (msg->msg_controllen) {
/*調用ip_cmsg_send處理控制信息,包括IP選項等...*/
err = ip_cmsg_send(sock_net(sk), msg, &ipc,
sk->sk_family == AF_INET6);
if (unlikely(err)) {
kfree(ipc.opt);
return err;
}
if (ipc.opt)
free = 1;
connected = 0;
}
/*如果發送的數據中沒有IP選項控制信息,則從正在使用的socket中獲取IP選項信息*/
if (!ipc.opt) {
struct ip_options_rcu *inet_opt;
rcu_read_lock();
inet_opt = rcu_dereference(inet->inet_opt);
if (inet_opt) {
memcpy(&opt_copy, inet_opt,
sizeof(*inet_opt) + inet_opt->opt.optlen);
ipc.opt = &opt_copy.opt;
}
rcu_read_unlock();
}
saddr = ipc.addr;
ipc.addr = faddr = daddr;
/*源路由選項處理*/
if (ipc.opt && ipc.opt->opt.srr) {
if (!daddr)
return -EINVAL;
faddr = ipc.opt->opt.faddr;
connected = 0;
}
tos = get_rttos(&ipc, inet);
if (sock_flag(sk, SOCK_LOCALROUTE) ||
(msg->msg_flags & MSG_DONTROUTE) ||
(ipc.opt && ipc.opt->opt.is_strictroute)) {
tos |= RTO_ONLINK;
connected = 0;
}
/*多播報文處理*/
if (ipv4_is_multicast(daddr)) {
if (!ipc.oif)
ipc.oif = inet->mc_index;
if (!saddr)
saddr = inet->mc_addr;
connected = 0;
} else if (!ipc.oif)
ipc.oif = inet->uc_index;
/*路由相關處理,獲取對應的路由緩存*/
if (connected)
rt = (struct rtable *)sk_dst_check(sk, 0);
if (!rt) {
struct net *net = sock_net(sk);
fl4 = &fl4_stack;
flowi4_init_output(fl4, ipc.oif, sk->sk_mark, tos,
RT_SCOPE_UNIVERSE, sk->sk_protocol,
inet_sk_flowi_flags(sk),
faddr, saddr, dport, inet->inet_sport);
security_sk_classify_flow(sk, flowi4_to_flowi(fl4));
rt = ip_route_output_flow(net, fl4, sk);
if (IS_ERR(rt)) {
err = PTR_ERR(rt);
rt = NULL;
if (err == -ENETUNREACH)
IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);
goto out;
}
err = -EACCES;
if ((rt->rt_flags & RTCF_BROADCAST) &&
!sock_flag(sk, SOCK_BROADCAST))
goto out;
if (connected)
sk_dst_set(sk, dst_clone(&rt->dst));
}
if (msg->msg_flags&MSG_CONFIRM)
goto do_confirm;
back_from_confirm:
saddr = fl4->saddr;
if (!ipc.addr)
daddr = ipc.addr = fl4->daddr;
/* Lockless fast path for the non-corking case. */
/*快速路徑(大部分情況下都沒有使用CORK,直接新建skb,並直接發送*/
if (!corkreq) {
/*將sock相應的skb隊列中的所有skb合併成一個數據報文(skb),實際使用skb_shinfo->frag_list將所有skb連接起來。
爲什麼要這樣?這裏將所有skb都合併後,可能導致這個包的size大於mtu,那到IP層的時候還會進行進一步分片?
原因是:udp是面向數據報文的,報文必須完整,屬於同一個報文的數據必須要放到同一個skb中,否則對端無法知道這是同一個報文。
那IP層怎麼處理呢?就不考慮同一個報文的問題?IP層分片會攜帶相關頭信息,對端會根據這些信息進行重組,重組後對傳輸層來說就是一個報文。
分片其實就是IP層應該負責的。此時IP分片實際就是將原來skb->frag_list中的skb摘出來,不會做其它的操作,效率很高。*/
skb = ip_make_skb(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
msg->msg_flags);
err = PTR_ERR(skb);
if (!IS_ERR_OR_NULL(skb))
err = udp_send_skb(skb, fl4);
goto out;
}
lock_sock(sk);
if (unlikely(up->pending)) {
/* The socket is already corked while preparing it. */
/* ... which is an evident application bug. --ANK */
release_sock(sk);
net_dbg_ratelimited("cork app bug 2\n");
err = -EINVAL;
goto out;
}
/*
* Now cork the socket to pend data.
*/
/*緩存目的地址、目的端口、源地址和源端口信息,便於在發送處理時方便獲取信息。*/
fl4 = &inet->cork.fl.u.ip4;
fl4->daddr = daddr;
fl4->saddr = saddr;
fl4->fl4_dport = dport;
fl4->fl4_sport = inet->inet_sport;
/*設置AF_INET標記,表明正在處理UDP數據包*/
up->pending = AF_INET;
do_append_data:
up->len += ulen;
/*調用IP層接口函數ip_append_data,進入IP層處理,主要工作爲:
將數據拷貝到適合的skb(利用發送隊列中現有的或新創建)中,可能有兩種情況: 1. 放入skb的線性
區(skb->data)中,或者放入skb_shared_info的分片(frag)中,同時還需要考慮MTU對skb數據進行分割。*/
err = ip_append_data(sk, fl4, getfrag, msg, ulen,
sizeof(struct udphdr), &ipc, &rt,
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
if (err)
udp_flush_pending_frames(sk);
else if (!corkreq)
/*發送UDP數據報*/
err = udp_push_pending_frames(sk);
else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
up->pending = 0;
release_sock(sk);
out:
ip_rt_put(rt);
if (free)
kfree(ipc.opt);
if (!err)
return len;
/*
* ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting
* ENOBUFS might not be good (it's not tunable per se), but otherwise
* we don't have a good statistic (IpOutDiscards but it can be too many
* things). We could add another new stat but at least for now that
* seems like overkill.
*/
if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
}
return err;
do_confirm:
dst_confirm(&rt->dst);
if (!(msg->msg_flags&MSG_PROBE) || len)
goto back_from_confirm;
err = 0;
goto out;
}
二、ip_make_skb
這個函數中調用__ip_append_data將要發送的數據組織成一個個skb;
調用__ip_make_skb將sock相應的skb隊列中的所有skb合併成一個數據報文(skb),使用skb_shinfo->frag_list將所有skb連接起來。
這兩個函數的分析後面都有。
struct sk_buff *ip_make_skb(struct sock *sk,
struct flowi4 *fl4,
int getfrag(void *from, char *to, int offset,
int len, int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
struct ipcm_cookie *ipc, struct rtable **rtp,
unsigned int flags)
{
struct inet_cork cork;
struct sk_buff_head queue;
int err;
if (flags & MSG_PROBE)
return NULL;
__skb_queue_head_init(&queue);
cork.flags = 0;
cork.addr = 0;
cork.opt = NULL;
err = ip_setup_cork(sk, &cork, ipc, rtp);
if (err)
return ERR_PTR(err);
err = __ip_append_data(sk, fl4, &queue, &cork,
¤t->task_frag, getfrag,
from, length, transhdrlen, flags);
if (err) {
__ip_flush_pending_frames(sk, &queue, &cork);
return ERR_PTR(err);
}
return __ip_make_skb(sk, fl4, &queue, &cork);
}
三、udp_send_skb
主要作用:
1.skb生成UDP頭;
2.處理校驗和:軟件校驗和、硬件校驗和、無校驗和(如果禁用);
3.調用ip_send_skb將skb發往ip層處理;
4.更新發送成功或失敗的統計計數器。
static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
{
struct sock *sk = skb->sk;
struct inet_sock *inet = inet_sk(sk);
struct udphdr *uh;
int err = 0;
int is_udplite = IS_UDPLITE(sk);
int offset = skb_transport_offset(skb);
int len = skb->len - offset;
__wsum csum = 0;
/*
* Create a UDP header
*/
/*生成udp頭*/
uh = udp_hdr(skb);
uh->source = inet->inet_sport;
uh->dest = fl4->fl4_dport;
uh->len = htons(len);
uh->check = 0;
/*處理輕量級UDP校驗和*/
if (is_udplite) /* UDP-Lite */
csum = udplite_csum(skb);
/*如果socket校驗和選項被關閉,(setsockopt帶SO_NO_CHECK參數),將不進行校驗*/
else if (sk->sk_no_check_tx) { /* UDP csum disabled */
skb->ip_summed = CHECKSUM_NONE;
goto send;
}
/*如果硬件支持校驗和,將調用udp4_hwcsum來設置它。如果數據包是分段的,內核將在
軟件中生成校驗和,在udp4_hwcsum源碼中可以看到這一點。*/
else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
goto send;
}
/*調用udp_csum生成校驗和*/
else
csum = udp_csum(skb);
/* add protocol-dependent pseudo-header */
/*添加僞頭,生成校驗字*/
uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
sk->sk_protocol, csum);
/*如果校驗和爲 0,則根據RFC 768,校驗爲全 1*/
if (uh->check == 0)
uh->check = CSUM_MANGLED_0;
send:
err = ip_send_skb(sock_net(sk), skb);
/*發送失敗,並且錯誤是ENOBUFS(沒有內存)、且錯誤queue(inet->recverr)沒有啓用,更新NDBUFERRORS*/
if (err) {
if (err == -ENOBUFS && !inet->recverr) {
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_SNDBUFERRORS, is_udplite);
err = 0;
}
}
/*發送成功,更新OUTDATAGRAMS統計*/
else
UDP_INC_STATS_USER(sock_net(sk),
UDP_MIB_OUTDATAGRAMS, is_udplite);
return err;
}
1. ip_send_skb
這個函數很簡單,直接調用 ip_local_out發送skb,如果調用失敗,更新相應的統計計數。
int ip_send_skb(struct net *net, struct sk_buff *skb)
{
int err;
err = ip_local_out(skb);
if (err) {
if (err > 0)
err = net_xmit_errno(err);
if (err)
IP_INC_STATS(net, IPSTATS_MIB_OUTDISCARDS);
}
return err;
}
net_xmit_errno:將底層錯誤轉換爲IP和UDP層所能理解的錯誤,如果發成錯誤,更新IP協議的OUTDISCARDS統計。
2. ip_local_out
static inline int ip_local_out(struct sk_buff *skb)
{
return ip_local_out_sk(skb->sk, skb);
}
3. ip_local_out_sk
在這個函數中調用__ip_local_out發送skb。
int ip_local_out_sk(struct sock *sk, struct sk_buff *skb)
{
int err;
err = __ip_local_out(skb);
if (likely(err == 1))
err = dst_output_sk(sk, skb);
return err;
}
4. __ip_local_out
int __ip_local_out(struct sk_buff *skb)
{
return __ip_local_out_sk(skb->sk, skb);
}
5. __ip_local_out_sk
1.設置IP數據包的總長度;
2.計算校驗和;
3.通過IP層NF_INET_LOCAL_OUT hook函數,放行後調用des_output_sk函數繼續發送skb。
注意這裏如果程序中沒有定義CONFIG_NETFILTER,nf_hook()是直接返回1的,所以在ip_local_out_sk函數中會繼續調用顯示調用dst_output_sk函數發送skb。
int __ip_local_out_sk(struct sock *sk, struct sk_buff *skb)
{
struct iphdr *iph = ip_hdr(skb);
iph->tot_len = htons(skb->len);
ip_send_check(iph);
return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT, sk, skb, NULL,
skb_dst(skb)->dev, dst_output_sk);
}
6. dst_output_sk
/* Output packet to network from transport. */
static inline int dst_output_sk(struct sock *sk, struct sk_buff *skb)
{
return skb_dst(skb)->output(sk, skb);
}
這個output函數指針是在哪裏設置的了?
在udp_sendmsg函數中調用ip_route_output_flow設置的,調用關係爲:
ip_route_output_flow->__ip_route_output_key->__mkroute_output,在__mkroute_output函數中設置rth->dst.output = ip_output;
ip_output函數就放到網絡層繼續分析了。
四、ip_append_data
參考:Understanding Linux Network Internals.pdf
https://blog.csdn.net/xiaoyu_750516366/article/details/84981102
http://blog.chinaunix.net/uid-14528823-id-4462540.html
這個函數的內容也很多,主要作用:
將傳輸層要發送的數據組織成一個個skb,將skb掛到sk->sk_write_queue的隊列上。函數重點是如何將數據組織成對應的skb。
主要依據是MSG_MORE標記、設備是否支持分散/聚合IO。一個skb對應一個IP報文。
先看一下執行ip_append_data函數後,skb的組織結構。
1.就一個SKB能裝下所有數據的時候,不需要分片,最簡單。
2.需要分片,沒有設置MSG_MORE,設備不支持NETIF_F_SG
3.需要分片,設置MSG_MORE,設備不支持NETIF_F_SG
4.設備支持NETIF_F_SG(MSG_MORE設置與否不管)
5.設備不支持NETIF_F_SG(MSG_MORE設置與否不管)
6.多個frags時,設備支持NETIF_F_SG(MSG_MORE設置與否不管)
7.多個skb共用一個分頁
8.幾個小函數對比
9.關於間須fraggap
10.ip_append_data函數主要流程。
cork結構和 ipcm_cookie結構比較重要,還沒來得及仔細分析,先把網友的粘貼過來了,感謝前人努力。
struct cork
inet_sock中的cork成員非常關鍵,它影響了多次連續的ip_append_data()調用過程中該函數的執行流程。
struct inet_sock {
...
struct {
// 可取下面的IPCORK_OPT和IPCORK_ALLFRAG兩個值的組合
unsigned int flags;
// 記錄一個IP片段可以容納的數據量,其實就是mtu,之所以記錄是爲了不用每次都計算一遍
unsigned int fragsize;
// 保存了IP選項和路由信息
struct ip_options *opt;
struct rtable *rt;
// 當前IP報文(注意不是IP片段)中已經放入的數據長度,初始化時爲0
int length; /* Total length of all frames */
__be32 addr;
struct flowi fl;
} cork;
};
#define IPCORK_OPT 1 /* ip-options has been held in ipcork.opt */
#define IPCORK_ALLFRAG 2 /* always fragment (for ipv6 for now) */
struct ipcm_cookie
該結構作爲ip_append_data()的一個入參,讓高層協議將一些控制信息傳遞給ip_append_data()。
struct ipcm_cookie
{
// IP地址,UDP調用ip_append_data()時傳遞的是目的地址
__be32 addr;
// 出口設備的網絡設備索引
int oif;
// IP選項
struct ip_options *opt;
};
ip_append_data
/*
* ip_append_data() and ip_append_page() can make one large IP datagram
* from many pieces of data. Each pieces will be holded on the socket
* until ip_push_pending_frames() is called. Each piece can be a page
* or non-page data.
*
* Not only UDP, other transport protocols - e.g. raw sockets - can use
* this interface potentially.
*
* LATER: length must be adjusted by pad at tail, when it is required.
*/
int ip_append_data(struct sock *sk, struct flowi4 *fl4,
int getfrag(void *from, char *to, int offset, int len,
int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
struct ipcm_cookie *ipc, struct rtable **rtp,
unsigned int flags)
{
struct inet_sock *inet = inet_sk(sk);
int err;
if (flags&MSG_PROBE)
return 0;
if (skb_queue_empty(&sk->sk_write_queue)) {
err = ip_setup_cork(sk, &inet->cork.base, ipc, rtp);
if (err)
return err;
}
/*只有第一個skb需要設置傳輸層頭,不爲空時,表示sk_write_queue已有sbk,
所以後面skb不用添加傳輸層頭,transhdrlen設置爲0*/
else {
transhdrlen = 0;
}
/*調用__ip_append_data函數完成主要功能*/
return __ip_append_data(sk, fl4, &sk->sk_write_queue, &inet->cork.base,
sk_page_frag(sk), getfrag,
from, length, transhdrlen, flags);
}
__ip_append_data
static int __ip_append_data(struct sock *sk,
struct flowi4 *fl4,
struct sk_buff_head *queue,
struct inet_cork *cork,
struct page_frag *pfrag,
int getfrag(void *from, char *to, int offset,
int len, int odd, struct sk_buff *skb),
void *from, int length, int transhdrlen,
unsigned int flags)
{
struct inet_sock *inet = inet_sk(sk);
struct sk_buff *skb;
struct ip_options *opt = cork->opt;
int hh_len;
int exthdrlen;
int mtu;
int copy;
int err;
int offset = 0;
unsigned int maxfraglen, fragheaderlen, maxnonfragsize;
int csummode = CHECKSUM_NONE;
struct rtable *rt = (struct rtable *)cork->dst;
u32 tskey = 0;
skb = skb_peek_tail(queue);
/*exthdrlen:擴展首部,是否啓用擴展首部是由路由項決定的。*/
exthdrlen = !skb ? rt->dst.header_len : 0;
mtu = cork->fragsize;
if (cork->tx_flags & SKBTX_ANY_SW_TSTAMP &&
sk->sk_tsflags & SOF_TIMESTAMPING_OPT_ID)
tskey = sk->sk_tskey++;
/*L2層首部長度*/
hh_len = LL_RESERVED_SPACE(rt->dst.dev);
/*IP層首部長度,包括選項部分*/
fragheaderlen = sizeof(struct iphdr) + (opt ? opt->optlen : 0);
/*8字節對齊後,每個IP分配的最大長度,包括IP首部*/
maxfraglen = ((mtu - fragheaderlen) & ~7) + fragheaderlen;
maxnonfragsize = ip_sk_ignore_df(sk) ? 0xFFFF : mtu;
/*長度判斷,由於IP首部的total字段佔4個字節,所以一個IP報文的長度(包括IP首部)最大就是0XFFFF,
多次調用ip_append_data函數後,可能會出現超過該值。*/
if (cork->length + length > maxnonfragsize - fragheaderlen) {
ip_local_error(sk, EMSGSIZE, fl4->daddr, inet->inet_dport,
mtu - (opt ? opt->optlen : 0));
return -EMSGSIZE;
}
/*
* transhdrlen > 0 means that this is the first fragment and we wish
* it won't be fragmented in the future.
*/
/*校驗相關,先忽略*/
if (transhdrlen &&
length + fragheaderlen <= mtu &&
rt->dst.dev->features & NETIF_F_V4_CSUM &&
!exthdrlen)
csummode = CHECKSUM_PARTIAL;
/*更新sock中緩存的報文長度*/
cork->length += length;
/*這種情況下,調用ip_ufo_append_data處理,先忽略*/
if ((((length + fragheaderlen) > mtu) || (skb && skb_is_gso(skb))) &&
(sk->sk_protocol == IPPROTO_UDP) &&
(rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
(sk->sk_type == SOCK_DGRAM) && !sk->sk_no_check_tx) {
err = ip_ufo_append_data(sk, queue, getfrag, from, length,
hh_len, fragheaderlen, transhdrlen,
maxfraglen, flags);
if (err)
goto error;
return 0;
}
/* So, what's going on in the loop below?
*
* We use calculated fragment length to generate chained skb,
* each of segments is IP fragment ready for sending to network after
* adding appropriate IP header.
*/
/*sk_write_queue隊列爲空,即第一次填充數據,直接去分配skb*/
if (!skb)
goto alloc_new_skb;
/*這個大循環中,將length長數據填充到skb中。*/
while (length > 0) {
/* Check if the remaining data fits into current packet. */
/*判斷最後一個skb的剩餘空間是否能夠容納當前數據*/
copy = mtu - skb->len;
/*最後一個skb不用考慮8字節對齊,如果最後一個skb裝不下,就需要考慮8字節對齊(後面還要新生成skb,它就不是最後一個skb了),
更新容納量爲maxfraglen - skb->len*/
if (copy < length)
copy = maxfraglen - skb->len;
/*copy==0,表示該skb裝滿了,並且是8字節對齊的,一點都不能裝了,此時fraggap=0,直接分配新skb裝數據;
copy<=0,表示*該skb裝滿了,但是沒有8字節對齊,需要將sbk最後的fraggap長度數據剪切到新的skb中,再在新skb中裝數據*/
if (copy <= 0) {
char *data;
unsigned int datalen;/*本次待拷貝的數據量*/
unsigned int fraglen;/*分片的長度*/
unsigned int fraggap;/*間隙長度*/
unsigned int alloclen;/*分配的skb緩衝區大小*/
struct sk_buff *skb_prev;
alloc_new_skb:
skb_prev = skb;
/*計算間隙fraggap的大小*/
if (skb_prev)
fraggap = skb_prev->len - maxfraglen;
else
fraggap = 0;
/*
* If remaining data exceeds the mtu,
* we know we need more fragment(s).
*/
/*datalen(待添加的數據長度)=length(原始數據長度)+fraggap(間隙長度)*/
datalen = length + fraggap;
/*新生成一個skb也裝不下,待添加的數據長度datalen更新爲最大分片長度maxfraglen - fragheaderlen*/
if (datalen > mtu - fragheaderlen)
datalen = maxfraglen - fragheaderlen;
/*分片的長度*/
fraglen = datalen + fragheaderlen;
/*分配的skb緩衝區大小:
1.當設置了MSG_MORE標誌(表示後面還有數據到達),並且設備不支持分散/聚合IO時,直接分配MTU大小的緩存;
2.否則,分配剛好夠用的緩存區長度fraglen*/
if ((flags & MSG_MORE) &&
!(rt->dst.dev->features&NETIF_F_SG))
alloclen = mtu;
else
alloclen = fraglen;
/*第一個skb需要再加上IPsec相關的頭部長度*/
alloclen += exthdrlen;
/* The last fragment gets additional space at tail.
* Note, with MSG_MORE we overallocate on fragments,
* because we have no idea what fragment will be
* the last.
*/
/* 最有一個skb需要加上IPsec相關的尾部長度*/
if (datalen == length + fraggap)
alloclen += rt->dst.trailer_len;
/*transhdrlen不爲0,表示分配的是第一個skb,需要考慮l4層首部,此時調用sock_alloc_send_skb分配內存*/
if (transhdrlen) {
skb = sock_alloc_send_skb(sk,
alloclen + hh_len + 15,
(flags & MSG_DONTWAIT), &err);
}
/*後面分配的skb調用sock_wmalloc處理*/
else {
skb = NULL;
if (atomic_read(&sk->sk_wmem_alloc) <=
2 * sk->sk_sndbuf)
skb = sock_wmalloc(sk,
alloclen + hh_len + 15, 1,
sk->sk_allocation);
if (unlikely(!skb))
err = -ENOBUFS;
}
if (!skb)
goto error;
/*
* Fill in the control structures
*/
/*校驗和相關*/
skb->ip_summed = csummode;
skb->csum = 0;
/*預留l2頭部長度*/
skb_reserve(skb, hh_len);
/* only the initial fragment is time stamped */
skb_shinfo(skb)->tx_flags = cork->tx_flags;
cork->tx_flags = 0;
skb_shinfo(skb)->tskey = tskey;
tskey = 0;
/*
* Find where to start putting bytes.
*/
/*skb->tail += len,是數據區擴大len字節,爲存放三層頭首部和數據預留空間*/
data = skb_put(skb, fraglen + exthdrlen);
/*設置IP頭、傳輸層頭指針位置*/
skb_set_network_header(skb, exthdrlen);
skb->transport_header = (skb->network_header +
fragheaderlen);
/*設置data指針位置,就是數據開始存儲的地方*/
data += fragheaderlen + exthdrlen;
/*完成上一個skb的fraggap數據剪切到當前skb,並且重新計算兩個skb的校驗和。*/
if (fraggap) {
skb->csum = skb_copy_and_csum_bits(
skb_prev, maxfraglen,
data + transhdrlen, fraggap, 0);
skb_prev->csum = csum_sub(skb_prev->csum,
skb->csum);
data += fraggap;
pskb_trim_unique(skb_prev, maxfraglen);
}
/*調用getfrag拷貝copy字節數據內容到skb中*/
copy = datalen - transhdrlen - fraggap;
if (copy > 0 && getfrag(from, data + transhdrlen, offset, copy, fraggap, skb) < 0) {
err = -EFAULT;
kfree_skb(skb);
goto error;
}
offset += copy;
length -= datalen - fraggap;
transhdrlen = 0;
exthdrlen = 0;
csummode = CHECKSUM_NONE;
/*
* Put the packet on the pending queue.
*/
/*skb添加到sk_write_queue*/
__skb_queue_tail(queue, skb);
continue;
}
/*最後一個skb的剩餘空間可以裝下剩餘數據*/
if (copy > length)
copy = length;
/*設備不支持分散/聚合IO時,將數據拷貝到原來skb的線性緩衝區*/
if (!(rt->dst.dev->features&NETIF_F_SG)) {
unsigned int off;
off = skb->len;
if (getfrag(from, skb_put(skb, copy),
offset, copy, off, skb) < 0) {
__skb_trim(skb, off);
err = -EFAULT;
goto error;
}
}
/*設備支持分散/緩衝IO時,將數據拷貝到skb的frags數組中。--後面這段沒有分析,直接拷貝的別人的分析,後面自己再分析一下*/
else {
int i = skb_shinfo(skb)->nr_frags;
err = -ENOMEM;
/*在分片列表(frags)中使用原有分片(返回相應分片的指針)或分配新頁來存放數據*/
if (!sk_page_frag_refill(sk, pfrag))
goto error;
/*
* 如果傳輸控制塊(sock)中的緩存頁pfrag,不是當前skb->shared_info中的最後一個分片(分散聚集IO頁面)所在的頁面,則直接使用該頁面,
* 將其添加 到分片列表(分散聚集IO頁面數組)中,否則說明傳輸控制塊(sock)中的緩存頁pfrag就是分散聚集IO頁面的最後一個頁面,
* 則直接向其中拷貝數據即可。
*/
if (!skb_can_coalesce(skb, i, pfrag->page,
pfrag->offset)) {
err = -EMSGSIZE;
if (i == MAX_SKB_FRAGS)
goto error;
__skb_fill_page_desc(skb, i, pfrag->page,
pfrag->offset, 0);
skb_shinfo(skb)->nr_frags = ++i;
get_page(pfrag->page);
}
copy = min_t(int, copy, pfrag->size - pfrag->offset);
/*拷貝數據至skb中非線性區分片(分散聚集IO頁面)中*/
if (getfrag(from,
page_address(pfrag->page) + pfrag->offset,
offset, copy, skb->len, skb) < 0)
goto error_efault;
/*移動相應數據指針*/
pfrag->offset += copy;
/*增加分片大小*/
skb_frag_size_add(&skb_shinfo(skb)->frags[i - 1], copy);
/*增加skb數據相關大小*/
skb->len += copy;
skb->data_len += copy;
skb->truesize += copy;
/*增加sock發送緩存區已分配數據大小*/
atomic_add(copy, &sk->sk_wmem_alloc);
}
offset += copy;
/*length減去已經拷貝的大小,如果拷完了,則結束循環,否則繼續拷貝*/
length -= copy;
}
return 0;
error_efault:
err = -EFAULT;
error:
cork->length -= length;
IP_INC_STATS(sock_net(sk), IPSTATS_MIB_OUTDISCARDS);
return err;
}
五、udp_push_pending_frames
將輸出隊列上的多個片段合成一個完整的ip數據報文,並通過ip_output輸出。調用ip_finish_skb構造skb;調用udp_send_skb發送skb。
1. udp_push_pending_frames
/*
* Push out all pending data as one UDP datagram. Socket is locked.
*/
int udp_push_pending_frames(struct sock *sk)
{
struct udp_sock *up = udp_sk(sk);
struct inet_sock *inet = inet_sk(sk);
struct flowi4 *fl4 = &inet->cork.fl.u.ip4;
struct sk_buff *skb;
int err = 0;
skb = ip_finish_skb(sk, fl4);
if (!skb)
goto out;
err = udp_send_skb(skb, fl4);
out:
up->len = 0;
up->pending = 0;
return err;
}
2. ip_finish_skb
static inline struct sk_buff *ip_finish_skb(struct sock *sk, struct flowi4 *fl4)
{
return __ip_make_skb(sk, fl4, &sk->sk_write_queue, &inet_sk(sk)->cork.base);
}
3. __ip_make_skb
將sock相應的skb隊列中的所有skb合併成一個數據報文(skb),使用skb_shinfo->frag_list將所有skb連接起來。填充IP頭,對skb的組織進行了修改:
https://www.cnblogs.com/codestack/p/9265886.html
http://blog.chinaunix.net/uid-14528823-id-4468600.html
這個圖就對應着函數中while ((tmp_skb = __skb_dequeue(queue)) != NULL)操作。
/*
* Combined all pending IP fragments on the socket as one IP datagram
* and push them out.
*/
struct sk_buff *__ip_make_skb(struct sock *sk,
struct flowi4 *fl4,
struct sk_buff_head *queue,
struct inet_cork *cork)
{
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 *)cork->dst;
struct iphdr *iph;
__be16 df = 0;
__u8 ttl;
/*從sk_write_queue鏈表中獲取skb*/
skb = __skb_dequeue(queue);
if (!skb)
goto out;
tail_skb = &(skb_shinfo(skb)->frag_list);
/* move skb->data to ip header from ext header */
/*調整skb->data到ip頭*/
if (skb->data < skb_network_header(skb))
__skb_pull(skb, skb_network_offset(skb));
while ((tmp_skb = __skb_dequeue(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總長度增加
skb->data_len += tmp_skb->len;//skb的data長度增加
skb->truesize += tmp_skb->truesize;
tmp_skb->destructor = NULL;
tmp_skb->sk = NULL;
}
/* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
* to fragment the frame generated here. No matter, what transforms
* how transforms change size of the packet, it will come out.
*/
/*在不啓用路由MTU時,允許對輸出數據報進行分片*/
skb->ignore_df = ip_sk_ignore_df(sk);
/* DF bit is set when we want to see DF on outgoing frames.
* If ignore_df is set too, we still allow to fragment this frame
* locally. */
/*如果啓用了路由MTU發現功能,或者輸出數據報的長度小於MTU且本地傳輸控制塊輸出的IP
數據報不能分片,則給IP首部添加禁止分片標誌*/
if (inet->pmtudisc == IP_PMTUDISC_DO ||
inet->pmtudisc == IP_PMTUDISC_PROBE ||
(skb->len <= dst_mtu(&rt->dst) &&
ip_dont_fragment(sk, &rt->dst)))
df = htons(IP_DF);
/*如果IP選項信息已經保存到傳輸控制塊中,則獲取IP選項信息指針,用於構建IP首部中的選項*/
if (cork->flags & IPCORK_OPT)
opt = cork->opt;
/*ttl獲取*/
if (cork->ttl != 0)
ttl = cork->ttl;
else if (rt->rt_type == RTN_MULTICAST)
ttl = inet->mc_ttl;
else
ttl = ip_select_ttl(inet, &rt->dst);
/*ip頭信息填充*/
iph = ip_hdr(skb);
iph->version = 4;
iph->ihl = 5;
iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
iph->frag_off = df;
iph->ttl = ttl;
iph->protocol = sk->sk_protocol;
ip_copy_addrs(iph, fl4);
ip_select_ident(net, skb, sk);
if (opt) {
iph->ihl += opt->optlen>>2;
ip_options_build(skb, opt, cork->addr, rt, 0);
}
skb->priority = (cork->tos != -1) ? cork->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
*/
cork->dst = NULL;
skb_dst_set(skb, &rt->dst);
if (iph->protocol == IPPROTO_ICMP)
icmp_out_count(net, ((struct icmphdr *)
skb_transport_header(skb))->type);
ip_cork_release(cork);
out:
return skb;
}
🥴🥴🥴🥴🥴🥴