トライしたかったnftablesの基本（その４）＆久しぶりにGNS（その27）

# ドメイン名によるフィルタリング
基本＋αの内容。nftablesを用いて、ドメイン名によるフィルタリングをGNS3環境で実施。

## ネットワーク＋環境
![TopologyByGNS3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/3f079a26-342b-f421-a0a0-7dbad08e6abb.png)
ドメイン名によるフィルタリングを時間するには、リアルネットワークに接続するほうがベター。そこで、「[久しぶりにGNS3（その13：リアルネットワークへ接続その４）](https://qiita.com/infinite1oop/items/165b64a84d3da8239732)」で用いた、GNS3 VM NATによりリアルネットワークに接続し、GNS３下のLinux VM（ここではVMwareでのxubuntu）内で、nftablesを用いたフィルタリングを行うこととした。また、左側のPCからのpingにより、フィルタリングの動作を確認する。なお、ルーティングを簡略化するため、Linux VMもNAT化している。

## ドメイン名によるフィルタリングとは
nftablesでもiptablesでも、送信元IPアドレスや宛先IPアドレスでのフィルタリングは可能であり、その部分にドメイン名を指定することはできるが、設定時のドメイン名に相当するIPアドレスで固定化されてしまう。当然のことながら、ドメイン名に相当するIPアドレスは固定ではなく、変更されてしまうこともある。そこで、ドメイン名に相当するIPアドレスフィルタリングルールを定期的に更新する仕組みを導入する。

## 参考URL
- [Firewall rules based on Domain name instead of IP address](https://unix.stackexchange.com/questions/557388/firewall-rules-based-on-domain-name-instead-of-ip-address)
- [DNS-based firewall rules on OpenWrt 22.03 using IP sets and fw4](https://peterkieser.com/2022/12/28/dns-based-firewall-rules-on-openwrt-22-03-using-ip-sets-and-fw4/)
- [Sets](https://wiki.nftables.org/wiki-nftables/index.php/Sets)
- [Linuxにおける新たなパケットフィルタリングツール「nftables」入門](https://knowledge.sakura.ad.jp/22636/)
- [man nftables 日本語訳](https://qiita.com/JhonnyBravo/items/a79d39dfd8543c1e95d8)

## 設定
### Linux VM
GNS3上のLinux VMの設定である。
![VM_Configure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/b6e9332c-2f03-b4e9-cc7c-7e3ab1d64970.png)
![VM_NetworkAdapterのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/1b00f9c1-615d-e772-e8aa-de53b333a486.png)
Network I/Fで”Allow GNS3 to override non custom VMware adapter”にチェックを入れる。すると、下記のように、VMwareのネットワーク I/Fが、「カスタム」となるようだ。
![VM_NetAdapter1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/37b03d4b-e1d0-2e8d-c280-5c82de22ca72.png)
![VM_NetAdapter2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/42749149-01a1-075a-b8eb-b894fb29ffb3.png)

### NAT
まず、Linux VMをNAT化する。「[トライしたかったnftablesの基本（その２）](https://qiita.com/infinite1oop/items/0c999a4511e855274a88)」の設定とほぼ同じ。説明はそちらを参照。以降、”sudo”省略、root権限での設定である。
```shell:
nft create table ip nat_filter
nft create chain ip nat_filter dst_nat {type nat hook prerouting priority dstnat \;}
nft create chain ip nat_filter src_nat {type nat hook postrouting priority srcnat \;}
nft add rule ip nat_filter src_nat oif ens33 masquerade

nft create chain ip nat_filter filter_1 {type filter hook forward priority 0 \; policy drop \;}
nft add rule ip nat_filter filter_1 iif ens38 oif ens33 accept
nft add rule ip nat_filter filter_1 ct state related,established accept
```
I/F "ens33"がNAT外側のI/Fである（"ens38"が内側）。

### 指定したドメインのみOK
指定ドメインのみ通過させる、table/chain/ruleを作成する。
```shell:
nft create table ip ok_domain 
nft create chain ip ok_domain forward_1 {type filter hook forward priority 0 \; policy drop \;}
nft add set ip ok_domain allowed_domain { type ipv4_addr \; }
nft add rule ip ok_domain forward_1 iif "ens38" udp dport 53 accept
nft add rule ip ok_domain forward_1 iif "ens38" ip daddr @allowed_domain accept
nft add rule ip ok_domain forward_1 ct state related,established accept
```
- "ok_domain"というtable、"forward_1"というchain内に、IPv4を対象とした"allowed_domain"というセットを作り、後にセット内にドメイン名に相当するIPアドレスを格納。
- クライアント（上図の左側PC）からの名前解決を行えるよう、NAT内側（I/F "ens38"）からのDNS（UDP port 53）を許可。
- NAT内側から宛先"allowed_domain"セットへの通信を許可。
- セッションを持つなど関連ステートを持つ通信を許可。

### 定期実行スクリプト
```shell:domain.sh
nft flush set ip ok_domain allowed_domain
cat /tmp/allowed_domain_list | while read line
do
  ipaddrs=`dig +short $line`
  for ip in $ipaddrs
  do
    sudo nft add element ip ok_domain allowed_domain {$ip}
  done
done
```
- "allowed_domain"セットをクリア（flush）。
- 許可するドメイン名が記載されたファイル”allowed_domain_list”からドメイン名読み込み。
- digによりドメイン名に相当するIPアドレス取得
- 取得したIPアドレスを"allowed_domain"セットに追加（"add element"）

下記はドメイン名が記載されたファイルのサンプルである。
```shell:allowed_domain_list
yomiuri.co.jp
bbc.co.uk
```
### cron
上述したスクリプトを、cronにより定期的に実行。crontabコマンドにより編集する。
```shell:
crontab -u root -e
```
下記は５分ごとに実施の例。
```shell:
*/5 * * * * /tmp/domain.sh
```
小生の環境では、"/var/spool/cron/crontabs/cron"に上記内容が格納された。

## 設定状況
```shell
$ sudo nft -a list ruleset
table ip nat_filter { # handle 1
	chain dst_nat { # handle 1
		type nat hook prerouting priority dstnat; policy accept;
	}

	chain src_nat { # handle 2
		type nat hook postrouting priority srcnat; policy accept;
		oif "ens33" masquerade # handle 5
	}

	chain filter_1 { # handle 6
		type filter hook forward priority filter; policy drop;
		iif "ens38" oif "ens33" accept # handle 7
		ct state established,related accept # handle 10
	}
}
table ip ok_domain { # handle 2
	set allowed_domain { # handle 2
		type ipv4_addr
		elements = { 151.101.0.81, 151.101.2.133,
			     151.101.64.81, 151.101.66.133,
			     151.101.128.81, 151.101.130.133,
			     151.101.192.81, 151.101.194.133 }
	}

	chain forward_1 { # handle 1
		type filter hook forward priority filter; policy drop;
		iif "ens38" udp dport 53 accept # handle 3
		iif "ens38" ip daddr @allowed_domain accept # handle 4
		ct state established,related accept # handle 5
	}
}
```
”elements”内に、ドメイン名に相当するIPアドレスが記載されている。

なお、結果はほぼ自明なので、Wiresharkデータは省略。

## 最後に
テストで用いた、上述していないドメインgoogle.comに相当するIPアドレスは、頻繁に変わっていた。cronの定期実行をかなり頻繁に行う必要がありそう。または、別の方法があるのかもしれない。