socket函數的domain、type、protocol解析


轉載

原文地址:https://blog.csdn.net/liuxingen/article/details/44995467

內核中的socket概覽

 

圖一:socket概覽

 

內核中套接字是一層一層進行抽象展示的,把共性的東西抽取出來,這樣對外提供的接口可以盡量的統一。內核中把套接字的定義會抽象出來展示,如struct sock->struct inet_sock->struct tcp_sock從抽象到具體。還會把套接字的操作也會抽象,下面我們會提到怎么進行抽象展示的。

 

Socket函數中的三個參數其實就是把抽象的socket具體化的條件,domain參數決定了圖中所示的第二層通信域,type決定了第三層的通信模式,protocol決定了第四層真正的通信協議。

 

Domain參數

 

Domain參數指定了通信的”域”(在后文中會用family替代domain),我們是在IPv4還是IPv6這個范圍內通信,也就決定了我們通信的地址是IPv4格式還是IPv6格式。通常可選的定義如下:

 

名稱

目的

AF_UNIX, AF_LOCAL

本地通信

AF_INET

IPv4網絡通信

AF_INET6

IPv6網絡通信

AF_PACKET

鏈路層通信

 

在Linux系統中AF_*和PF_*是等價的。

 

在內核源碼中net目錄下面有Af_開頭的一系列文件(如:Af_inet.c、Af_inet6.c、Af_unix.c等),每一個文件分別代表了一種協議族。

 

在Net.h中定義了一個結構體:

 

//net_proto_family結構體定義了每一個協議族的新建socket句柄

struct net_proto_family {

    int     family;

    int     (*create)(struct net *net, struct socket *sock,

                  int protocol, int kern);

    struct module   *owner;

};

//Af_inet.c中的PF_INET domain的定義

static const struct net_proto_family inet_family_ops = {

    .family = PF_INET,

    .create = inet_create,

    .owner  = THIS_MODULE,

};

 

         在協議棧初始化的通過sock_register(const struct net_proto_family *ops)(socket.c)函數把協議棧支持的協議族family加入net_families數組中。

 

         當我們通過socket系統調用創建套接字的時候流程走到__sock_create函數(SYSCALL_DEFINE3->sock_create->__sock_create)的時候根據family在net_families數組中取得對應協議族的create句柄,所以對於PF_INET協議族的套接字就是調用inet_create來新建socket。

 

         通過上面分析可知family這個參數決定了調用哪個協議族create函數來新建socket,說得可能不准確點就是決定了你使用net/目錄下面的哪個Af_*.c文件中的函數。

 

Type參數

 

Type就是socket的類型,對於AF_INET協議族而言有流套接字(SOCK_STREAM)、數據包套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)。

 

/**

 * enum sock_type - Socket types

 * @SOCK_STREAM: stream (connection) socket

 * @SOCK_DGRAM: datagram (conn.less) socket

 * @SOCK_RAW: raw socket

 * @SOCK_RDM: reliably-delivered message

 * @SOCK_SEQPACKET: sequential packet socket

 * @SOCK_DCCP: Datagram Congestion Control Protocol socket

 * @SOCK_PACKET: linux specific way of getting packets at the dev level.

 *        For writing rarp and other similar things on the user level.

*/

enum sock_type {

    SOCK_STREAM = 1,

    SOCK_DGRAM  = 2,

    SOCK_RAW    = 3,

    SOCK_RDM    = 4,

    SOCK_SEQPACKET  = 5,

    SOCK_DCCP   = 6,

    SOCK_PACKET = 10,

};

 

在內核協議棧中有一個很重要的結構體,定義了每一個協議族中套接字在傳輸層的操作集合。PS:這里我理解傳輸層包括了套接字類型(STREAM,DGRAM)和具體的傳輸層協議(TCP,UDP)

 

/* This is used to register socket interfaces for IP protocols.  */

struct inet_protosw {

    struct list_head list;

 

        /* These two fields form the lookup key.  */

    unsigned short   type;     /* This is the 2nd argument to socket(2). */

    unsigned short   protocol; /* This is the L4 protocol number.  */

 

    struct proto     *prot;

