netfilter禁止ping发送

源代码地址 https://gitee.com/junwuming/Linux-Network/tree/master/Netfilter

实验目的

了解Netfilter框架,掌握Linux内核模块编程的方法,熟悉Netfilter框架进行数据拦截的方法,能够利用Netfilter框架实现网络数据包的控制。

功能描述

能够利用Netfilter框架和钩子函数实现以下功能:

  • 禁止Ping发送;

  • 禁止某个IP地址数据包的接收;

  • 禁止某个端口的数据响应;

需求分析

  1. 禁止ping发送

    需要在发出的包里面检查发出包的类型,禁止ICMP报文发送。即使需要在OUT处挂载钩子函数,过滤ICMP报文

  2. 禁止某个IP地址的数据包接收

    需要在接收的包里面检查包的IP地址,禁止特定IP数据包接收。即使需要在IN处挂载钩子函数,过滤目标IP报文。

  3. 禁止某个端口的数据响应

    需要在接收的包里面检查包的目的端口地址,禁止特定端口的数据包接收。即使需要在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点(新版的内容可能有点区别)

image-20200613122457545

钩子函数

ping模块

在OUT钩子处,挂上如下图所示的功能函数:

image-20200613125947238

IP. . 模块

在IN钩子处,挂上如下图所示的功能函数:

image-20200613130116215

port模块

在IN钩子处,挂上如下图所示的功能函数:

image-20200613130203444

内核交互

使用sockopt实现和内核的交互,进而实现动态配置内核。sockopt有get和set方法,二者的操作方式如下所示:

image-20200613124840907

在本实验中在内核使用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;

}

实验测试

内核交互模块测试

  1. 用户层:读取内核band_status, 设置内核band_status,再次读取更新的band_status作为校验。

    image-20200613204053604

  2. 结果正确

ping模块测试

  1. 打开ping,关闭ip,port模块

    image-20200613204232969

  2. 用户层测试ping baidu.com的结果

    image-20200613204317492

  3. dmesg查看内核日志

    image-20200613204357602

  4. ping实现成功

port模块测试

  1. 打开port模块,关闭其他的模块,以TCP 1234测试为例(UDP是一样的、就不测试了)

    image-20200613204533893

  2. 使用sendip对本机的1234发送TCP报文,查看内核信息(snedip使用man sendip可查)

    其中192.168.224.132为本机的ifconfig出来的ip地址

    image-20200613205716356

    sendip -p ipv4 -p tcp -td 1234 -d ceshidata 192.168.224.132

    image-20200613205046096

    image-20200613205108883

  3. 结果正确

IP模块测试

  1. 开启ip模块,关闭其他的模块(防止可能出现的干扰),设置目标IP 百度的IP (ping一下获得)使用sendip模拟百度数据到发送本机,禁止本机接收来自百度的数据。

  2. 使用sendip模拟百度对本机的发送报文,查看内核信息(snedip使用man sendip可查)

    image-20200613205546031

    image-20200613205620497

    image-20200613205758907

    image-20200613205825508

  3. 结果正确

实验注意

  1. 教材的内核版本为3.x,至今的多为内核4.15以上的版本,数据结构变化。

参考文献

[ 1 ] : 理解 Linux 下的 Netfilter/iptables

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值