可任意操作nf_conntrack的nf_sockopt_ops

内核与用户态通信的接口简直太多了,有时候如果非要将它们分个三六九等也是不合适的,比如臭名昭著的ioctl,一旦臭起来就抽到底了,没人说它得好。有时候它并非想象中的那么坏,绝大多数是因为人们误用了它们,然后哪位大师说了一句它不好,从此以后人们就随大师而去了...对于ioctl,对应到socket类型文件描述符上,就是get/setsockopt两个接口函数,其实我不明白从函数名称上区分操作类型和从命令类型上区分有什么不同,一个对于UNIX文件描述符统一的ioctl为何会在socket上衍生出两个函数,这实在是不应该,比如我在一个setsockopt上面调用一个GETxxx命令,会怎样?当然会出错,但我不认为这样回答就等于理解了全部,这个API根本就不应该这么设计。当然我不是什么大师,也没人会跟随我的思想。
       我说ioctl不好开始也是听说的,但是后来发现,这个系统调用层次设计得太乱,基本思想就是“我试着去处理,如果我处理不了的话则往下层传递”,最终谁也不能保证一个命令会不会被处理,关键是谁也不对此负责,唯一可以识别的就是一个返回值。
       我突然发现已经厌倦了长篇大论,也不再对牛弹琴,更不想和中毒太深的人之间存在或者引发激烈的冲突或者简单的误会,可能更多的是我发现时间太宝贵了,而写纯粹的自我观点最花费时间,这些东西即便不写出来也是完全属于我自己,因此此处略去两页纸。
       socket option中,有一个我比较中意的,那就是nf_sockopt_ops。可能是最近一直在玩conntrack,所以什么东西都尽量以conntrack为核心,那么我希望除了Netfilter灵活的框架给与conntrack硕大的舞台之外,还要有丰富的交互接口,这样才够筋道。Netfilter的灵活我不再赘述,因为我已经写了不少了,我会建立一个群,随时可以讨论。熟练掌握Netfilter的你应该可以将skb从任何地方拽到任何地方,完全处于你的股掌之中。至于用户的交互接口,已有的conntrack,conntrackd,以及procfs/net中的nf_conntrack文件...这些如果满足不了要求,那么就需要找一些新的。其实,对于以编程为生的人,不会喜欢conntrack-tools这样的管理员工具,如果你使用的是一种“真正的语言”而不是像bash这样的脚本,你也不会喜欢procfs这样的接口,这么说吧,实话说吧,如果你使用一种“真正的语言”,比如C,那么你肯定希望用最复杂的方法完成最简单的操作,比如创建一个socket,然后....,最终调用一个set/getsocketopt。如果你做不到这些,你会想办法做到,一天过去了,两天过去了,...工作量就是这么回事,显得自己很忙也就这么回事,因为这是“真正的语言”,而不是脚本那种谁都能看到的文本。
       确实,如果你在C程序中,解析procfs中的文件确实比较麻烦,SO_ORIGINAL_DST这个命令是获取REDIRECT到本地的skb的原始目标地址元组的,它即使用nf_sockopt_ops实现,作者在get函数的前面加了一个小tip:

/* Fast function for those who don‘t want to parse /proc (and I don‘t
   blame them). */

事实上,作者是有所不知,他应该自己去parse /proc/net/ip_conntrack试试。如果调用conntrack-tools然后截取输出更麻烦,因此你真的就需要一个C API级别的接口,那么set/getsockopt就再好不过了。这两个sockopt系统调用接口和ioctl系统调用接口几乎是一样的,也是职责不明确的链式尝试执行,也就是说,只要你在某个地方定义了实现,并且挂入执行路径,那么它就一定可以被找到并执行。
       近期由于需要在TCP socket上增加一个功能,需要获取保存在conntrack上的信息,我最初的方案实际上就是在凑工作量,怎么做的呢?答案很老套,就是在skb上新增一个字段,然后在PREROUTING HOOK上将conntrack中的信息copy到这个新增字段中,然后在sock中也新增一个同样字段,在传输层将skb中的字段传递给sock,然后就是make kernel-image,等待,出错,rework,等待...loop and loop...事实上非得这样自虐吗?定义一个nf_sockopt_ops怎么样?其get方法如下:

