`

使用sockopt与内核交换数据

阅读更多
本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn: yfydz_no1@hotmail.com
来源:http://yfydz.cublog.cn
1. 前言
打开一个网络socket后可以使用set/getsockopt(2)可实现用户空间与内核的通信,本质和ioctl差不多,区别在于set /getsockopt不用新建设备,直接利用系统已有的socket类型就可以进行,可用setsockopt函数向内核写数据,用 getsockopt向内核读数据。

本文内核代码版本为2.6.19.2。

2. 基本过程
首先在内核中要登记相关协议的set/getsockopt的选项命令字和相关的处理函数,然后在用户空间打开该协议的socket后就可以直接调用set/getsockopt来指定命令字执行相关的数据交互操作,常见的TCP、UDP的socket都用这两个系统调用来 iptables<->netfilter,ipvsadm<->ip_vs就是这么实现的。

3. set/getsockopt(2)
set/getsockopt(2)函数的基本使用格式为:
int setsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen)
第一个参数是socket描述符;第2个参数proto是sock协议,IP RAW的就用SOL_SOCKET/SOL_IP等,TCP/UDP socket的可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高层的socket是都可以使用低层socket的命令字的;第3个参数cmd是操作命令字,由自己定义;第4个参数是数据缓冲区起始位置指针,set操作时是将缓冲区数据写入内核,get的时候是将内核中的数据读入该缓冲区;第5个参数数据长度。

4. 内核实现

内核实现新的sockopt命令字有两类,一类是添加完整的新的协议后引入,一类是在原有协议命令集的基础上增加新的命令字。
sockopt命令字定义没有什么特别之处,就是一个整数,只要对这个协议内部是一个唯一的的即可,不象ioctl的命令字还有一定格式要求。

4.1 完整协议

每个协议都是用struct proto结构(include/net/sock.h)来描述的,Linux内核中缺省定义了三种:TCP、UDP和RAW,所有非TCP、UDP的都用RAW来描述。在net/core/sock.c的sock_get/setsockopt()函数中内核实现了一个所有socket共同的 sockopt读写命令集合,在各个协议的内部再单独定义各自协议的独有命令字。
struct proto中有setsockopt和getsocket成员函数,用来定义每个协议的独有相关的命令字。

如对于UDP协议的setsockopt成员函数:
static int udp_setsockopt(struct sock *sk, int level, int optname,
     char __user *optval, int optlen)
{
// 先判断是否是UDP层,不是的话调IP层的sockopt处理
 if (level != SOL_UDP)
  return ip_setsockopt(sk, level, optname, optval, optlen);
// 是UDP级别命令,调用UDP协议本身的sockopt处理
 return do_udp_setsockopt(sk, level, optname, optval, optlen);
}

static int do_udp_setsockopt(struct sock *sk, int level, int optname,
     char __user *optval, int optlen)
{
 struct udp_sock *up = udp_sk(sk);
 int val;
 int err = 0;
 if(optlen<sizeof(int))
  return -EINVAL;
 if (get_user(val, (int __user *)optval))
  return -EFAULT;
// 实际UDP独有的命令字就这两个
 switch(optname) {
 case UDP_CORK:
  if (val != 0) {
   up->corkflag = 1;
  } else {
   up->corkflag = 0;
   lock_sock(sk);
   udp_push_pending_frames(sk, up);
   release_sock(sk);
  }
  break;
// UDP封装,在IPSEC的NAT-T时使用  
 case UDP_ENCAP:
  switch (val) {
  case 0:
  case UDP_ENCAP_ESPINUDP:
  case UDP_ENCAP_ESPINUDP_NON_IKE:
   up->encap_type = val;
   break;
  default:
   err = -ENOPROTOOPT;
   break;
  }
  break;
 default:
  err = -ENOPROTOOPT;
  break;
 };
 return err;
}

所以要实现一个新协议的sockopt控制,只需要类似方法处理即可,定义好struct proto结构后将其注册到系统中即可,对于IP族内协议用inet_register_protosw()函数,其他协议族可类似处理。

4.2 命令扩充

实际使用中单独定义新协议的可能性不是很大,通常只是添加新的命令字即可,对于TCP、UDP的新命令字的添加,需要自己修改内核tcp/udp实现代码,把自己的命令字添加进去后重新编译内核才能生效。

对于IP RAW级别的命令字,netfilter提供了nf_register_sockopt()和nf_unregister_sockopt()来动态登记或取消sockopt命令字,这样可以不用修改内核原来的代码。方法是将netfilter的sockopt操作集合定义为一个链表,要定义新的opt操作就定义一个新的opt操作节点挂接到该链表中,在系统sockopt调用时,会依次查找链表中的命令字,匹配上了就可以成功调用,因此opt的命令字不能和原来IP RAW中定义相同,不过命令字是个32位的数,取值范围很大,只要稍微注意一点是不会冲突的。

netfilter的sock是RAW级别的。

sockopt操作节点结构,结构比较简单明了,就是定义各自命令字的范围空间和相关的处理函数:

/* include/linux/netfilter.h */
struct nf_sockopt_ops
{
// 链表节点
 struct list_head list;
// 协议族
 int pf;
 /* Non-inclusive ranges: use 0/0/NULL to never get called. */
// set命令的最小值
 int set_optmin;
// set命令的最大值
 int set_optmax;
// set函数实现
 int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);
 int (*compat_set)(struct sock *sk, int optval,
   void __user *user, unsigned int len);
// get命令的最小值
 int get_optmin;
// get命令的最大值
 int get_optmax;
// get函数实现
 int (*get)(struct sock *sk, int optval, void __user *user, int *len);
 int (*compat_get)(struct sock *sk, int optval,
   void __user *user, int *len);
 /* Number of users inside set() or get(). */
 unsigned int use;
 struct task_struct *cleanup_task;
};
 
opt操作结构登记和撤销函数:
/* net/netfilter/nf_sockopt.c */
// nf的sockopt的链表,所有sockopt命令处理都挂接到这个链表
static LIST_HEAD(nf_sockopts);
/* Functions to register sockopt ranges (exclusive). */
int nf_register_sockopt(struct nf_sockopt_ops *reg)
{
 struct list_head *i;
 int ret = 0;
// 加锁
 if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
  return -EINTR;
// 检查当前链表中是否已经挂接了该sockopt操作节点
 list_for_each(i, &nf_sockopts) {
  struct nf_sockopt_ops *ops = (struct nf_sockopt_ops *)i;
  if (ops->pf == reg->pf
      && (overlap(ops->set_optmin, ops->set_optmax,
    reg->set_optmin, reg->set_optmax)
   || overlap(ops->get_optmin, ops->get_optmax,
       reg->get_optmin, reg->get_optmax))) {
   NFDEBUG("nf_sock overlap: %u-%u/%u-%u v %u-%u/%u-%u\n",
    ops->set_optmin, ops->set_optmax,
    ops->get_optmin, ops->get_optmax,
    reg->set_optmin, reg->set_optmax,
    reg->get_optmin, reg->get_optmax);
   ret = -EBUSY;
   goto out;
  }
 }
// 新节点,添加到opt链表中
 list_add(&reg->list, &nf_sockopts);
out:
// 解锁
 mutex_unlock(&nf_sockopt_mutex);
 return ret;
}
EXPORT_SYMBOL(nf_register_sockopt);
void nf_unregister_sockopt(struct nf_sockopt_ops *reg)
{
 /* No point being interruptible: we're probably in cleanup_module() */
 restart:
 mutex_lock(&nf_sockopt_mutex);
 if (reg->use != 0) {
// 操作节点还在使用中,阻塞进程直到所有操作完成
  /* To be woken by nf_sockopt call... */
  /* FIXME: Stuart Young's name appears gratuitously. */
  set_current_state(TASK_UNINTERRUPTIBLE);
  reg->cleanup_task = current;
  mutex_unlock(&nf_sockopt_mutex);
  schedule();
  goto restart;
 }
// 从链表中删除
 list_del(&reg->list);
 mutex_unlock(&nf_sockopt_mutex);
}
EXPORT_SYMBOL(nf_unregister_sockopt);
 
下面来看一下具体调用流程是如何进行的,首先打开的socket是一个RAW类型的IP socket,对这类socket的setsockopt操作会调用到ip_setsockopt()函数:
/* net/ipv4/ip_sockglue.c */
int ip_setsockopt(struct sock *sk, int level,
  int optname, char __user *optval, int optlen)
{
 int err;
 if (level != SOL_IP)
  return -ENOPROTOOPT;
// 先按普通IP的sockopt操作执行
 err = do_ip_setsockopt(sk, level, optname, optval, optlen);
#ifdef CONFIG_NETFILTER
// 内核要支持netfilter
 /* we need to exclude all possible ENOPROTOOPTs except default case */
 if (err == -ENOPROTOOPT && optname != IP_HDRINCL &&
  optname != IP_IPSEC_POLICY && optname != IP_XFRM_POLICY
#ifdef CONFIG_IP_MROUTE
  && (optname < MRT_BASE || optname > (MRT_BASE + 10))
#endif
    ) {
// 如果IP中没有这个opt命令字,调用netfilter的sockopt
  lock_sock(sk);
  err = nf_setsockopt(sk, PF_INET, optname, optval, optlen);
  release_sock(sk);
 }
#endif
 return err;
}
/* net/netfilter/nf_sockopt.c */
int nf_setsockopt(struct sock *sk, int pf, int val, char __user *opt,
    int len)
{
// 实际调用nf_sockopt函数
 return nf_sockopt(sk, pf, val, opt, &len, 0);
}

static int nf_sockopt(struct sock *sk, int pf, int val,
        char __user *opt, int *len, int get)
{
 struct list_head *i;
 struct nf_sockopt_ops *ops;
 int ret;
 if (mutex_lock_interruptible(&nf_sockopt_mutex) != 0)
  return -EINTR;
// 扫描netfilter的sockopt链表
 list_for_each(i, &nf_sockopts) {
// 取出opt操作节点
  ops = (struct nf_sockopt_ops *)i;
// 根据协议,命令字范围判断是否处理该命令字
  if (ops->pf == pf) {
   if (get) {
// get操作
    if (val >= ops->get_optmin
        && val < ops->get_optmax) {
// opt结构节点使用计数加1
     ops->use++;
     mutex_unlock(&nf_sockopt_mutex);
     ret = ops->get(sk, val, opt, len);
     goto out;
    }
   } else {
// set操作
    if (val >= ops->set_optmin
        && val < ops->set_optmax) {
     ops->use++;
     mutex_unlock(&nf_sockopt_mutex);
     ret = ops->set(sk, val, opt, *len);
     goto out;
    }
   }
  }
 }
 mutex_unlock(&nf_sockopt_mutex);
 return -ENOPROTOOPT;
 
 out:
 mutex_lock(&nf_sockopt_mutex);
// 操作完成,opt结构节点使用减一
 ops->use--;
 if (ops->cleanup_task)
  wake_up_process(ops->cleanup_task);
 mutex_unlock(&nf_sockopt_mutex);
 return ret;
}

这样,自己定义的nf的opt节点就可以被遍历到,操作也就有效.
 

具体实例, ip_vs opt操作节点:
/* net/ipv4/ipvs/ip_vs_ctl.c */
static struct nf_sockopt_ops ip_vs_sockopts = {
 .pf  = PF_INET,
// 定义set命令字范围
 .set_optmin = IP_VS_BASE_CTL,
 .set_optmax = IP_VS_SO_SET_MAX+1,
 .set  = do_ip_vs_set_ctl,
// get命令字范围
 .get_optmin = IP_VS_BASE_CTL,
 .get_optmax = IP_VS_SO_GET_MAX+1,
 .get  = do_ip_vs_get_ctl,
};

set/get函数就很简单了,就进行一些合法性检查,然后根据命令字进行相关处理即可:
static int
do_ip_vs_set_ctl(struct sock *sk, int cmd, void __user *user, unsigned int len)
{
 int ret;
 unsigned char arg[MAX_ARG_LEN];
 struct ip_vs_service_user *usvc;
 struct ip_vs_service *svc;
 struct ip_vs_dest_user *udest;
// 用户权限检查
 if (!capable(CAP_NET_ADMIN))
  return -EPERM;
// 数据长度检查
 if (len != set_arglen[SET_CMDID(cmd)]) {
  IP_VS_ERR("set_ctl: len %u != %u\n",
     len, set_arglen[SET_CMDID(cmd)]);
  return -EINVAL;
 }
// 拷贝数据
 if (copy_from_user(arg, user, len) != 0)
  return -EFAULT;
 /* increase the module use count */
// ipvs模块使用计数
 ip_vs_use_count_inc();
// 加锁
 if (mutex_lock_interruptible(&__ip_vs_mutex)) {
  ret = -ERESTARTSYS;
  goto out_dec;
 }
// 以下进行具体的命令实现操作:
 if (cmd == IP_VS_SO_SET_FLUSH) {
  /* Flush the virtual service */
  ret = ip_vs_flush();
  goto out_unlock;
......
 
5. 用户空间

用户空间的操作很简单,就是用socket(2)打开相关协议类型的socket,直接调用set/getsockopt函数就可以进行操作了.
实例: ipvsadm
int ipvs_init(void)
{
 socklen_t len;
 len = sizeof(ipvs_info);
// 打开RAW类型的socket
 if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
  return -1;
// 读取ipvs基本信息
 if (getsockopt(sockfd, IPPROTO_IP, IP_VS_SO_GET_INFO,
         (char *)&ipvs_info, &len))
  return -1;
 return 0;
}
 

5. 结论
 
用setgetsockopt()在用户空间和内核空间传递数据也是常用方法之一,比较简单方便,而且可以在同一个socket中对不同的命令传送不同的数据结构。
新命令字的添加可以按新协议添加,也可以添加到现有的实现中,但没有特别需求的话,netfilter提供的动态登记opt命令字可以动态添加删除sockopt操作命令字,而且不用修改内核原有的程序。
分享到:
评论

相关推荐

    45-sockopt.rar

    网络编程从大的方面说就是对信息的发送到接收,中间传输为物理线路的作用。网络编程最主要的工作就是在发送端把信息通过规定好的协议进行组装包,在接收端按照规定好的协议把包进行解析,从而提取出对应的信息,达到...

    C#美化的文件传输控件

    int sockopt = (int)server.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout); Console.WriteLine("Default timeout: {0}", sockopt); server.SetSocketOption(SocketOptionLevel....

    zttp:zmq 套接字 http 服务器

    直通车 ... 基于想法。... v4+ 之后的任何 libzmq 都可以使用,但用于连接路由器套接字类型的 RAW sockopt 可能会被弃用 用 var z = require ( 'zttp' ) . listen ( 'tcp://127.0.0.1:3000' ) ; z . route

    检查linux系统支持的socket选项

    测试linux系统支持的socket设置选项

    c语言开发图书管理系统项目源码+数据+可运行程序

    c语言开发图书管理系统项目源码+数据+可运行程序 主要功能有:1、以管理员或读者不同身份注册账户。2、登录、找回密码、修改密码。3、管理员:图书入库,清除库存,统计书籍数量,统计读者借书情况,在馆书籍排序,读者排序。4、读者:查看个人借阅信息,借书,还书,按书名、作者、出版社检索图书,查询全部在馆图书。

    基于transformer的多模态脑肿瘤分割.zip

    本项目旨在利用Transformer模型实现多模态脑肿瘤分割。多模态脑肿瘤分割对于脑肿瘤的诊断和治疗具有重要意义。 我们采用Transformer模型,通过分析脑部MRI、CT、PET等不同模态的医学影像数据,实现对脑肿瘤区域的自动分割。项目使用的数据集包括公开的脑肿瘤影像数据集,如BRATS、Medical Segmentation Decathlon等,并进行了预处理,包括图像增强、分割和特征提取等。 在运行环境方面,我们使用Python编程语言,基于TensorFlow、PyTorch等深度学习框架进行开发。为了提高计算效率,我们还使用了GPU加速计算。此外,我们还采用了Docker容器技术,确保实验结果的可重复性。 项目完成后,将实现对多模态脑肿瘤的快速、准确分割,为脑肿瘤的诊断和治疗提供有力支持。同时,项目成果也可应用于其他医学影像分析领域。

    马尔文高浓度池说明书 High Concentration Cell (ZEN1010)

    马尔文高浓度池说明书 High Concentration Cell (ZEN1010)

    如何提高APP商业变现能力.docx

    如何提高APP商业变现能力.docx

    node-v12.20.0-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    R语言绘制SCI科研生存ROC曲线源代码.zip

    把input里面的数据替换成自己的数据,打开R,点Run,可以直接出图!文件适合有R语言基础的同学。

    高电压技术(第三版)课后习题答案

    高电压技术(第三版)课后习题答案

    Linux线程同步:深入理解与实践.pdf

    linux之线程同步一。 本文深入探讨了Linux线程同步的几种常见机制,并提供了代码示例。希望这些信息能够帮助你更好地理解和应用线程同步技术。在编写多线程程序时,请务必谨慎,确保线程安全。

    1999-2022年企业持续绿色创新水平数据.dta

    1999-2022年企业持续绿色创新水平数据.dta

    数据结构实验代码三个方法求最大公约数.rar

    数据结构实验代码

    机械设计无阀滤池-30Tsw16可编辑非常好的设计图纸100%好用.zip

    机械设计无阀滤池-30Tsw16可编辑非常好的设计图纸100%好用.zip

    机械设计RJ45安防线连接器焊锡机sw18可编辑非常好的设计图纸100%好用.zip

    机械设计RJ45安防线连接器焊锡机sw18可编辑非常好的设计图纸100%好用.zip

    SwiftUI嵌入UIKit(SwiftUI顺传值到UIKit)

    SwiftUI嵌入UIKit(SwiftUI顺传值到UIKit)

    node-v12.18.4-darwin-x64.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Python代码实现基于朴素贝叶斯算法的垃圾邮件分类(源码+全部数据)

    当处理垃圾邮件分类问题时朴素贝叶斯算法是一种经典且常用的方法。朴素贝叶斯算法基于贝叶斯定理特征条件独立性假设,能够高效地处理文本分类问题。 以下是一个使用Python实现基于朴素贝斯算法的垃圾邮件分类的示例: 1. 数据准备: 首先,需要准备训练数据集和测试数据集。训练数据集包含已标记的垃圾邮件和非垃圾邮件的文样本,通过这些样本进行模型训练。测试数据集用于评估模型的性能。 2. 数据预处理: 将文本样本转换为特征向量是朴素贝叶斯算法的关键步骤。可以使用词袋模型或者TF-IDF等方法将文本样本表示为向量。 3. 特征选择: 根据问题的具体特点,可以选择保留所有特征或者进行特征选择。常见的特征选择方法有卡方检验、互信息等。 4. 模型训练: 使用训练数据集训练朴素贝叶斯分类模型。常见的朴素贝叶斯分类器有多项式朴素贝叶斯、伯努利朴素贝叶斯和高斯朴素贝叶斯等。 5. 模型评估: 使用测试数据集评估模型的性能,常见的评估指标包括准确率、召回率、精确率和F1值等。 下面是一个简单的Python实现示例: ```python import numpy as np from

    httpsyy70958.com29875videoplayvid45958.m3u8..m3u

    httpsyy70958.com29875videoplayvid45958.m3u8..m3u

Global site tag (gtag.js) - Google Analytics