深入Linux网络核心堆栈之七
--[ 7 - 结束语
希望你现在至少对Netfilter有了一个初步的了解,如何使用它以及你能用它来做什么。你同样也应当有了一些使特定的网络通信从运行在本机的嗅探软件中隐藏的知识了。如果你需要本文中涉及的源代码的tar包,请直接给我发email。我同样很乐意接收任何的指正、批评或者建议。好了,把一切都留给你和你的想象力,来做一些我在这儿展现的有趣的事吧!
--[ A - 轻量级防火墙
----[ A.1 - 概述
轻量级防火墙(LWFW)是一个简单的内核模块,用于演示我们在第4节中涉及的基本的数据包过录技术。LWFW也通过ioctl()系统调用提供了一个控制接口。
由于LWFW的源代码已经有足够的文档了,我在这儿只给出它如何工作的简单概述。当LWFW模块被加载后,它的第一个任务就是尝试注册控制设置。注意在LWFW的ioctl()控制接口可用之前,需要在/dev下创建一个字符设备文件。如果控制设备注册成功,"in use"标志被清除并且对NF_IP_PRE_ROUTE进行hook的函数被注册。清除函数执行相反的操作。
LWFW对数据包丢弃提供三个基本的选项。按照处理的顺序列出如下:
-- 源接口
-- 源IP地址
-- 目的TCP端口
这些规则的设置由ioctl()接口完成。当一个数据包被接收,LWFW按照我们设定的规则进行检查。如果匹配了其中的任意一条规则,那么hook函数将返回NF_DROP,然后Netfilter将悄无声息的丢弃这个数据包。否则,hook函数返回NF_ACCEPT,数据包将继续它的旅程。
最后,有必要提一下的是LWFW的统计日志。无论任何时候数据包进入hook函数,LWFW都将收到的数据包的计数累加。单独的规则检查函数负责增加它们各自的丢弃的数据包的计数。注意,当规则的值被改变时,它的丢弃数据包的计数被重置为0。lwfwstats程序利用LWFW_GET_STATS这个IOCTL来获取统计数据结构的一个副本并显示其内容。
----[ A.2 - 源代码 : lwfw.c
<++> lwfw/lwfw.c
/* Light-weight Fire Wall. Simple firewall utility based on
* Netfilter for 2.4. Designed for educational purposes.
*
* Written by bioforge - March 2003.
*/
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/net.h>
#include <linux/types.h>
#include <linux/skbuff.h>
#include <linux/string.h>
#include <linux/malloc.h>
#include <linux/netdevice.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <asm/errno.h>
#include <asm/uaccess.h>
#include "lwfw.h"
/* Local function prototypes */
static int set_if_rule(char *name);
static int set_ip_rule(unsigned int ip);
static int set_port_rule(unsigned short port);
static int check_ip_packet(struct sk_buff *skb);
static int check_tcp_packet(struct sk_buff *skb);
static int copy_stats(struct lwfw_stats *statbuff);
/* Some function prototypes to be used by lwfw_fops below. */
static int lwfw_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg);
static int lwfw_open(struct inode *inode, struct file *file);
static int lwfw_release(struct inode *inode, struct file *file);
/* Various flags used by the module */
/* This flag makes sure that only one instance of the lwfw device
* can be in use at any one time. */
static int lwfw_ctrl_in_use = 0;
/* This flag marks whether LWFW should actually attempt rule checking.
* If this is zero then LWFW automatically allows all packets. */
static int active = 0;
/* Specifies options for the LWFW module */
static unsigned int lwfw_options = (LWFW_IF_DENY_ACTIVE
| LWFW_IP_DENY_ACTIVE
| LWFW_PORT_DENY_ACTIVE);
static int major = 0; /* Control device major number */
/* This struct will describe our hook procedure. */
struct nf_hook_ops nfkiller;
/* Module statistics structure */
static struct lwfw_stats lwfw_statistics = {0, 0, 0, 0, 0};
/* Actual rule ''definitions''. */
/* TODO: One day LWFW might actually support many simultaneous rules.
* Just as soon as I figure out the list_head mechanism... */
static char *deny_if = NULL; /* Interface to deny */
static unsigned int deny_ip = 0x00000000; /* IP address to deny */
static unsigned short deny_port = 0x0000; /* TCP port to deny */
/*
* This is the interface device''s file_operations structure
*/
struct file_operations lwfw_fops = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
lwfw_ioctl,
NULL,
lwfw_open,
NULL,
lwfw_release,
NULL /* Will be NULL''ed from here... */
};
MODULE_AUTHOR("bioforge");
MODULE_DESCRIPTION("Light-Weight Firewall for Linux 2.4");
/*
* This is the function that will be called by the hook
*/
unsigned int lwfw_hookfn(unsigned int hooknum,
struct sk_buff **skb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
unsigned int ret = NF_ACCEPT;
/* If LWFW is not currently active, immediately return ACCEPT */
if (!active)
return NF_ACCEPT;
lwfw_statistics.total_seen++;
/* Check the interface rule first */
if (deny_if && DENY_IF_ACTIVE) {
if (strcmp(in->name, deny_if) == 0) { /* Deny this interface */
lwfw_statistics.if_dropped++;
lwfw_statistics.total_dropped++;
return NF_DROP;
}
}
/* Check the IP address rule */
if (deny_ip && DENY_IP_ACTIVE) {
ret = check_ip_packet(*skb);
if (ret != NF_ACCEPT) return ret;
}
/* Finally, check the TCP port rule */
if (deny_port && DENY_PORT_ACTIVE) {
ret = check_tcp_packet(*skb);
if (ret != NF_ACCEPT) return ret;
}
return NF_ACCEPT; /* We are happy to keep the packet */
}
/* Function to copy the LWFW statistics to a userspace buffer */
static int copy_stats(struct lwfw_stats *statbuff)
{
NULL_CHECK(statbuff);
copy_to_user(statbuff, &lwfw_statistics,
sizeof(struct lwfw_stats));
return 0;
}
/* Function that compares a received TCP packet''s destination port
* with the port specified in the Port Deny Rule. If a processing
* error occurs, NF_ACCEPT will be returned so that the packet is
* not lost. */
static int check_tcp_packet(struct sk_buff *skb)
{
/* Seperately defined pointers to header structures are used
* to access the TCP fields because it seems that the so-called
* transport header from skb is the same as its network header TCP packets.
* If you don''t believe me then print the addresses of skb->nh.iph
* and skb->h.th.
* It would have been nicer if the network header only was IP and
* the transport header was TCP but what can you do? */
struct tcphdr *thead;
/* We don''t want any NULL pointers in the chain to the TCP header. */
if (!skb ) return NF_ACCEPT;
if (!(skb->nh.iph)) return NF_ACCEPT;
/* Be sure this is a TCP packet first */
if (skb->nh.iph->protocol != IPPROTO_TCP) {
return NF_ACCEPT;
}
thead = (struct tcphdr *)(skb->data + (skb->nh.iph->ihl * 4));
/* Now check the destination port */
if ((thead->dest) == deny_port) {
/* Update statistics */
lwfw_statistics.total_dropped++;
lwfw_statistics.tcp_dropped++;
return NF_DROP;
}
return NF_ACCEPT;
}
/* Function that compares a received IPv4 packet''s source address
* with the address specified in the IP Deny Rule. If a processing
* error occurs, NF_ACCEPT will be returned so that the packet is
* not lost. */
static int check_ip_packet(struct sk_buff *skb)
{
/* We don''t want any NULL pointers in the chain to the IP header. */
if (!skb ) return NF_ACCEPT;
if (!(skb->nh.iph)) return NF_ACCEPT;
if (skb->nh.iph->saddr == deny_ip) {/* Matches the address. Barf. */
lwfw_statistics.ip_dropped++; /* Update the statistics */
lwfw_statistics.total_dropped++;
return NF_DROP;
}
return NF_ACCEPT;
}
static int set_if_rule(char *name)
{
int ret = 0;
char *if_dup; /* Duplicate interface */
/* Make sure the name is non-null */
NULL_CHECK(name);
/* Free any previously saved interface name */
if (deny_if) {
kfree(deny_if);
deny_if = NULL;
}
if ((if_dup = kmalloc(strlen((char *)name) + 1, GFP_KERNEL))
== NULL) {
ret = -ENOMEM;
} else {
memset(if_dup, 0x00, strlen((char *)name) + 1);
memcpy(if_dup, (char *)name, strlen((char *)name));
}
deny_if = if_dup;
lwfw_statistics.if_dropped = 0; /* Reset drop count for IF rule */
printk("LWFW: Set to deny from interface: %s\n", deny_if);
return ret;
}
static int set_ip_rule(unsigned int ip)
{
deny_ip = ip;
lwfw_statistics.ip_dropped = 0; /* Reset drop count for IP rule */
printk("LWFW: Set to deny from IP address: %d.%d.%d.%d\n",
ip & 0x000000FF, (ip & 0x0000FF00) >> 8,
(ip & 0x00FF0000) >> 16, (ip & 0xFF000000) >> 24);
return 0;
}
static int set_port_rule(unsigned short port)
{
deny_port = port;
lwfw_statistics.tcp_dropped = 0; /* Reset drop count for TCP rule */
printk("LWFW: Set to deny for TCP port: %d\n",
((port & 0xFF00) >> 8 | (port & 0x00FF) << 8));
return 0;
}
/*********************************************/
/*
* File operations functions for control device
*/
static int lwfw_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case LWFW_GET_VERS:
return LWFW_VERS;
case LWFW_ACTIVATE: {
active = 1;
printk("LWFW: Activated.\n");
if (!deny_if && !deny_ip && !deny_port) {
printk("LWFW: No deny options set.\n");
}
break;
}
case LWFW_DEACTIVATE: {
active ^= active;
printk("LWFW: Deactivated.\n");
break;
}
case LWFW_GET_STATS: {
ret = copy_stats((struct lwfw_stats *)arg);
break;
}
case LWFW_DENY_IF: {
ret = set_if_rule((char *)arg);
break;
}
case LWFW_DENY_IP: {
ret = set_ip_rule((unsigned int)arg);
break;
}
case LWFW_DENY_PORT: {
ret = set_port_rule((unsigned short)arg);
break;
}
default:
ret = -EBADRQC;
};
return ret;
}
/* Called whenever open() is called on the device file */
static int lwfw_open(struct inode *inode, struct file *file)
{
if (lwfw_ctrl_in_use) {
return -EBUSY;
} else {
MOD_INC_USE_COUNT;
lwfw_ctrl_in_use++;
return 0;
}
return 0;
}
/* Called whenever close() is called on the device file */
static int lwfw_release(struct inode *inode, struct file *file)
{
lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
MOD_DEC_USE_COUNT;
return 0;
}
/*********************************************/
/*
* Module initialisation and cleanup follow...
*/
int init_module()
{
/* Register the control device, /dev/lwfw */
SET_MODULE_OWNER(&lwfw_fops);
/* Attempt to register the LWFW control device */
if ((major = register_chrdev(LWFW_MAJOR, LWFW_NAME,
&lwfw_fops)) < 0) {
printk("LWFW: Failed registering control device!\n");
printk("LWFW: Module installation aborted.\n");
return major;
}
/* Make sure the usage marker for the control device is cleared */
lwfw_ctrl_in_use ^= lwfw_ctrl_in_use;
printk("\nLWFW: Control device successfully registered.\n");
/* Now register the network hooks */
nfkiller.hook = lwfw_hookfn;
nfkiller.hooknum = NF_IP_PRE_ROUTING; /* First stage hook */
nfkiller.pf = PF_INET; /* IPV4 protocol hook */
nfkiller.priority = NF_IP_PRI_FIRST; /* Hook to come first */
/* And register... */
nf_register_hook(&nfkiller);
printk("LWFW: Network hooks successfully installed.\n");
printk("LWFW: Module installation successful.\n");
return 0;
}
void cleanup_module()
{
int ret;
/* Remove IPV4 hook */
nf_unregister_hook(&nfkiller);
/* Now unregister control device */
if ((ret = unregister_chrdev(LWFW_MAJOR, LWFW_NAME)) != 0) {
printk("LWFW: Removal of module failed!\n");
}
/* If anything was allocated for the deny rules, free it here */
if (deny_if)
kfree(deny_if);
printk("LWFW: Removal of module successful.\n");
}
<-->