static int
getXXXYYYZZZ(struct sock *sk, int optval, void __user *user, int *len)
{
    const struct inet_sock *inet = inet_sk(sk);
    const struct nf_conntrack_tuple_hash *h;
    struct nf_conntrack_tuple tuple;

    memset(&tuple, 0, sizeof(tuple));
    tuple.src.u3.ip = inet->rcv_saddr;
    tuple.src.u.tcp.port = inet->sport;
    tuple.dst.u3.ip = inet->daddr;
    tuple.dst.u.tcp.port = inet->dport;
    tuple.src.l3num = PF_INET;
    tuple.dst.protonum = sk->sk_protocol;
...//如何想象
    h = nf_conntrack_find_get(sock_net(sk), &tuple);
...//天高任飞翔
}

下面我来告诉你使用set/getsockopt或者ioctl的好处吧,我说这话说明我承认了“虽然它在API方面设计得确实不怎么样,但是好处在别处”。和procfs的文件IO相比,ioctl可以实现直接的内存copy,使用procfs的代价是数据必须先转成字符串,然后写入内核,在内核中再根据“相关的规则”将数据转回裸格式,这意味着需要进行“一次数据交换”,而交换的编解码规则必须让数据交换双方(即procfs的操作者以及内核)都知道才行,对于二进制数据而言,这意味着必须起码进行一次BASE64编码,然后在内核中再BASE64解码,这个事实必须让内核和用户态的Reader,Writer同时知晓。所有这一切只是因为在命令行上,你很难“定义结构体并将其进行传输”,而在系统的底层,本质上就是使用指针在各个结构体之间或者内部进行数据直接寻址的。因此要想使得数据和编码无关,就必须支持直接的内存copy操作。
       好了,以上就是我想说的使用ioctl比使用procfs好的一点。但是,不知注意到没有,更好的并不是”你如何使用它“,对于不爱编程的我来讲,更好的好处在于它能被如何使用。这其实并不是一个意思。你在一个socket描述符上调用set/getsockopt命令,并不意味着你做的事情一定和这个socket有关,对于我以及别的非标准程序员而言,它仅仅意味着将执行流带入了内核空间,仅此而已,如果再能带进去些数据,那就更好了,说实话,写一个getsockopt(sd, SO_IP, REBOOT, NULL, NULL)来实现系统重启也还是不错的。
       对于数据处理的偏好,我相信大多数人比较喜欢点分十进制字符串表示的IP地址,而不喜欢uint32_t表示的IP地址,这是为什么呢?也没啥深刻的原因,就是处理层次的不同。普通用户喜欢域名而讨厌点分十进制,一样的道理,但即便是域名,也是字符串,我们能从键盘输入计算机的,实际上都是字符串,我们平时看到的文字,也都是字符串,我们的大脑识别信息的基本单位就是符号,其实就是字符串(请不要提颜色,声音,气味之类的...),如果我给出一个数字123,它是数字吗?不!它是3个字符组成的字符串!因此我相信很多人不能盲写socket程序的原因就是因为不能熟练处理结构体sockaddr。因此很多人都十分中意的一个函数就是inet_addr,把繁琐的工作交给计算机!有人问,在内核中怎么使用inet_addr呢?答案是不能使用,那么很多程序员可能就要开始自己动手写了,当然作为一种练习未尝不可,并且很多公司的面试题中真的可能让你自己动手用笔将inet_addr写在纸上。然而如果想高效的解决问题,在编程过程中,重用已有的实现是更好的选择。
       但是,重用的前提是你要能找到你要重用的东西。对于内核态的inet_addr这个函数,没有现成的实现,这个时候你要想到的是一个线索,沿着这个线索你可以找到对应的实现。有没有什么内核接口直接接收一个点分十进制的字符串IP地址呢?当然有!考虑一下你用bonding模块的时候,是不是有一个/sys/class/net/bond0/bonding/arp_ip_target呢?沿着这个线索,就可以找到bonding_store_arp_targets这个函数,哦,原来是in_aton,它来自一个公共的组件net/core/utils.c,注释如下:

/*
 * Convert an ASCII string to binary IP.
 * This is outside of net/ipv4/ because various code that uses IP addresses
 * is otherwise not dependent on the TCP/IP stack.
 */

对于经常折腾Linux内核网络协议栈的家伙来讲,经常需要如此做。
       CSDN又在送书了,书单很全,内容都不错,先到先得,可想而知,能顺利拿到自己心仪的书将是多么难了。但是我不怕,我要的是一本和编程无关的关于Cisco防火墙的书,看它呆在那里不合群,也没人要,挺可怜的,现在气温急剧下降,最近持续低温,编程书早就登堂入室,和程序员围炉夜话,看那本Cisco防火墙书一直没人要,当然正合我意

时间: 2024-08-06 11:34:44

可任意操作nf_conntrack的nf_sockopt_ops的相关文章

K/3Cloud WebAPI 调用任意操作实现方案

