Chinaunix首页 | 论坛 | 博客
  • 博客访问: 189688
  • 博文数量: 38
  • 博客积分: 1576
  • 博客等级: 上尉
  • 技术积分: 516
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-24 23:06
文章分类
文章存档

2017年(1)

2013年(1)

2011年(23)

2010年(4)

2009年(9)

我的朋友

分类:

2011-04-22 11:43:42

以下内容来自
关于unix网络编程的一个不错的blog,作者应该是搞协议栈,对于写应用程序的人来说,看这些东西有利于分析问题;推荐看看.
 
套接字选项(一)
2006年08月11日 星期五 下午 02:24   
套接字选项这个话题在socket编程里,可能已经属于中高级话题了,之所以在一开始就把这个话题提上来讲,是因为我们的一个近阶段目标是能够把 MY_PF_INET域的RAW协议走通
,并在上面跑起一个ping程序,所以,按照ping程序的要求,接下来,我们必须实现套接字选项系统调用 setsockopt在MY_PF_INET中RAW协议中的相关实现。
    下面是该系统调用函数的原型:
    #include
    int setsockopt( int socket, int level, int option_name,
                        const void *option_value, size_t option_len);
    第一个参数socket是套接字描述符。第二个参数level是被设置的选项的级别,如果想要在套接字级别上设置选项,就必须把level设置为 SOL_SOCKET。option_name指定准备
设置的选项,option_name可以有哪些取值,这取决于level,以linux 2.6内核为例(在不同的平台上,这种关系可能会有不同),在套接字级别上(SOL_SOCKET),option_name可
以有以下取值:
    SO_DEBUG,打开或关闭调试信息。
    当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置SOCK_DBG(第10)位,或清SOCK_DBG位。
    SO_REUSEADDR,打开或关闭地址复用功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为1或0。
    SO_DONTROUTE,打开或关闭路由查找功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
    SO_BROADCAST,允许或禁止发送广播数据。
    当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
    SO_SNDBUF,设置发送缓冲区的大小。
    发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防
止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。
    SO_RCVBUF,设置接收缓冲区的大小。
    接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。
 

套接字选项(二)
2006年08月11日 星期五 下午 02:24    (接上文)

    SO_KEEPALIVE,套接字保活。
    如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时器,否则关闭保活定时器。对于所有协议,该操
作都会根据option_value置或清sock->sk->sk_flag中的 SOCK_KEEPOPEN位。
    SO_OOBINLINE,紧急数据放入普通数据流。
    该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
    SO_NO_CHECK,打开或关闭校验和。
    该操作根据option_value的值,设置sock->sk->sk_no_check。
    SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。
    这个值在0到6之间(包括0和6),由option_value指定。赋给sock->sk->sk_priority。
    SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。
    该选项的参数(option_value)是一个linger结构:
        struct linger {
            int   l_onoff;    /* 延时状态(打开/关闭) */
            int   l_linger;   /* 延时多长时间 */
        };
如果linger.l_onoff值为0(关闭),则清sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为linger.l_linger。
    SO_PASSCRED,允许或禁止SCM_CREDENTIALS 控制消息的接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。
    SO_TIMESTAMP,打开或关闭数据报中的时间戳接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打开,则还需设sock->sk->sk_flag中的SOCK_TIMESTAMP位,同时,将全局变量
netstamp_needed加1。
    SO_RCVLOWAT,设置接收数据前的缓冲区内的最小字节数。
    在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为1。
    SO_RCVTIMEO,设置接收超时时间。
    该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。
    SO_SNDTIMEO,设置发送超时时间。
    该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。
    SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。
    该选项最终将设备赋给sock->sk->sk_bound_dev_if。
    SO_ATTACH_FILTER和SO_DETACH_FILTER。
    关于数据包过滤,它们最终会影响sk->sk_filter。
    以上所介绍的都是在SOL_SOCKET层的一些套接字选项,如果超出这个范围,给出一些不在这一level的选项作为参数,最终会得到- ENOPROTOOPT的返回值。但以上的分析仅限
于这些选项对sock-sk的值的影响,这些选项真正如何发挥作用,我们的探索道路将漫漫其修远。
 

