开源路由器系统——VyOS服务网络隔离与防火墙配置

我们在之前的文章中,已经重点谈论了有关VyOS的基础用法,这已经足够让VyOS来管理一个小型的家庭网络了。不过对你来说,我的朋友,这可能还远远不够。因为你可能有各种各样的网络情况,想要使用VyOS对网络进行更加精细的控制,或者想要将您的IPv4公网地址利用起来,或者想要将您的家庭内网与对外提供网络服务的内网隔离起来……别担心,我们接下来就来深入研究一下这些进阶的问题。

这篇文章主要涵盖的问题有

  • 再会防火墙:IP/Bridge规则、Input/Forward Hook、转发、Prerouting/Postrouting Hook、TCP、UDP、ICMP与ICMPv6,ConnTrack等核心概念
  • IPv4/v6防火墙安全性控制:允许哪些流量?
  • 家庭网络区域控制:如何用防火墙隔离普通家庭网络和服务网络?
  • IPv4端口转发(DNAT)、DMZ主机:将来自公网的IPv4数据包转发到不同的网络接口中。
  • NerChat! Relink可用区网络配置方案

本文推荐具有一定计算机网络基础的读者阅读,尤其是当您希望在家庭网络中部署对外开放的网络服务时,本文中所介绍的内容会特别有用。

在阅读本文之前,不妨试试先看看我的上一篇有关VyOS基本用法的文章——当然,不看也是没关系的。本文的内容与上一篇文章相对独立,提供对防火墙配置的详细见解,完全可以把思路用在其他的路由器系统上。

再注,本文只针对运营商下发IPv6前缀的情况进行针对性配置,如果您运营商并没下发IPv6前缀,可以考虑看看光猫中是否有“允许DHCP透传”类似的选项,将它打开。

farewell,firewall.

我们需要精细的控制网络流量。因此在更深一步之前,我们不得不更深刻的认识我们的老朋友,Linux防火墙——netfilter。

netfilter的学习是一个漫长而艰辛的工作,涉及到多种协议和底层的知识。但切记我们认知防火墙和应用防火墙,不需要完全掌握所有的知识,所以本文提倡循序渐进学习的原则,只会给出一套基本的理论,帮助读者快速掌握netfilter的基础知识,这样大家就可以在想要自学的时候随时学习了。

VyOS的防火墙和nftables几乎没有任何区别,它们的名字、行为、配置方法和思路非常接近。实际上,你可以完全按照nftables的逻辑来理解和配置VyOS的防火墙,非常方便快捷。Netfilter为防火墙的行为绘制了一个非常详细的、已经高度简化后的示意图,之后的内容请配合防火墙的示意图理解。

流量的分层

Linux的防火墙将所有操作系统处理的数据包和抽象的连接,分成了四层:

  • Application层:系统中应用发送和接收的网络流量
  • IP层:处理不同网桥、不同桥的网络接口间的数据流动和网络接口与应用程序间的数据流动,处理IP层报文
  • Bridge层:处理网桥中的数据流动,处理以太网帧
  • ARP层:处理所有的ARP数据流动,处理ARP报文

我们主要的工作都集中在IP层——防火墙的一切在这里开始。我们需要面对的重要IP层协议包括TCP、UDP、IP、IPv6、ICMP、ICMPv6,我们的动作有:修改、放行、丢弃。所谓防火墙,就是用规则匹配不同的数据包,然后再对这些数据包施以统一的动作。所以,防火墙规则可以简化成(匹配规则, 动作)的元组。

IP层防火墙规则的Hook点与优先级

整个Linux网络栈是一个整体,我们不能随意控制其中的流量,我们只能在指定的位置添加规则。这些可以添加规则的地方被称为“Hook点”。

在IP层,我们需要重点关注的Hook点有四个:Input、Forward、Prerouting和Postrouting。

对每个Hook点,可以挂很多个(匹配规则, 动作)的规则上去,但这些规则谁先执行,谁后执行呢?这就是优先级(priorities)起作用的时候了。

每个防火墙规则,实际上都有一个优先级,当数据包抵达Hook point的时候,会按优先级从小到大的顺序执行这些规则。

关于优先级,有一些约定俗成的规定,比如SNAT默认工作在100优先级,DNAT默认工作在-100优先级等等,后面会提到。

  • Prerouting:数据包刚刚抵达IP栈,此时还不知道数据包的目标接口。这是DNAT主要工作的地方,DNAT将公网收到的数据包的目标地址改为某个内网主机的内网地址,从而实现端口转发。这里也是conntrack开始追踪连接的位置。
  • Input:数据包的目标是本地网络服务,比如本地连接的返回流量、公网尝试连接路由器TCP端口等。
  • Forward:数据包的目标不是本地地址,需要转发到其他网络接口。比如来自公网的、目标地址是路由器的下级主机的数据包。
  • Postrouting:数据包接近出站,这是SNAT主要工作的地方,SNAT的主要作用是将一个由下级主机发送的IPv4数据包的源地址改为路由器的IPv4地址,从而实现在内网到外网之间的数据包的路由。

