ipset如何与netfilter内核模块进行通信

ipset如何与netfilter内核模块进行通信最近需要使用ipset,iptables,和netfilter,所以把三者的源代码看大概阅读了一遍。前面我们学习过应用层ipset和netfilter模块之间通信是采用的netlink套接字用户空间的ipset命令通过libipset.so这个库和内核通讯一、ipset主流程下面是我总结的主流程 二、用户层如何将创建set的名称和类型传递到内核层的我们都知道ip…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

最近需要使用ipset,iptables,和netfilter,所以把三者的源代码看大概阅读了一遍。

前面我们学习过应用层ipset和netfilter模块之间通信是采用的netlink套接字

用户空间的ipset命令通过 libipset.so 这个库和内核通讯

一、ipset主流程

下面是我总结的主流程

ipset如何与netfilter内核模块进行通信

 

二、用户层如何将创建set的名称和类型传递到内核层的

我们都知道ipset可以创建不同类型set,如”hash:ip”,”hash:ip,port”,”hash:net,port”等

从执行命令到内核态,其流程为

ipset命令行 -> libipset.so -> ip_set.ko内核模块 ->根据set类型选择ip_set_hash_ip.ko内核模块

那么应用层是如何解析set的命令和类型的,并且是如何将set名称和类型传递到内核态的呢?

ipset_parse_argv函数中去解析ipset的Create命令

ipset如何与netfilter内核模块进行通信

ipset_parse_setname是解析刚创建ipet集合的名称

ipset_parse_typename是解析刚创建ipset集合的类型

ret = ipset_parse_setname(session, IPSET_SETNAME, arg0);函数是将将arg0的值传递了session的setname成员

因为我更关注set type类型,所以进入ipset_parse_typename函数

ipset如何与netfilter内核模块进行通信

/* Find the corresponding type */
typename = ipset_typename_resolve(str);

通过注释可得知,ipset_typename_resolve是找到对应的set类型,大胆猜想下,命令行是“hash:ip”,通过“hash:ip”我们能够获取到typename类型名称

ipset如何与netfilter内核模块进行通信

报告大哥,发现线索typelist链表,函数意思是遍历typelist链表,用ipset_match_typename()来匹配类型名称,匹配成功则返回类型名称。现在需要找到往typelist链表中添加元素的函数!!!

ipset_type_add函数!

ipset_type_add函数!

ipset_type_add函数!

重要的事情说三遍!看看它的英文注释,如下:

/**
 * ipset_type_add – add (register) a userspace set type
 * @type: pointer to the set type structure
 *
 * Add the given set type to the type list. The types
 * are added sorted, in descending revision number.
 *
 * Returns 0 on success or a negative error code.
 */

添加一个用户态的set集合类型,ok,找到了,only you

 

何人在调用ipset_type_add函数?

打开ipset_hash_ip.c文件,找到其_init函数

ipset如何与netfilter内核模块进行通信

看看ipset_hash_ip0结构体定义和初始化

/* Initial release */
static struct ipset_type ipset_hash_ip0 = {
	.name = "hash:ip",
	.alias = { "iphash", NULL },
	.revision = 0,
	.family = NFPROTO_IPSET_IPV46,
	.dimension = IPSET_DIM_ONE,
	.elem = {
		[IPSET_DIM_ONE - 1] = {
			.parse = ipset_parse_ip4_single6,
			.print = ipset_print_ip,
			.opt = IPSET_OPT_IP
		},
	},
	.cmd = {
		[IPSET_CREATE] = {
			.args = {
				IPSET_ARG_FAMILY,
				/* Aliases */
				IPSET_ARG_INET,
				IPSET_ARG_INET6,
				IPSET_ARG_HASHSIZE,
				IPSET_ARG_MAXELEM,
				IPSET_ARG_NETMASK,
				IPSET_ARG_TIMEOUT,
				/* Ignored options: backward compatibilty */
				IPSET_ARG_PROBES,
				IPSET_ARG_RESIZE,
				IPSET_ARG_GC,
				IPSET_ARG_NONE,
			},
			.need = 0,
			.full = 0,
			.help = "",
		},
		[IPSET_ADD] = {
			.args = {
				IPSET_ARG_TIMEOUT,
				IPSET_ARG_NONE,
			},
			.need = IPSET_FLAG(IPSET_OPT_IP),
			.full = IPSET_FLAG(IPSET_OPT_IP)
				| IPSET_FLAG(IPSET_OPT_IP_TO),
			.help = "IP",
		},
		[IPSET_DEL] = {
			.args = {
				IPSET_ARG_NONE,
			},
			.need = IPSET_FLAG(IPSET_OPT_IP),
			.full = IPSET_FLAG(IPSET_OPT_IP)
				| IPSET_FLAG(IPSET_OPT_IP_TO),
			.help = "IP",
		},
		[IPSET_TEST] = {
			.args = {
				IPSET_ARG_NONE,
			},
			.need = IPSET_FLAG(IPSET_OPT_IP),
			.full = IPSET_FLAG(IPSET_OPT_IP)
				| IPSET_FLAG(IPSET_OPT_IP_TO),
			.help = "IP",
		},
	},
	.usage = "where depending on the INET family\n"
		 "      IP is a valid IPv4 or IPv6 address (or hostname),\n"
		 "      CIDR is a valid IPv4 or IPv6 CIDR prefix.\n"
		 "      Adding/deleting multiple elements in IP/CIDR or FROM-TO form\n"
		 "      is supported for IPv4.",
	.description = "Initial revision",
};