套接字选项(三)
2006年08月11日 星期五 下午 02:25   
如果不在套接字级别上设置选项,即setsockopt系统调用的参数level不设为SOL_SOCKET,那么sys_setsockopt的实现会直接调用sock->ops->setsockopt。对MY_PF_INET域的RAW协
议来讲,sock->ops = myinet_sockraw_ops,而myinet_sockraw_ops.setsockopt = sock_common_setsockopt。
    而sock_common_setsockopt直接调用sock->sk->sk_prot->setsockopt。对于RAW协议来讲,即myraw_setsockopt。
    下面关注myraw_setsockopt的实现。对于RAW协议来讲,level还可以有两种取值:SOL_IP和SOL_RAW。 myraw_setsockopt首先检查level是否为SOL_IP,如果是,调用
myip_setsockopt函数,该函数实现IP级别上的选项,否则,为SOL_RAW级别上的选项,SOL_RAW级别上只有一个选项,即ICMP_FILTER,在MY_IPPROTO_ICMP协议下有效。它激活绑定
到MY_IPPROTO_ICMP协议的一个用于myraw socket特殊的过滤器。该值对每种ICMP消息都有一个位(掩码),可以把那种ICMP消息过滤掉,缺省时是不过滤ICMP消息。
    对于ICMP_FILTER选项,myraw_setsockopt调用myraw_seticmpfilter函数,它把option_value赋给 sock->sk->filter,option_value是一个结构体:
                struct icmp_filter {
                    __u32       data;
                };
它是一个32位的位掩码。
    关于该位掩码,我们目前知道的是最低位为回显应答的位掩码,由于目前我们的MY_PF_INET域代码还没完善,我们在PF_INET域上进行测试,把下面的代码添加到一个ping程序
中,ping程序就收不到来自服务器的回应包了:
    #include
    #include
    #include
    #include
    #include
    int main()    
    {
        struct icmp_filter filter;        
        socklen_t size = sizeof( struct icmp_filter );
        int fd = socket( PF_INET, SOCK_RAW, IPPROTO_ICMP );
        if( fd < 0 )
            perror("error: ");
        getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
        printf("the filter: %x\n", filter.data );
        filter.data = 1;
        int err = setsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, sizeof(struct icmp_filter) );
        if( err < 0 )
            perror("error: ");
        memset( &filter, 0, sizeof( struct icmp_filter ) );
        getsockopt( fd, SOL_RAW, ICMP_FILTER, &filter, &size );
        printf("new filter: %x\n", filter.data);
        close(fd);
        return 0;
    }
 

套接字选项(四)
2006年08月11日 星期五 下午 02:26   

继续讲关于myraw_setsockopt的实现,如果level是SOL_IP,则调用myip_setsockopt函数。 myip_setsockopt的操作对像是struct socket sock的成员struct sock sk。并把sk强
制转化为struct inet_sock: inet = inet_sk(sk)。
    如果option_name在MRT_BASE和MRT_BASE+10之间,则调用myip_mroute_setsockopt函数,关于mroute,后面再给出分析。
    IP_OPTIONS:设置将由该套接字发送的每个包的IP选项。
    其option_value是一个结构体struct ip_options。该选项首先分配一个这样的结构体,然后用这个结构体替代inet->opt指向的结构体。如果协议类型是 SOCK_STREAM的话,
从struct tcp_sock *tp中,tp->ext_header_len减去旧的inet->opt->optlen, 再加上新的opt->optlen。最后调用tcp_sync_mss进行同步,有关TCP的一些细节,我们在实现TCP协
议时再分析。
    IP_PKTINFO:传递一条包含pktinfo结构(该结构提供一些来访包的相关信息)的IP_PKTINFO辅助信息。
    这个选项只对数据报类的套接字有效。
    struct in_pktinfo
    {
        unsigned int ipi_ifindex; /* 接口索引 */
        struct in_addr ipi_spec_dst; /* 路由目的地址 */
        struct in_addr ipi_addr; /* 头标识目的地址 */
    };
