本文主要内容:Netfilter的原理和实现浅析,以及示例模块。

内核版本:2.6.37

Author:zhangskd @ csdn blog

概述

Netfilter为多种网络协议(IPv4、IPv6、ARP等)各提供了一套钩子函数。

在IPv4中定义了5个钩子函数,这些钩子函数在数据包流经协议栈的5个关键点被调用。

这就像有5个钓鱼台,在每个钓鱼台放了一个鱼钩(钩子函数),把经过的数据包钓上来,

然后根据自定义的规则,来决定数据包的命运:

可以原封不动的放回IPv4协议,继续向上层递交;可以进行修改,再放回IPv4协议;也可以直接丢弃。

Netfilter主要采用连接跟踪(Connection Tracking)、包过滤(Packet Filtering)、地址转换(NAT)、包处理

(Packet Mangling)四种技术。

(1) IP层的5个钓鱼台

enum nf_inet_hooks {
NF_INET_PRE_ROUTING,
NF_INET_LOCAL_IN,
NF_INET_FORWARD,
NF_INET_LOCAL_OUT,
NF_INET_POST_ROUTING,
NF_INET_NUMHOOKS
};

支持的协议类型:

enum {
NFPROTO_UNSPEC = 0,
NFPROTO_IPV4 = 2,
NFPROTO_ARP = 3,
NFPROTO_BRIDGE = 7,
NFPROTO_IPV6 = 10,
NFPROTO_DECNET = 12,
NFPROTO_NUMPROTO,
};

(2) 钩子函数

typedef unsigned int nf_hookfn(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn) (struct sk_buff *)); /* 处理函数返回值 */
#define NF_DROP 0 /* drop the packet, don't continue traversal */
#define NF_ACCEPT 1 /* continue traversal as normal */
#define NF_STOLEN 2 /* I've taken over the packet, don't continue traversal */
#define NF_QUEUE 3 /* queue the packet (usually for userspace handling) */
#define NF_REPEAT 4 /* call this hook again */
#define NF_STOP 5
#define NF_MAX_VERDICT NF_STOP

(3) Netfilter实体

在使用Netfilter时,需要定义一个nf_hook_ops实例。

struct nf_hook_ops {
struct list_head list;
/* User fills in from here down. */
nf_hookfn *hook; /* 要注册的钩子函数 */
struct module *owner;
u_int8_t pf; /* 协议类型 */
unsigned int hooknum; /* 哪个钓鱼台 */
/* Hooks are ordered in asending priority. */
int priority; /* 数值越小,优先级越高 */
};
typedef __u8 u_int8_t;

(4) 注册与注销

/* Functions to register/unregister hook points. */
int nf_register_hook(struct nf_hook_ops *reg);
void nf_unregister_hook(struct nf_hook_ops *reg);

实现

Netfilter定义了一个全局链表:

struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
EXPORT_SYMBOL(nf_hooks);
static DEFINE_MUTEX(nf_hook_mutex);

(1) 注册函数

注册函数会把nf_hook_ops放入nf_hooks相应的位置中。

int nf_register_hook(struct nf_hook_ops *reg)
{
struct nf_hook_ops *elem;
int err; err = mutex_lock_interruptible(&nf_hook_mutex);
if (err < 0)
return err; list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
if (reg->priority < elem->priority)
break;
} list_add_rcu(&reg->list, elem->list.prev); /* 把netfilter实例添加到队列中 */
mutex_unlock(&nf_hook_mutex);
return 0;
}

(2) 注销函数

void nf_unregister_hook(struct nf_hook_ops *reg)
{
mutex_lock(&nf_hook_mutex);
list_del_rcu(&reg->list); /* 把netfilter实例从队列中删除 */
mutex_unlock(&nf_hook_mutex);
synchronize_net();
}

(3) 内核接口

内核的Netfilter钩子函数调用:

NF_HOOK

|--> NF_HOOK_THRESH

|--> nf_hook_thresh

|--> nf_hook_slow

|--> nf_iterate