//

三、netlink套接字初始化

想使用netlink套接字,必然要先创建netlink套接字,应该有如下代码

skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);

 但是我并没有在ipset源代码中查找到。

后来在书上看到ipset源代码中是采用libmnl库来使用netlink套接字,使用ipset_mnl_init函数来进行初始化操作

static struct ipset_handle *
ipset_mnl_init(mnl_cb_t *cb_ctl, void *data)
{
	struct ipset_handle *handle;

	assert(cb_ctl);
	assert(data);

	handle = calloc(1, sizeof(*handle));
	if (!handle)
		return NULL;

	handle->h = mnl_socket_open(NETLINK_NETFILTER);
	if (!handle->h)
		goto free_handle;

	if (mnl_socket_bind(handle->h, 0, MNL_SOCKET_AUTOPID) < 0)
		goto close_nl;

	handle->portid = mnl_socket_get_portid(handle->h);
	handle->cb_ctl = cb_ctl;
	handle->data = data;
	handle->seq = time(NULL);

	return handle;

close_nl:
	mnl_socket_close(handle->h);
free_handle:
	free(handle);

	return NULL;
}

mnl_socket_open函数传递NETLINK_NETFILTER类型,创建netlink套接字

mnl_socket_bind绑定进程pid,此处传递的是MNL_SOCKET_AUTOPID

mnl_socket_get_portid 通过给定的netlink套接字获取netlink端口id

 

四、通过netlink函数和内核态进行交互

在同一个文件mnl.c中发现ipset_mnl_query函数,其中调用了mnl_socket_recvfrom和mnl_socket_sendto,和内核态进行通信

下面就是看下libmnl的api官方文档

static int
ipset_mnl_query(struct ipset_handle *handle, void *buffer, size_t len)
{
	struct nlmsghdr *nlh = buffer;
	int ret;

	assert(handle);
	assert(buffer);

	nlh->nlmsg_seq = ++handle->seq;
#ifdef IPSET_DEBUG
	ipset_debug_msg("sent", nlh, nlh->nlmsg_len);
#endif
	if (mnl_socket_sendto(handle->h, nlh, nlh->nlmsg_len) < 0)
		return -ECOMM;

	ret = mnl_socket_recvfrom(handle->h, buffer, len);
#ifdef IPSET_DEBUG
	ipset_debug_msg("received", buffer, ret);
#endif
	while (ret > 0) {
		ret = mnl_cb_run2(buffer, ret,
				  handle->seq, handle->portid,
				  handle->cb_ctl[NLMSG_MIN_TYPE],
				  handle->data,
				  handle->cb_ctl, NLMSG_MIN_TYPE);
		D("nfln_cb_run2, ret: %d, errno %d", ret, errno);
		if (ret <= 0)
			break;
		ret = mnl_socket_recvfrom(handle->h, buffer, len);
		D("message received, ret: %d", ret);
	}
	return ret;
}

关于mnl_socket_recvfrom和mnl_socket_sendto和mnl_cb_run2函数的含义,请自行查找api

用户态和内核态通信,必然会遵循某种特定的规则,我们称之为通信规则

在ip_set.h文件中,有如下命令的定义

/* Message types and commands */
enum ipset_cmd {
	IPSET_CMD_NONE,
	IPSET_CMD_PROTOCOL,	/* 1: Return protocol version */
	IPSET_CMD_CREATE,	/* 2: Create a new (empty) set */
	IPSET_CMD_DESTROY,	/* 3: Destroy a (empty) set */
	IPSET_CMD_FLUSH,	/* 4: Remove all elements from a set */
	IPSET_CMD_RENAME,	/* 5: Rename a set */
	IPSET_CMD_SWAP,		/* 6: Swap two sets */
	IPSET_CMD_LIST,		/* 7: List sets */
	IPSET_CMD_SAVE,		/* 8: Save sets */
	IPSET_CMD_ADD,		/* 9: Add an element to a set */
	IPSET_CMD_DEL,		/* 10: Delete an element from a set */
	IPSET_CMD_TEST,		/* 11: Test an element in a set */
	IPSET_CMD_HEADER,	/* 12: Get set header data only */
	IPSET_CMD_TYPE,		/* 13: Get set type */
	IPSET_CMD_GET_BYNAME,	/* 14: Get set index by name */
	IPSET_CMD_GET_BYINDEX,	/* 15: Get set name by index */
	IPSET_MSG_MAX,		/* Netlink message commands */

