默认分类 17 0

    Netfilter的二层实现

    Netfilter的二层实现

            三层Netfilter的文档很多,很多时候查一下就能获取到。而使用二层Netfilter的文档经常让没有相应基础的人感觉不知所云。因此,我觉得写个二层Netfilter的使用文档,能帮助更多和我一样的使用者。

    还是IPtables、EBtables说起

            Iptables是unix/linux自带的一款优秀且开放源代码的基于包过滤(对OSI模型的四层或者是四层以下进行过滤)的防火墙工具,它的功能十分强大,使用非常灵活,可以对流入和流出服务器的数据包进行很精细的控制,属于网络层防火墙,使用Netfilter进行底层实现。

            Netfilter是由Rusty Russell提出的自Linux 2.4添加的内核防火墙框架,该框架既简洁又灵活,可实现安全策略应用中的许多功能,如数据包过滤、数据包处理、地址伪装、透明代理、动态网络地址转换(Network Address Translation,NAT),以及基于用户及媒体访问控制(Media AccessControl,MAC)地址的过滤和基于状态的过滤、包速率限制等。

            Netfilter的架构就是在数据包在通过网络协议栈的若干位置放置了一些检测点(HOOKS),不同防火墙功能的子模块通过netfilter框架提供的API注册相应的HOOK回调函数,并在回调函数中实现对数据包的过滤、解包修改后重新打包等操作。已注册的HOOK回调函数会在数据包流经netfilter框架HOOK点时被系统自动调用执行。

            Ebtables和iptables类似,都是Linux系统下网络数据包过滤的配置工具。ebtables是以太网桥防火墙,以太网桥工作在数据链路层,ebtables来过滤数据链路层数据包。 2.6以后的内核内置了ebtables,但需要启用对应模块才能对Bridge二层报文进行处理

    二层Netfilter转发框架

            默认情况下,只经过网桥的流量不会被iptables处理。为了让网桥上的流量经过iptables处理,应用层的做法是使用如下命令(规则要匹配双向流量或双向分别使用各自规则):

    sudo ebtables -t broute -A BROUTING -p IPv4 --ip-proto tcp -j redirect --redirect-target DROP

            其作用是强制桥上所有tcp包走路由而非桥,不足是broute表的redirect将数据包的接收设备设置成了桥物理端口而非桥设备自己,导致后续iptables nat表PREROUTING链只能使用DNAT取代REDIRECT。

    优雅的做法是使用如下命令:

    sudo modprobe br_netfilter

            其作用是让桥直接在二层调用iptables,然后继续进行二层转发。可以通过抓包工具验证前者ttl比后者ttl小1,那是因为前者走了路由(必须启用ip_forward,否则无效),而后者直接通过br_forward转发(无需启用ip_forward)。
    在同时启用上述两种方法的情形下,对于同一个数据包,如符合broute规则,就强制走路由,直接在三层调用iptables;如不符合broute规则,就通过br_netfilter在二层调用iptables。在二层调用iptables后,如二层数据包的最终目标MAC为多播MAC或网桥MAC(或网桥处于promisc模式),该数据包仍将移交三层处理。通过br_netfilter注册的ip_sabotage_in钩子函数,可以避免同一数据包再次遍历三层PREROUTING链(同一数据包不可能同时经过二层和三层的FORWARD或POSTROUTING链)。

    58e238faf170a4582597aed6c4737287.png
    58e238faf170a4582597aed6c4737287.png

     如果体现在内核配置上,则是打开功能:

    CONFIG_BRIDGE_NETFILTER

    Br_netfilter和netfilter的联动

    以Linux3.0.8为例,打开CONFIG_BRIDGE_NETFILTER以后,在br.c网桥初始化函数__init br_init(void)中,br_netfilter_init()函数定义为

    /* br_netfilter.c */
    #ifdef CONFIG_BRIDGE_NETFILTER
    extern int br_netfilter_init(void);
    extern void br_netfilter_fini(void);
    extern void br_netfilter_rtable_init(struct net_bridge *);
    #else
    #define br_netfilter_init()    (0)
    #define br_netfilter_fini()    do { } while(0)
    #define br_netfilter_rtable_init(x)
    #endif

    于网桥设备初始化的时候,初始化br_netfilter。调用br_netfilter_init时,会注册一系列钩子函数到netfilter。

    ret = nf_register_hooks(br_nf_ops, ARRAY_SIZE(br_nf_ops));

    以br_nf_ops中的br_nf_forward_ip进行说明

    /* br_netfilter.c */
    
    //其中一个注册的函数,
        {
            .hook = br_nf_forward_ip,    //
            .owner = THIS_MODULE,
            .pf = PF_BRIDGE,
            .hooknum = NF_BR_FORWARD,
            .priority = NF_BR_PRI_BRNF - 1,
        }
    
    //br_nf_forward_ip的定义为
    static unsigned int br_nf_forward_ip(unsigned int hook, struct sk_buff *skb,
                         const struct net_device *in,
                         const struct net_device *out,
                         int (*okfn)(struct sk_buff *))
    {
        struct nf_bridge_info *nf_bridge;
        struct net_device *parent;
        u_int8_t pf;
    
        if (!skb->nf_bridge)
            return NF_ACCEPT;
    
        /* Need exclusive nf_bridge_info since we might have multiple
         * different physoutdevs. */
        if (!nf_bridge_unshare(skb))
            return NF_DROP;
    
        parent = bridge_parent(out);
        if (!parent)
            return NF_DROP;
    
        if (skb->protocol == htons(ETH_P_IP) || IS_VLAN_IP(skb) ||
            IS_PPPOE_IP(skb))
            pf = PF_INET;
        else if (skb->protocol == htons(ETH_P_IPV6) || IS_VLAN_IPV6(skb) ||
             IS_PPPOE_IPV6(skb))
            pf = PF_INET6;
        else
            return NF_ACCEPT;
    
        nf_bridge_pull_encap_header(skb);
    
        nf_bridge = skb->nf_bridge;
        if (skb->pkt_type == PACKET_OTHERHOST) {
            skb->pkt_type = PACKET_HOST;
            nf_bridge->mask |= BRNF_PKT_TYPE;
        }
    
        if (pf == PF_INET && br_parse_ip_options(skb))
            return NF_DROP;
    
        /* The physdev module checks on this */
        nf_bridge->mask |= BRNF_BRIDGED;
        nf_bridge->physoutdev = skb->dev;
        if (pf == PF_INET)
            skb->protocol = htons(ETH_P_IP);
        else
            skb->protocol = htons(ETH_P_IPV6);
    
        NF_HOOK(pf, NF_INET_FORWARD, skb, bridge_parent(in), parent,
            br_nf_forward_finish);
    
        return NF_STOLEN;
    }

    br_nf_forward_ip最终调用到了网络层的NF_INET_FORWARD钩子,从而实现了桥转发调用netfilter。

    Br_netfilter 和Netfilter定义区别

    协议类型定义:

    Br_netfilterNetfilter定义值
    NFPROTO_UNSPEC0
    NFPROTO_IPV42
    NFPROTO_ARP3
    PF_BRIDGENFPROTO_BRIDGE7
    NFPROTO_IPV610
    NFPROTO_DECNET12

    挂载点定义

    Br_netfilterNetfilter定义值
    NF_BR_PRE_ROUTINGNF_INET_PRE_ROUTING0
    NF_BR_LOCAL_INNF_INET_LOCAL_IN1
    NF_BR_FORWARDNF_INET_FORWARD2
    NF_BR_LOCAL_OUTNF_INET_LOCAL_OUT3
    NF_BR_POST_ROUTINGNF_INET_POST_ROUTING4
    NF_BR_BROUTING 5

    可以看到,协议的实际定义是一样的,因此在进行自定义钩子函数编程的时候,只要打开CONFIG_BRIDGE_NETFILTER,允许我们不关注实际IP层与数据链路层的交互,只定义协议类型和调用号即可。br_netfilter仅可用于网桥设备。!