static inline int NF_HOOK(uint8_t pf, unsigned int hook, struct sk_buff *skb,
struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *))
{
/* INT_MIN表示要调用钓鱼台的所有钩子函数 */
return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN);
} static inline int NF_HOOK_THRESH(uint8_t pf, unsigned int hook, struct sk_buff *skb,
struct net_device *in, struct net_device *out, int (*okfn)(struct sk_buff *), int thresh)
{
int ret = nf_hook_thresh(pf, hook, skb, in, out, okfn, thresh);
if (ret == 1)
ret = okfn(skb); /* 如果skb没被处理掉,调用此函数 */ return ret;
}
/**
* nf_hook_thresh - call a netfilter hook
* Returns 1 if the hook has allowed the packet to pass.
* The function okfn must be invoked by the caller in this case.
* Any other return value indicates the packet has been consumed by the hook.
*/
static inline int nf_hook_thresh(u_int8_t pf, unsigned int hook, struct sk_buff *skb,
struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int thresh)
{
#ifndef CONFIG_NETFILTER_DEBUG
/* 如果协议pf的hook点上没有已注册的nf_hook_ops实例,直接返回1 */
if (list_empty(&nf_hooks[pf][hook]))
return 1;
#endif return nf_hook_slow(pf, hook, skb, indev, outdev, okfn, thresh);
} /* Returns 1 if okfn() needs to be executed by the caller, -EPERM for NF_DROP, 0 otherwise. */
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev,
struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh)
{
struct list_head *elem;
unsigned int verdict;
int ret = 0; /* We may already have this, but read-locks nest anyway */
rcu_read_lock(); elem = &nf_hooks[pf][hook];
next_hook:
verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh); if (verdict == NF_ACCEPT || verdict == NF_STOP) {
ret = 1;
} else if (verdict == NF_DROP) {
kfree_skb(skb);
ret = -EPERM;
} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {
if (! nf_queue(skb, elem, ph, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS))
goto next_hook;
} rcu_read_unlock(); return ret;
}
unsigned int nf_iterate(struct list_head *head, struct sk_buff *skb, unsigned int hook,
const struct net_device *indev, const struct net_device *outdev, struct list_head **i,
int (*okfn)(struct sk_buff *), int hook_thresh)
{
unsigned int verdict; /*
* The caller must not block between calls to this function because of risk of
* continuing from deleted element.
*/
list_for_each_continue_rcu(*i, head) {
struct nf_hook_ops *elem = (struct nf_hook_ops *) *i; /* 优先级>=hook_thresh的都会被执行 */
if (hook_thresh > elem_priority)
continue; verdict = elem->hook(hook, skb, indev, outdev, okfn); /* 已注册的执行函数 */ if (verdict != NF_ACCEPT) {
#ifdef CONFIG_NETFILTER_DEBUG
if (unlikely((verdict & NF_VERDICT_MASK) > NF_MAX_VERDICT)) {
NFDEBUG("Evil return from %p(%u).\n", elem->hook, hook);
continue;
}
#endif if (verdict != NF_REPEAT)
return verdict;
*i = (*i)->prev;
}
} return NF_ACCEPT;
}

使用

以下是一个简单的模块,加载到一个HTTP服务器上。

通过在PRE_ROUTING处注册my_hookfn,改变接收数据包的源IP为8.8.8.8(Google DNS server)。

当客户端向服务器发送一个请求时,肯定收不到服务器的响应:)

#include <linux/netfilter.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/inet.h> /**
* Hook function to be called.
* We modify the packet's src IP.
*/
unsigned int my_hookfn(unsigned int hooknum,
struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
struct iphdr *iph;
iph = ip_hdr(skb); /* log the original src IP */
printk(KERN_INFO"src IP %pI4\n", &iph->saddr); /* modify the packet's src IP */
iph->saddr = in_aton("8.8.8.8"); return NF_ACCEPT;
} /* A netfilter instance to use */
static struct nf_hook_ops nfho = {
.hook = my_hookfn,
.pf = PF_INET,
.hooknum = NF_INET_PRE_ROUTING,
.priority = NF_IP_PRI_FIRST,
.owner = THIS_MODULE,
}; static int __init sknf_init(void)
{
if (nf_register_hook(&nfho)) {
printk(KERN_ERR"nf_register_hook() failed\n");
return -1;
}
return 0;
} static void __exit sknf_exit(void)
{
nf_unregister_hook(&nfho);
} module_init(sknf_init);
module_exit(sknf_exit);
MODULE_AUTHOR("zhangsk");
MODULE_LICENSE("GPL");

