linux内核IOCTL网络控制框架实现分析
来源:
作者:
时间:2007-08-27
Tag:
点击:
4.4、二次跳转
闲话少说,步入正题。接下来我们看看sock->ops->ioctl函数指针调用了什么函数,首先看看 sock变量的结构类型struct socket,大家要多注意这个结构,在后面我们也列出了相关结构相互引用图中涉及到的这个结构的几个字段,以加深大家的印象.结构的源代码在include/linux/Net.h文件中:
struct socket
{
socket_state state;
unsigned long flags;
struct proto_ops *ops;
struct inode *inode;
struct fasync_struct *fasync_list; /* Asynchronous wake up list */
struct file *file; /* File back pointer for gc */
struct sock *sk;
wait_queue_head_t wait;
…
};
套接字就是通过结构中ops指针来执行具体的ioctl控制函数的。struct proto_ops定义在同样的头文件中:
struct proto_ops {
int family;
int (*release) (struct socket *sock);
int (*bind) (struct socket *sock, struct sockaddr *umyaddr, int sockaddr_len);
int (*connect) (struct socket *sock, struct sockaddr *uservaddr, int sockaddr_len, int flags);
int (*socketpair) (struct socket *sock1, struct socket *sock2);
int (*accept) (struct socket *sock, struct socket *newsock, int flags);
int (*getname) (struct socket *sock, struct sockaddr *uaddr, int *usockaddr_len, int peer);
unsigned int (*poll) (struct file *file, struct socket *sock, struct poll_table_struct *wait);
int (*ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg);
int (*listen) (struct socket *sock, int len);
int (*shutdown) (struct socket *sock, int flags);
int (*setsockopt) (struct socket *sock, int level, int optname, char *optval, int optlen);
int (*getsockopt) (struct socket *sock, int level, int optname, char *optval, int *optlen);
int (*sendmsg) (struct socket *sock, struct msghdr *m, int total_len, struct scm_cookie *scm);
int (*recvmsg) (struct socket *sock, struct msghdr *m, int total_len, int flags, struct scm_cookie *scm);
int (*mmap) (struct file *file, struct socket *sock, struct vm_area_struct * vma);
};
补充一下基础知识,一个套接字接口在逻辑上有三个要素:网域,类型和规程(协议).
网域:表明套接字接口用于哪一中网络或这说哪一族网络规程.就是我们通常说的地址族(family),常见的有AF_UNIX/AF_INET/AF_X25/AF_IPX等待.
类型:表明通讯中所遵循的模式,主要有两种模式:”有连接”和”无连接”,对应到以太网就是SOCK_STREAM和SOCK_DGRAM两种.
规程:具体的网络协议.通常,网域和类型基本就能够确定使用的规程了.
这里的proto_ops结构就是通过不同的实例来支持具体的网域的不同类型、规程所使用的通信函数,每个网域都有多种类型、多种规程,所以也有多个proto_ops实例,给这个实例赋值具体规程的处理函数,如ipv4的有连接和无连接实例所指定的控制函数都是inet_ioctl(如果处理不同也可以指向不同的控制函数),这样可以使具体的控制操作转向具体的处理,细节实现我们下一小节介绍.
构造内核时,内核会初始化网络地址族,即初始化net_families[NRPORO]全局量,这是一个静态指针数组。每个网域地址族的初始化函数都由其中一个元素来表征,例如,“INET”和它的初始程序地址分别是PF_INET(等同于AF_INET)和inet_create。当套接口启动时被初始化时,要调用每一网域初始化程序,为具体的类型指定处理函数,内核初始化网域地址族后net_families[NRPORO]变量的相关字段取值状态示意图如下:
对IPV4地址族来说,这个初始化函数就是inet_create,其代码在net/ipv4/af_inet.c中:
static int inet_create(struct socket *sock, int protocol)
{
…
switch (sock->type) {
case SOCK_STREAM:
if (protocol && protocol != IPPROTO_TCP) //类型与规程检测
goto free_and_noproto;
protocol = IPPROTO_TCP;
prot = &tcp_prot;
sock->ops = &inet_stream_ops; //此处指定函数跳转表
break;
case SOCK_SEQPACKET:
goto free_and_badtype;
case SOCK_DGRAM:
if (protocol && protocol != IPPROTO_UDP)
goto free_and_noproto;
protocol = IPPROTO_UDP;
sk->no_check = UDP_CSUM_DEFAULT;
prot=&udp_prot;
sock->ops = &inet_dgram_ops; //此处指定函数跳转表
break;
case SOCK_RAW:
if (!capable(CAP_NET_RAW)) //检验是否有创建原始套接字的权限
…
sock->ops = &inet_dgram_ops;//
if (protocol == IPPROTO_RAW)
sk->protinfo.af_inet.hdrincl = 1;
break;
default:
goto free_and_badtype;
}
…
}
从上面的代码可以看出:已注册的网域的类型所对应的操作被存在socket结构的ops 指针中,它就是指向具体的proto_ops数据结构实例,如inet_stream_ops、inet_dgram_ops等。proto_ops结构由地址族类型和一系列指向与特定地址族对应的socket操作函数的指针组成。ops 字段通过地址族标识符来索引,接下来我们看看proto_ops结构。
前面说过,具体的ioctl执行过程时通过两次跳转而来,其中第二次就是针对各个不同层次、类型的套接字。我们来看看内核中所定义的各个具体的proto_ops结构实例以分析不同的控制执行流程. 内核中为每个规程定义了一个proto_ops结构实例,常见的如下:
1、在net/ipv4/Af_inet.c文件中:
struct proto_ops inet_stream_ops = {
…
poll: tcp_poll,
ioctl: inet_ioctl,
listen: inet_listen,
…
};
struct proto_ops inet_dgram_ops = {
…
poll: datagram_poll,
ioctl: inet_ioctl,
listen: sock_no_listen,
…
};
可见这两个实例有相当多的处理函数都是一样的,并且最终调用相同的控制函数inet_ioctl.
2、在net/ipv6/Af_inet6.c文件中提供了inet6_stream_ops和inet6_dgram_ops,其地址族及ioctl处理函数分别为PF_INET6和inet6_ioctl:
struct proto_ops inet6_stream_ops = {
family: PF_INET6,
…
ioctl: inet6_ioctl, /* must change */
…
};
struct proto_ops inet6_dgram_ops = {
family: PF_INET6,
…
ioctl: inet6_ioctl, /* must change */
…
};
3、在net/packet/Af_ packet 6.c文件中提供了packet_ops_spkt和packet_ops,其地址族及ioctl处理函数分别为PF_PACKET和packet_ioctl:
struct proto_ops packet_ops = {
family: PF_PACKET,
…
ioctl: packet_ioctl,
…
};
还有x25和ipx、netlink、unix域等等地址族所对应的文件提供了各自的协议规程操作函数指针以支持不同的ioctl处理函数,大家有兴趣可以参考内核相关源码.
可见,通过二次跳转表,内核可以支持不同协议规程做不同的操作,包括控制处理。本文把重点放在ipv4的ioctl控制函数,引导大家深入到其处理源码.

