===== WireGuard Port Forwarding =====
I am assuming that you have already setup WireGuard both on the internal and on the external server. See [[gentoo:wireguard|this]] page for more details on the topic.
Using WireGuard for port-forwarding between an external, public accessible host, and an internal non-accessible host (like behind a CGNAT) is a less known topic and i will cover it here.
You will need to use NFTables, see [[[[gentoo:nft|here]] for more details to better understand the following.
Assumptions:
* You have one **internal** host which is behind CGNAT, and is not accessible from the internet
* You have one **external** host which has a public IP address
* Both hosts have WireGuard setup according to the configuration above
* Internal host expose port 22 for SSH (optional: port 8443 for HTTPS and port 80 for HTTP)
* External host needs to route those three ports to the internal host
* The WireGuard subnet is //10.100.0.0/24// with internal host being 10.100.0.1 and external host 10.100.0.2
* External host **public** network interface is called **enp1s0**
* Both hosts WireGuard network iunterface is called **wg0**
What we need to do is create nftable rules to ensure that:
* packets reaching external host on enp1s0 on port 2022 get routed to 10.100.0.1 port 22
* packets reaching external host on enp1s0 on port 443 get routed to 10.100.0.1 port 8443 (optional)
* packets reaching external host on enp1s0 on port 80 get routed to 10.100.0.1 port 80 (optional)
* return packets from the internal host get properly re-routed to the original sender out from enp1s0
**Note:** forwarding ports for HTTP and HTTPS is only required if you don't plan to host a reverse proxy on the external server, which is the recomended approach. See [[router:proxy_chain|this page]] for more details on chaining proxies.
**Note:** see [[selfhost:nginx|here]] on why i use port 8443 instead of port 443 on the internal server. This is to differentiate //internal// connections, which have lesser security, from //external// connections which get additional SSO layers.
**Note:** if you forward HTTPS, you **must** also forward HTTP (80), otherwise Let's Encrypt will be unable to renew your certificates.
What we need:
* A dedicated table called **wg**
* A **prerouting** chain to apply DNAT to incoming packes
* Rules to route port 2022/80/443 to the wg tunnel ports
* A **postrouting** chain to ensure that all reply packets are properly SNAT back to outside
* Return masquerading rules to ensure the return packets get sent back out of enp1s0
Create the wg table:
nft add table ip wg
Create the base chains:
nft 'add chain ip wg prerouting { type nat hook prerouting priority -100 ; }'
nft 'add chain ip wg postrouting { type nat hook postrouting priority 100 ; }'
Create the in-bound rules:
# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 80 counter' # <<- optional for debugging purposes
# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 443 counter' # <<- optional for debugging purposes
# nft 'add rule ip wg prerouting iifname enp1s0 tcp dport 2022 counter' # <<- optional for debugging purposes
nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 2022 : 10.100.0.1 . 22 }'
#nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 80 : 10.100.0.1 . 80 }' optional
#nft 'add rule ip wg prerouting iifname enp1s0 dnat to tcp dport map { 443 : 10.100.0.1 . 8443 }' optional
Create the SNAT return rule (the counter rule is only for debugging, you can omit that rule):
# nft 'add rule ip wg postrouting ip daddr 10.100.0.1 counter' # <<- optional for debugging purposes
nft 'add rule ip wg postrouting ip daddr 10.100.0.1 masquerade'
This is the resulting NFTables setup:
nft list table wg
table ip wg {
chain prerouting {
type nat hook prerouting priority dstnat; policy accept;
iifname "enp1s0" tcp dport 443 counter packets 100 bytes 5712
iifname "enp1s0" tcp dport 2022 counter packets 0 bytes 0
iifname "enp1s0" tcp dport 80 counter packets 0 bytes 0
iifname "enp1s0" dnat ip to tcp dport map { 2022 : 10.100.0.1 . 22 }
iifname "enp1s0" dnat ip to tcp dport map { 80 : 10.100.0.1 . 80 }
iifname "enp1s0" dnat ip to tcp dport map { 443 : 10.100.0.1 . 8443 }
}
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip daddr 10.100.0.1 counter packets 390 bytes 25945
ip daddr 10.100.0.1 masquerade
}
}