ipi_ifindex指的是接收包的接口的唯一索引。ipi_spec_dst指的是路由表记录中的目的地址,而ipi_addr 指的是包头中的目的地址。如果给 sendmsg传递了IP_PKTINFO,那么外
发的包会通过在ipi_ifindex中指定的接口发送出去,同时把ipi_spec_dst设置为目的地址。
    myip_setsockopt的代码实现中只是根据option_value是否为0,置或清inet->cmsg_flags的IP_CMSG_PKTINFO位。
    IP_RECVTTL:
    该选项根据option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_TTL位,具体用途,留待日后分析。
    IP_RECVTOS:
    如果打开了这个选项,则IP_TOS辅助信息会与来访包一起传递。它包含一个字节用来指定包头中的服务/优先>级字段的类型。该字节为一个布尔整型标识。该选项根据
option_value的值是否为0,置或清inet->cmsg_flags的IP_CMSG_TOS位。
    IP_RECVOPTS:
    用一条IP_OPTIONS控制信息传递所有来访的IP选项给用户。路由头标识和其它选项已经为本地主机填好.此选项不支持SOCK_STREAM套接字。该选项根据option_value的值是否
为0,置或清inet->cmsg_flags的IP_CMSG_RECVOPTS位。
    IP_RETOPTS:
    等同于IP_RECVOPTS但是返回的是带有时间戳的未处理的原始选项和在这段路由中未填入的路由记录项目。该>选项根据 option_value的值是否为0,置或清inet->cmsg_flags
的IP_CMSG_RETOPTS位。
    IP_TOS:
    设置源于该套接字的每个IP包的Type-Of-Service(TOS 服务类型)字段。它被用来在网络上区分包的优先级>。TOS是单字节的字段。定义了一些的标准TOS标识:
IPTOS_LOWDELAY用来为交互式通信最小化延迟时间,IPTOS_THROUGHPUT用来优化吞吐量,IPTOS_RELIABILITY用来作可靠性优化, IPTOS_MINCOST应该被用作“填充数据”,对于这
些数据,低速传输是无关紧要的。至多只能声明这些 TOS 值中的一个,其它的都是无效的,应当被清除。缺省时,Linux首先发送IPTOS_LOWDELAY数据报,但是确切的做法要看配置
的排队规则而定。一些高优先级的层次可能会要求一个有效的用户标识0或者CAP_NET_ADMIN能力。优先级也可以以于协议无关的方式通过( SOL_SOCKET, SO_PRIORITY )套接字选项
来设置。
    该选项的操作置inet->tos = val,sk->sk_priority = rt_tos2priority(val),同时,清sk->sk_dst_cache。
    IP_TTL:设置从此套接字发出的包的当前生存时间字段。
    该选项置inet->uc_ttl = option_value。
    IP_HDRINCL:
    该选项只对SOCK_RAW有效,如果提供的话,用户可在用户数据前面提供一个ip头。该选项的操作根据option_value是否为零,置inet->hdrincl为1或0。
    IP_MTU_DISCOVER:
    为套接字设置Path MTU Discovery setting(路径MTU发现设置)。该选项的操作置inet->pmtudisc = option_value,option_value只允许取值0,1,2。
    IP_SOL层上余下的选项还有:
    IP_RECVERR,IP_MULTICAST_TTL,IP_MULTICAST_LOOP,IP_MULTICAST_IF, IP_ADD_MEMBERSHIP,IP_DROP_MEMBERSHIP,IP_MSFILTER,IP_BLOCK_SOURCE,
IP_UNBLOCK_SOURCE,IP_ADD_SOURCE_MEMBERSHIP,IP_DROP_SOURCE_MEMBERSHIP, MCAST_JOIN_GROUP,MCAST_LEAVE_GROUP,MCAST_JOIN_SOURCE_GROUP,
MCAST_LEAVE_SOURCE_GROUP,MCAST_BLOCK_SOURCE,MCAST_UNBLOCK_SOURCE, MCAST_MSFILTER,IP_ROUTER_ALERT,IP_FREEBIND,IP_IPSEC_POLICY, IP_XFRM_POLICY。
    在涉及到相关内容时,再进行一一分析。
 

向套接字写数据2006年08月11日 星期五 下午 02:26   套接字写有多个实现接口,我们只以其中一个接口write为线索,对套接字写(网络数据发送)的流程进行分析。系统调用
write会调用内核函数sys_write,sys_write调用vfs_write完成实际的写操作。
    vfs_write会先调用file->f_op->write(file从套接字描述符获得)。如果file->f_op-> write不存在,则调用do_sync_write。该函数会调用sock_aio_write,
