2017/05/25(Thu)EdgeRouter X(ER-X)で802.1q VLAN + Hairpin NATを実現する方法
2017/05/24 03:21
諸注意
config内に日本語で補足を入れているが、文字化けの原因になる可能性があるので注意。コメントは入れるなら英語で入れた方が良いかも。(自分はそうしている)また下記のconfigは、実環境を参考に、説明用の設定値に変更しながら手打ちしたものなので、syntax error等があるかもしれない。その場合は適宜修正して、できれば本記事にコメントを頂けるとありがたい。
何故Hairpin NATが必要か
一つのISPを家庭内LANと自宅サーバで共有するネットワークを構築し、かつ、ルータとサーバが同一ではない場合、家庭内LANから自宅サーバに設定したドメインでアクセスすると、ルータの管理画面やsshにアクセスしてしまうという問題が起こる。これは、LAN内のPCからの通信がルータを経由する際、宛先のグローバルアドレスがルータ自身のアドレスであるため、ルータが応答を返してしまうからである。このようなパケットも、インターネット側からアクセスされたものと想定して、NAT変換等を自動的に行う仕様のルータ(Hairpin NAT, NAT Loopback, NAT Reflection対応ルータなどと呼ばれる)もあるが、殆どの家庭用ルータは非対応で、EdgeRouterもデフォルトはオフとなっている。
簡易設定は簡単だが…
EdgeRouterは結構作り込まれているので、Web管理画面からチェックを1つ入れるだけでHairpin NATを有効にすることが出来るのだが、
実はこれには罠があり、ethポート(ルータの物理ポート)をuntaggedで利用している場合向けの機能となっているようだ。また、この機能はport-forwardというNAT設定を簡略的に行う設定項目となってしまうため、firewallが自動設定に纏められたりして、細かな制御や確認がし辛いという難点がある。
port-forward {
auto-firewall enable
hairpin-nat enable
lan-interface switch0
rule 1 {
description myserver-http
forward-to {
address 192.168.11.10
port 80
}
original-port 80
protocol tcp
}
wan-interface eth1
}
natルールを使って記述する方法
nat設定方法で指定する場合、グローバルアドレスが固定IPでなければconfigが書けないという制約があるものの、EdgeRouter - NAT Hairpin (Nat Inside-to-Inside / Loopback / Reflection)にも紹介されている方法でよいはず(untagged環境では未検証)。以下のrule 20とrule 6020を参照してほしい。この設定で、以下のようなiptablesコマンドと同様の設定がされる。