	/* Commands in userspace: */
	IPSET_CMD_RESTORE = IPSET_MSG_MAX, /* 16: Enter restore mode */
	IPSET_CMD_HELP,		/* 17: Get help */
	IPSET_CMD_VERSION,	/* 18: Get program version */
	IPSET_CMD_QUIT,		/* 19: Quit from interactive mode */

	IPSET_CMD_MAX,

	IPSET_CMD_COMMIT = IPSET_CMD_MAX, /* 20: Commit buffered commands */
};

这里我们以IPSET_CMD_CREATE为例子,在内核代码(我的内核版本是3.10)搜索IPSET_CMD_CREATE

找到如下的结构体

static const struct nfnl_callback ip_set_netlink_subsys_cb[IPSET_MSG_MAX] = {

    [IPSET_CMD_NONE]    = {

        .call        = ip_set_none,
        .attr_count    = IPSET_ATTR_CMD_MAX,
    },
    [IPSET_CMD_CREATE]    = {

        .call        = ip_set_create,
        .attr_count    = IPSET_ATTR_CMD_MAX,
        .policy        = ip_set_create_policy,
    },
    [IPSET_CMD_DESTROY]    = {

        .call        = ip_set_destroy,
        .attr_count    = IPSET_ATTR_CMD_MAX,
        .policy        = ip_set_setname_policy,
    },
    [IPSET_CMD_FLUSH]    = {

        .call        = ip_set_flush,
        .attr_count    = IPSET_ATTR_CMD_MAX,
        .policy        = ip_set_setname_policy,
    },

}

上面标明IPSET_CMD_CREATE命令的处理函数为ip_set_create

此时从用户态发送命令到内核态

内核态响应用户态的命令

流程已经跑通了。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/185131.html原文链接:https://javaforall.net

(0)
上一篇 2022年10月6日 下午11:00
下一篇 2022年10月6日 下午11:00


相关推荐

  • DIKW体系(Data-Information-Knowlege-Wisdom)

    DIKW体系(Data-Information-Knowlege-Wisdom)这个世界上 失败的人除了天分太差之外 只有以下几点 懒 方向不对 方法不对 没有坚持 你是哪一种呢 关于 DIKW 体系 DIKW 体系是关于数据 信息 知识及智慧的体系 可以追溯至托马斯 斯特尔那斯 艾略特所写的诗 岩石 在首段 他写道 我们在哪里丢失了知识中的智慧 又在哪里丢失了信息中的知识 Whereisthewi

    2026年3月17日
    2
  • linux 下vim删除多行「建议收藏」

    linux 下vim删除多行「建议收藏」1、按下Esc键进入命令模式2、然后输入下列命令:setnu#让当前文档内容显示行号,后面删除的时候是根据行号来删除的3、看下面例子:3,24d#敲下回车之后就会删除第3~24行的全部内容

    2022年6月15日
    135
  • 解决cydia bigboss源空白

    解决cydia bigboss源空白如果出现 cydia 源空白可以按照以下方式来操作先删除 bigboss 源从管理页面进入软件包管理找到 cydiainstall 并选择重新安装在主页面更多源中添加 bigboss 原文地址 Bigboss 源空白

    2026年3月19日
    2
  • 使用vscode + 腾讯云 AI 代码助手组合提高效率

    使用vscode + 腾讯云 AI 代码助手组合提高效率

    2026年3月12日
    2
  • 黑盒测试9种常用方法

    黑盒测试9种常用方法包括 等价类划分 边界值分析 BVA 因果图 判定 决策 表 组合覆盖 pairwise 正交表法 OATS 功能图法 场景设计法 错误推测法 其中易考 等价类划分 BVA 和场景法 其中同时属于白盒测试的有 BVA 功能图法

    2026年3月26日
    2
  • HashMap底层实现原理_计算机底层原理

    HashMap底层实现原理_计算机底层原理文章目录前言一、快速入门二、使用步骤1.引入库2.读入数据总结学习内容:学习时间:学习产出:前言一、pandas是什么?二、使用步骤1.引入库2.读入数据总结前言提示:以下是本篇文章对HashMap的实现原理内容,下面案例可供参考提示:以下是本篇文章正文内容,下面案例可供参考一、快速入门示例:有一定基础的小伙伴们可以选择性的跳过该步骤HashMap是Java程序员使用频率最高的用于映射键值对(key和value)处理的数据类型。随着JDK版本的跟新,JDK1.8对HashMap底层的实现进行

    2025年10月19日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号