「2018年05月」の記事

自力でIPv4 over IPv6をした(Tunnel編)

2018年5月29日 / 未分類

これまでのあらすじ

やめて!So-netのサービス変更で、MAP-Eなv6プラスを適用されたら、DS-liteでIPv4ネットワークと繋がってるRTX1200の精神まで燃え尽きちゃう!
お願い、死なないで城之内!あんたが今ここで倒れたら、舞さんや遊戯との約束はどうなっちゃうの? ライフはまだ残ってる。ここを耐えれば、マリクに勝てるんだから!
次回、「RTX1200死す」。デュエルスタンバイ!

VPSを利用した自力IPv4 over IPv6

夕方から夜のPPPoE経由の通信が遅いなら、プロバイダの提供するIPv4 over IPv6が使い物にならないなら、自力でやってしまえばいいじゃない、ということでやってみた。
自宅とVPSとの間にIPv6でトンネル張って、その中にIPv4の通信を通すのである。
VPSのOSはUbuntu 18.04、自宅のルータはYAMAHA RTX1200。

とはいえ、本当に自力で1からやれるほどLinuxの仕組みに詳しいわけではないので、やはり先人の知恵にすがるわけである。

やることは以下の4つ。

  • トンネルデバイスの作成とルーティング設定
  • カーネルパラメータの設定
  • ファイアウォールの設定
  • RTX1200の設定

これらを、Ubuntuの流儀(systemd)に従って実施する。

トンネルデバイスの作成とルーティング設定

/etc/systemd/network 以下に.netdevファイルを置くとデバイスが作られ、.networkファイルでネットワークの設定ができる。

[NetDev]
Name=tun0
Kind=ip6tnl

[Tunnel]
Mode=ipip6
Local=【VPSのIPv6アドレス】
Remote=【ルータのIPv6アドレス】
EncapsulationLimit=none

最後の1行(EncapsulationLimit=none)が非常に重要(後述)

[Match]
Name=tun0

[Route]
Destination=【自宅側のネットワーク】
Scope=link
[Match]
Name=ens3

[Network]
Address=【VPSのIPv4アドレス】
Gateway=【VPSのIPv4ゲートウェイ】
DNS=【VPSのIPv4 DNSサーバ(1)】
DNS=【VPSのIPv4 DNSサーバ(2)】
Address=【VPSのIPv6アドレス】
Gateway=【VPSのIPv6ゲートウェイ】
DNS=【VPSのIPv6 DNSサーバ】
Tunnel=tun0

本当は「Tunnel=tun0」だけで済ませたかったのだが、その場合なぜかnetplanの設定が無視される(Name=ens3にマッチしたからこちらが優先された?)ので、やむなくIPv4/v6アドレス等を記載している。

カーネルパラメータの設定

IPv4パケットのフォワーディングを有効にし、各インタフェースのrp filterを無効にする。
rp filterが有効な場合、インターフェースと無関係なアドレス情報を持ったパケットが破棄されてしまうらしい。
今回tun0にはIPアドレスを振っていないため、パケットを破棄されないために無効にする。
ens3でも無効にしているがこれは元文献がそうしていたので同じようにしている。
rp filterを有効にした場合に本当に破棄されてしまうのか、検証はしていない。

net.ipv4.conf.all.forwarding=1
net.ipv4.conf.ens3.rp_filter=0
net.ipv4.conf.tun0.rp_filter=0
net.nf_conntrack_max=65535

ファイアウォールの設定

iptables -F
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -j ACCEPT
iptables -A FORWARD -i tun0 -j ACCEPT
iptables -A FORWARD -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -F
ip6tables -P INPUT DROP
ip6tables -P FORWARD DROP
ip6tables -P OUTPUT ACCEPT
ip6tables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -j ACCEPT
ip6tables -A INPUT -s 【自宅のIPv6ネットワーク】 -j ACCEPT
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT

IPv6ではICMPv6が重要な役割を占めていて、周辺情報等がICMPv6でやり取りされているので通してやらないと通信できなくなるので注意。
22番ポートは自宅以外からもアクセスすることを想定して公開。
これらを設定した後、iptables-persistentパッケージを導入して設定を固定。
iptables-persistentパッケージインストール後にファイアウォールの設定を変えてそれを反映させたい場合には以下のコマンドで。

dpkg-reconfigure iptables-persistent

このパッケージ、やってることはip6?tables-saveとip6?tables-restoreなのでそれでできる人はそれでも構わない。

RTX1200の設定

VPSとの間にトンネル作ってIPv4パケットはそちらに流すように設定するだけ

tunnel select 1
 tunnel encapsulation ipip
 tunnel endpoint address 【VPSのIPv6アドレス】
 ip tunnel tcp mss limit auto
 tunnel enable 1
ip route default gateway tunnel 1

EncapsulationLimitの罠