nat {
/* inbound-interfaceがpppoe0(on eth1)向けのNAT設定 */
rule 10 {
description myserver-http
destination {
port 80
}
inbound-interface pppoe0
inside-address {
address 192.168.11.10
port 80
}
protocol tcp
type destination
}
/* inbound-interfaceがeth0向けのNAT設定 */
rule 20 {
description myserver-http-hairpin
destination {
address 192.51.100.123
port 80
}
inbound-interface eth0
inside-address {
address 192.168.11.10
port 80
}
protocol tcp
type destination
}
/* DNATはrule 1~4999、SNAT/Masqueradeはrule 5000~9999と決まっていることに注意 */
/* Internetの通常NAT設定 */
rule 6000 {
description internet-share
outbound-interface pppoe0
protocol all
source {
address 192.168.11.0/24
}
type masquerade
}
/* Hairpin NAT設定 */
rule 6020 {
description myserver-hairpin-nat
destination {
address 192.168.11.10
}
outbound-interface eth0
source {
address 192.168.11.0/24
}
type masquerade
}
}
802.1q VLAN上でHairpin NATを行う
802.1q VLANを使う設定をしている場合は、おそらくこんな感じでbridgeを定義しているはず。(2021/07/25 追記) brdigeを使ってVLANを組むのは推奨されていないっぽいので、次項を参照して下さい。
interfaces {
bridge {
br10 {
address 192.168.11.1/24
description myhome-lan
}
br20 {
description onu-zone
}
:
}
ethernet {
/* eth0: br10をuntagged */
eth0 {
bridge-group bridge br10
}
/* eth1: br20をuntagged */
eth1 {
bridge-group bridge br20
}
/* eth2: 以下はbr10をVLAN ID=10で、br20をVLAN ID=20でtaggedする */
eth2 {
vif 10 {
bridge-group br10
}
vif 20 {
bridge-group br20
}
}
:
}
}
Hairpin NATを行う設定でinbound-interface eth0やoutbound-interface eth0の部分をbr10に直せばよいのかというと、それだけでは動かない。しばらく頭を悩ませたが、パケットキャプチャなどをしているうちに*1、パケットがbridgeに行った後に破棄されている感じであることが分かった。
で、そんな折に見つけたのがコレ。bridgeをpromiscuous modeにすると動くという解決方法。
Port Forwarding working externally / Hairpin doesn't work when accessing via LAN (EdgeOS v1.4.0)
VLAN環境下では、eth側の処理とbridge側の処理をそれぞれ経由するが、bridge内のsoftware処理では「パケットの宛先が(bridgeの192.168.11.0/24でない)グローバルアドレスが付いているのでdrop」と動作しているようだ。なので、br10をpromiscuousにしてやれば、この動作を防ぐ事が出来、Hairpin NATを有効に出来る。
設定としてはこうなる。(上記で書いていたrule 10, 6000は省略)
natのinterface名をbr10に変更し、set bridge br10 promiscuous enableを追加してやればよい。
nat {
:
/* inbound-interfaceがeth0向けのNAT設定 */
rule 20 {
description myserver-http-hairpin
destination {
address 192.51.100.123
port 80
}
inbound-interface br10
inside-address {
address 192.168.11.10
port 80
}
protocol tcp
type destination
}
:
/* Hairpin NAT設定 */
rule 6020 {
description myserver-hairpin-nat
destination {
address 192.168.11.10
}
outbound-interface br10
source {
address 192.168.11.0/24
}
type masquerade
}
}
:
interfaces {
bridge {
br10 {
address 192.168.11.1/24
description myhome-lan
promiscuous enable
}
br20 {
description onu-zone
}
:
}
ethernet {
/* eth0: br10をuntagged */
eth0 {
bridge-group bridge br10
}
/* eth1: br20をuntagged */
eth1 {
bridge-group bridge br20
}
/* eth2: 以下はbr10をVLAN ID=10で、br20をVLAN ID=20でtaggedする */
eth2 {
vif 10 {
bridge-group br10
}
vif 20 {
bridge-group br20
}
}
:
}
}
802.1q VLAN上でHairpin NATを行う(改良版)
前項では、bridgeを使ってVLANを作っていたが、本来はswitch-portを使って定義するものらしい。(公式のドキュメントをよく読んでいなかったので、switch-portはnon VLAN専用だと思い込んでいた)
まず最初に、set interfaces switch switch0 switch-port vlan-aware enableしておけば、switch-portがVLANで分離可能になり、bridge同様、「物理ポート定義(interfaces ethernet ethX)と切り離してVLANを管理できる」
よって、最初はこんな感じで2つのVLANが切ってあるはず。(pvidはUntagged port, vidはTagged port)
interfaces {
switch switch0 {
switch-port {
/* eth0: VLAN 10をuntagged */
interface eth0 {
vlan {
pvid 10
}
}
/* eth1: VLAN 20をuntagged */
interface eth1 {
vlan {
pvid 20
}
}
/* eth2: VLAN 10と20をtaggedで、untaggedは設定なし */
interface eth2 {
vlan {
vid 10
vid 20
}
}
:
vlan-aware enable
}
vif 10 {
address 192.168.11.1/24
description myhome-lan
}
vif 20 {
description onu-zone
}
:
}
ethernet {
/* こちら側にVLANの設定は一切無し */
eth0 {
duplex auto
speed auto
}
eth1 {
duplex auto
speed auto
}
eth2 {
duplex auto
speed auto
}
:
}
}
bridgeではHairpin NATの設定を行っても、プロミスキャスにしないと動作しない*2が、以下のように同様のNAT設定を入れるだけでswitchの場合はうまく動作した。(本来こちらが期待する動作)また前項ではNATルールにポート番号も指定していたが、基本的にグローバルアドレス向けの通信は全部Hairpinに吸い込んで良いはずなので、指定を外した。(protocolもallで良いかもしれないが、とりあえずtcpだけで事足りる)
nat {
:
/* inbound-interfaceがVLAN 10(eth0)向けのNAT設定 */
rule 20 {
description myserver-http-hairpin
destination {
address 192.51.100.123
}
inbound-interface switch0.10
inside-address {
address 192.168.11.10
}
protocol tcp
type destination
}
:
/* Hairpin NAT設定 */
rule 6020 {
description myserver-hairpin-nat
destination {
address 192.168.11.10
}
outbound-interface switch0.10
source {
address 192.168.11.0/24
}
type masquerade
}
}
:
interfaces {
switch switch0 {
switch-port {
/* eth0: VLAN 10をuntagged */
interface eth0 {
vlan {
pvid 10
}
}
/* eth1: VLAN 20をuntagged */
interface eth1 {
vlan {
pvid 20
}
}
/* eth2: VLAN 10と20をtaggedで、untaggedは設定なし */
interface eth2 {
vlan {
vid 10
vid 20
}
}
:
vlan-aware enable
}
vif 10 {
address 192.168.11.1/24
description myhome-lan
}
vif 20 {
description onu-zone
}
:
}
ethernet {
/* こちら側にVLANの設定は一切無し */
eth0 {
duplex auto
speed auto
}
eth1 {
duplex auto
speed auto
}
eth2 {
duplex auto
speed auto
}
:
}
}
こうすることで、VLAN 10配下のPC(192.168.11.20/24)が198.51.100.123:80にアクセスすると、ルータが192.168.11.1をSrcIPに置き換えて、LAN内のサーバ192.168.11.10にアクセスし、サーバはルータに応答を返却(SrcIP: 192.168.11.0, DstIP: 192.168.11.1)、Masquerade設定により戻りパケットのSrcIPが198.51.100.123に、DstIPが192.168.11.20に変更され通信が完結する。クライアント
SrcIP: 192.168.11.20 (自分自身)
DstIP: 198.51.100.123 (自宅内サーバのグローバルIPアドレス)
↓
ルータ(以下のように置換)
SrcIP: 192.168.11.1 (ルータがサーバに対して直接リーチできるアドレスに変換)
DstIP: 192.168.11.10
↓
サーバの応答
SrcIP: 192.168.11.10
DstIP: 192.168.11.1
↓
ルータ(以下のように置換)
SrcIP: 198.51.100.123
DstIP: 192.168.11.20
というわけで、サーバから見たアクセス元が全てルータになる点は仕様。
ちなみに、VLANが複数ある場合、NATのrule 20とrule 6020のセットをVLANの数だけ増やせば対応可能。inbound-interfaceおよびoutband-interfaceを該当のVLAN用のswitch0.xに設定し、そのVLANで扱っているローカルアドレスにinside-address addressおよびsource addressを置き換えればうまくいく。