Anonymous
Not logged in
Talk
Contributions
Create account
Log in
LinuxizeNet
Search
Editing
Linux router with WAN, LAN and DMZ
From LinuxizeNet
Namespaces
Page
Discussion
More
More
Page actions
Read
Edit
History
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
Turning a Linux box into a router: enabling IP forwarding, addressing the three interfaces with systemd-networkd, and a minimal iptables policy that publishes a mail server and a web server from the DMZ. {{Box|1=Interfaces and addresses used throughout: '''eth0''' WAN, '''eth1''' LAN <code>10.0.1.0/24</code>, '''eth2''' DMZ <code>10.0.0.0/24</code>. The router holds the <code>.1</code> in each subnet β <code>10.0.1.1</code> on the LAN, <code>10.0.0.1</code> on the DMZ.}} {| class="wikitable" ! Role !! Interface !! Address |- | Router, WAN || eth0 || from the ISP (DHCP or static) |- | Router, LAN || eth1 || <code>10.0.1.1/24</code> |- | Router, DMZ || eth2 || <code>10.0.0.1/24</code> |- | Mail server || β || <code>10.0.0.10</code> |- | Web server || β || <code>10.0.0.20</code> |} == Enable IP forwarding == Without this the kernel drops anything not addressed to itself, and no amount of firewall rules will make traffic cross between interfaces. Immediately, until reboot: <syntaxhighlight lang="bash"> sysctl -w net.ipv4.ip_forward=1 </syntaxhighlight> Permanently: <syntaxhighlight lang="bash"> cat >/etc/sysctl.d/99-router.conf <<'EOF' net.ipv4.ip_forward = 1 # Uncomment if the router also routes IPv6: # net.ipv6.conf.all.forwarding = 1 EOF sysctl --system </syntaxhighlight> Check the value in force: <syntaxhighlight lang="bash"> sysctl net.ipv4.ip_forward cat /proc/sys/net/ipv4/ip_forward </syntaxhighlight> === Disable it again === <syntaxhighlight lang="bash"> sysctl -w net.ipv4.ip_forward=0 </syntaxhighlight> To make that stick, set the value to <code>0</code> in <code>/etc/sysctl.d/99-router.conf</code> and run <code>sysctl --system</code> β deleting the file only reverts to the kernel default, which is also <code>0</code>, but leaves nothing documenting the intent. Turning forwarding off stops routing but leaves the NAT and filter rules loaded. To stand the router down completely, flush them as well β see [[#Tearing it down|Tearing it down]]. {{Box|1=systemd-networkd can set forwarding itself, but the option was renamed: <code>IPForward=</code> in <code>[Network]</code> is deprecated from systemd 256 in favour of <code>IPv4Forwarding=</code> / <code>IPv6Forwarding=</code>. <code>sysctl</code> behaves the same on every version, so the examples above use it.}} == Interfaces with systemd-networkd == One file per interface in <code>/etc/systemd/network/</code>. The numeric prefix only controls the order the files are read. WAN β <code>/etc/systemd/network/10-wan.network</code>: <syntaxhighlight lang="ini"> [Match] Name=eth0 [Network] DHCP=ipv4 # Static instead? Drop DHCP= and use: # Address=203.0.113.10/24 # Gateway=203.0.113.1 # DNS=1.1.1.1 [DHCPv4] UseDNS=yes UseNTP=yes </syntaxhighlight> LAN β <code>/etc/systemd/network/20-lan.network</code>: <syntaxhighlight lang="ini"> [Match] Name=eth1 [Network] Address=10.0.1.1/24 ConfigureWithoutCarrier=yes </syntaxhighlight> DMZ β <code>/etc/systemd/network/30-dmz.network</code>: <syntaxhighlight lang="ini"> [Match] Name=eth2 [Network] Address=10.0.0.1/24 ConfigureWithoutCarrier=yes </syntaxhighlight> Apply and inspect: <syntaxhighlight lang="bash"> systemctl enable --now systemd-networkd networkctl reload networkctl status eth1 </syntaxhighlight> Handing out addresses on the LAN is a separate job β see [[DHCP and NTP for the LAN with systemd-networkd]]. == Minimal firewall == Default deny inbound and forwarded, allow outbound, and let conntrack carry return traffic. Everything published from the DMZ is opened explicitly. === Policies and baseline === <syntaxhighlight lang="bash"> # Start from a clean slate iptables -F iptables -t nat -F iptables -X iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT # Loopback, and anything conntrack already knows about iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -m conntrack --ctstate INVALID -j DROP </syntaxhighlight> === Services on the router itself === Only from the LAN. Nothing on the router listens to the WAN. <syntaxhighlight lang="bash"> iptables -A INPUT -i eth1 -p tcp --dport 22 -j ACCEPT # ssh iptables -A INPUT -i eth1 -p udp --dport 67 -j ACCEPT # dhcp server iptables -A INPUT -i eth1 -p udp --dport 123 -j ACCEPT # ntp server iptables -A INPUT -i eth1 -p udp --dport 53 -j ACCEPT # dns, if the router resolves iptables -A INPUT -i eth1 -p tcp --dport 53 -j ACCEPT # Ping, rate limited, from anywhere iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 5/s -j ACCEPT </syntaxhighlight> === Publish the DMZ servers (DNAT) === <code>25</code> is plain SMTP with STARTTLS, <code>587</code> is the submission port (also STARTTLS) and <code>465</code> is implicit TLS submission. Drop whichever of the two submission ports your mail server does not offer. <syntaxhighlight lang="bash"> # Mail -> 10.0.0.10 iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport \ --dports 25,465,587,993 -j DNAT --to-destination 10.0.0.10 # Web -> 10.0.0.20 iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport \ --dports 80,443 -j DNAT --to-destination 10.0.0.20 </syntaxhighlight> DNAT only rewrites the destination. The packet still has to survive <code>FORWARD</code>, which is where the matching accepts belong: <syntaxhighlight lang="bash"> iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m conntrack --ctstate INVALID -j DROP iptables -A FORWARD -i eth0 -o eth2 -d 10.0.0.10 -p tcp -m multiport \ --dports 25,465,587,993 -j ACCEPT iptables -A FORWARD -i eth0 -o eth2 -d 10.0.0.20 -p tcp -m multiport \ --dports 80,443 -j ACCEPT </syntaxhighlight> === Egress and inter-zone policy === <syntaxhighlight lang="bash"> iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT # LAN -> internet iptables -A FORWARD -i eth2 -o eth0 -j ACCEPT # DMZ -> internet (updates, outbound mail) iptables -A FORWARD -i eth1 -o eth2 -j ACCEPT # LAN -> DMZ (administration) # DMZ -> LAN is deliberately absent. That asymmetry is the whole point of a # DMZ: if a published server is compromised it must not reach the LAN. # Return traffic for LAN-initiated connections is already covered by the # ESTABLISHED,RELATED rule above. </syntaxhighlight> === Source NAT === <syntaxhighlight lang="bash"> iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE </syntaxhighlight> <code>MASQUERADE</code> takes the WAN address as it finds it, which is what you want on a dynamic ISP address. On a static WAN address <code>SNAT</code> is marginally cheaper because it does not look the address up per connection: <syntaxhighlight lang="bash"> iptables -t nat -A POSTROUTING -o eth0 -j SNAT --to-source 203.0.113.10 </syntaxhighlight> === Reaching the servers from the LAN === A LAN client resolving the public name gets the WAN address, sends the packet to the router, and the reply comes back from <code>10.0.0.10</code> directly β so the connection never completes. Either give the LAN a split-horizon DNS view that answers with the internal address, or add hairpin NAT: <syntaxhighlight lang="bash"> iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -d 10.0.0.10 -p tcp -m multiport \ --dports 25,465,587,993 -j MASQUERADE iptables -t nat -A POSTROUTING -s 10.0.1.0/24 -d 10.0.0.20 -p tcp -m multiport \ --dports 80,443 -j MASQUERADE </syntaxhighlight> Split-horizon DNS is the tidier of the two β hairpin NAT hides the client's real address from the server, so its logs show the router for every internal visitor. == Make the rules survive a reboot == Debian and Ubuntu: <syntaxhighlight lang="bash"> apt install iptables-persistent iptables-save > /etc/iptables/rules.v4 </syntaxhighlight> RHEL, CentOS, Rocky: <syntaxhighlight lang="bash"> dnf install iptables-services systemctl enable --now iptables iptables-save > /etc/sysconfig/iptables </syntaxhighlight> {{Box|type=warning|1=Save only after you have confirmed you can still reach the box. A default-DROP <code>INPUT</code> policy saved with no working ssh rule locks you out until you get physical or console access.}} == Verify == <syntaxhighlight lang="bash"> # Rules, with counters, as they are actually loaded iptables -L -v -n --line-numbers iptables -t nat -L -v -n # Watch a DNAT entry appear while testing from outside conntrack -L -d 10.0.0.20 # From an external host nc -vz <wan-address> 443 nc -vz <wan-address> 993 </syntaxhighlight> If a published port does not answer, check the <code>FORWARD</code> counters first. Traffic that was DNATed but has no matching <code>FORWARD</code> accept is the usual cause, and it shows up as a rising policy-drop count rather than an error anywhere. == Tearing it down == <syntaxhighlight lang="bash"> sysctl -w net.ipv4.ip_forward=0 iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT iptables -F iptables -t nat -F iptables -X </syntaxhighlight> {{Box|1=On current distributions <code>iptables</code> is usually a front end to nftables (<code>iptables-nft</code>). The syntax above is unchanged; <code>iptables -V</code> reports which backend is in use, and rules created this way are visible with <code>nft list ruleset</code>.}} [[Category:Networking]] [[Category:Linux]] [[Category:Howto]]
Summary:
Please note that all contributions to LinuxizeNet may be edited, altered, or removed by other contributors. If you do not want your writing to be edited mercilessly, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource (see
LinuxizeNet:Copyrights
for details).
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Template used on this page:
Template:Box
(
edit
)
Navigation
Navigation
Main page
Recent changes
Random page
Help about MediaWiki
Wiki tools
Wiki tools
Special pages
Page tools
Page tools
User page tools
More
What links here
Related changes
Page information
Page logs