当初、/etc/sytemd/network/01-tun0.netdevの最後の「EncapsulationLimit=none」の行は入れてなかった。
だがその状態だとトンネル経由の通信がうまくいかない。パケットは出ていくが戻りがない。
VPS側で見てみるとパケットは投げ返しているようだったので、ルータで何かが起きていると思ってルータのログを見てみた。

[IPv6] received malformed option from 【VPSのIPv6アドレス】
[IPv6] received malformed option from 【VPSのIPv6アドレス】 
[IPv6] received malformed option from 【VPSのIPv6アドレス】 
...

 

パケットが変だと言われている。VPSでキャプチャしてみた結果がこちら。
8.8.8.8に対するpingとその応答。192.168.100.4は自宅マシン。

表示されているのはルータで破棄される定めの応答パケット。
IPv6ヘッダ内のNext HeaderがDestination Options for IPv6 (60)となっており、Tunnel Encapsulation Limitの情報が含まれていることがわかる。
そしてその中のNext HeaderはIPIP (4)で、IPヘッダが続くことを示している。
ping送信のパケットはDestination Options for IPv6 (60)はなく、IPv6ヘッダ内のNext HeaderはIPIP (4)となっていた。
IPパケットのカプセル化は入れ子が可能であり、Tunnel Encapsulation Limitはその上限を表している(参考:RFC2473)。
上限を示すためにこのオプションがあるのだが、残念なことにRTX1200ではこれをmalformedとして扱ってしまうようだ。

調べてみると、このオプションがついているパケットを検出するIPSシグネチャがあるようだ。

デフォルトではオフなようだが、RFC2473で規定されている通常のパケットを検出するとかお前は何を言っているんだ(RTX1200もだが)。

閑話休題。
LinuxのトンネルデバイスではTunnel Encapsulation Limitのデフォルト値がRFCで推奨されている4となっている。
これをnoneにすることで受信したパケットに前述のオプションがついていない場合は戻りパケットにもつけないようにできる。
この設定をすることにより、トンネルが正常に機能するようになった。

おわりに

PPPoEを経由しないと速くて快適である。みんなも早くこっちにおいでよ。

おわび

次回に予定していたIPsec編は、IPsecでのトンネルがうまく作れないため無期限延期となります。

YAMAHA RTX1200でMAP-Eした(けどやめた)

2018年5月28日 / 未分類

事の始まり

So-netでIPoE(IPv6)オプションを利用していたのだが、先日こんなお知らせが届いた。

v6プラスはJPNEが提供するサービスで、IPv4 over IPv6の技術としてはMAP-Eを使っている模様。
IPoE(IPv6)オプションを利用していた時は(公式には情報は何もなかったが)DS-liteでmfeedと繋げてIPv4 over IPv6をすることができた。
今回、方式が変わるのでその対応をしてみた、という話である。
使用機器はYAMAHAのRTX1200。

YAMAHA RTX1200でMAP-E

といっても先人がいろいろと情報を出してくれている。

基本は上記ページの通りにすればよい。
ただ、自身に割り振られたIPv6アドレスによってゲートウェイのアドレス、利用可能なポート番号が変わるのでそこは注意しないといけない。
このあたりの詳細は下記ページに書いてある。

これでIPv6アドレスから各種パラメータを導き出すことが可能になったのだが、割り振られたIPv6アドレスが変わったりするとまたパラメータを導出しなくてはならない。
恐らくIPv6アドレスが変わることはないだろうとは思うが、変わらない保証はない。
変わるたびに手動で導出するのは面倒だったので、割り振られたIPv6ネットワークを引数で与えるとRTX1200のMAP-E関連の設定を出力してくれるスクリプトを作成した。
MAP-Eの仕様については下記資料を参考にした。

スクリプトはこちら。PSIDオフセットとEAビット長は現在の状況からの推測。

#!/usr/bin/python3
# -*- coding: utf-8 -*-

#########################################
# RTX1200用MAP-EのNAT設定出力スクリプト #
#########################################

import sys
from ipaddress import *

ra_net = IPv6Network(sys.argv[1])

## RTXの設定用
# NAT Descriptor番号の開始番号
ndesc = 600000

# Tunnelの番号
tunnel = 1

# 指定可能なポート範囲の個数
# see: http://www.rtpro.yamaha.co.jp/RT/manual/rt-common/nat/nat_descriptor_masquerade_port_range.html
max_portrange_num = 3

## MAP-Eのパラメータ
# Port-set ID Offset
psid_offset = 4

# BRのIPv6アドレス
br_ipv6_addr = IPv6Address("2404:9200:225:100::64")

# Mapping Rule Tableの情報
# (rule_ipv6_prefix, rule_ipv4_prefix, ea_bits_length)
map_rule = [
    ( IPv6Network("240b:10::/31"),  IPv4Network("106.72.0.0/15"), 25 ),
    ( IPv6Network("240b:12::/31"),  IPv4Network("14.8.0.0/15"),   25 ),
    ( IPv6Network("240b:250::/31"), IPv4Network("14.10.0.0/15"),  25 ),
    ( IPv6Network("240b:252::/31"), IPv4Network("14.12.0.0/15"),  25 ),
]