一般情况下,除SNAT外,防火墙不需要处理出站的流量,所以我们几乎所有的时间都只在处理入站流量。

常用匹配规则

防火墙提供精细的流量控制,为了实现对流量的精细匹配,防火墙可以解开几乎所有常用的协议的数据包,去匹配您提供的规则。这些可以匹配的规则实在太多,因此根据循序渐进的原则,我们主要关注如下匹配规则项: 源接口、目的接口(不一定存在)、源/目标IP/IPv6地址、源/目标端口(对TCP/UDP)、协议类型、ICMP/ICMPv6的具体协议类型(如RA、echo-reply等)、ct状态

大多数规则都比较直观,这个ct状态指的是ConnTrack状态:new、established、related、invalid和untracked。

状态 描述
new Netfilter 目前只在这对主机之间看到了单向数据包。至少有一个数据包是有效初始化序列的一部分,例如 TCP 连接的 SYN 数据包。
established Netfilter 已经看到有效数据包在这对主机之间双向传输。对于 TCP 连接,三次握手已成功完成。
related 该连接是在主连接之后发起的,符合主连接的正常操作。常见例子是在 FTP 控制通道的要求下建立的 FTP 数据通道。
invalid 分配给不遵循连接预期行为的数据包。
untracked 分配给已被明确排除在连接跟踪之外的数据包的虚拟状态。

我们没有手动修改conntrack逻辑的时候,只需要考虑new,established,related和invalid四个状态即可。默认情况下established,related都是直接放通即可,通过控制匹配conntrack new的数据包来控制是否允许外部访问,这样做的好处就是,不会彻底阻断从外向内的数据包,可以允许已经正常合法建立的连接能够正常继续通信不受默认防火墙阻断规则的干扰,但不允许新的非法连接建立。

防火墙要允许哪些流量?

防火墙的三大动作:修改、放行、丢弃,所有防火墙默认丢弃所有Input和Forward的流量,默认允许所有Output的流量,我们只讨论定义在Input和Forward上的放行规则。

路由器的两大接口:内网网桥(下文中的eth1),公网接口(下文中的pppoe0)。

路由器要处理的四大协议:tcp/udp、IP/IPv6、ICMP/ICMPv6

常见的流量与匹配规则

一些常见的流量,比如DNS和DHCP,在防火墙中需要按其底层协议匹配。这些常见协议如下:

流量类型 IPv4 IPv6 基本匹配规则 conntrack状态匹配规则
SSH请求 目标端口是22的tcp new
DNS请求 目标端口是53的udp new
DHCP请求 源端口是68、目标端口是67的udp new
echo PING请求 类型为echo-request的icmp new
echo PING6请求 类型为echo-request的icmpv6 new

尽管udp和icmp包和他们的协议可能无状态,但仍然建议使用conntrack标记这些包的状态。需要严格限制包的合法性,防止包泄露。

注意这里并不需要DHCPv6,因为内网设备获得地址不需要通过DHCPv6实现,而路由器向公网的DHCP请求又有conntrack处理,默认放通,因此不需要关心。

此外,有关IPv6网络的邻居发现协议(ND)也需要注意。这些协议对IPv6网络的正常运行至关重要。这些就不需要conntrack,在指定的接口放通即可。

流量类型 匹配规则
路由广播(RA) 类型为nd-router-advert的icmpv6
路由收集(RS) 类型为nd-router-solicit的icmpv6
邻居广播(NA) 类型为nd-neighbor-solicit的icmpv6
邻居收集(NS) 类型为nd-neighbor-advert的icmpv6

根据不同协议的数据包在不同接口间的流动,可以认为路由器的防火墙可以选择仅放行如下流量:

标记为可选、推荐等的规则,建议根据实际网络情况自行选配,比如是否开启了DNS服务器,是否运行DHCP服务器等等。

  1. input(目标为路由器的流量)

    连接来源 连接类型 规则目的 是否必要
    所有 ct state=established/related 允许已建立的连接通过,包括路由器向外建立连接的正常响应以及用new状态控制新连接 必要
    内网 DNS请求 允许内网查询路由器运行的内网DNS服务器 可选
    内网 DHCP请求 允许内网请求路由器DHCP服务器 推荐
    内网 PING请求、PING6请求 允许内网主机PING路由器 可选
    内网 RS 允许内网主机请求路由器广播 必要
    内网 NS 允许内网主机请求发现路由器(防止潜在的IPv6地址冲突等) 必要
    内网 SSH 允许内网主机连接路由器ssh控制路由器 可选
    公网 RA 允许路由器通过RA获知公网使用的地址和前缀分配机制(通常是DHCPv6) 必要
    公网 PING请求,PING6请求 允许公网PING路由器 可选

    注意我们并没有配置任何DHCPv6,因为内网不需要通过DHCPv6获得地址,公网的DHCPv6响应会被conntrack跟踪。

    注意对于dnat,由于在prerouting阶段完成的dnat修改后,目标地址改为路由器的内网地址,因此不会走input hook,会走forward。

  2. forward(路由器内网向公网、公网向内网的流量)

    连接来源 连接类型 规则目的 是否必要
    所有 ct state=established/related 允许已建立的连接通过,包括内网向外建立连接的正常响应以及用new状态控制新连接 必要
    内网 新连接(ct state=new) 允许内网所有主动向外的连接,包括v4和v6 必要
    公网 IPv4 TCP/UDP新连接 允许来自公网的端口转发流量访问,一般用于有公网v4时的路由器级端口转发或DMZ主机 可选
    公网 PING请求、PING6请求 允许来自公网的主机PING内网主机 可选
    公网 普通的IPv6 TCP/UDP 新连接 允许来自公网的流量直接访问内网主机,通常用于允许公网访问内网IPv6服务器。 可选

    注意我们允许所有来自公网的IPv4 TCP/UDP新连接通过forward转发,是因为IPv4 NAT配合conntrack天然阻断了向内的访问,所以只需要配置好DNAT规则,就可以很好的控制DNAT流量,不需要在防火墙处做额外的限制。