问题提出:如何实现[销售订单] 的作废.整单关闭.反关闭? 用过WebAPI调用的小伙伴,从提供的文档说明中 K/3 Cloud WebAPI接口说明文档 http://club.kisdee.com/forum.php?mod=viewthread&tid=714662 我们知道Web API有标准接口有: WebAPI 说明中提供的标准调用接口, 我们可以看出,除了"用户验证"接口外. 其他的都是配置在单据上的操作列表调用. 这时当单据上,当关联有配置较多的业务操作,我们该

EDKII: 磁盘操作之 BlockIo(2), DiskIo(2)

EDKII中提供了几种磁盘操作方法,包括同步/异步,块操作/任意操作.整理如下: 函数 备注 BlockIo BlockIo2 DiskIo DiskIo2 操作方式   按Block 按Block 任意大小.位置 任意大小.位置 同.异步   阻塞 异步 阻塞 异步 成员Revision   有 无 有 有 成员Media 设备信息 有 有 无 无 函数Reset   有 有(阻塞) 无 无 函数ReadBlocks   有 有 有 有 函数WriteBlocks   有 有 有 有 函数Fl

(转) unity 在移动平台中,文件操作路径详解

http://www.unitymanual.com/thread-23491-1-1.html 今天,这篇文章其实是个老生常谈的问题咯,在网上类似的文章也比比皆是,在此我只是做个详细总结方便大家能够更好.更快的掌握,当然,如有不足的地方 欢迎指正!!! 相信大家在开发过程中,难免会保存一些文件在客户端进行本地化操作.如:配置文件,状态文件,Assetbundle文件等等... 最近总有人问我:1.保存了一个xml在客户端,能读取里面的数据,可是不能修改,甚至一修改就报错...2.我在电脑上操作

Mac下通过VMware Fusion安装centos虚拟机操作记录

下面介绍下利用VMware Fusion工具在Mac上安装centos虚拟机的做法:1)下载VMware Fusion工具下载地址(包括注册码):http://www.macx.cn/thread-2138527-1-1.html2)下载centos7的iso镜像3)点击下载的VMware-Fusion-8.0.0-2985594.dmg文件进行安装,安装好之后千万不要手欠去删除桌面上的VMware Fusion4)点击安装好的VMware Fusion 5)点击底部Dock栏中的VMware

docker基础操作

Docker desktop23.example.com 172.25.254.23 Selinx iptables disabled    rhel7.2版本 1.安装 yum install -y docker-engine-1.10.3-1.el7.centos.x86_64.rpm docker-engine-selinux-1.10.3-1.el7.centos.noarch.rpm systemctl start docker 2.简单命令 docker version# 查看版本信

使用webstorm操作git

0. 前言 在上一篇文章中,讲述了使用webstorm去调试node程序,最近研究了一下如何使用webstorm去操作git. 对于git的使用,大家的使用方式均有不同,最王道的方式非命令行莫属,基于git的GUI软件还是很多的,大家可自行研究使用.之前使用eclipse svn插件去操作版本管理,还是很便捷的一件事情.而今用惯了webstorm,当然里面也集成了对svn的支持,只是最近一直在用git,所以想试着用webstorm中的git集成工具进行版本管理.当然之前都是以敲命令行的方式去做,

QT开发(二十四)——QT文件操作

QT开发(二十四)--QT文件操作 一.QT文件操作简介 QT中的IO操作通过统一的接口简化了文件与外部设备的操作方式,QT中文件被当作一种特殊的外部设备,文件操作与外部设备操作相同. 1.IO操作的主要函数接口 打开设备:bool open(OpenMode mode) 读取数据:QByteArray read(qint64 maxSize) 写入数据:qint64 write(const QByteArray & byteArray) 关闭设备:void close() IO操作的本质是连续

第34课 缓冲区操作和目录操作

1. 缓冲区操作 (1)Qt中缓冲区的概念 ①缓冲区的本质为一段连续的存储空间 ②QBuffer是Qt是缓冲区相关的类 ③在Qt中可以将缓冲区看作一种特殊的IO设备 ④文件流辅助类可以直接用于操作缓冲区 (2)QBuffer缓冲区的使用方式 QByteArray array; QBuffer buffer(&array);//缓冲区操作类 if (buffer.open(QIODevice::WriteOnly)) { QDataStream out(&buffer); //文件辅助类可以

redis 操作指令集合

redis是什么: Redis is an open source, BSD licensed, advanced key-value store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets and sorted sets. redis是开源,BSD许可,高级的key-value存储系统. 可以用来存储字符串,哈希结构,链表,集合,因