由于inet_ioctl函数内容分支很多,但功能、处理不难理解,所以我把一些不常见的内容都省去,挑简单重要的说,完全在于抛砖引玉:
static int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
…
switch(cmd)
{
case FIOSETOWN://设置属主
case SIOCSPGRP://设置进程组
err = get_user(pid, (int *) arg);
if (err)
return err;
if (current->pid != pid && current->pgrp != -pid &&
!capable(CAP_NET_ADMIN))
return -EPERM;
sk->proc = pid;
return(0);
case FIOGETOWN://获取属主
case SIOCGPGRP://获取进程组
return put_user(sk->proc, (int *)arg);
case SIOCGSTAMP://
if(sk->stamp.tv_sec==0)
return -ENOENT;
err = copy_to_user((void *)arg,&sk->stamp,sizeof(struct timeval));
if (err)
err = -EFAULT;
return err;
case SIOCADDRT://增加路由
case SIOCDELRT://删除路由
case SIOCRTMSG:
return(ip_rt_ioctl(cmd,(void *) arg));//IP路由配置
case SIOCDARP://删除arp项
case SIOCGARP://获取arp项
case SIOCSARP://创建/修改arp项
return(arp_ioctl(cmd,(void *) arg));//arp配置
case SIOCGIFADDR://获取接口地址
case SIOCSIFADDR://设置接口地址
case SIOCGIFBRDADDR://获取广播地址
case SIOCSIFBRDADDR://设置广播地址
case SIOCGIFNETMASK://获取网络掩码
case SIOCSIFNETMASK://设置网络掩码
case SIOCGIFDSTADDR://获取p2p地址
case SIOCSIFDSTADDR://设置p2p地址
case SIOCSIFPFLAGS: //
case SIOCGIFPFLAGS:
case SIOCSIFFLAGS://设置接口标志
return(devinet_ioctl(cmd,(void *) arg));//网络接口相关配置,linux内核自带的ifconfig
//的很多处理都是通过这里实现的
case SIOCGIFBR:
case SIOCSIFBR://网桥设置,稍后的实例就是介绍如何截获网桥控制钩子
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE) //如果内核支持网桥功能
#ifdef CONFIG_KMOD//若支持内核模块动态加载
if (br_ioctl_hook == NULL)//网桥钩子为空则动态请求模块
request_module("bridge");//加载网桥模块
#endif
if (br_ioctl_hook != NULL)
return br_ioctl_hook(arg);//通过钩子函数处理命令参数
#endif
case SIOCGIFDIVERT://
case SIOCSIFDIVERT:
#ifdef CONFIG_NET_DIVERT
return(divert_ioctl(cmd, (struct divert_cf *) arg));
#else
return -ENOPKG;
#endif /* CONFIG_NET_DIVERT */
return -ENOPKG;
case SIOCADDDLCI://
case SIOCDELDLCI:// 数据链路连接标识控制
#ifdef CONFIG_DLCI
lock_kernel();
err = dlci_ioctl(cmd, (void *) arg);//控制函数
unlock_kernel();
return err;
#endif
#ifdef CONFIG_DLCI_MODULE
#ifdef CONFIG_KMOD
if (dlci_ioctl_hook == NULL)//如果钩子函数为空,则加载模块
request_module("dlci");
#endif
if (dlci_ioctl_hook) {//钩子函数指针不空
lock_kernel();
err = (*dlci_ioctl_hook)(cmd, (void *) arg);//调用钩子函数
unlock_kernel();
return err;
}
#endif
return -ENOPKG;
default:
…
return err;
}
/*NOTREACHED*/
return(0);
}
从上面的函数代码来看,同套接字有关的控制请求主要有如下几类:
1、文件操作
2、套接字操作
3、路由选项操作
4、接口操作
5、ARP高速缓存操作
6、网桥控制
7、数据链路连接标识控制
结合代码中的注释,读者不难理解具体的控制分支。具体的控制处理就转到具体的函数里面去处理了,例如关于内核自带的命令工具ifconfig对ip地址的配置处理,基本都在devinet_ioctl函数中;关于arp命令的处理都在arp_ioctl中处理;关于路由配置都在ip_rt_ioctl中处理。其中参数arg是用户空间传来的自定义的数据,可以是结构,可以是联合或其它一些更复杂的类型,由具体的业务模块来解释处理。在随后的实践中,我们就是通过arg的不同解释来做不同的处理。
0
最新评论共有 4 位网友发表了评论
查看所有评论
发表评论
热点关注
