tcp/ip 协议栈Linux内核源码分析13 udp套接字发送流程二
生活随笔
收集整理的這篇文章主要介紹了
tcp/ip 协议栈Linux内核源码分析13 udp套接字发送流程二
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
內核版本:3.4.39
繼續UDP套接字發送,上一篇講到了sock_sendmsg,這里繼續,下面是sock_sendmsg的相關代碼
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size) {/* kiocb為內核通用的IO請求結構 */struct kiocb iocb;struct sock_iocb siocb;int ret;/* 初始化同步的內核IO請求結構 */init_sync_kiocb(&iocb, NULL);iocb.private = &siocb;/* 發送消息 */ret = __sock_sendmsg(&iocb, sock, msg, size);/* 返回結果表明該消息已經加入隊列,要等待完成事件 */if (-EIOCBQUEUED == ret)ret = wait_on_sync_kiocb(&iocb);return ret; } EXPORT_SYMBOL(sock_sendmsg)這里__sock_sendmsg只是做了安全性檢查,然后就調用了__sock_sendmsg_nosec函數。
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size) {int err = security_socket_sendmsg(sock, msg, size);return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size); }再繼續看__sock_sendmsg_nosec,代碼如下:
static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock,struct msghdr *msg, size_t size) {/* 獲得套接字在sock_sendmsg中設置的IO請求, */struct sock_iocb *si = kiocb_to_siocb(iocb);sock_update_classid(sock->sk);sock_update_netprioidx(sock->sk);/* 初始化套接字的IO請求字段 */si->sock = sock;si->scm = NULL;si->msg = msg;si->size = size;/* 根據不同的套接字類型,調用其發送數據函數 */return sock->ops->sendmsg(iocb, sock, msg, size); }?到此,我們完成了數據包從用戶空間到內核空間的流程跟蹤。接下來的數據包發送過程,將根據不同的協議,走不同的流程。
我們分析UDP的發送,UDP的sendmsg操作函數為udp_sendmsg,代碼如下:
int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,size_t len) {/* 從inet通用套接字得到inet套接字 */struct inet_sock *inet = inet_sk(sk);/* 從inet通用套接字得到UDP套接字 */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;int err, is_udplite = IS_UDPLITE(sk);/* 是否有數據包聚合:或者UDP套接字設置了聚合選項,或者數據包消息指明了還有更多數據UDP_CORK 或者 MSG_MORE,表示使用單個數據包發送多個數據*/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;/* 數據包長度檢查 */ if (len > 0xFFFF)return -EMSGSIZE;/** Check the flags.*//* 檢查消息標志,UDP不支持帶外數據 *if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */return -EOPNOTSUPP;ipc.opt = NULL;ipc.tx_flags = 0;/* 設置正確的分片函數 */getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;fl4 = &inet->cork.fl.u.ip4;if (up->pending) {/** There are pending frames.* The socket lock must be held while it's corked.*//* 該UDP套接字還有待發的數據包 */ lock_sock(sk);/* 常見的上鎖雙重檢查機制 */if (likely(up->pending)) {/* 若待發的數據不是INET數據,則報錯返回 */if (unlikely(up->pending != AF_INET)) {release_sock(sk);return -EINVAL;}/* 調到追加數據處 */goto do_append_data;}release_sock(sk);}ulen += sizeof(struct udphdr);/** Get and verify the address.*/if (msg->msg_name) {/* 若指定了目標地址,則對其進行校驗 */struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;/* 檢查長度 */if (msg->msg_namelen < sizeof(*usin))return -EINVAL;/* 檢查協議族。目前只支持AF_INET和AF_UNSPEC協議族 */if (usin->sin_family != AF_INET) {if (usin->sin_family != AF_UNSPEC)return -EAFNOSUPPORT;}/* 若通過了檢查,則設置目的地址與目的端口 */daddr = usin->sin_addr.s_addr;dport = usin->sin_port;/* 目的端口不能為0 */if (dport == 0)return -EINVAL;} else {/* 如果沒有指定目的地址和目的端口,則當前套接字的狀態必須是已連接,即已經調用過connect設置了目的地址 */if (sk->sk_state != TCP_ESTABLISHED)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;}ipc.addr = inet->inet_saddr;ipc.oif = sk->sk_bound_dev_if;/* 設置時間戳標志 */err = sock_tx_timestamp(sk, &ipc.tx_flags);if (err)return err;/* 發送的消息包含控制數據 */if (msg->msg_controllen) {/* 雖然這個函數的名字叫作send,其實并沒有任何發送動作,而只是將控制消息設置到ipc中 */err = ip_cmsg_send(sock_net(sk), msg, &ipc);if (err)return err;/* 設置釋放ipc.opt的標志 */if (ipc.opt)free = 1;connected = 0;}if (!ipc.opt) {/* 如果沒有使用控制消息指定IP選項,則檢查套接字的IP選項設置。如果有,則使用套接字的IP選項 */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;}/*若有下列情況之一的:1)套接字設置了本地路由標志。2)發送消息時,指明了不做路由。3)設置了IP嚴格路由選項。則設置不查找路由標志*/tos = RT_TOS(inet->tos);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;/* 若源地址為0,則使用套接字的多播地址 */ 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 == NULL) {struct net *net = sock_net(sk);fl4 = &fl4_stack;/* 根據套接字和數據包的信息,初始化flowi4—這是查找路由的key */flowi4_init_output(fl4, ipc.oif, sk->sk_mark, tos,RT_SCOPE_UNIVERSE, sk->sk_protocol,inet_sk_flowi_flags(sk)|FLOWI_FLAG_CAN_SLEEP,faddr, saddr, dport, inet->inet_sport,sock_i_uid(sk));/* 查找出口路由 */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_BH(net, IPSTATS_MIB_OUTNOROUTES);goto out;}err = -EACCES;/* 若路由是廣播路由,并且套接字非廣播套接字 */if ((rt->rt_flags & RTCF_BROADCAST) &&!sock_flag(sk, SOCK_BROADCAST))goto out;/* 若該UDP為已連接狀態,則保存這個路由緩存 */ if (connected)sk_dst_set(sk, dst_clone(&rt->dst));}/* 如果數據包設置了MSG_CONFIRM標志,則是要告訴鏈路層,對端是可達的。調到do_confrim處,可以發現其實現方法是在有neibour信息的情況下,直接更新neibour確認時間戳為當前時間。 */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選項或MSG_MORE標志。這也是最常見的情況。 */if (!corkreq) {/* 每次都生成一個UDP數據包 */skb = ip_make_skb(sk, fl4, getfrag, msg->msg_iov, ulen,sizeof(struct udphdr), &ipc, &rt,msg->msg_flags);err = PTR_ERR(skb);/* 成功生成了數據包 */if (skb && !IS_ERR(skb))/* 發送UDP數據包 */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 *//*現在馬上要做cork處理,但發現套接字已經cork了。因此這是一個應用程序bug。釋放套接字鎖,并返回錯誤。*/release_sock(sk);LIMIT_NETDEBUG(KERN_DEBUG pr_fmt("cork app bug 2\n"));err = -EINVAL;goto out;}/** Now cork the socket to pend data.*//* 設置cork中的流信息 */ fl4 = &inet->cork.fl.u.ip4;fl4->daddr = daddr;fl4->saddr = saddr;fl4->fl4_dport = dport;fl4->fl4_sport = inet->inet_sport;up->pending = AF_INET;do_append_data:/* 增加UDP數據長度 */up->len += ulen;/* 向IP數據包中追加新的數據 */err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, 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)// 若不在cork即阻塞,則發送所有未決的數據包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; } EXPORT_SYMBOL(udp_sendmsg);一般情況下,在使用UDP發送數據包時很少會使用CORK或MSG_MORE標志,因為我們希望在每次調用發送接口時,就發送一次UDP數據包。因此可以不必考慮CORK和MSG_MORE的情況,而繼續追蹤udp_send_skb函數。?
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協議,則調用相應的校驗和計算函數。* 輕量級UDP協議簡單說就是可以校驗指定長度的數據長度而不是全部* 減少數據丟棄分線。具體google下*/if (is_udplite) /* UDP-Lite */csum = udplite_csum(skb);/* 禁止了UDP校驗和 */else if (sk->sk_no_check == UDP_CSUM_NOXMIT) { /* UDP csum disabled */skb->ip_summed = CHECKSUM_NONE;goto send;} else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum *//* 硬件支持校驗和的計算 */udp4_hwcsum(skb, fl4->saddr, fl4->daddr);goto send;} else/* 一般情況下的校驗和計算 */csum = udp_csum(skb);/* add protocol-dependent pseudo-header *//* 計算UDP的校驗和,需要考慮偽首部 */uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,sk->sk_protocol, csum);/* 如果校驗和為0,則需要將其設置為0xFFFF。因為UDP的零校驗和,有特殊的含義,表示沒有校驗和。*/if (uh->check == 0)uh->check = CSUM_MANGLED_0;send:/* 發送IP數據包 */err = ip_send_skb(skb);if (err) {if (err == -ENOBUFS && !inet->recverr) {UDP_INC_STATS_USER(sock_net(sk),UDP_MIB_SNDBUFERRORS, is_udplite);err = 0;}} elseUDP_INC_STATS_USER(sock_net(sk),UDP_MIB_OUTDATAGRAMS, is_udplite);return err; }至此,UDP已經完成了自己的工作,后面的發送工作將交由IP層來負責。?
參考文檔:
1.?《Linux環境編程:從應用到內核》
2.??淺析Linux網絡子系統(一)?
總結
以上是生活随笔為你收集整理的tcp/ip 协议栈Linux内核源码分析13 udp套接字发送流程二的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 社会抚养费是什么意思
- 下一篇: tcp/ip 协议栈Linux内核源码分