sock_aio_write又会调用 __sock_sendmsg,然后到myinet_sendmsg,最后才到sk->sk_prot->sendmsg,对于RAW协议来讲,即myraw_sendmsg。
    sock_aio_write的函数原型如下:
        static ssize_t sock_aio_write(struct kiocb *iocb, const char __user *ubuf,
                      size_t size, loff_t pos)
ubuf是用户待发送数据,size是数据长度,pos是文件位置(永远为零)。在这个函数里,会把用户待发送数据封装成一个struct msghdr结构:
        struct msghdr {
            void    *   msg_name;   /* Socket name          */
            int     msg_namelen;    /* Length of name       */
            struct iovec *  msg_iov;    /* Data blocks          */
            __kernel_size_t msg_iovlen; /* Number of blocks     */
            void    *   msg_control;    /* Per protocol magic (eg BSD file descriptor passing) */
            __kernel_size_t msg_controllen; /* Length of cmsg list */
            unsigned    msg_flags;
        };
    如果用户代码为: write(fd, "abcdef", 6 ),则在sock_aio_write中封装成的msghdr结构为:
        struct msghdr thehdr{
            .msg_name = NULL,
            .msg_namelen = 0,
            .msg_iov.iov_base = "abcdef",
            .msg_iov.iov_len = 6,
            .msg_iovlen = 1,
            .msg_control = NULL,
            .msg_controllen = 0,
            .msg_flags = 0
        };
    myraw_sendmsg的函数原型为:
        static int myraw_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
                                    size_t len)
所以,它拿到的是已经封装好的消息msg。该函数所做的第一件事情是检查len,其最大长度是16位(0xffff),然后,确认msg->msg_flags中没有MSG_OOB(RAW不支持带外数据的发送
)。
    如果msg->msg_namelen不等于零,则name中存储的是域和目的地址的信息,如果等于零,则当前必须是已经建立了TCP连接的,否则数据不知道发往哪儿。
    接下来,查看控制数据缓冲区长度是否为零,如果不是,则有控制信息msg->msg_control,调用ip_cmsg_send发送控制信息(实际上,主要是填充一个结构体struct
ipcm_cookie ipc,从代码来看,该结构应该用于构建ip头)。
    inet->hdrincl表示需要自己来构建ip头,所以如果inet->hdrincl==1,并且,ipc->opt!=NULL则返回出错信息:无效参数。
    接下来判断目的地址是否为组播地址(组播地址的最高四位为1110),是则作相应处理。
    接下来,声明并初始化一个struct flowi结构,如果不是自己构建ip头,则调用raw_probe_proto_opt
    接下来的内容,暂时未能很好理解,留待下文分析。
 

操作网络设备的几个命令2006年08月11日 星期五 下午 02:37  一般,用户在shell中使用ifconfig命令对网络接口进行参数配置,及接口的打开,关闭等操作。ifconfig实现网络
接口配置的原理在于代表网络接口的结构体struct net_device的成员ip_ptr。前文已经讲过,ip_ptr实际指向的是一个结构体struct in_device,in_device有一个成员struct
in_ifaddr *ifa_list,它指向一个链表,链表的每一项代表一个IP地址。对这个链表操作即可实现对网络接口的配置。
    网络接口的操作命令按功能可以分为两组,第一组为查询命令:SIOCGIFADDR,SIOCGIFBRDADDR,SIOCGIFDSTADDR, SIOCGIFNETMASK。分别用于查询网络接口的IP地址,广播
地址,目的地址,子网掩码。第二组为设置命令:SIOCSIFADDR, SIOCSIFFLAGS,SIOCSIFBRDADDR,SIOCSIFNETMASK,SIOCSIFDSTADDR。分别用于设置网络接口的IP地址,标志位
,广播地址,子网掩码,目的地址。这些命令所要查询和设置的信息全部在结构体struct in_ifaddr中。
    用户空间的应用程序通过系统调用ioctl使用这些命令,ioctl的函数原型如下:
        #include
        int ioctl(int d, int request, ...);
    以上九个命令使用的参数为同一类型,即struct ifreq,其定义可在include/linux/if.h中找到。
    下面是两组命令在my_inet模块中的使用示例,因为整个my_inet模块代码还很不完善,通过my_inet模块新添加的IP地址并不能正常使用。
