如何扩展Linux的ip_conntrack multi zone conntrack机制

2024-04-30 13:58

本文主要是介绍如何扩展Linux的ip_conntrack multi zone conntrack机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

inux中有一个基于Netfilter的连接跟踪机制,即ip_conntrack,每一个conntrack表示的就是一个流,该流里面保存了大量的信息字段,这些字段本地有效,指导着数据包的转发策略,但是我觉得这些字段信息还不够详细,试想,一个nfmark字段好像就可以做到一切了,但是我如果想为一个数据流绑定一个字符串怎么办呢?也许你会说使用iptables+ipset+nfmark可以完成一切,这也是UNIX/Linux哲学的风格,一种后现代主义的风格,但是最近我上了不归路,非要在ip_conntrack里面扩充一个字段,为我们产品加入一个基于用户名字符串的访问控制和审计功能,于是我有了以下看似可以的方案,顺便鄙视一下纸上谈兵的人:
1.完全学ipmark的样子,在sk_buff和nf_conn里面均加一个mark字段,分别代表数据包的mark和数据流的mark
作罢的原因:
需要重新编译内核,而我不希望为了一个小小的功能重新编译内核,背后的思想是我比较崇尚热插拔。
2.不动sk_buff,只在nf_conn里面加一个字段,skb仅仅作为一个中转,在iptables的target通过skb找到nf_conn,设置nf_conn的info字段
作罢的原因:
Linux严格控制内核模块的版本,模块依赖的头文件一点都不能动,如果我改变了net/netfilter/nf_conntrack.h,那么新编译的所有的依赖nf_conntrack.ko的模块中的符号CRC码都会变化从而无法通过内核的验证,我不得不学Netfilter的一个项目xtables-addons中compat-xtables的样子,把所有的会改变CRC码的导出函数全部再重新实现一遍,然而,天啊,我起初的想法太天真了,没完美了的循环依赖,以至于我想骂两句:
第一:
ip_conntrack为何不让人扩展?虽然它有一个extend机制,但是MD简直就是自说自话,全部都是预定义好的,就下面的枚举里面的几类:

enum nf_ct_ext_id
{
    NF_CT_EXT_HELPER,
    NF_CT_EXT_NAT,
    NF_CT_EXT_ACCT,
    NF_CT_EXT_ECACHE,
    NF_CT_EXT_NUM,
};

你加一个新的类型,就会改变内核头文件,既然不让扩展,为何还叫extend呢?你干脆直接放进nf_conn就可以了,搞成extend感觉上好像多么的模块化,多么的可插拔,实际上你能扩展的东西只能是逻辑,而不能是数据结构!

第二:

Linux为何把extend写的那么死呢?当我突然感到这是合理的时候,我就三缄其口了,后面我会说到,数据结构需要可以自解释,即自己解释自己。虽然人可以看到一个结构体马上说出它的含义,但是程序却很难将一堆数据对应到一个结构体!自解释,如果不知道自解释,那就说明你根本就TM就不懂计算机!虽然你可能很精通编程...

思路

既然不能扩展nf_conn的extend,也不能在nf_conn本身加新的字段,那么只能重新编译内核了,在重新编译内核的时候,加入且仅仅加入一个extend类型,作为一个中间层,在这个extend中实现一个可插拔的注册机制,以后再想加入新的扩展就可以直接在这个extend的机制上进行了。然而,我还是不想编译内核,这是一个思想!我希望做最小的改动。万事都难不倒偏执的人,我采用了一个常规却不常用的方法,那就是默默地扩展结构体的大小,这也正是在《JAVA编程思想》里面学到的一个思想。

思想

这其实是一种OO的思想,找到一个基类,然后扩展它,在扩展继承的过程中实现你自己的逻辑,我扩展的是内核的nf_conn_counter结构体:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
};

我希望它成为下面的样子:

struct nf_conn_counter {
    u_int64_t packets;
    u_int64_t bytes;
    unsigned char *info;
};

但是我又不能改变结构体的定义,所以我采用下面等价的办法:

struct conn_info_extends_nf_conn_counter {
      struct nf_conn_counter base;
      char *info;
}