Netfilter的使用和实现的更多相关文章

  1. (一)洞悉linux下的Netfilter&iptables:什么是Netfilter?

    转自:http://blog.chinaunix.net/uid-23069658-id-3160506.html 本人研究linux的防火墙系统也有一段时间了,由于近来涉及到的工作比较纷杂,久而久之 ...

  2. netfilter的钩子——数据包在内核态得捕获、修改和转发

    转发:http://blog.csdn.net/stonesharp/article/details/27091391 数据包在内核态得捕获.修改和转发(基于 netfilter)    忙活了好几天 ...

  3. Netfilter/iptables的匹配方式及处理方法

    匹配方式: 匹配方式是netfilter筛选数据包的最基本单元. 内置的匹配方式: 1.接口的匹配方式: iptables -t filter -A FORWARD -i eth0 -o eth1 - ...

  4. iptables/Netfilter 学习

    开始学iptables,因为它是和路由器技术紧密结合在一起的. iptables的命令看起来眼花缭乱,随便找两个: iptables -A FORWARD -p tcp -s -d -j ACCEPT ...

  5. netfilter分析

    转自:http://blog.sina.com.cn/s/blog_a31ff26901013n07.html 一.概述 1. Netfilter/IPTables框架简介 Netfilter/IPT ...

  6. (转)Netfilter分析

    看到一篇讲Netfilter框架的,如果有一点基础了的话对于捋清整个框架很好帮助,转下来细细阅读. 转自http://aichundi.blog.163.com/blog/static/7013846 ...

  7. Linux数据包路由原理、Iptables/netfilter入门学习

    相关学习资料 https://www.frozentux.net/iptables-tutorial/cn/iptables-tutorial-cn-1.1.19.html http://zh.wik ...

  8. 对于数据包的截取,使用linux中的netfilter钩子函数

    http://blog.csdn.net/wswifth/article/details/5115358 在师哥的代码(packet.c)中使用的是Linux2.4内核中的一个子系统:netfilte ...

  9. 深入Linux网络核心堆栈(对于netfilter的用法和讲解)

    http://blog.csdn.net/wswifth/article/details/5115475 注册一个hook函数是围绕nf_hook_ops数据结构的一个非常简单的操作,nf_hook_ ...

  10. Netfilter/iptables防火墙

    http://os.51cto.com/art/201107/273443.htm [51CTO独家特稿]Linux系统管理员们都接触过Netfilter/iptables,这是Linux系统自带的免 ...

随机推荐

  1. Android开发基础规范(二)

    转载请把头部出处链接和尾部二维码一起转载,本文出自逆流的鱼yuiop:http://blog.csdn.net/hejjunlin/article/details/52614696 前言:Androi ...

  2. linux中exec和xargs命令的区别和优劣分析

    find的exec及ok命令 exec命令的格式为: exec command {} \; exec后面跟着的是操作命令,然后跟着{}表示每一个参数,然后空格,然后"\;".{}之 ...

  3. Linux目录架构详解

    Linux和Windows操作系统的显著区别之一就是目录架构的不同.Linux操作系统的目录架构遵循文件系统层级结构标准.不知你是否使用ls命令浏览过Linux的根目录"/",亲爱 ...

  4. Effective C++ ——构造/析构/赋值运算符

    条款五:了解C++默认编写并调用那些函数 是否存在空的类? 假设定义类为class Empty{}:当C++编译器处理过后会变成如下的形式: class Empty{ Empty(){} ~Empty ...

  5. java虚拟机 jvm 方法区实战

    和java堆一样,方法区是一块所有线程共享的内存区域,用于保存系统的类信息,类的信息有哪些呢.字段.方法.常量池.方法区也有一块内存区域所以方法区的内存大小,决定了系统可以包含多少个类,如果系统类太多 ...

  6. shell的追踪与调试选项

    选项: -n :不执行shell脚本,只检查语法问题.没有问题则没有输出. -v :执行shell脚本前,现将shell脚本的命令输出到屏幕上.输出一段,执行一段. -x :将使用到的所有shell脚 ...

  7. 浅谈Android布局

    在前面的博客中,小编介绍了Android的极光推送以及如何实现登录的一个小demo,对于xml布局页面,摆控件这块的内容,小编还不是很熟练,今天小编主要简单总结一下在Android中的布局,学习过An ...

  8. Android软件设置自动检查更新

    如果让我推荐功能强大的第三方集成开发包,我一定会推荐友盟,有着强大的软件统计,分析功能(原谅我,我不是打广告). 这一篇介绍友盟的自动更新功能,但是首先你得拥有友盟. 友盟的集成步骤 1.1 导入SD ...

  9. centos7安装jdk,tomcat,msyql(MariaDB)

    操作系统版本 CentOS Linux release 7.2.1511 (Core) 安装jdk 下载jdk-8u66-linux-x64.rpm上传到linux上 先改用户权限 然后 rpm -i ...

  10. android开发要避免的那些坑

    SparseArray 目前有很多地方从性能优化方说使用SparseArray来替换hashMap,来节省内存,提高性能. Linkify.addLinks() 这个类可以更方便的为文本添加超链接. ...