<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          UDP協(xié)議源碼分析

          共 8212字,需瀏覽 17分鐘

           ·

          2021-02-19 11:08

          UDP協(xié)議User Datagram Protocol 的簡稱, 中文名是用戶數(shù)據(jù)報協(xié)議,是OSI(Open System Interconnection,開放式系統(tǒng)互聯(lián)) 參考模型中一種無連接的傳輸層協(xié)議,提供面向事務的簡單不可靠信息傳送服務,位于?TCP/IP協(xié)議?模型的?傳輸層,如下圖:


          也就是說?UDP協(xié)議?是建立中?IP協(xié)議(網(wǎng)絡層)之上的,IP協(xié)議?用于區(qū)分網(wǎng)絡上不同的主機(IP協(xié)議源碼分析),而?UDP協(xié)議?用于區(qū)分同一臺主機上不同的進程發(fā)送(接收)的網(wǎng)絡數(shù)據(jù),如下圖所示:


          從上圖可以看出,UDP協(xié)議?通過?端口號?來區(qū)分不同進程的數(shù)據(jù)包。

          UDP協(xié)議頭

          下面我們來看看?UDP協(xié)議?的協(xié)議頭部,如下圖所示:

          從上圖可知,UDP頭部?由四個字段組成:源端口、目標端口、數(shù)據(jù)包長度?和?校驗和

          源端口?用于指示本機的進程,而?目標端口?用于指示遠端的進程。數(shù)據(jù)包長度?表示這個 UDP 數(shù)據(jù)包總長度(包括UDP頭部和 數(shù)據(jù)長度),而?校驗和?用于校驗數(shù)據(jù)包在傳輸?shù)倪^程中是否損壞了。

          下面我們看看?UDP頭部?在內(nèi)核中的表示方式,如下代碼:

          struct udphdr {    __u16   source;  // 源端口    __u16   dest;    // 目標端口    __u16   len;     // 數(shù)據(jù)包長度    __u16   check;   // 校驗和};

          可以看出,udphdr?結構的字段與?UDP頭部?結構圖中的字段一一對應。最后,我們來看看?UDP頭部?在數(shù)據(jù)包的具體位置,如下圖:

          下面我們主要通過 UDP 數(shù)據(jù)包的發(fā)送和接收兩個過程來分析 UDP 在內(nèi)核中的實現(xiàn)原理。

          UDP數(shù)據(jù)包發(fā)送

          數(shù)據(jù)的發(fā)送是由應用層調用?send()?或者?write()?系統(tǒng)調用,將數(shù)據(jù)傳遞到傳輸層協(xié)議處理,如下圖:

          從上圖可以看出,用戶態(tài)?的應用程序調用?send()?系統(tǒng)調用時會觸發(fā)調用?內(nèi)核態(tài)?的?sys_send()?內(nèi)核函數(shù),而?sys_send()?最終會調用?inet_sendmsg()?函數(shù)發(fā)送數(shù)據(jù)。

          inet_sendmsg()?函數(shù)會根據(jù)用戶使用的傳輸層協(xié)議選擇不同的數(shù)據(jù)發(fā)送接口,比如 UDP 協(xié)議就會使用?udp_sendmsg()?函數(shù)發(fā)送數(shù)據(jù)。

          我們來分析一下 UDP 協(xié)議的發(fā)送接口?udp_sendmsg()?函數(shù)的實現(xiàn),代碼如下(由于?udp_sendmsg()?函數(shù)的實現(xiàn)比較復雜,所以我們分段分析):

          int udp_sendmsg(struct sock *sk, struct msghdr *msg, int len){    int ulen = len + sizeof(struct udphdr);    struct ipcm_cookie ipc;    struct udpfakehdr ufh;    struct rtable *rt = NULL;    int free = 0;    int connected = 0;    u32 daddr;    u8  tos;    int err;

          udp_sendmsg()?函數(shù)的參數(shù)含義如下:

          • sk:Socket 對象。

          • msg:要發(fā)送的數(shù)據(jù)實體,其類型為?msghdr?結構。

          • len:要發(fā)送的數(shù)據(jù)長度。

          上面的代碼主要定義了一些局部變量,如:

          • ulen?變量就是要發(fā)送的數(shù)據(jù)總長度(UDP頭部長度和數(shù)據(jù)長度之和)。

          • rt?變量表示數(shù)據(jù)傳輸?shù)穆酚尚畔ⅲ漕愋蜑?rtable?結構。

          • ufh?變量是調用 IP 層?ip_build_xmit()?函數(shù)時的上下文,主要用于構建?UDP協(xié)議頭部?,其類型為?udpfakehdr?結構。

          我們接口分析?udp_sendmsg()?函數(shù):

              // 是否提供了接收數(shù)據(jù)的目標IP地址和端口    if (msg->msg_name) {        // 接收數(shù)據(jù)的目標IP地址和端口        struct sockaddr_in *usin = (struct sockaddr_in*)msg->msg_name;
          if (msg->msg_namelen < sizeof(*usin)) return -EINVAL;
          if (usin->sin_family != AF_INET) { if (usin->sin_family != AF_UNSPEC) return -EINVAL; }
          // 設置接收數(shù)據(jù)的目標IP地址和端口 ufh.daddr = usin->sin_addr.s_addr; ufh.uh.dest = usin->sin_port;
          if (ufh.uh.dest == 0) return -EINVAL; } else { if (sk->state != TCP_ESTABLISHED) return -ENOTCONN;
          ufh.daddr = sk->daddr; // 使用綁定Socket的IP地址 ufh.uh.dest = sk->dport; // 使用綁定Socket的端口 connected = 1; }

          上面的代碼主要完成以下幾個工作:

          • 如果用戶發(fā)送數(shù)據(jù)時提供了目標 IP 地址和端口,就把用戶提供的目標 IP 地址和端口復制到?ufh?變量中。

          • 否則就把綁定到 Socket 對象的目標 IP 地址和端口復制到?ufh?變量中,并且設置?connected?變量為 1。

          我們繼續(xù)分析?udp_sendmsg()?函數(shù),代碼如下:

              if (connected)        rt = (struct rtable*)sk_dst_check(sk, 0); // 獲取路由信息對象緩存
          if (rt == NULL) { // 如果路由信息對象還沒被緩存 // 調用 ip_route_output() 函數(shù)獲取路由信息對象 err = ip_route_output(&rt, daddr, ufh.saddr, tos, ipc.oif); if (err) goto out;
          err = -EACCES; if (rt->rt_flags & RTCF_BROADCAST && !sk->broadcast) goto out;
          if (connected) sk_dst_set(sk, dst_clone(&rt->u.dst)); // 設置路由信息對象緩存 }

          上面的代碼比較簡單,首先調用?sk_dst_check()?查看?路由信息對象?是否被緩存,如果已經(jīng)緩存,那么直接使用此?路由信息對象。否則調用?ip_route_output()?函數(shù)獲取?路由信息對象,并且調用?sk_dst_set()?設置?路由信息對象?緩存。

          路由信息對象?指明數(shù)據(jù)在傳送過程的?下一跳?主機的信息(通常為網(wǎng)關),有了?下一跳?主機的信息,就可以把數(shù)據(jù)轉發(fā)給?下一跳?主機,然后由?下一跳?主機繼續(xù)完成發(fā)送工作。

          我們繼續(xù)分析?udp_sendmsg()?函數(shù),代碼如下:

              ufh.saddr = rt->rt_src; // 設置源IP地址    if (!ipc.addr) // 如果沒有提供目標IP地址,使用路由信息的目標IP地址        ufh.daddr = ipc.addr = rt->rt_dst;    ufh.uh.len = htons(ulen);    ufh.uh.check = 0;    ufh.iov = msg->msg_iov;    ufh.wcheck = 0;
          // 構建MAC頭部、IP頭部和UDP頭部并且下發(fā)給IP協(xié)議層 err = ip_build_xmit(sk, (sk->no_check == UDP_CSUM_NOXMIT ? udp_getfrag_nosum : udp_getfrag), &ufh, ulen, &ipc, rt, msg->msg_flags);
          out: ip_rt_put(rt); ... return err;}

          上面的代碼主要把?路由信息對象?的源IP地址復制到?ufh?變量中,然后調用?ip_build_xmit()?函數(shù)完成數(shù)據(jù)發(fā)送的后續(xù)工作。ip_build_xmit()?函數(shù)的第一個參數(shù)用于復制?UDP頭部?和負載數(shù)據(jù)到數(shù)據(jù)包的函數(shù)指針,IP 層通過調用此函數(shù)把?UDP頭部?和負載數(shù)據(jù)復制到數(shù)據(jù)包中。

          ip_build_xmit()?函數(shù)是 IP 協(xié)議層的實現(xiàn),這里就不作說明,可以參考此文章:IP協(xié)議源碼分析。

          總的來說,udp_sendmsg()?函數(shù)的主要工作就是為要發(fā)送的數(shù)據(jù)包構建?UDP頭部,然后把數(shù)據(jù)包交由 IP 層完成接下來的發(fā)送操作,所以?UDP協(xié)議?的發(fā)送過程比較簡單。

          UDP數(shù)據(jù)包接收

          當網(wǎng)卡設備接收到數(shù)據(jù)包后,會交由內(nèi)核協(xié)議棧處理。內(nèi)核協(xié)議棧對數(shù)據(jù)包的處理是由下至上,如下圖所示:

          也就是說,物理層處理完數(shù)據(jù)包后會交由鏈路層處理,而鏈路層處理完交由網(wǎng)絡層處理,以此類推。

          所以當網(wǎng)絡層(IP協(xié)議)處理完數(shù)據(jù)包后,會交由傳輸層處理,在本文中介紹的傳輸層協(xié)議是?UDP協(xié)議,所以這里主要介紹的是?UDP協(xié)議?對數(shù)據(jù)包的處理過程。

          當 IP 協(xié)議層處理完數(shù)據(jù)包后,如果 IP 頭部的上層協(xié)議字段(protocol?字段)指明的是?UDP協(xié)議,那么就會調用?udp_rcv()?函數(shù)處理數(shù)據(jù)包。下面我們來分析一下?udp_rcv()?函數(shù)的實現(xiàn),代碼如下:

          int udp_rcv(struct sk_buff *skb, unsigned short len){    struct sock *sk;    struct udphdr *uh;    unsigned short ulen;    struct rtable *rt = (struct rtable*)skb->dst; // 路由信息對象    u32 saddr = skb->nh.iph->saddr; // 遠端IP地址(源IP地址)    u32 daddr = skb->nh.iph->daddr; // 本地IP地址(目標IP地址)
          uh = skb->h.uh; // UDP頭部 ... // 根據(jù)目標端口獲取對用的 Socket 對象 sk = udp_v4_lookup(saddr, uh->source, daddr, uh->dest, skb->dev->ifindex); if (sk != NULL) { udp_queue_rcv_skb(sk, skb); // 把數(shù)據(jù)包添加到Socket對象的receive_queue隊列中 sock_put(sk); return 0; } ...}

          udp_rcv()?函數(shù)主要完成兩個工作:

          • 調用?udp_v4_lookup()?函數(shù)獲取目標端口對應的?Socket?對象。

          • 調用?udp_queue_rcv_skb()?函數(shù)把數(shù)據(jù)包添加到?Socket?對象的?receive_queue?隊列中。

          UDP協(xié)議?使用了一個名為?udp_hash?的哈希表來保存所有綁定了端口的?Socket?對象,當應用程序調用?bind()?系統(tǒng)調用為?Socket?對象綁定端口時,就會將此?Socket?對象添加到?udp_hash?哈希表中。

          而?udp_v4_lookup()?函數(shù)就是根據(jù)目標端口從?udp_hash?哈希表中獲取對應的?Socket?對象,udp_v4_lookup()?函數(shù)實現(xiàn)如下:

          __inline__ struct sock *udp_v4_lookup(u32 saddr, u16 sport, u32 daddr, u16 dport, int dif){    struct sock *sk;
          read_lock(&udp_hash_lock); // 為udp_hash哈希表上鎖 sk = udp_v4_lookup_longway(saddr, sport, daddr, dport, dif); if (sk) sock_hold(sk); read_unlock(&udp_hash_lock); return sk;}

          udp_v4_lookup()?函數(shù)首先為?udp_hash?哈希表上鎖,然后調用?udp_v4_lookup_longway()?函數(shù)從?udp_hash?哈希表中獲取對應目標端口的?Socket?對象,udp_v4_lookup_longway()?函數(shù)的實現(xiàn)如下:

          struct sock *udp_v4_lookup_longway(u32 saddr, // 源IP地址(遠端IP地址)                      u16 sport, // 源端口(遠端端口)                      u32 daddr, // 目標IP地址(本地IP地址)                      u16 dport, // 目標端口(本地端口)                      int dif){    struct sock *sk, *result = NULL;    unsigned short hnum = ntohs(dport);    int badness = -1;
          // 根據(jù)目標端口從 udp_hash 哈希表中獲取對應的 Socket 對象 for (sk = udp_hash[hnum&(UDP_HTABLE_SIZE - 1)]; sk != NULL; sk = sk->next) { if (sk->num == hnum) { // 對比目標端口是否匹配 int score = 0;
          if (sk->rcv_saddr) { // 如果Socket設置了固定的本地接收IP if(sk->rcv_saddr != daddr) // 對比目標IP地址是否匹配 continue; score++; }
          if (sk->daddr) { // 如果Socket設置了固定的遠端接收IP if(sk->daddr != saddr) // 對比源IP地址是否匹配 continue; score++; }
          if (sk->dport) { // 如果Socket設置了固定的遠端接收端口 if(sk->dport != sport) // 對比源端口是否匹配 continue; score++; }
          if (sk->bound_dev_if) { // 如果Socket設置了固定的接收網(wǎng)絡設備 if(sk->bound_dev_if != dif) // 對比接收設備是否匹配 continue; score++; }
          if (score == 4) { // 完美匹配, 那么直接返回即可 result = sk; break; } else if(score > badness) { // 否則使用分數(shù)最高的Socket對象 result = sk; badness = score; } } }
          return result;}

          udp_v4_lookup_longway()?函數(shù)的主要邏輯就是根據(jù)目標端口從?udp_hash?哈希表中獲取對應的?Socket?對象。

          由于同一個端口有可能綁定了多個?Socket?對象,所以?udp_v4_lookup_longway()?函數(shù)查找?Socket?對象時使用了最優(yōu)匹配,也就是說除了匹配目標端口外,還可能會匹配源 IP 地址、源端口和目標 IP 地址等。

          找到目標端口對應的?Socket?對象后,就可以調用?udp_queue_rcv_skb()?函數(shù)把數(shù)據(jù)包添加到?Socket?對象的?receive_queue?隊列中,udp_queue_rcv_skb()?函數(shù)實現(xiàn)如下:

          static int udp_queue_rcv_skb(struct sock * sk, struct sk_buff *skb){    ...    if (sock_queue_rcv_skb(sk, skb) < 0) {        ...        return -1;    }    return 0;}

          從上面代碼可以看出,udp_queue_rcv_skb()?函數(shù)最終會調用?sock_queue_rcv_skb()?函數(shù)完成任務,所以我們來分析一下?sock_queue_rcv_skb()?函數(shù)的實現(xiàn),代碼如下:

          static inline int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb){    ...    // 把數(shù)據(jù)包添加到Socket對象的receive_queue隊列    skb_queue_tail(&sk->receive_queue, skb);    if (!sk->dead)        sk->data_ready(sk, skb->len); // 喚醒等待Socket對象就緒的進程    return 0;}

          sock_queue_rcv_skb()?通過調用?skb_queue_tail()?函數(shù)把?skb?數(shù)據(jù)包添加到?Socket?對象的?receive_queue?隊列中,并且喚醒等待?Socket?對象就緒的進程。

          當把數(shù)據(jù)包添加到?Socket?對象的?receive_queue?隊列后,UDP協(xié)議?的接收工作就此完畢。


          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大粗鸡巴久久久久 | 九九国产夫妻自拍 | 国产精品久久久久久久精 | 卡一卡二久久 | 青草免费视频 |