    const struct proto_ops *ops;

 

    unsigned char    flags;      /* See INET_PROTOSW_* below.  */

};

 

struct proto_ops結構體定義了每一種套接字類型 (SOCK_STREAM、SOCK_DGRAM、SOCK_RAW)的操作集合,在Af_inet.c中分別定義了inet_stream_ops、inet_dgram_ops、inet_sockraw_ops這三種類型的proto_ops:

 

const struct proto_ops inet_stream_ops = {

    .family        = PF_INET,

    .owner         = THIS_MODULE,

    .release       = inet_release,

    .bind          = inet_bind,

    .connect       = inet_stream_connect,

    .socketpair    = sock_no_socketpair,

    .accept        = inet_accept,

    .getname       = inet_getname,

    .poll          = tcp_poll,

    .ioctl         = inet_ioctl,

    .listen        = inet_listen,

    .shutdown      = inet_shutdown,

    .setsockopt    = sock_common_setsockopt,

    .getsockopt    = sock_common_getsockopt,

    .sendmsg       = inet_sendmsg,

    .recvmsg       = inet_recvmsg,

    .mmap          = sock_no_mmap,

    .sendpage      = inet_sendpage,

    .splice_read       = tcp_splice_read,

#ifdef CONFIG_COMPAT

    .compat_setsockopt = compat_sock_common_setsockopt,

    .compat_getsockopt = compat_sock_common_getsockopt,

    .compat_ioctl      = inet_compat_ioctl,

#endif

};

 

我們新建socket的時候指定了type為SOCK_STREAM那么在后面的套接字操作中(如connect)會調用對應的inet_stream_ops中對應的函數(如inet_stream_connect)。

 

 

 

圖2:inetsw、inet_protosw、proto_ops、proto的關系圖

 

Protocol參數

 

上文中我們通過family和type已經基本確定了新建的socket具體是什么類型的套接字,最后一步通過protocol來確定socket到底支持的哪個協議(TCP?UDP?)。

 

在inet_protosw結構體中我們已經解釋過proto_ops對應的是每一種套接字類型的操作集合,那么可知struct proto對應的就是具體協議的操作集合。在Tcp_ipv4.c中定義TCP協議的struct proto tcp_prot:

 

struct proto tcp_prot = {

    .name           = "TCP",

    .owner          = THIS_MODULE,

    .close          = tcp_close,

    .connect        = tcp_v4_connect,

    .disconnect     = tcp_disconnect,

    .accept         = inet_csk_accept,

    .ioctl          = tcp_ioctl,

    .init           = tcp_v4_init_sock,

    .destroy        = tcp_v4_destroy_sock,

    .shutdown       = tcp_shutdown,

    .setsockopt     = tcp_setsockopt,

    .getsockopt     = tcp_getsockopt,

    .recvmsg        = tcp_recvmsg,

    .sendmsg        = tcp_sendmsg,

    .sendpage       = tcp_sendpage,

    .backlog_rcv        = tcp_v4_do_rcv,

    .release_cb     = tcp_release_cb,

    .hash           = inet_hash,

    .unhash         = inet_unhash,

    .get_port       = inet_csk_get_port,

    .enter_memory_pressure  = tcp_enter_memory_pressure,

    .stream_memory_free = tcp_stream_memory_free,

    .sockets_allocated  = &tcp_sockets_allocated,

    .orphan_count       = &tcp_orphan_count,

    .memory_allocated   = &tcp_memory_allocated,

    .memory_pressure    = &tcp_memory_pressure,

    .sysctl_mem     = sysctl_tcp_mem,

    .sysctl_wmem        = sysctl_tcp_wmem,

    .sysctl_rmem        = sysctl_tcp_rmem,

    .max_header     = MAX_TCP_HEADER,

    .obj_size       = sizeof(struct tcp_sock),

    .slab_flags     = SLAB_DESTROY_BY_RCU,

    .twsk_prot      = &tcp_timewait_sock_ops,

    .rsk_prot       = &tcp_request_sock_ops,

    .h.hashinfo     = &tcp_hashinfo,

    .no_autobind        = true,

#ifdef CONFIG_COMPAT