区域控制:网络隔离

我们注意到,如果在内网需要开一个对公网提供服务的主机,它必须采用和普通内网设备不同的防火墙策略。也就是说,需要在内网中分成两个区域:“公开内网”和“私有内网”。这两个区域可以分属于路由器中两个不同的网桥控制,我们分配给他们不同的IPv4、IPv6网段,然后我们对两个网桥施以不同的防火墙规则——注意,两个网桥“开放”和“内部”是相对的,两个网桥的地位都是一样的,都属于路由器分管的内网。

如何划分不同的子网呢?我们还记得VyOS中有关IPv4和IPv6子网的配置方法,现在我们需要扩展到多个子网的情况:

  • IPv4
    • 内网的IPv4地址首先需要给网桥分配一个网段,对这种开放网和内部网而言,两个网桥应该对应两个不同的IPv4网段。比如192.168.1.1/24和192.168.2.1/24,注意网桥的地址段本身就带有地址,也就是说这种情况下路由器会有2个不同的内网地址。
    • 内网的IPv4地址分配完全是由DHCP服务器指派的,DHCP服务器可以划分两个不同的shared-network,他们有各自的default-router和name-server。
    • 内网的SNAT规则中,增加一个rule,转发另一条网桥的流量。
  • DNS
    • DNS服务器的listen-address监听两个内网网桥的地址。
  • IPv6
    • 前缀委派时,需要将前缀拆成2个子网,委派到2个不同的网桥上。interface br0中有sla-id "0",添加一个interface与sla-id的项即可。
    • 在两个接口上配置RA广播服务。

防火墙配置

两个网桥的防火墙配置几乎相同,与上面的配置思路基本一致,但“可选”的部分存在差异,并且两个网桥之间的流量也需要控制。

因此在上表的基础上做如下修改:

  • INPUT

    连接来源 连接类型 规则目的 是否必要
    私有内网 SSH 允许内网主机连接路由器ssh控制路由器 可选

    INPUT基本保持不变,这里其实更推荐直接把SSH监听到私有内网网络接口上,就不需要这条防火墙规则了。

  • FORWARD

    我们需要保证来自私有内网的流量可以直接访问公开内网,但不能反过来,所以需要写更明确的Forward规则,下表直接替换上面的Forward规则:

    连接来源 连接目标 连接类型 规则目的 是否必要
    * * ct state=established/related 允许已建立的连接通过,包括内网向外建立连接的正常响应以及用new状态控制新连接。 必要
    * 公网 新连接(ct state=new) 允许所有主动向公网的连接,包括v4和v6 必要
    * 公开内网 新连接 允许所有主动向公开内网的连接,包括v4和v6 可选

​ 这里直接允许了所有到公开内网的所有新连接,这是一种简便的写法,相当于同时放通了到公开内网的ping/ping6、TCP/UDP、IPv4/IPv6连接。和之前说的一样,到公开内网的IPv4端口转发,应该完全由DNAT模块控制,防火墙就不需要特意的去拦截什么了。实际上防火墙就相当于拦了所有到私有内网的流量,放通所有到公开内网和公网的流量而已。

DNAT与DMZ主机配置

前面一直在说端口转发和DNAT的事,我们正式的看看,在VyOS中,如何正确配置IPv4-IPv4的DNAT实现IPv4端口转发。这个内容仅限高级用户,因为它的前提是你的路由器可以分配到一个真正的公网IPv4地址——而这在中国大陆是相当奢侈的事情。

destination {
    rule 100 {
        description "Regular destination NAT from external"
        destination {
            port 3389
        }
        inbound-interface pppoe0
        protocol tcp
        translation {
            address 192.0.2.40
        }
    }
}

整个VyOS的NAT配置分为两个部分:“匹配”和“动作”,整个配置除了translation之外,几乎全都是用来匹配流量的,这和SNAT是完全一致的。translation中记录了需要如何修改这个连接,一般情况下我们只需要将匹配的连接的目标地址修改成我们希望接收这个连接的主机的地址即可——如果主机的网络地址是DHCP分配的,这需要配合DHCP中的static-mapping功能,将指定主机的内网地址固定住。具体的配置方法请参考上篇文章中的“IPv4 端口转发”那个部分的内容。