源代码地址
https://gitee.com/junwuming/Linux-Network/tree/master/Netfilter
实验目的
了解Netfilter框架,掌握Linux内核模块编程的方法,熟悉Netfilter框架进行数据拦截的方法,能够利用Netfilter框架实现网络数据包的控制。
功能描述
能够利用Netfilter框架和钩子函数实现以下功能:
-
禁止Ping发送;
-
禁止某个IP地址数据包的接收;
-
禁止某个端口的数据响应;
需求分析
-
禁止ping发送
需要在发出的包里面检查发出包的类型,禁止ICMP报文发送。即使需要在OUT处挂载钩子函数,过滤ICMP报文
-
禁止某个IP地址的数据包接收
需要在接收的包里面检查包的IP地址,禁止特定IP数据包接收。即使需要在IN处挂载钩子函数,过滤目标IP报文。
-
禁止某个端口的数据响应
需要在接收的包里面检查包的目的端口地址,禁止特定端口的数据包接收。即使需要在IN处挂载钩子函数,过滤目标报文。
实验环境
Linux zhao 4.15.0-106-generic #107~16.04.1-Ubuntu SMP Thu Jun 4 15:40:05 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
实验模块
实验分为三个模块
- 钩子模块
- 钩子函数
- 内核交互
综合部分:首先在内核实现ping、IP、port三个部分的功能,然后在用户层通过内核交互动态配置内核模块的功能。并检测是否有效!
钩子模块
备注:原理是针对内核3.x(实验是针对新版本的内核)
使用Netfilter模块在网络通信的各个模块设计hook点:下图为Linux内核为3.x的网络编程hook点(新版的内容可能有点区别)
钩子函数
ping模块
在OUT钩子处,挂上如下图所示的功能函数:
IP. . 模块
在IN钩子处,挂上如下图所示的功能函数:
port模块
在IN钩子处,挂上如下图所示的功能函数:
内核交互
使用sockopt实现和内核的交互,进而实现动态配置内核。sockopt有get和set方法,二者的操作方式如下所示:
在本实验中在内核使用band_status
数据结构,从用户层读取内核band_status
交互设置 band_status
从而开启或者关闭内核的功能!
实验编码
OUT钩子函数
static unsigned int nf_hook_out(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *iph;
if (!skb)
return(NF_ACCEPT);
iph = ip_hdr(skb);
unsigned int dest_ip = iph->daddr;
if(iph->protocol==IPPROTO_ICMP){ /*ICMP协议*/
/*丢弃ICMP报文*/
if (IS_BANDPING(b_status)) /*设置了禁止PING操作*/
{
// 注意是大端对齐 字节序
printk(KERN_ALERT "DROP ICMP packet from %d.%d.%d.%d\n",
(dest_ip & 0x000000ff) >> 0,
(dest_ip & 0x0000ff00) >> 8,
(dest_ip & 0x00ff0000) >> 16,
(dest_ip & 0xff000000) >> 24);
return(NF_DROP); /*丢弃该报文*/
}
}
return(NF_ACCEPT);
}
IN钩子函数
/* 接收数据、 端口、IP地址 */
/* 在LOCAL_IN挂接钩子 */
static unsigned int nf_hook_in(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
struct iphdr *iph;
if (!skb)
return(NF_ACCEPT);
iph = ip_hdr(skb);
unsigned int src_ip = iph->saddr;
if (IS_BANDIP(b_status)) /*判断是否禁止IP协议*/
{
/* TODO ? ip->saddr or ip->daddr */
if (b_status.band_ip == iph->saddr) /*接收报文的源IP地址符合*/
{
printk(KERN_ALERT "DROP IP packet from %d.%d.%d.%d\n",
(src_ip & 0x000000ff) >> 0,
(src_ip & 0x0000ff00) >> 8,
(src_ip & 0x00ff0000) >> 16,
(src_ip & 0xff000000) >> 24);
return(NF_DROP); /*丢弃该网络报文*/
}
}
struct tcphdr *tcph = NULL;
struct udphdr *udph = NULL;
unsigned short port_local = ntohs(b_status.band_port.port);
switch (iph->protocol) /*IP协议类型*/
{
case IPPROTO_TCP: /*TCP协议*/
/*丢弃禁止端口的TCP数据*/
if (IS_BANDPORT_TCP(b_status))
{
tcph = tcp_hdr(skb); /*获得TCP头*/
/*端口匹配*/
if (tcph->dest == b_status.band_port.port)
{
printk(KERN_ALERT "DROP TCP port %d IP packet from %d.%d.%d.%d\n",
port_local,
(src_ip & 0x000000ff) >> 0,
(src_ip & 0x0000ff00) >> 8,
(src_ip & 0x00ff0000) >> 16,
(src_ip & 0xff000000) >> 24);
return(NF_DROP); /*丢弃该数据*/
}
}
break;
case IPPROTO_UDP: /*UDP协议*/
/*丢弃UDP数据*/
if (IS_BANDPORT_UDP(b_status)) /*设置了丢弃UDP协议*/
{
udph = udp_hdr(skb); /*UDP头部*/
if (udph->dest == b_status.band_port.port) /*UDP端口判定*/
{
printk(KERN_ALERT "DROP UDP port %d IP packet from %d.%d.%d.%d\n",
port_local,
(src_ip & 0x000000ff) >> 0,
(src_ip & 0x0000ff00) >> 8,
(src_ip & 0x00ff0000) >> 16,
(src_ip & 0xff000000) >> 24);
return(NF_DROP); /*丢弃该数据*/
}
}
break;
default:
break;
}
return(NF_ACCEPT);
}
sockopt钩子
/* 初始化nf套接字选项 */
static struct nf_sockopt_ops nfsockopt = {
.pf = PF_INET,
.set_optmin = SOE_BANDIP,
.set_optmax = SOE_BANDIP+2,
.set = nf_sockopt_set,
.get_optmin = SOE_BANDIP,
.get_optmax = SOE_BANDIP+2,
.get = nf_sockopt_get,
};
内核初始化
static int __init ban_init(void)
{
b_status.band_ip = 0;
b_status.band_port.protocol=0;
b_status.band_port.port=0;
b_status.band_ping=0;
nf_register_net_hook(&init_net, &nf_ops_in);
nf_register_net_hook(&init_net, &nf_ops_out);
nf_register_sockopt(&nfsockopt); /*注册扩展套接字选项*/
printk(KERN_ALERT "netfilter example 2 install successfully\n");
return NF_SUCCESS;
}
static void __exit ban_exit(void)
{
nf_unregister_net_hook(&init_net, &nf_ops_in);
nf_unregister_net_hook(&init_net, &nf_ops_out);
nf_unregister_sockopt(&nfsockopt); /*注册扩展套接字选项*/
printk(KERN_ALERT "netfilter example 2 clean successfully\n");
}
Makefile
CONFIG_MODULE_SIG=n
obj-m += zjc.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
用户测试程序
使用sockopt的get和set和内核通信,实现band_status数据结构的读取和设置:
int main(int argc, char const *argv[]) {
setbuf(stdout, NULL);
socklen_t len = sizeof(status);
int sockfd;
printf("打开设备\n");
if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1) {
perror("设备打开失败");
return -1;
}
printf("读取设备");
if (getsockopt(sockfd, IPPROTO_IP, SOE_BANDPING, (void *) &status, &len)) {
perror("读取失败");
return -1;
}
get_band_status();
printf("设置内核\n");
set_band_status();
if (setsockopt(sockfd, IPPROTO_IP, SOE_BANDPING, &status, len)) {
perror("设置内核失败\n");
return -1;
}
if (setsockopt(sockfd, IPPROTO_IP, SOE_BANDIP, &status, len)) {
perror("设置内核失败\n");
return -1;
}
if (setsockopt(sockfd, IPPROTO_IP, SOE_BANDPORT, &status, len)) {
perror("设置内核失败\n");
return -1;
}
if (getsockopt(sockfd, IPPROTO_IP, SOE_BANDPING, (void *) &status, &len)){
return -1;
}
get_band_status();
return 0;
}
实验测试
内核交互模块测试
-
用户层:读取内核band_status, 设置内核band_status,再次读取更新的band_status作为校验。
-
结果正确
ping模块测试
-
打开ping,关闭ip,port模块
-
用户层测试ping baidu.com的结果
-
dmesg查看内核日志
-
ping实现成功
port模块测试
-
打开port模块,关闭其他的模块,以TCP 1234测试为例(UDP是一样的、就不测试了)
-
使用
sendip
对本机的1234发送TCP报文,查看内核信息(snedip使用man sendip可查)其中192.168.224.132为本机的ifconfig出来的ip地址
sendip -p ipv4 -p tcp -td 1234 -d ceshidata 192.168.224.132
-
结果正确
IP模块测试
-
开启ip模块,关闭其他的模块(防止可能出现的干扰),设置目标IP 百度的IP (ping一下获得)使用sendip模拟百度数据到发送本机,禁止本机接收来自百度的数据。
-
使用
sendip
模拟百度对本机的发送报文,查看内核信息(snedip使用man sendip可查) -
结果正确
实验注意
- 教材的内核版本为3.x,至今的多为内核4.15以上的版本,数据结构变化。
参考文献
[ 1 ] : 理解 Linux 下的 Netfilter/iptables