    .compat_setsockopt  = compat_tcp_setsockopt,

    .compat_getsockopt  = compat_tcp_getsockopt,

#endif

#ifdef CONFIG_MEMCG_KMEM

    .init_cgroup        = tcp_init_cgroup,

    .destroy_cgroup     = tcp_destroy_cgroup,

    .proto_cgroup       = tcp_proto_cgroup,

#endif

};

 

所以對於TCP socket當執行connect連接的時候經過的流程大致是SYSCALL_DEFINE3 connect(系統調用)->inet_stream_connect(inet_stream_ops中定義)-> tcp_v4_connect(tcp_prot中定義)。

 

Family&type&protocol結合

 

上面分別說明了family、type、protocol這三個參數代表的意義,接下來我們把這三個參數結合起來一起看一下最后的效果。

 

struct inet_protosw結構體在內核中是如何初始化的呢?

 

/* Upon startup we insert all the elements in inetsw_array[] into

 * the linked list inetsw.

 */

static struct inet_protosw inetsw_array[] =

{

    {

        .type =       SOCK_STREAM,

        .protocol =   IPPROTO_TCP,

        .prot =       &tcp_prot,

        .ops =        &inet_stream_ops,

        .flags =      INET_PROTOSW_PERMANENT |

                  INET_PROTOSW_ICSK,

    },

 

    {

        .type =       SOCK_DGRAM,

        .protocol =   IPPROTO_UDP,

        .prot =       &udp_prot,

        .ops =        &inet_dgram_ops,

        .flags =      INET_PROTOSW_PERMANENT,

       },

 

       {

        .type =       SOCK_DGRAM,

        .protocol =   IPPROTO_ICMP,

        .prot =       &ping_prot,

        .ops =        &inet_dgram_ops,

        .flags =      INET_PROTOSW_REUSE,

       },

 

       {

           .type =       SOCK_RAW,

           .protocol =   IPPROTO_IP,    /* wild card */

           .prot =       &raw_prot,

           .ops =        &inet_sockraw_ops,

           .flags =      INET_PROTOSW_REUSE,

       }

};

 

在Af_inet.c中定義了PF_INET協議族的四個初始化的inet_protosw結構體,在內核協議棧初始化的時候通過inet_register_protosw函數將這些結構體按照類型(type)hash到全局的inetsw數組中。

 

 

 

 

圖3:socket調用流程

 

上面是socket系統調用的一個主要的流程圖,左半部分在前文中已經提到過了。當流程走到inet_create函數的時候根據type去inetsw數組中找到對應類型套接字的inet_protosw結構體,我們前面提到協議棧中已經定義了PF_INET協議族支持的inet_protosw結構體,總共有4個。

 

找到inet_protosw結構體以后還需要進一步判斷protocol和inet_protosw中定義的protocol是否是一致的。內核中定義支持的protocol有一個特殊的值IPPROTO_IP(IPPROTO_IP為0),可以理解為一個通配符也可以理解為一個默認值,就是說我不指定protocol,由內核自己決定使用哪一個protocol。

 

那么內核根據什么來選擇protocol呢?就是根據內核定義的全局inetsw中對應類型的inet_protosw中的protocol。

 

說起來可能比較拗口,直接看一下代碼就很清楚了。

 

    list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {

 

        err = 0;

        /* Check the non-wild match. */

        if (protocol == answer->protocol) {

            if (protocol != IPPROTO_IP)

                break;

        } else {

            /* Check for the two wild cases. */

            if (IPPROTO_IP == protocol) {

                protocol = answer->protocol;

                break;

            }

            if (IPPROTO_IP == answer->protocol)

                break;

        }

        err = -EPROTONOSUPPORT;

    }

 

所以如果我們在新建套接字的時候使用socket(PF_INET,SOCK_STREAM,0),那么內核就會默認給你把protocol修正為IPPROTO_TCP。

 

好吧,其實整篇文章我就是想搞清楚這最后的一句話。
---------------------
作者:NoneSec
來源:CSDN
原文:https://blog.csdn.net/liuxingen/article/details/44995467
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM