netfilter

netfilter 是 linux 网络系统中的一个子系统,从名字也能看出来 netfilter 的作用:网络过滤,linux 内核中网络报文的处理会经历多个协议层,多个环节。netfilter 即在报文处理路径上加上检查点,每个检查点都可以设置一些报文检查规则,报文路过这些检查点的时候会按每个规则进行检查。每个检查点可以放置 1 个或多个规则。如果没有特定需求,netfilter 上边是没有过滤规则的,只有有特定需求的时候,比如要把来源于某个 ip 地址的报文过滤掉,这个时候才需要在检查点放置这个规则。netfilter 有点类似于贴吧中的内容检查,内容检查时有不同的规则,比如涉黄,迷信,造谣,广告,反动等,每条帖子都要经历重重检查,如果有任何一个检查点没有检查通过,则帖子就无法发布。

iptables 是用户态的工具,iptables 工作的基础即内核的 netfilter。常用的报文过滤,网络地址转换(NAT) 均可以通过 iptables 来实现。

netfilter 不仅可以在 ip 层设置检查点,也可以在 arp, netdev 层设置检查点。除了 iptables 还有 ip6tables, arptables 工具,可以分别在 ipv6 和 arp 协议中设置规则。本文中只记录 ip 层的内容。

五链五表

五链是 netfilter 中的内容,五表是 iptables 中的内容。

netfilter 在 ip 层有 5 个检查点,PRE_ROUTING, LOCAL_IN, FORWARD, LOCAL_OUT, POST_ROUTING,每个检查点都可以插入一个或者多个检查规则。多个规则是一条链(实际上在内核中是用一个数组来维护)。

iptables 中的五表分别是 filter, nat, mangle, raw,security 分别可以对报文进行过滤,地址转换,报文数据修改等内容。

五链和五表之间并不是一对一的关系,而是交叉对应的关系。

1 netfilter

1.1 struct netns_nf

每个网络命名空间都有一个 struct netns_nf 结构体,这个结构体中保存着 hooks_ipv4, hooks_ipv6 等检查规则 hook。hooks_ipv4 是一个二维数据,横坐标是检查点类型,即 NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING 中的一个;每一行是这个检查点内添加的规则。

include/net/netns/netfilter.h

struct netns_nf {
#if defined CONFIG_PROC_FS
    struct proc_dir_entry *proc_netfilter;
#endif
    const struct nf_queue_handler __rcu *queue_handler;
    const struct nf_logger __rcu *nf_loggers[NFPROTO_NUMPROTO];
#ifdef CONFIG_SYSCTL
    struct ctl_table_header *nf_log_dir_header;
#endif
    struct nf_hook_entries __rcu *hooks_ipv4[NF_INET_NUMHOOKS];
    struct nf_hook_entries __rcu *hooks_ipv6[NF_INET_NUMHOOKS];
#ifdef CONFIG_NETFILTER_FAMILY_ARP
    struct nf_hook_entries __rcu *hooks_arp[NF_ARP_NUMHOOKS];
#endif
#ifdef CONFIG_NETFILTER_FAMILY_BRIDGE
    struct nf_hook_entries __rcu *hooks_bridge[NF_INET_NUMHOOKS];
#endif
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV4)
    bool            defrag_ipv4;
#endif
#if IS_ENABLED(CONFIG_NF_DEFRAG_IPV6)
    bool            defrag_ipv6;
#endif
};

 

1.2 NF_HOOK

NF_HOOK 即设置检查点,ip 层的 5 个检查点均是通过 NF_HOOK 设置。

NF_HOOK 的入参如下,pf 和 hook 是类型字段,指定检查点的类型,在 ipv4 中 pf 均是 NFPROTO_IPV4, 在不同的位置设置检查点时,hook 的值分别取 NF_INET_PRE_ROUTING, NF_INET_LOCAL_IN, NF_INET_FORWARD, NF_INET_LOCAL_OUT, NF_INET_POST_ROUTING 中的一个;net, sk, skb, in, out 用来标志报文身份,网络命令空间,属于哪个 socket 等;最后一个参数 okfn, 是报文经过改检查点规则检查之后,检查通过之后调用的函数(从名字中也能看出来,okfn, ok 的时候调用的函数)。

NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
    struct net_device *in, struct net_device *out,
    int (*okfn)(struct net *, struct sock *, struct sk_buff *))

NF_HOOK 有两个声明,受编译宏 CONFIG_NETFILTER 控制,如果没有配置 netfilter,那么 NF_HOOK 中会直接调用 okfn,如果配置了 NF_HOOK, 那么报文会先经过规则检查之后,检查通过之后才会调用 okfn。

// 配置了 netfilter
static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk, struct sk_buff *skb,
    struct net_device *in, struct net_device *out,
    int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
    int ret = nf_hook(pf, hook, net, sk, skb, in, out, okfn);
    if (ret == 1)
        ret = okfn(net, sk, skb);
    return ret;
}

// 没有配置 netfilter
static inline int
NF_HOOK(uint8_t pf, unsigned int hook, struct net *net, struct sock *sk,
    struct sk_buff *skb, struct net_device *in, struct net_device *out,
    int (*okfn)(struct net *, struct sock *, struct sk_buff *))
{
    return okfn(net, sk, skb);
}

ip 层 5 个检查点所在的位置如下图所示:

PRE_ROUTING: 当 ip 层收到报文时,首先检查报文的合法性(校验和,头长度),然后进入 PRE_ROUTING 检查,PRE_ROUTING 检查 ok 之后调用 ip_rcv_finish() 函数,在这个函数中做路由,如果目的 ip 是本机地址,那么就会调用 ip_local_deliver() 将报文上传到上一层; 如果需要转发,那么就会调用ip_forward() 转发报文。

LOCAL_IN: 报文目的 ip 是本机地址的话会经过这个检查点

FORWARD: 需要转发的报文经过这个检查点

LOCAL_OUT: 本地发送的报文经过这个检查点

POST_ROUTING: 发送的报文经过这个检查点,报文可能是转发过来的,也有可能是本机产生向外发送的

根据数据的流向,可以将数据流分成 3 种:

接收流:网络接口 --> PRE_ROUTING --> LOCAL_IN --> 本地

转发流:网络接口 --> PRE_ROUTING --> FORWARD --> POST_ROUTING --> 网络接口

发送流:本地 --> LOCAL_OUT --> POST_ROUTING --> 网络接口

netfilter_第1张图片

 

1.3 NF_ACCEPT, NF_DROP, NF_QUEUE 

nf 检查点上规则的遍历是在 nf_hook_slow 函数中进行。从代码中也可以看出来,在一个检查点上多个规则通过一个数组来维护,用一个 for 循环来遍历规则。每个规则都会注册一个回调函数,遍历规则就是把报文(skb) 传递给回调函数,回调函数的返回值有 3 种,NF_ACCEPT, NF_DROP, NF_QUEUE。

NF_ACCEPT

接收这个报文,直接 break, 后边的规则不再检查。

返回值是 1,返回之后会继续执行 okfn 对报文做后续处理。

NF_DROP

丢弃报文, 后续不再处理

NF_QUEUE

把报文加入到队列,下一个规则继续处理。

int nf_hook_slow(struct sk_buff *skb, struct nf_hook_state *state,
         const struct nf_hook_entries *e, unsigned int s)
{
    unsigned int verdict;
    int ret;

    for (; s < e->num_hook_entries; s++) {
        verdict = nf_hook_entry_hookfn(&e->hooks[s], skb, state);
        switch (verdict & NF_VERDICT_MASK) {
        case NF_ACCEPT:
            break;
        case NF_DROP:
            kfree_skb(skb);
            ret = NF_DROP_GETERR(verdict);
            if (ret == 0)
                ret = -EPERM;
            return ret;
        case NF_QUEUE:
            ret = nf_queue(skb, state, s, verdict);
            if (ret == 1)
                continue;
            return ret;
        default:
            /* Implicit handling for NF_STOLEN, as well as any other
             * non conventional verdicts.
             */
            return 0;
        }
    }

    return 1;
}

 

1.4 netfilter 使用

通过内核模块可以向 5 个检查点加载报文处理规则。

如下内核模块向 PRE_ROUTING 注册一个规则,丢掉 icmp 报文。ping 命令收发的报文就是 icmp 报文,可以在安装模块之前 ping 某个地址,可以 ping 通,然后 insmod nf_icmp.ko 安装模块,安装模块之后就 ping 不通了,以此来验证是否生效。

struct nf_hook_ops:

这个数据结构中指定要注册的规则的 hook 函数,以及这个规则注册在哪个检查点,以及这个规则的优先级。

nf_register_net_hook:

向 netfilter 注册规则,第一个参数是指定一个网络的命名空间,init_net 是 linux 默认的网络命名空间。

nf_unregister_net_hook:

从 netfilter 注销规则。

#include 
#include 
#include 
#include 
#include 
#include 

static struct nf_hook_ops nf_ops;

unsigned int nf_hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state)
{
  struct iphdr *iph = NULL;
  if (!skb) {
    printk("skb is null\n");
    return NF_ACCEPT;
  }

  iph = ip_hdr(skb);
  printk("proto: 0x%x\n", iph->protocol);
  if (iph->protocol == IPPROTO_ICMP) {
    printk("icmp, drop\n");
    return NF_DROP;
  }

  return NF_ACCEPT;
}

static int nf_icmp_init(void)
{
  printk("nf icmp module init\n");

  nf_ops.hook      = nf_hook_func;
  nf_ops.hooknum   = NF_INET_PRE_ROUTING;
  nf_ops.pf        = PF_INET;
  nf_ops.priority  = NF_IP_PRI_FIRST;

  nf_register_net_hook(&init_net, &nf_ops);

  return 0;
}

static void nf_icmp_exit(void)
{
  printk("nf icmp module exit\n");
  nf_unregister_net_hook(&init_net, &nf_ops);
}

module_init(nf_icmp_init);
module_exit(nf_icmp_exit);

MODULE_LICENSE("GPL");
obj-m += nf_icmp.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

2 iptables

2.1 介绍

iptables,ip 说明这个工具工作在 ip 层,tables 说明这个工具的工作方式,通过多个表来工作。

iptables 基于 netfilter 来实现,常常被用来做防火墙,通过 iptables 可以对报文进行过滤,修改或者 NAT。

iptables 包括的概念比较多,功能很丰富,当然也很复杂,本文中只对一些基本的概念和操作做梳理。

如果操作过一些网络设备商(华三,华为,思科) 的设备的话,我们可以发现 linux 中的 iptables 与这些网络设备中的 acl(access control list) 功能是类似的,主要功能即对报文进行过滤。

iptables 中有 5 种表, 分别是 filter, nat, mangle, raw, security,每种表的解释如下:

filter

对报文进行过滤,ACCEPT 或 DROP。

nat

报文地址转换,可以对目的 ip 和端口号,或者源 ip 和端口号进行转换。nat 是我们常说的内网和外网的分界线,内网和外网也常称为私网和公网。nat 的出现延缓了 ipv4 地址的耗尽。

像 docker 这种虚拟机只能怪也会用到 nat。

在 windows 笔记本上安装的 ubunt 虚拟机也可以将网络设置成 nat 模式。

mangle

可以修改数据包的一些标志位,或者给数据包打上标签,然后可以根据标签做报文过滤或者策略路由。

raw

决定数据包是否被状态跟踪机制处理

security

安全相关

 

几个概念:

下边这几个概念也可以说是 iptables 配置命令的组成元素,在使用 iptables 设置规则的时候,这几个元素是必要的。

表:iptables 中的表,就是上边的 5 个表,

链:linux 网络协议栈 ip 层的 5 个链

匹配:指定要操作的是什么数据类型的数据包, icmp ? tcp ? udp ? 端口号是多少 ?

目标:针对匹配规则的数据应该采取什么样的操作,ACCEPT ? DROP ? ...?

举例:

iptables -t filter -I INPUT -p icmp -j REJECT

这个命令可以达到丢弃接收到的 icmp 报文的目的,配置规则之后,就 ping 不通网址了,比如 ping www.baidu.com。

-t filter 指定了表

-I INPUT 指定了链

-p icmp 指定了匹配规则,只对 icmp 协议有效

-j REJECT 目标,对匹配的报文进行丢弃,并向对端发送不可达信息

使用 iptables 命令时,如果不适用 -t 指定表,那么默认操作的是 filter 表。

iptables 中的五表和 netfilter 中的五链并不是 一一对应的或者谁包含谁的关系,而是交叉对应的关系。

PRE_ROUTING

LOCAL_IN

FORWARD

LOCAL_OUT

POST_ROUTING

filter

nat

mangle

raw

security

 

上表中 iptables 表和 netfilter 链之间的对应关系,也可以通过命令行来查看,

iptables -t filter -L 可以查看 filter 表,从下图中可以看出,filter 表的规则可以设置到 INPUT, FORWARD, OUTPUT 3 个链上。

netfilter_第2张图片

下图显示了 nat 表,nat 规则除了不使用 FORWARD 链,其它 4 个链都会使用。

netfilter_第3张图片

 

2.2 举例

nat:

iptables -t nat -A PREROUTING -i eth0 -d 10.194.139.67 -p tcp --dport 6161 -j DNAT --to 172.16.0.2:6161

在调研 firecracker(一种类似于 docker 的虚拟机) 的时候,一开始两台机器上的 firecracker 不通,配置了这条规则以后,就通了。

filter:

iptables -I INPUT -p icmp -j REJECT

不接收 icmp 报文。

raw:

iptables -t raw -A PREROUTING -d 1.2.3.4 -p tcp --dport 80 -j NOTRACK

conntrack 功能停掉。

mangle:

iptables -t mangle -A PREROUTING -i ens32 -j TTL --ttl-inc 1

ttl 是每转发一次就会减去 1,到 0 的时候就不转发了,如果想让本台机器这个环节不减去 1,就可以设置 mangle 规则,每次加上 1,这样就不减了

你可能感兴趣的:(网络)