#include
#include
#include
#include
#include "my_inet.h"
#include
#include
#include
int main()
{
    struct ifreq req;
    strncpy( req.ifr_name, "eth0", IFNAMSIZ );
    struct sockaddr_in *sin;
    int fd = socket( MY_PF_INET, SOCK_RAW, MY_IPPROTO_ICMP );
    if( fd < 0 ){
        perror("error: ");
        return -1;
    }
    sin = (struct sockaddr_in *)&req.ifr_addr;
    if( ioctl( fd, SIOCGIFADDR, &req) == 0 )
        printf("%s\n", inet_ntoa(sin->sin_addr.s_addr) );
    else
        perror("ioctl error: ");
    sin = (struct sockaddr_in *)&req.ifr_broadaddr;
    if( ioctl( fd, SIOCGIFBRDADDR, &req) == 0 )
        printf("%s\n", inet_ntoa(sin->sin_addr.s_addr) );
    else
        perror("ioctl error: ");
    sin = (struct sockaddr_in *)&req.ifr_dstaddr;
    if( ioctl( fd, SIOCGIFDSTADDR, &req) == 0 )
        printf("%s\n", inet_ntoa(sin->sin_addr.s_addr) );
    else
        perror("ioctl error: ");
    sin = (struct sockaddr_in *)&req.ifr_netmask;
    if( ioctl( fd, SIOCGIFNETMASK, &req) == 0 )
        printf("%s\n", inet_ntoa(sin->sin_addr.s_addr) );
    else
        perror("ioctl error: ");
    close( fd );
    return 0;
}
#include
#include
#include
#include
#include "my_inet.h"
#include
#include
#include
int main()
{
    struct ifreq req;
    strncpy( req.ifr_name, "eth0:0", IFNAMSIZ );
    struct sockaddr_in *sin;
    int fd = socket( MY_PF_INET, SOCK_RAW, MY_IPPROTO_ICMP );
    if( fd < 0 ){
        perror("error: ");
        return -1;
    }
    sin = (struct sockaddr_in *)&req.ifr_addr;
    sin->sin_family = MY_PF_INET;
    if( inet_aton("172.16.48.10", &sin->sin_addr) == 0 ){
        perror("inet_aton error: ");
        return -1;
    }
    if( ioctl( fd, SIOCSIFADDR, &req ) == 0 ){
        printf("success!\n");
    }else{
        perror("ioctl failed: ");
    }
    req.ifr_flags = IFF_UP;
    if( ioctl( fd, SIOCSIFFLAGS, &req ) == 0 ){
        printf("success!\n");
    }else{
        perror("ioctl failed: ");
    }
    sin->sin_family = MY_PF_INET;
    if( inet_aton("172.16.48.255", &sin->sin_addr) == 0 ){
        perror("inet_aton error: ");
        return -1;
    }
    if( ioctl( fd, SIOCSIFBRDADDR, &req ) == 0 ){
        printf("success!\n");
    }else{
        perror("ioctl failed: ");
    }
    sin->sin_family = MY_PF_INET;
    if( inet_aton("255.255.255.0", &sin->sin_addr) == 0 ){
        perror("inet_aton error: ");
        return -1;
    }
    if( ioctl( fd, SIOCSIFNETMASK, &req ) == 0 ){
        printf("success!\n");
    }else{
        perror("ioctl failed: ");
    }
    close( fd );
    return 0;
}
    要想让上述的代码发挥实际的作用,只要把所有的MY_PF_INET改成PF_INET即可。功能再作增强,就是一个ifconfig程序了。
 

加入对raw socket(原始套接字)类型的支持2006年11月24日 星期五 下午 07:04    变量inetsw_array是inet域的一个全局数组,其类型是struct inet_protosw,该结构体的定义
如下:
    struct inet_protosw {
        struct list_head    list;
        unsigned short      type;
        int                 protocol;
        struct proto        *prot;
        const struct proto_ops *ops;
        int              capability;
        char             no_check;
        unsigned char    flags;
    };
    type是指套接字的类型,也就是系统调用socket的第二个参数,inet域支持的套接字类型有SOCK_STREAM(流套接字),它是一个有序的,可靠的,基于连接的双向字节流;