rule_v6_net = None
rule_v4_net = None
ea_bits_len = None

# Mapping Rule Tableとのマッチング
for rule in map_rule:
    if ra_net[0] in rule[0]:
        rule_v6_net = rule[0]
        rule_v4_net = rule[1]
        ea_bits_len = rule[2]
        break

if rule_v6_net is None:
    print("No matched rules.")

# MAP-E IPv4アドレス、CE IPv6アドレス、PSIDの算出
ea_bits = (int(ra_net[0]) >> (128 - rule_v6_net.prefixlen - ea_bits_len)) & \
          ((1 << ea_bits_len) - 1) psid_len = ea_bits_len - (32 - rule_v4_net.prefixlen) v4_suffix = ea_bits >> psid_len
map_ipv4_addr = rule_v4_net[0] + v4_suffix
psid = ea_bits & ((1 << psid_len) - 1)
ce_ipv6_addr = ra_net[0] + (int(map_ipv4_addr) << 24) + (psid << 8)

# Port-setの算出
portset = []
prefix_len = 16 - (psid_len + psid_offset)
for i in range(1, 2 ** prefix_len):
    port_min = (i << (psid_len + psid_offset)) + (psid << psid_offset)
    port_max = port_min + 2 ** psid_offset - 1
    portset.append((port_min, port_max))

# output config
descs = []
for i in range(int(len(portset) / max_portrange_num) + 1):
    if i * max_portrange_num == len(portset):
        break

    start_i = i * max_portrange_num
    end_i = start_i + max_portrange_num

    print("nat descriptor type %d masquerade" % ndesc)
    print("nat descriptor address outer %d %s" % (ndesc, str(map_ipv4_addr)))
    print("nat descriptor address inner %d auto" % ndesc)
    print("nat descriptor masquerade port range %d %s" % \
          (ndesc, \
           " ".join(map(lambda x: "-".join(map(str, x)), portset[start_i:end_i]))))
    print("nat descriptor timer %d 600" % ndesc)
    print("nat descriptor timer %d tcpfin 30" % ndesc)
    descs.append(ndesc)
    ndesc += 1

print("tunnel select %d" % tunnel)
print("tunnel encapsulation ipip")
print("tunnel endpoint address %s %s" % (ce_ipv6_addr, br_ipv6_addr))
print("ip tunnel nat descriptor %s" % " ".join(map(str, descs)))

これの出力を使ってMAP-EによるIPv4 over IPv6の実現はできたのだが、Webサイトを見ていると結構な確率で応答が遅かったりタイムアウトしたりしていた。
原因はポートの枯渇であると容易に推測できた。何せ240個しかIPマスカレードに使えるポートがないのである。OSやらアプリやらWebサイトやらが裏でいろいろなところに繋いでいる昨今ではすぐなくなっても仕方がない。
RTX1200にログインして確認してみると、IPマスカレードの状況はこんな感じだった。

> show nat descriptor masquerade port summary 
Interface Desc Num Outer Address Used / All
------------------- ---------- --------------------------- -----------
PP[01](1) 1 ipcp/aaa.bbb.ccc.ddd 3/20000
TUNNEL[1](1) 600000 xxx.yyy.zzz.www 48/ 48
TUNNEL[1](2) 600001 xxx.yyy.zzz.www 48/ 48
TUNNEL[1](3) 600002 xxx.yyy.zzz.www 48/ 48
TUNNEL[1](4) 600003 xxx.yyy.zzz.www 48/ 48
TUNNEL[1](5) 600004 xxx.yyy.zzz.www 48/ 48
------------------- ---------- --------------------------- -----------

pp 1はPPPoEなので無視。TUNNELがMAP-Eのもの。
見事に使い切っている。いや、何度かやり直してちょうど使い切ってた瞬間のをコピペしたのだけど。
それでもブラウザのタブをせいぜいが10個くらい開いた程度のものである。
スマホやiPad、その他インターネットに繋がるデバイスがあるところではこんなものでは使い物にならないので泣く泣くIPv4通信をPPPoE経由に戻したのである。

Rev.14系以降のファームウェア(RTX1200はRev.10系)ではポートセービングIPマスカレードという機能があって、これを使えばこの問題は解決されるようだ。
市販のv6プラス対応ルータも恐らく同様の機能を有しているのであろう。
しかしRTX1200にはその機能はないのである。
さらばMAP-E。さらばv6プラス。

余談

先週からいろいろと試行錯誤している間に、RTX1210では公式にv6プラス対応のファームウェアが出ていた。

RTX1200にもファームウェアアップデートが出ているが、v6プラス対応もポートセービングIPマスカレード追加も書いてないのでRTX1200だと使い物にならないままだろう。

TOPへ戻る