info是最关键的。我需要做的仅仅就是在为nf_conn_counter分配空间的时候为其多加一个指针的空间即可,至于这个指针指向什么,自有调用者解释。在我的需求中,它可能就是一个字符串,存在info信息。acct_extend原始定义为(之所以选择对acct开刀,是因为它足够简单,在字面上里面,其表示统计信息,加入一个info也无可厚非):

static struct nf_ct_ext_type acct_extend __read_mostly = {
    .len    = sizeof(struct nf_conn_counter[IP_CT_DIR_MAX]),
    .align    = __alignof__(struct nf_conn_counter[IP_CT_DIR_MAX]),
    .id    = NF_CT_EXT_ACCT,
};

将其修改为:

struct info_compat {
        struct nf_conn_counter nc[IP_CT_DIR_MAX];
        unsigned char * info;
};

static struct nf_ct_ext_type acct_extend __read_mostly = {
        .len    = sizeof(struct info_compat),
        .align  = __alignof__(struct info_compat),
        .id    = NF_CT_EXT_ACCT,
};

到此为止,我没有修改任何内核头文件,接下来我来写一个测试模块来进行测试:

#include <linux/ip.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <net/netfilter/nf_conntrack.h>
#include <net/netfilter/nf_conntrack_acct.h>


MODULE_AUTHOR("xtt");
MODULE_DESCRIPTION("gll");
MODULE_LICENSE("GPL");
MODULE_ALIAS("XTT and GLL");

struct nf_info {
        struct nf_conn_counter nc[IP_CT_DIR_MAX];
        char *info;
};

static unsigned int ipv4_conntrack_info (unsigned int hooknum,
                                      struct sk_buff *skb,
                                      const struct net_device *in,
                                      const struct net_device *out,
                                      int (*okfn)(struct sk_buff *))
{
        u32 addr = ip_hdr(skb)->daddr;
    // 测试我家的路由器的地址192.168.1.1
        if (addr == 0x0101a8c0) {
                struct nf_conn *ct;
                enum ip_conntrack_info ctinfo;
                struct nf_conn_counter *acct;
                struct nf_info *info;
                unsigned char *cn = NULL;
                ct = nf_ct_get(skb, &ctinfo);
                if (!ct || ct == &nf_conntrack_untracked)
                        return NF_ACCEPT;

                acct = nf_conn_acct_find(ct);
                if (acct) {
                        info = (struct nf_info *)acct;
                        info->info = (unsigned char*) kzalloc(32, GFP_ATOMIC);
                        if (!info->info) {
                                return NF_ACCEPT;
                        } 
            // 测试将1234567890作为字符串设置到conntrack
                        memcpy(info->info, "1234567890", min(32, strlen("1234567890")));

                }
        }
        return NF_ACCEPT;
}

static struct nf_hook_ops ipv4_conn_info __read_mostly = {
                .hook          = ipv4_conntrack_info,
                .owner          = THIS_MODULE,
                .pf            = NFPROTO_IPV4,
                .hooknum        = NF_INET_LOCAL_OUT,
                .priority      = NF_IP_PRI_CONNTRACK + 1,
};

static int __init test_info_init(void)
{
        int err;
        err = nf_register_hook(&ipv4_conn_info);
        if (err) {
                return err;
        }
        return err;
}

static void __exit test_info_exit(void)
{
        nf_unregister_hook(&ipv4_conn_info);
}

module_init(test_info_init);
module_exit(test_info_exit);



到底成功了没有呢?我需要将这一切展示在/proc/net/nf_conntrack里面,但是由于我使用了acct机制,所以我需要打开内核的acct选项:
sysctl -w net.netfilter.nf_conntrack_acct=1
真正修改的地方在/net/netfilter/nf_conntrack_standalone.c的ct_seq_show函数:

if (seq_printf(s, "use=%u ", atomic_read(&ct->ct_general.use)))
    goto release;

{
    struct nf_info {
        struct nf_conn_counter acct[2];
        char *info;
    };
    struct nf_conn_counter *acct;
    struct nf_info *info;
    acct = nf_conn_acct_find(ct);
    if (acct) {
        info = (struct nf_info *)acct;
        if (info->info) {
            if (seq_printf(s, "info=%s\n", info->info)) {
                goto release;
            }
        }
    }
}

