はじめに
実家を出てから、Wimax(初期)、Softbank airと乗り換えながらも10年近く無線インターネットを使いつづけてきたのだが、サービスが成熟してくると旧回線を締め出したり値上げをしたりと、(無線だから仕方がないにせよ)モッサリ速度でありながらユーザに冷たく当たってくる事業者に嫌気がさしてきた。
スマホもmioに移行が済んでることだし、一念発起してmioひかりを引くことにした。
折角の光だからDS-Liteでサクサクインタネットをしたい、ルータに高いお金払うのもちょっと気が引ける、ということで中華マルチポートPCで自作ルータにして動かしてやろうと…
思ったのが失敗だった。
しょうもない見落としが原因でむちゃくちゃハマったり構築に色々と苦労をしたので、ここに構築手順を残しておく。
使用機材
Qotomという中華メーカーのファンレスNUCを使った。
AliExpress.com Product – VGA Mini PC with 4 LAN port apply to Firewall and Router, Bay Trail j1900 low power consumption 10W, X86 Mini PC 12V
筆者の構成は 4GB mem 32G SSD。Wifiは技適のなさそうなモジュールだったので止めておいた。
その他、NETGEARの無線AP。
設定内容
当該機種には独立したGbEポートが4個搭載されているが、それら全てをブリッジに接続し「ツーツー(ハブ化)」の状態にした上で、firewallの設定で外に行って欲しくないものや入ってきて欲しくないものをブロックする格好にした。
外と内を区別するため、enp1s0をwan側ポートに定め、enp[2-4]s0はlanポートとした。
(firewallの設定においてこの決めごとを使う)
家の中でマルチキャストするサービスがいくつか動いているものについてはv4アドレスにのみアナウンスすることでパケットを外に漏らさないようにする。
使うソフトウェアは以下。
- Archlinux(ベースシステム)
- systemd-networkd(インストール済み)
- dnsmasq
- nftables
- avahi (mdns利用に必要)
- nss-mdns (mdns利用に必要)
インターネット接続の有効化 (IPv6)
とりあえずブリッジを作成し、IPv6でインターネットへ接続する設定をする。
/etc/systemd/network/ 以下に以下のファイルを作成する
- bridge.netdev → ブリッジの定義
- bridge.network → ブリッジのネットワーク設定(インターネットへの接続を担う)
- all_port.network → 物理ポートのネットワーク設定(ブリッジとつなげるのみ)
bridge.netdev
[NetDev]
Name=br0
Kind=bridge
bridge.network
[Match]
Name=br0
all_port.network
[Match]
Name=enp[1-4]s0
[Network]
Bridge=br0
/etc/resolv.conf にプロバイダが提供するDNSを記述する。
nameserver 2001:XXXX:XXXX:XXXX
nameserver 2001:XXXX:XXXX:XXXX
この時点で systemctl restart systemd-networkd を実行、 ip a にてインタフェースの状態を確認すると以下のような表示になっているはずである。
[root@shortpath network]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
3: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ae:d8:52:ff:ff:ff brd ff:ff:ff:ff:ff:ff
inet6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global mngtmpaddr noprefixroute dynamic
valid_lft 2591603sec preferred_lft 604403sec
inet6 fe80::acd8:XXXX:XXXX:XXXX/64 scope link
valid_lft forever preferred_lft forever
4: enp2s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
5: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
6: enp4s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq master br0 state DOWN group default qlen 1000
link/ether 00:0e:c4:ff:ff:ff brd ff:ff:ff:ff:ff:ff
また、www.google.com (IPv6 readyのホスト) にcurlを実行すると、結果が表示されるはずである。
[root@shortpath ~]# curl -v www.google.com
* Rebuilt URL to: www.google.com/
* Trying 2404:6800:4004:81a::2004...
* TCP_NODELAY set
* Connected to www.google.com (2404:6800:4004:81a::2004) port 80 (#0)
> GET / HTTP/1.1
> Host: www.google.com
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 302 Found
< Cache-Control: private
< Content-Type: text/html; charset=UTF-8
< Referrer-Policy: no-referrer
< Location: http://www.google.co.jp/?gfe_rd=cr&ei=1LuZWbjuHYPf8AfX9oXQBw
< Content-Length: 261
< Date: Sun, 20 Aug 2017 16:41:56 GMT
<
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/?gfe_rd=cr&ei=1LuZWbjuHYPf8AfX9oXQBw">here</A>.
</BODY></HTML>
* Connection #0 to host www.google.com left intact
この時点では、IPv4ホストへの接続はできない。
トンネルの作成 (IPv4を利用可に)
gw.transix.jp (IIJでのIPv4 over IPv6のゲートウェイサーバ) にip4ip6トンネルを作成し、IPv4ホストへの接続を可能にする。
以下ファイルを作成
- transix.netdev → gw.transix.jpへのトンネルの定義
- transix.network → トンネルの有効化と、デフォルトゲートウェイ化する設定
- bridge.network → トンネルとの関連付けとIPFowardを設定
transix.netdev
[NetDev]
Name=transix
Kind=ip6tnl
[Tunnel]
Local=2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX # br0に割り当てられたIPv6アドレス
Remote=2404:8e01::feed:100 # gw.transix.jp へのアドレス(IIJてくろぐより)
Mode=ipip6
transix.network
[Match]
Name=transix
[Route]
Destination=0.0.0.0/0
bridge.network
[Match]
Name=br0
# 以下を追加
[Network]
Address=192.168.0.1
Tunnel=transix
IPForward=ipv4
再度 systemctl restart systemd-networkd でネットワーク接続を再構築する。 ip a の表示は以下のように変わる。
4: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether ae:d8:52:ff:ff:ff brd ff:ff:ff:ff:ff:ff
inet 192.168.0.1/24 brd 192.168.0.255 scope global br0
valid_lft forever preferred_lft forever
inet6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX/64 scope global mngtmpaddr noprefixroute dynamic
valid_lft 2591751sec preferred_lft 604551sec
inet6 fe80::acd8:XXXX:XXXX:XXXX/64 scope link
valid_lft forever preferred_lft forever
5: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
link/tunnel6 :: brd ::
6: transix@br0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1452 qdisc noqueue state UNKNOWN group default qlen 1000
link/tunnel6 2409:250:9160:XXXX:XXXX:XXXX:XXXX:XXXX peer 2404:8e01::feed:100
inet6 fe80::28b5:4dff:fe04:b99d/64 scope link
valid_lft forever preferred_lft forever
ここで cookpad.com (IPv4 onlyのホスト)にcurlを試す。
[root@shortpath ~]# curl -v cookpad.com
* Rebuilt URL to: cookpad.com/
* Trying 52.197.89.148...
* TCP_NODELAY set
* Connected to cookpad.com (52.197.89.148) port 80 (#0)
> GET / HTTP/1.1
> Host: cookpad.com
> User-Agent: curl/7.55.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Date: Sun, 20 Aug 2017 17:00:16 GMT
< Content-Type: text/html; charset=iso-8859-1
< Content-Length: 228
< Connection: keep-alive
< Server: Apache
< Location: https://cookpad.com/
<
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>301 Moved Permanently</title>
</head><body>
<h1>Moved Permanently</h1>
<p>The document has moved <a href="https://cookpad.com/">here</a>.</p>
</body></html>
* Connection #0 to host cookpad.com left intact
これでルータのインターネット接続の設定は完了となる。
参考にしたてくろぐブログでは物理インタフェースにIPv4を設定している。
筆者は「結線されていない」物理インタフェースにIPv4を振っていたため通信ができず、1週間悩んだ。
結線されていない状態でもipコマンドのTXやRXのカウントが増え、動いているように見えてしまうためハマるので注意。
なお、systemd-networkdの IPFoward=ipv4 の設定をすることでカーネルパラメータ net.ipv4.ip_forward が自動的に 1 に変更される。
ならびにトンネルの設定により自動的に ip6_tunnel カーネルモジュールがロードされる。
dnsmasqの設定
次に、IPv4ルータとして機能させるためdnsmasqをインストール、構成する。
前の手順で利用していた resolv.conf を /etc/resolv.dnsmasq.conf にコピーする。
/etc/dnsmasq.conf に以下の2行を記載する。
# リースの設定
dhcp-range=192.168.0.17,192.168.0.128,255.255.255.0,12h
# dnsmasqにDNSプロキシをさせる
resolv-file=/etc/resolv.dnsmasq.conf
systemctl enable dnsmasq
systemctl start dnsmasq
/etc/resolv.confの内容を localhostを見るように変更する
nameserver 127.0.0.1
nameserver ::1
avahi-daemonの設定
/etc/avahi/avahi-daemon.confを開き、enable-ipv6をnoに設定する。
ちなみに下の方に publish-aaaa-on-ipv4=yes といった形で「ipv4のアナウンスでipv6アドレスを配るよ」という設定があるがこれは変更しなくても良いだろう。
systemctl enable avahi-daemon
systemctl start avahi-daemon
nftablesの設定
iptablesの代わりにnftablesというツールでネットフィルタの設定をした。
ブリッジ層でwan側ポートに出入りするパケットにフラグを付け、lanとwanのインタフェース間でフォワードがされるパケットのフラグとプロトコルを見てフィルタするという構造にした。
フラグを付けた理由は、ブリッジ層ではL2の情報しか参照することができず、かつフォワード時にはどのインタフェースから入ったかの情報が参照できないためである。
ブリッジの通信でフォワードを制御したいときは、br_filterカーネルモジュールが必要になる場合がある。
sysctlで net.bridge.bridge-nfではじまる設定が存在すれば機能はロードされているので、br_filterのロードの設定は不要。
/etc/modules-load.d に br_filter.confを作成し、以下の br_filterと記載する。
br_filterがロードされると、以下の設定が利用可能になる。
net.bridge.bridge-nf-call-arptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-filter-pppoe-tagged = 0
net.bridge.bridge-nf-filter-vlan-tagged = 0
net.bridge.bridge-nf-pass-vlan-input-dev = 0
nf-call-*tables が有効になっている必要がある。
この状態で、nftables.confを書く。
/etc/nftables.conf
#!/usr/bin/nft -f
# ipv4/ipv6 Simple & Safe Firewall
# you can find examples in /usr/share/nftables/
# ブリッジのテーブル"filter"
table bridge filter {
chain forwarding {
type filter hook forward priority -100; policy accept;
# mark wan port
iifname enp1s0 mark set 0x100 accept
oifname enp1s0 mark set 0x101 accept
}
}
# IPv4のテーブル"filter"
table filter {
# ルータ自身への入力
chain incoming {
type filter hook input priority 0; policy accept;
ct state {established, related} accept
ct state invalid drop
oifname lo accept
mark 0x100 drop # 外からの入力はdrop
}
chain fowarding {
type filter hook forward priority 0; policy accept;
# lan側のインタフェース間ではforwardしたいため、wanインタフェース向けのみdrop
mark 0x101 drop
mark 0x100 drop
}
}
# IPv6のテーブル"filter"
table ip6 filter {
chain incoming {
type filter hook input priority 0; policy accept;
ct state {established, related} accept
ct state invalid drop
oifname lo accept
tcp dport {ssh, http} accept
# ICMPのpingなどはaccept
icmpv6 type {echo-request,nd-neighbor-solicit} accept
# wanインタフェースからの入力はdrop
mark 0x100 drop
}
chain fowarding {
type filter hook forward priority 0; policy accept;
ct state {established, related} accept
ct state invalid drop
# ICMPは全通し(ICMPによるIP自動割り当てのため)
ip6 nexthdr icmpv6 accept;
# wanからlan向けは拒否
mark 0x100 drop
}
}
systemctl enable nftables
systemctl start nftables
参考文献
http://techlog.iij.ad.jp/contents/dslite-raspi
全般的な設定方法の参考にした
https://wiki.archlinuxjp.org/index.php/Systemd-networkd#link_.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB
https://www.freedesktop.org/software/systemd/man/systemd.network.html
systemd-networkdの設定
https://wiki.archlinuxjp.org/index.php/Dnsmasq
dnsmasqの設定
https://www.systutorials.com/docs/linux/man/5-avahi-daemon/
avahiの設定
http://www.gcd.org/blog/2006/04/29/
bridge上を流れるIPパケットのフィルタ方法の参考
https://wiki.nftables.org/wiki-nftables/index.php/
nftablesの設定コマンドの参考
nftablesすげーいいよ! という説明資料