SOCK_DGRAM(数据报套接字),该类型的套接字是不可靠的,无连接的,有可能乱序的;SOCK_RAW(原始套接字),所谓原始,是因为该类型的套接字不提供传输层的服务,协议栈只
为该类型的套接字自动添加网络层首部,常用于网络层的附属协议(icmp, igmp等)。
    protocol是一个传输层协议号,传输层的协议包括IPPROTO_TCP,IPPROTO_UDP;或者对于SOCK_RAW来讲,它是一个通配协议号IPPROTO_IP,用于通配网络层的附属协议icmp,
igmp等。对于传输层协议来讲,IPPROTO_TCP对应的套接字类型总是SOCK_STREAM,IPPRTO_UDP对应的套接字类型总是STREAM_DGRAM,所以在socket系统调用时,可以不必指定协议
号,而直接使用通配符IPPROTO_IP。
    prot是一个传输层协议绑定的操作集,比如对于IPPROTO_TCP,它就是tcp_prot,对于IPPROTO_UDP,它就是udp_prot。而对于类型为SOCK_RAW的套接字,它没有相应的传输层
协议,而是用于通配所有的网络层附属协议,所以,prot就是所有网络层附属协议共用的一个操作集raw_prot。
    ops是套接字类型绑定的操作集,对应于SOCK_STREAM, SOCK_DGRAM, SOCK_RAW,操作集分别为inet_stream_ops,inet_dgram_ops,inet_sockraw_ops。
    capability是操作这类别套接字所需要的权限,除了原始套接字需要CAP_NET_RAW权限之外,其它两类套接字不需要特殊权限(-1)。
    flags的可能取值如下:
    #define INET_PROTOSW_REUSE 0x01      /* Are ports automatically reusable? */
    #define INET_PROTOSW_PERMANENT 0x02  /* Permanent protocols are unremovable. */
    #define INET_PROTOSW_ICSK      0x04  /* Is this an inet_connection_sock? */
    inetsw是一个链表数组,每一项都是一个struct inet_protosw结构体的链表,总共有SOCK_MAX项,在inet_init函数对INET域进行初始化的时候,调用函数
inet_register_protosw把数组inetsw_array中定义的套接字类型全部注册到inetsw数组中,相同套接字类型,不同协议类型的在数组的同一项,以套接字类型为索引,在系统实际
使用的时候,只使用inetsw,而不使用inetsw_array,目前inet域不存在相同套接字类型的多个协议(原始套接字使用通配符,所以也不存在这个问题)。
    使用系统调用socket创建一个RAW类型的套接字,并且网络层附属协议为icmp的时候,首先会在函数__sock_create中创建一个传输层的struct socket,如下:
    struct socket {
        socket_state            state;
        unsigned long           flags;
        const struct proto_ops  *ops;
        struct fasync_struct    *fasync_list;
        struct file             *file;
        struct sock             *sk;
        wait_queue_head_t       wait;
        short                   type = SOCK_RAW;
    };
    这里需要补充一下关于协议域的问题,Linux内核支持多个协议域,除了当前正关心的INET域之外,还有UNIXF域,IPX协议等等,一个域包含一个协议族。完成一个相对独立的
完整的网络通讯能力。每个域都有一个域号,比如INET域的域号为AF_INET(2),所有的已注册域号都包含在一个全局数组net_families中,net_families是类型为struct
net_proto_family的数组,该结构体的定义如下:
    struct net_proto_family{
        int         family;
        int         (*create)(struct socket *sock, int protocol);
        short       authentication;
        short       encryption;
        short       encrypt_net;
        struct module   *owner;
    };
    INET域就在net_families数组的第三项(下标为2),其结构体定义如下:
    static struct net_proto_family inet_family_ops = {
        .family = PF_INET,
        .create = inet_create,
        .owner  = THIS_MODULE,
    };
    通过函数sock_register注册到数组中,因为socket系统调用的第一个参数指定了协议域号,所以__sock_create可以通过net_families[2]->create调用到INET域的创建函数,
完成进一步的socket创建工作。
阅读(4228) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~