在测试的时候,/proc/net/nf_conntrack中拥有了一个信息:
ipv4 2 tcp 6 431985 ESTABLISHED src=192.168.1.109 dst=192.168.1.1 sport=33591 dport=50 packets=2 bytes=112 src=192.168.1.1dst=192.168.1.109 sport=50 dport=33591 packets=1 bytes=60 [ASSURED] mark=0 zone=0 use=2 info=1234567890
这就说明这种方法是可行的,改动的地方并不多,关键是你要找到一个危险性最少的开刀部位,然后按照OO的思想扩展它,给它它所有没有的行为。曾经,我对nat的extend进行了偷梁换柱,但那是不对的,正确的做法是在原有结构体的地址后面紧跟着进行扩展,类似0长度数组那种。
我需要解释一下程序的自解释了。Linux在实现nf_conntrack的extend的时候,为何将类型数值以及定义的顺序用一个枚举写死呢?换句话说那就是为何不允许用户随意定义extend呢?答案是:那很难!Why?试想,如果我把一个结构体给了一个extend type。请问我把这个type存在哪?除了事先自定义一个type序列,仅存的办法就是把这个type序列存在extend本身了,这就遇到了一个循环定义的问题,我们对此是没有办法的,一个程序很难看到一对数字后,然后就可以把它们看作一个结构体,起码的字段分界就无法解决。虽然可以用OO的思想之外,剩下的解决方案就是寻求一种自解释的数据格式。我能想到的就是ASN.1了。实际上,ASN.1也是一种事先定义的类型格式序列,只不过该序列是经过标准化的而已,一个ASN.1序列是不需要解释的,它可以自己解释自己,需要的仅仅是文档。一个ASN.1序列可以将一段数据解释为一个结构体,或者反过来也可以。OpenSSL里面的d2i/i2d就是做这个的。难道不是吗?
清明时节雨纷纷,就这样在没有雨的一整天过去了,没有从《JAVA编程思想》中看到什么思想,依然没有感悟到JAVA的思想,依然没有。


这篇关于如何扩展Linux的ip_conntrack multi zone conntrack机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/949048

相关文章

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法

《ElasticSearch+Kibana通过Docker部署到Linux服务器中操作方法》本文介绍了Elasticsearch的基本概念,包括文档和字段、索引和映射,还详细描述了如何通过Docker... 目录1、ElasticSearch概念2、ElasticSearch、Kibana和IK分词器部署

Linux流媒体服务器部署流程

《Linux流媒体服务器部署流程》文章详细介绍了流媒体服务器的部署步骤,包括更新系统、安装依赖组件、编译安装Nginx和RTMP模块、配置Nginx和FFmpeg,以及测试流媒体服务器的搭建... 目录流媒体服务器部署部署安装1.更新系统2.安装依赖组件3.解压4.编译安装(添加RTMP和openssl模块

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Ubuntu固定虚拟机ip地址的方法教程

《Ubuntu固定虚拟机ip地址的方法教程》本文详细介绍了如何在Ubuntu虚拟机中固定IP地址,包括检查和编辑`/etc/apt/sources.list`文件、更新网络配置文件以及使用Networ... 1、由于虚拟机网络是桥接,所以ip地址会不停地变化,接下来我们就讲述ip如何固定 2、如果apt安

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

linux进程D状态的解决思路分享

《linux进程D状态的解决思路分享》在Linux系统中,进程在内核模式下等待I/O完成时会进入不间断睡眠状态(D状态),这种状态下,进程无法通过普通方式被杀死,本文通过实验模拟了这种状态,并分析了如... 目录1. 问题描述2. 问题分析3. 实验模拟3.1 使用losetup创建一个卷作为pv的磁盘3.

查询SQL Server数据库服务器IP地址的多种有效方法

《查询SQLServer数据库服务器IP地址的多种有效方法》作为数据库管理员或开发人员,了解如何查询SQLServer数据库服务器的IP地址是一项重要技能,本文将介绍几种简单而有效的方法,帮助你轻松... 目录使用T-SQL查询方法1:使用系统函数方法2:使用系统视图使用SQL Server Configu

使用Java实现获取客户端IP地址

《使用Java实现获取客户端IP地址》这篇文章主要为大家详细介绍了如何使用Java实现获取客户端IP地址,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 首先是获取 IP,直接上代码import org.springframework.web.context.request.Requ

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查