首页 > 代码库 > 可任意操作nf_conntrack的nf_sockopt_ops

可任意操作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防火墙书一直没人要,当然正合我意

可任意操作nf_conntrack的nf_sockopt_ops