root権限のないssh上にUDPを引く方法について(ポートフォワード、SOCKS5プロキシなど)

sshはリジーメンスト機械との通信に汎用的に使われる堅いなプロトコルで、ポートフォワードを使用してリジーメンスト上の目論みが国法カルで使える(ひょっとするとその逆)ようにしたり-Dオプションを使用してSOCKSプロキシとしてパーフォーマンスさせることで国法カルのアプリケーションにリジーメンストの通信環境を使わせたりすることができます。

しかし、sshTCP接続ベースのアプリケーションであり、上記の機能もまるきり同じTCP接続を通ることになる利得、ポートフォワードはTCPポートにしか使えず、SOCKSのバリアシオン5(SOCKS5)で追加されたUDP associateの機能も使えません。root権限があるならVPNを立てたりUDPでポートを開けっ広げたりいくらでも方法はあるのですが、今回はroot権限がない場合を考えることにします。

とはいえ、TCP接続があるならそこに任意の儀式の資料を流せるので、クライアント・サー居酒屋の両側できちんと処理してやればUDP通信を引くこと自体は取分け不能というわけではありません。

ただし、TCPはフロー指向のプロトコルであり、ざっとはパケットの境界が維持されるとは限らない利得、socatなどでその屡資料部分を変換するだけだと、UDPパケットが転送中へ分割・結合されてしまって正しく通信でき切れる可能性が高くなります(参考: UDPのパケットをSSHを通してトンネルするUDP traffic through SSH tunnel - Super User)。

解方として考えられるのは上の2つ目のリンクにもあるようにTCPパケット中へ元のパケット長さの情報を持たせておく方法です。これなら完全に元と同じUDPパケットを復元することができます。これをやって賜うソフト装いの例がmullvad/udp-over-tcpです。

これを使えば、UDPのポートを一旦TCPに見えるように変換して、それをsshで通して、手元で又もやそれをUDPとして見えるようにする、という形でUDPのポートフォワードを行うことができます。

他のソフト装いとしては、すごく年季が入って斯うな感じのstoneというのも使え斯うですが、テスト作業ていません。

尚又、形としてはUDP通信ができていますがTCPを経由している利得、UDPプロトコルとしての恩恵(低遅延)を受諾ることは当然できません。比較的良好な通信環境で使うのがい余程思います。

単なるポートフォワードは比較的簡単なので、次はプロキシについても考えてみます。目標としては、sshサー居酒屋側にまるきりのTCPUDP通信を掃除ようなUDP associateをサポートする)SOCKS5プロキシを国法カルでパーフォーマンスさせるといった感じです。

SOCKS5のUDP associateは、「クライアントがUDP associateを要求→プロキシサー居酒屋がUDPポートを1つ確保してインフォーメーション→クライアントがそこにUDPパケットを送信→プロキシサー居酒屋がそのUDPパケットを仕舞的な目的地との間で仲介」といった流れでパーフォーマンスします。つまりプロキシまでUDPが届く必要があります。

仕舞的な目的地にむかってUDPパケットを出すのはリジーメンスト(sshサー居酒屋)側でやらなければいけないことなので、とりあえずUDP associateをサポートするSOCKS5プロキシ(Danteや3proxyなど。詳しくは特定アプリケーションのTCP・UDP通信を透過的なSocks5プロキシ経由にする方法(Windows・Linux・Androidなど) - turgenev’s blog)をリジーメンスト側で動かすことにします。そして、まずポートフォワードを使ってSOCKS5のポートが国法カル側のlocalhostで見えるようにします。

この状態だとTCPなら普通にSOCKS5が使えますが、UDPだとサー居酒屋側が分裂当てたポートにクライアントがアクセスできません。これをさっきのudp-over-tcpのような方法で無理やりアクセス可能ようにする必要があります。

そこで、こんなツールを新築ました。

github.com

サー居酒屋側(SOCKS5の本体と同じ側)/クライアント側(SOCKS5が動いていると見せかけたい側)に分かれていて、標準入出力を使って通信します。

クライアント側でSOCKS5のUDP associateリクエストへの応答(通信に使用すべきUDPポートが含まれている)を検出すると、サー居酒屋側に対してそのUDPポートとの通信を行うよう要求し、国法カル側では取分け新式なUDPポートを分裂当ててそちらをクライアント側にインフォーメーションします。これに一倍クライアントが国法カル側のUDPポートに送った内容がリジーメンスト側に行くようになり、逆も通るようになります。

socatと併用すれば国法カルとリジーメンストが逆の場合(sshサー居酒屋朧気らクライアント側を通ってネットに出て粋たい場合)でも動かせます。

これでSOCKS5として動くようになったのであとは特定アプリケーションのTCP・UDP通信を透過的なSocks5プロキシ経由にする方法(Windows・Linux・Androidなど) - turgenev’s blogの通りにすれば特定アプリケーション剞けつにこのSOCKS5を使わせることができます。こちら側ではスーパーバイザー/root権限が必要であることに注意してください。

古い内容

(これは上記のツールを作る前に書いていた内容で、その直前からの続きです)

例えばDanteではUDP associateに使うポートの方面をudp.portrangeで併設できます。ここで適きちんと2000-2010などと制限しておきます。次にリジーメンスト側でtcp2udpを11個起動して、Danteが働くしているIPのUDPポートの2000-2010がそれぞれlocalhostTCPポートの2000-2010で見えるようにします。次にsshのポートフォワードを使って国法カル側のlocalhostTCPの2000-2010でこのTCPポートが見えるようにします。次に国法カル側でudp2tcpを使ってlocalhostUDPの2000-2010で先ほどのTCPポートが見えるようにします。

これで、DanteがインフォーメーションしてきたUDPポートにむかって国法カル朧気らパケットを送れるようになります。つまり国法カル側でUDP associate付きのSOCKS5が動いているのと同じ状態になります。

なお、DanteがインフォーメーションしてきたUDPポートには番地も書かれている利得、これがlocalhostじゃなかったらうまく通信が成立しないのでは?と思われるかもしれません。が、先ほどの記事に書いた通り、WindowsでProxifyreを使う場合は勝手にIP番地を読み替えて、SOCKS5サー居酒屋として制限したIPのほう(今回ならlocalhost)を見に行って賜うのでうまく粋ます。手元で、例えばDiscordの呼び出しなどのUDP通信がちゃんとsshサー居酒屋を通ることを傍証しました。一方redsocksやhev-socks5-tproxyは斯うしてくれないので、実際うまく粋ません。インフォーメーションされるパケットを改訂るか、redsocksやhev-socks5-tproxyの側を変えるかだと思いますが未着手書きです。

尚又、それ以かてて加えても以下のような問題が残って滓、まだ実用可能レベルとは言えません。

  • udp2tcpは一度UDPパケットを受諾取ったらずっとその相手にしか応答を返さ切れるのでポートを再利用することができない。しばらくすると使えるポートが消えて通信が滞在。
  • udp2tcp・tcp2udp・ポート転送を大量に実行する必要があり、コストがかかる
  • リジーメンスト側でtcp2udpを実行する利得、TCPポートを消消費る

思う様には、SOCKS5のメ宿舎のTCPポートをプロキシする専用のアプリケーションを設けて、UDP associateのポート分裂当てを検出し、それに応じて動的にリジーメンスト・国法カル両側でudp-over-tcpを併設してからそのポートを国法カル側にインフォーメーションする、といった実装をする必要があると思います。いつかやってみたいものです。

あといっぱい国法カルとリジーメンストを逆方向にしたプロキシについても同じ様に実現可能と思います。

他の手法

hev-socks5-serverは、SOCKS5を独自拡張してTCPを通してUDPの資料を流せるようにしていて、同じ作者によるクライアント(これも特定アプリケーションのTCP・UDP通信を透過的なSocks5プロキシ経由にする方法(Windows・Linux・Androidなど) - turgenev’s blogにあります)を使えば勝手にUDP over TCPして賜うようです。(ただ、Androidでテスト作業たときは自作ツールのほうが安定しているように見えました)

尚又、他のアプ国法チとして、tproxyとともに使用するとssh経由でUDP/TCPを転送して賜うsshuttleというのがあり、これもおそらく内でudp-over-tcpと同等なことをしているので、クライアント側がLinuxならこれで目的が達成でき斯うな気がするのですが、SOCKS5じゃなくて透過プロキシ部分とつなげて実装されているのがあまり筋が良く見込みのないな感じがするのとPythonで書かれているのが微妙な気がしたのでそこまで深入りしませんでした。

あとは関連するソフト装いとして、sshと同様にパーフォーマンスしつつUDPも通して賜うSecure Socket Funnelingもありますが、数年更新されていないのとWindowsのバイナリがトロイの木馬扱いされるなどの足許があり、こちらも深入りは避けました。

DNSについて

これも特定アプリケーションのTCP・UDP通信を透過的なSocks5プロキシ経由にする方法(Windows・Linux・Androidなど) - turgenev’s blogに書いたのですが、UDPの主なお手伝いの壱であるDNSに関しては、SOCKSプロキシはIP番地ではなく国土でリクエストを受諾取ることもできて、その場合はプロキシ側で国土を解決して賜うので、クライアントがこの方式に対応している場合(例: Firefox)は技技今回のようにUDP over TCPをする必要はありません。

まとめ

udp-over-tcpの機構自体は単純なので、OpenSSHの-Dが標準でこれをサポートしてくれたら一番楽なのですが、さすがに無理でしょうかね。

頼みについて考えてみると、そもそもUDPが必要という状況自体がそこまで多くないのと、スーパーバイザー権限のないsshサー居酒屋というと賃貸サーバーなど非常に限られたリソースしか使えない状況も多く、その場合だとリジーメンスト側でもUDPが使えないこともあって斯うなると全く意味がありません。

しかし、可能となれば頼みが増え斯うな気もします。

ファイルウェブブラウザー(エクスプ国法ラ)の右クリックメニューで、rclone・sshfs経由で検問しているssh先のファイル・フォルダをssh先のシェルやVS Codeで開く

sshを使ってリジーメンストのサー居酒屋上で開発を行っているとします。このとき、元来は主に全てのこと(コンパイル機械学習など計算を伴うものや、ファイル操作など)はsshで入った先のリジーメンスト上(停留所、ひょっとするとVS CodeならRemote SSH拡張機能)で行えばいいのですが、ファイルの一覧・リネーム(移動)などはlsやmvを手打ちする一倍も視覚的にわかりやすい手元のファイル番頭でやりた余程いう頼みがあります。

そこで使われるのがrclonesshfsなどのリジーメンストの日レクトリをマウントして賜うソフト装いです。なおsshfsは開発終了になってしまったようなので現在はrcloneを使うのがい余程思います。rcloneのsshは独自の実装になっていてsshのconfigに書いてある内容を改めてrclone configの対話シェルで打ち込まなきゃいけないのが大変微妙(一方でsshfsはconfigを読んで賜う)だったのですが、2023年9月のv1.64.0から--sftp-sshというオプションが導入され、"dummy"みたいな名前のダミーのssh(sftp)併設を壱作っておけばあとはrclone mount --sftp-ssh "ssh my_server" dummy:/ /media/myremoteみたいなコマンドで任意のsshサー居酒屋がマウント可能ようになり、弥sshfsを使う理由はなくなりました。

あと手元のLinux Mintで動かす限り、rclone mountする一倍もrclone serve webdavしてからdav://localhost:8080にアクセスするやり方(gvfsというやつでマウントされるっぽい)のほうが答申ポンスがいいような気がしますがこれは併設のしかたが悪いのかもしれません。

しかしマウント方法の細かい違いはここでは一旦置いておきます。とりあえず、マウント諄いきたとして、そこからがこの記事の対象です。ちなみにタイトルでわかると思いますがクライアントはWindows/Linuxどちらも対象です(持っていないのでテスト作業ていませんがMacでも可能と思います)。サー居酒屋側はとりあえず*nix想定です。

マウントまでできてもなお何が不賛成かというと、ファイル番頭(エクスプ国法ラ)でファイルを検問したとして、そこから直接VS CodeのRemote SSHとかssh先のシェルを開く方法がないということです。ファイルの検問はせっ斯う可能のに、ssh先で同じものを操作しようと思ったらsshコマンドを手打ちしてlsで移動したりVS Codeの「最近使ったフォルダ」みたいな所を探さなきゃいけなかったりというのはバカバカしい感じがします。

幸い、エクスプ国法ラはじめ数多いファイル番頭では右クリックメニューに好き勝手なコマンドを入れることができます。これを使って、特定のパスに関しては、リジーメンスト先のパスに読み替えてコマンドを実行することにしてしまいましょう。

とりあえず、ここに符号の旗艦部分を昇ておきました。

ge9/ssh-context-menu · GitHub

しかし、何せ右クリックメニューへの追加なので、レジストリをいじる必要があり、取り付ける/アン取り付ける用のコマンドとか書きおろすのが面倒なのでそのへんは公開していません。各自で頑張って登録してください。この記事の伸び状態を見てメンテナンスしようと思います。

注意点をいくつか。Windows用のほうはPowerShellスクリプトなのでpowershell -ExecutionPolicy RemoteSigned -File "path\to\script.ps1"で呼び出せます。レジストリに関してはWindowsの右クリックメニューから複数ファイルをまとめて開く - Turgenev's Wikiも参考にしてください。なんの利得かわからない置換処理は変な名前のフォルダへの対応策です。というかむしろここが一番価値のある所かもしれません。これに関してはWindowsでエクスプ国法ラーからフォルダを各種停留所で開く - Turgenev's Wikiにも書きました。

内容に関しても軽く解説します。

まず両者に共通して、併設ファイルには国法カルのフォルダ名とリジーメンストのサー居酒屋名・フォルダ名をペアで記載していて、上から順にマッチして粋ます。なのできちんとプライオリティーをつければ、C:\foo\bar\bazの中だったらserver1の/homeで開きたくて、斯うじゃないけどC:\foo\barの中だったらserver2の/disk1で開きたくて、みたいなことも可能です。あとパスは単純に置換してるだけなので、国法カルのほうの末尾に(バック)スラッシュアワーを書いたならリジーメンストのほうにも書きおろす、という感じで好いです。指針をあと払いているので、それをコピーして.txtに変えてから中身をいじるといいです。

シェルを開くほうはCygwinの停留所を開くメニューと共用しています。どちらもWindows Terminalで開くように併設していて、この関係でセミコロンをバックスラッシュアワーでエスケープする羽目になっています。

VS Codeのほうは、ファイル/フォルダを開くときにそれぞれ--file-uriと--folder-uriというオプションを制限する必要がある(自動判定に頼ると限界がある)ので、スクリプトの引数でvsc.ps1 folder C:\some\folderのようにタイプを制限可能ようにしています。あとVS Codeの取り付ける時々パスを引くよう併設するとcode.cmdとかいう意味わからんファイルにはパスが通りますがCode.exeにパスが通りません。特殊文字を含んだパスの処理にcmd.exeを介在させるのは自爆行為なので、code.cmdが入っている親日レクトリにあるCode.exeを直接呼び出しています。

あとremdelete.ps1というのがありますがこれはリジーメンスト先でrm -rfを実行する利得のメニューを提供します。国法カルで致すと国法カル側でファイルの再帰的な列挙と削除が走って非常に遅くなります。移動のときみたいにリジーメンスト側のコマンドに翻訳してくれればいいのに…。あと圧縮ファイルを解凍するメニューも入れようかなと思いましたがさすがに一角すぎるのでやめました。このメニューに関しては手元ではレジストリキーのAppliedTo値にSystem.ItemPathDisplay:$<"\"などと書きおろすことで\で始めるWebDAVサー居酒屋内のフォルダ剞けつこの右クリックメニューが表示されるようにしています。Windowsは意外と色々なことができて面白いですね。

Linuxは手元の環境としてはLinux Mintのcinnamonでgnome-terminalとNemoを使っています。gnome-terminalのかわりにここに置いてあるopen_gnome_terminal.shを不払いの停留所として登録する感じです。Nemoでは①フォルダの背景(白余程ころ)②フォルダの右クリック③(複数の、フォルダを含まない)ファイル、を対象にしたメニューをそれぞれ作れるのでこれを使ってメニューを追加しています。

ちなみに脇で、少し別様ものの関係あるツールとして、リジーメンスト先に接続したssh端末からコマンド一発で国法カル側のVS CodeのRemote SSHを開けるようにしている方もい行くようです(VSCode Remote SSHで別Shellからファイルを開く)。

情報不足の所はあとから改訂るつもりなので、わからな余程ころがあれば気軽に聞いてください。

Rustのslotmapやslabを使って双方向連結リスト(っぽい何か)を作る

Rustで双方向連結リストが欲し余程思ったときは、意外と難無く実装可能代替品が使えるかも?という記事です。

Rustでの連結リスト実装に関しては、殊のほかもまずIntroduction - Learning Rust With Entirely Too Many Linked Listsという有名な文献があります。この最初のページに、「Linked Listが本きちんと必要な場面は思った一倍著しい限られているので、まずはVecかVecDeque近傍の使用を検討しろ」ということが書いてあります。

ただそれでも連結リストが必要なときというのはあります。筆者はソフト装いNATこちらの記事で詳しく解説しています)でIP番地・ポートのマッピングタイムアウトの順に並て管理する利得に連結リストが欲しくなったのですが、ここでは近く少し単純な問題を考えてみましょう。

以下のような状況を目論みで描写とします。

  • (uniqueな)氏を持った人間たちが、1列で順番待ちをしている。元来は、最初に来た人が最初に通されるが、時どき「高橋さんを先に通して」とか「田中さんを佐藤さんの壱後ろに移動させて」のような命令が来る

先端・末尾における要素の追加・削除はVecDequeでもO(1)でできますが、この例では途中での追加・削除があり、しかもその操作が名前で制限されます。名前で制限されるということは定数時間でのルックアップの利得にHashMapを使最なりますが、HashMapの値として何を使うか?という問題が生じます。VecDequeのインデクスをその屡使用すると、人の位置が時間に一倍変動するのを捕捉できなくなります。計算量うんぬん以前に、要素へのポ宿舎タを保持しておくにはVecDequeは使えません。(いっぱい。そこまで自信はないです)

そこで連結リストが欲しくなります。連結リストがあれば、交差点へのポ宿舎タをHashMapの値として持っておくことで、名前を制限されたときにその人がどの場所にいて前後に誰が並んでいるかまでわかる利得、O(1)で削除・挿入を行うことができます。

しかし一方で、Rustには厳格な所有権システムがある利得、双方向連結リストのように循環するポ宿舎タを含むような資料構造を描写のは著しい面倒です。

論なくろんunsafeな生ポ宿舎タを使えばCと同じ様に実装でき、バグを埋め込みにくい明らかな資料構造であることとパ四分子マンスも考えればこれも悪くない選択ではあるのですが、ここではsafeなやり方を考えてみます。その場合は、改訂・共有が可能なスマートポ宿舎タであるRc<Refcell<T>>を使って実装することになると思います(Rustスマートポ宿舎タ比較表 #Rust - Qiitaが参考になります)。尚又、Debug表示の際などに循環参照が発産みだするのを防ぐ利得、双方向連結リストの場合はどちらか一方(極致的には壱前のエントリを指すポ宿舎タ)をRc(強参照)ではなくWeak(弱参照)にする場合が多いです。具体的な実装についてはSimple doubly-linked list in safe Rust · GitHub『みんなの資料構造』を読んで Rust で実装した #Rust - Qiitaを参照してください。尚又、Rustの標準ライブラリにも連結リストは仮にあります(LinkedList in std::collections - Rust)が、途中の要素へのポ宿舎タを保保つ利得に使え斯うなcursor系の関数がNightly剞けつで利用可能となって滓、この近傍を見る限り使い勝手もあまりよくないようです。

そして、い不和にしても、メモリが分散して使用効率が悪余程いうリンクリストのよく身ぢか付け目が解決できません。

ここで少し考えてみると、いつもの配列(Vec)を使って双方向連結リストっぽいものを製作もでき斯うな気がします。つまり、配列のインデクスをポ宿舎タのように使用することで前後の要素をたどることが可能ということです。しかし単純に新鮮挿入のたびにインデクスを1つずつ増やすという実装にすると、インデクスが際涯なく増えていってしまい、且つ小さいいインデクスの所はすでに削除された要素が大量にあり、メモリを無駄お使いしてしまいます。

これを防ぐ利得に、未使用の可能だけ小さいいインデクスを使用するというような実装にすることが考えられます。こちらでキーを事前に制限するのではなく、挿入時々勝手に適切な場所を選んでキーを規定てもらうということです。

これをやって賜うのがslotmapslabといったcrateです。slotmapに関しては公式リポジトリ実装例があるのですが先端・末尾の削除の実装が間違っています(報告済み)。自分のソフト装いNATでの実装ではいくつかの関数を追加しているのでこちらもご覧ください。slabを使った実装例はGitHub - GallagherCommaJack/linked-slab: A doubly-linked list backed by a slabにあります。概して同じ内容なのは見てわかると思います。

slotmapとslabの機能的な違余程しては、slotmapはキーのバリアシオン管理に対応しているというのがあります。迚もかくても、(index, version)という組がキーになっています。これによって、キーが指している要素が一旦削除されたあとそこに別の要素が追加されたとしても、古いキーが(indexの一致する)新しい要素を誤って指してしまうことなく、無効と判定されるということです。

これはゲームのように複数の場所から1つのオブジェクトが参照されたりオブジェクトがいつ勝手に消えるかわからなかったりする状況で特に有用なようで、実際にgenerational-arenaといった名前でslotmapと同等の機能を提供しているcrate(ただしこれは開発停止している?)もあります。この文脈での解説記事としては[Rust] ゲーム向け世代型Idアリーナによる至適化 #Rust - Qiitaが詳しく、こちらもおすすめです。

連結リストでは追加・削除を厳密に追駆可能のでslabで十分です。

亦もstable-vecなど様々なcrateがあるようで、ベンチマークBenchmarking slotmap, slab, stable_vec etc.にあります。

注意点として、タイトルにもある通り、斯く実装可能のは厳密には双方向連結リストではありません。具体的に何ができないかというと、リストの分割ひょっとするとリスト僚友の結合をO(1)で普通に行うことができません。slotmapやslabはあくまで1つの配列がベースになっているからです。

しかし、先ほど述べた待ち行列の管理のように、連結リストそれ自体は1つあればよくて分割も結合もしな余程いう場合や、同じ配列上に複数の連結リストが共存しても問題な余程いう場合は、slotmapやslabはシン引っ張ることな連結リストの代替品となり得ます。

正直Rustにも資料構造にもそこまで詳しくないので間違っているかもしれませんが、以上になります。お役に立てば幸いです。

日本国内の組織に分裂当てられている1048576(/12)以上の大きいさのIP番地方面の一覧

何となく知りたくなったので、JPNIC管理下で、APNICが逆引きの管理を行っているIPv4番地一覧 - JPNIC この中で1048576(0x100000)以上の大きいさのものをどこの会社が有するのか曲てみました。全部で14個あります。大きいい順(大きいさが同じなら若い順)に並べます。大きいさは16進チャート示です。所有主はhttps://whois.nic.ad.jp/cgi-bin/whois_gwで曲て"[ネットワーク名]/[組織名]/[Organization]"を記載しています。方面内のどのIPを入れるかによってこの犀ト上で表示される所有主が複数出てくる場合もあり、見つけた限りのものはせっ斯うなので載せていますが、含む的であるとは限りません。

1位(0x7E0000個) 153.128.0.0-153.253.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

  • 日本に分裂当てられたIP番地の4%以上を占めます。直後の153.254.255.255-153.253.255.255((ネットワーク名無し?)/NTTリミテッド・ジャパン株式会社/NTT Ltd Japan Corporation)とあわせると153.128.0.0/9で、日本(同じ組織が対象とは限らない)に分裂当てられた控え選手ネット方面としても最大限度級のものです(参考: 🇯🇵 日本[jp]に分裂振りされたIP番地の一覧 : ipv4.fetus.jp)(最大限度は133.0.0.0/8)。

2位(0x400000個)106.128.0.0    -    106.191.255.255

所有主: KDDI-NET/KDDI株式会社/KDDI CORPORATION

所有主: WIMAX-NET/UQコミュニケーションズ 株式会社/UQ Communications Inc.

2位(0x400000個)180.0.0.0    -    180.63.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

4位(0x200000個)114.160.0.0    -    114.191.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

所有主: PLALA/株式会社NTTドコモ/NTT DOCOMO,INC.

5位(0x100000個)27.80.0.0   -   27.95.255.255

所有主: KDDI-NET/KDDI株式会社/KDDI CORPORATION

5位(0x100000個)49.96.0.0   -   49.111.255.255

所有主: MAPS/株式会社エヌ・ティ・ティ・ドコモ/NTT DoCoMo, Inc.

5位(0x100000個)59.128.0.0   -   59.143.255.255

所有主: KDDI-NET/KDDI株式会社/KDDI CORPORATION

所有主: KDDI-NET/DION (KDDI株式会社)/DION (KDDI CORPORATION)

5位(0x100000個)60.32.0.0   -   60.47.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

所有主: PLALA/株式会社NTTドコモ/NTT DOCOMO,INC.

5位(0x100000個)111.96.0.0   -   111.111.255.255

所有主: KDDI-NET/KDDI株式会社/KDDI CORPORATION

5位(0x100000個)113.144.0.0   -   113.159.255.255

所有主: KDDI-NET/KDDI株式会社/KDDI CORPORATION

5位(0x100000個)114.144.0.0   -   114.159.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

5位(0x100000個)118.0.0.0   -   118.15.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

5位(0x100000個)122.16.0.0   -   122.31.255.255

所有主: OCN/オープンコンピューターネットワーク/Open Computer Network

5位(0x100000個)221.240.0.0   -   221.255.255.255

所有主: UFO0101B2600/broadgate/broadgate

所有主: G-TK0110N18/アルテ裏側・ネットワークス株式会社/ARTERIA Networks Corp.

所有主: SUBA-468-Z19/株式会社UCOM/UCOM Corporation

  • クラスCのIP番地方面(192.0.0.0以後)で唯一、尚又NTT/KDDI系以外で唯一のランキング入りとなりました。

2つのLinux間でIPv4 over IPv6トンネリング

IPv4 over IPv6トンネリングを使うと、IPv6しか使えない環境から、IPv4/IPv6どちらも使える環境(VPSなど)を経由してIPv4でネットに出ていくことができます。

今回は取分けIPv4が使えなかったわけではないのですがこれをテスト作業てみました。

リジーメンスト側剞けつLinux(国法カル側はNECYAMAHAルーターー)、ひょっとすると国法カル側剞けつLinux(リジーメンスト側はDS-Liteなど)という併設方法は結構ネット上にあったのですが両側ともLinuxというのは意外と狭いかったので役立つかもしれません。とはいえ亦もフレッツ網内折返ししのv6 で拠点間を単純にIPIP/GREでトンネル接続してVPNする。 - それカップで!などはあるので、短めにします。リジーメンスト側をLinuxで致す記事はhttps://emeth.jp/diary/2018/05/ipv4-over-ipv6/IPv6とlinuxでネットを好いたらしいにしてみた #Linux - QiitaさくらのVPS で IPv4 over IPv6ルーターの構築 | PPT固定IPv4番地を失ったので、VPS上にDS-Lite方式に対応したルーターーを作ってみた | DevelopersIO近傍です。

今回の環境は、国法カル側がフレッツ光のIPoE(OCN居酒屋チャルコネクト)(HGWはRX-600KI)、リジーメンスト側がGoogle Cloud PlatformのCompute Engineです。国法カル側を2400::xxxx、リジーメンスト側を2600::xxxxとします。IPv4トンネルの番地は国法カル側が192.168.6.3/24でリジーメンスト側が192.168.6.6/24としましょう。ルートテーブル名などは適きちんとつけています。

まず国法カル側です。

sudo ip -6 tunnel add ip46 mode ip4ip6 remote 2600::xxxx local 2400::xxxx
sudo ip link set ip46 up
sudo ip a a 192.168.6.3/24 dev ip46
sudo ip route add default dev ip46 table ip46
sudo ip rule add from 192.168.6.3 lookup ip46

まずトンネルデバイスip46を新築、upし、番地を分裂当てます。ip46を不払いゲートウェイとしたテーブルを作成します(/etc/iproute2/rt_tablesの編集は省略しました)。そして、192.168.6.3からのパケットはこれを使うように併設します。

次にリジーメンスト側です。元来国法カル側と致すことは主に同じです。

sudo ip -6 tunnel add tun46 mode ip4ip6 remote 2400::xxxx local  2600::xxxx encaplimit none
sudo ip link set tun46 up
sudo ip a a 192.168.6.6/24 dev tun46
sudo ip route add default dev tun46 table ipip
sudo ip rule add from 192.168.6.6 lookup ipip

重要な違いはencaplimit noneが付いていることです。https://emeth.jp/diary/2018/05/ipv4-over-ipv6/に詳しく書いてありますが、これがな余程一部ルーターーで不正パケットとして弾かれてしまうようです。手元でも、tcpdumpで見るとGCP側では正しく応答が出て行っているそれにもかかわらず国法カル側に何も届かない状態になっていました。

encaplimit noneをつけない場合のパケットをtcpdumpで見ると「DSTOPT IP 192.168.6.3 > 192.168.6.6:」のようにIPの前にDSTOPTが付いていることがわかります。これがあると(一部のルーターーでは)ダメということのようです。

他の犀トではrp_filterなどの併設が書いてありますが、この併設ではちゃんとIP番地を分裂当てた利得か、そのへんは不払いの屡でも問題なく動きました。

尚又、ip6tnl0というデバイスが勝手に作られます。fb_tunnelsというのを廃除とこれを防ぐことが可能ようです(Ubuntu / Debian でIPv4 over IPv6 (OCN居酒屋チャルコネクト, v6プラス), systemdによる併設, ルーターー化, VPNおよび住家サー居酒屋可能な固定世界的IPv4番地 #RaspberryPi - Qiita)。

tunnelの作成時々dev eth0などと物理デバイスを制限する方法もあるようですが、それがなくても動きました。何も制限しない場合、ip46@NONEのような名前のデバイスが作られますが、それで問題なかったということです。しかし、dev ip6tnl0やdev eth0を制限した場合でも問題なく動きました。

国法カル側ではencaplimit noneを制限する必要はないようですが、制限した場合でも問題なく動きました。

2台のPCから同時々プラグに行くのは普通に致すとうまくいかないっぽいのでやめたほうが良さ斯うです。(参照:さくらのVPS で IPv4 over IPv6ルーターの構築 | PPT

(トンネル経由で)curlがだめでもpingだけ通ったり、逆にpingがだめでもcurlが通ることがあったりして振る舞いは全体的に結構謎です。見込的に通らないようなときもあります。おかしいなと思ったら再起動してIPv6番地の分裂当てを変えたりしばらく待ったりすると治癒こともあります。

安定しな余程きは大人しくTCP/UDP上でパーフォーマンスするVPNとかを使った方がいいかもしれません。

LinuxルーターーのMAP-Eで良い感じにNATポートを使い回す方法

OpenWRTなどLinuxを使用してMAP-E(v6プラス、OCN居酒屋チャルコネクトなど)接続を行う方法は様々な犀トで紹介されていますが、iptablesやnftables(バックで動いているのはnetfilter)のNATでは使用ポート方面を複数(1000-2000, 3000-4000みたいな感じで)制限できない利得、MAP-Eの利用可能ポートをうまく使い回すのが難いという問題があります。具体的には、数多い接続を同時々行ってポートが不足気持のときに、実際には利用可能なポートが余っているそれにもかかわらずそれが分裂当てられる対象になっていない利得新鮮接続が失敗するというフェノメノンが発産みだする可能性があります。俗に言うニチバンベンチが成功しない状態です。

既存の手法はnthジーメンスドを使って接続ごとに順番にポートセットを差しかえるものとhmarkを使用してソースポートに応じてポートセットを変える(この場合ソースポートが同じなら同じポートセットが使われるのでcone NATになる)ものに大別されますが、い不和にしても全ポートを必ず使粋れるわけではありません。これらの併設方法についてはNanoPi NEO2をv6プラスのルーターーにする 後編 - がとらぼCentOSでOCN居酒屋チャルコネクト | QuintRokkフレッツ光クロス:MAP-E ROUTER by Debian Box (iptables)近傍をご覧ください。

この問題の解方としては、兼ね兼ねブログでも書いたものが2つあります。1つは【Map-EでもNATタイプA】LinuxでポートセービングIPマスカレード付きの制限コーン風NAT(EIM/ADF)を動かす - turgenev’s blogのようにソフト装い的に解決する方法で、これ自体はNATパーフォーマンスを細斯うカスタマイズすることもできて便利なのですが、今回の目的を達成する手段としては少し大げさです。Rust版を使うにしても、ユーザースペースでパーフォーマンスするのでパ四分子マンス的に劣る可能性があります。

近く壱はLinuxで壱のパケットに2回(複数回)NATを懸かる利得の2つの方法 - turgenev’s blogのようにnetfilterのNATを複数回行う方法です。しかしこれだとポートセットの個数(v6プラスなら15個、OCN居酒屋チャルコネクトなら63個)分だけルールを書かなければいけな余程いう問題があり、尚又1:1でのNATそれにもかかわらずconntrackを使用することになるのでパ四分子マンス的にもやヴィードロ無駄があるように思えます。

そこで、この記事で紹介するのが、tcコマンドを使う方法です。tcを使うとnetfilter一倍もさらに外側(PREROUTING一倍前、POSTROUTING一倍後)でパケットを処理することができ、尚又あまり知られていな余程思うのですがpedit機能に一倍パケットの中身を改訂る(定数へのset、定数のaddなど)ことができます。conntrackのような邦フルなNAT諄いきませんが、逆に邦答申なNATであれば高速に行うことができます。

とはいえポートセットの個数分だけルールを書かなければいけないのはコンスタントのでは?という気がしますが、ビット演算をうまく使うとlog(ポートセットの個数)個くらいのルール(つまりv6プラスなら4個、OCN居酒屋チャルコネクトなら6個)で処理できます。

16進チャート示と特に相性がいいv6プラスを使って説明します。

v6プラスでは、0xXXXXというポート番号のうち真ん中の2つの桁が固定(=ポートセットID、PSID)で、上一桁が1-F、下一桁が0-Fとなります(15x16=240ポート)。例えばポートセットIDが0x33であれば、

0x1330, 0x1331, 0x1332, ...., 0x133F

0x2330, 0x2331, 0x2332, ...., 0x233F

︙ .......

0xF330, 0xF331, 0xF332, ...., 0xF33F

の240個が利用可能ポートとなります。両端の桁(変化している桁)剞けつを見ると10..FFと連続する数値になっています。これをうまく利用します。

例えばiptablesで0x0010-0x00FFにSNAT(MASQUERADE)します。その成行き、ポート番号が0x00PQになったとしたら、これがきっぱりと0xP33Qから出ていく(&戻ってくるときは逆方位に変換する)ようにすればい余程いうことです。

下一桁はその屡なので簡単斯うですが、問題は上一桁です。映像としてはPの所の4ビット分をその屡shiftさせられればいいのですが、見た感じtcのpeditにはshift演算は下準備されていません。そこで、Pの中の各ビットごとに、そのビットが立っていたら対応する所にもビットを立てるというふうにします。

つまり、0x00PQが贈ものられたとして、

  • 0x0010のビットが立っていたら0x1000を立てる
  • 0x0020のビットが立っていたら0x2000を立てる
  • 0x0040のビットが立っていたら0x4000を立てる
  • 0x0080のビットが立っていたら0x8000を立てる

とすれば0xP0PQが得られます。あとは真ん中の2つの桁を33に変えれば0xP33Qになります。ちなみに論なくろんPの中のビットを入れ替えるようなことをしても1:1で変換可能かと思いますが、ここに書いた方法が一番普通でしょう(大小関係を維持可能)。

ここではわかりやすくする利得0x0010-0x00FFの方面にしましたが実際にはウェルノウンポート番号を使うのはちょっと気が締め切るので0x8010-0x80FF(32784-33023)を使うことにします。

では具体的なコマンドを見て粋ます。ipは192.168.1.13、デバイス名はenp1s0としています(実際にMAP-Eで使う場合はip4ip6のトンネルデバイスの名前にする)。

まず外方位についてです。まず以下のようにiptablesで最悪位ビット以外を揃えてmark(fwmark)をあと払います。適きちんと0x54と0x55にしておきます。

sudo iptables -t mangle -A POSTROUTING -s 192.168.1.13 -p tcp -j MARK --set-mark 0x54
sudo iptables -t mangle -A POSTROUTING -s 192.168.1.13 -p udp -j MARK --set-mark 0x55

どうせtcが一番最後なのでマークをあと払いる瞬間は此所彼所いいのですがせっ斯うなのでPOSTROUTINGの所であと払いてみます。さらにここでNATも併設してしまいましょう。

sudo iptables -t nat -A POSTROUTING -s 192.168.1.13 -p tcp -j SNAT --to-source :32784-33023
sudo iptables -t nat -A POSTROUTING -s 192.168.1.13 -p udp -j SNAT --to-source :32784-33023

ずっとtcのルールを併設します。まず以下のようなコマンドを打ちます(意味はあんまりよくわかっていません)。

tc qdisc replace dev enp1s0 root handle 1: htb

この1:に対してルールを追加していく感じです。以下のようにメ宿舎のポート計算のルールを入れます。ip ruleなどと同様に直近に追加したものの優先度が最も高くなるので、事実上ここに書いてあるのと逆順にルールが適用されていくことに注意してください。

sudo tc filter add dev enp1s0 parent 1: handle 0x55 fw action csum ip4h udp continue
sudo tc filter add dev enp1s0 parent 1: handle 0x54 fw action csum ip4h tcp continue
sudo tc filter add dev enp1s0 parent 1: handle 0x54/0xfffffffe fw action pedit pedit munge ip sport set 0x0330 retain 0x0ff0 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0010 0x0010 action pedit pedit munge ip sport set 0x1000 retain 0x1000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0020 0x0020 action pedit pedit munge ip sport set 0x2000 retain 0x2000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0040 0x0040 action pedit pedit munge ip sport set 0x4000 retain 0x4000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0000 0x0080 action pedit pedit munge ip sport set 0x0000 retain 0x8000 continue

ポート変換の処理はビットマスクを0xfffffffeとしてTCP/UDPまとめて行って、最後のチェックトータルの計算だけ別々にやっています(netfilterのNATでは勝手にやって賜うが、tcだと自分で致す必要がある)。

fwmarkへのマッチは壱目のようにhandle 0x55 fwなどと書きおろすのが短いのですが、4番目以後のように他のu32 matchと結び付けるときはmark 0x55 0xffffffffなどとするしかない気がします。

ポートの計算は、特定のビットが立っているか見て対応する所にビットを立てるという先ほどの説明の通りです。retainを使うことで制限したビット剞けつ変更できます。0x8010-0x80FFを使うことにした関係で、8に関係ルールは「0x80が立っていない場合に0x8000を消去する」ものにする必要があり、少し見た目が違います(違いがある所を太字にしました)。あと、今回は使用する上位ビットと低劣ビットの方面が被らないので気にする必要はないのですが、仮に上位ビットから先に処理していったほうが安心です。

最後にcontinueをあと払いているのは、これがな余程ルールを一個評価した瞬間で評価が終了してしまうからです。

なお、peditの増大はbyte-wiseのようです。つまり例えば0x3333に0xffffを増大すると0x3332になるのではなく0x3232になります。(今回は増大は使わないのでこれで困ることはありません。)

では次に内方位の併設です。こちらもまずparentとなるものを追加します。

tc qdisc add dev enp1s0 ingress handle ffff:

次にメ宿舎のルールです。

sudo tc filter add dev enp1s0 parent ffff: handle 0x65 fw action csum ip4h udp continue
sudo tc filter add dev enp1s0 parent ffff: handle 0x64 fw action csum ip4h tcp continue
sudo tc filter add dev enp1s0 parent ffff: handle 0x64/0xfffffffe fw action pedit pedit munge ip dport set 0x8000 retain 0xf000 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x8000 0x8000 action pedit pedit munge ip dport set 0x0080 retain 0x0080 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x4000 0x4000 action pedit pedit munge ip dport set 0x0040 retain 0x0040 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x2000 0x2000 action pedit pedit munge ip dport set 0x0020 retain 0x0020 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x1000 0x1000 action pedit pedit munge ip dport set 0x0010 retain 0x0010 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe action pedit pedit munge ip dport set 0 retain 0x0ff0 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match ip protocol 17 0xff match u16 0 1fff at 6 match ip dport 0x0330 0x0ff0 action skbedit mark 0x65 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match ip protocol 6 0xff match u16 0 1fff at 6 match ip dport 0x0330 0x0ff0 action skbedit mark 0x64 continue

まずTCPUDPパケットにfwmarkをあと払います。PSIDの判定はなんとなくあと払いていますが実際のMAP-Eでは必ず成立する条件なので無くてもい余程思います。「0 1fff」の近傍はIP Fragmentationの利得で、tc-u32(8) - Linux manual pageの最後の方を参考にしました。fwmarkの変更はskbeditというのを使うとできます。番号は適きちんと0x64と0x65にしておきました。次にPSIDの部分を0にします。それから今度は先ほどと逆に上位ビットの判定成行きを低劣ビットに反映して粋ます。低劣ビットから処理して粋ます。最後に上一桁を0x8000に併設して完成です。

デバッグについては、tc一倍も外側(きっぱりとLinuxから出入りするポート番号)を見たかったらtcpdump、tcとiptablesの間を見たかったらiptablesの-j LOGやconntrackを使うとい余程思います。tcpdumpに-Xをあと払いるとパケットの中身が見れて-vvをあと払いるとチェックトータルの正誤などが見れます。ちなみにv6プラスの分裂当てポートはIPv6(MAP-E方式)使用可能ポート傍証 | 監視・防犯テレビカメラの設置なら「目ゼック」、OCN居酒屋チャルコネクトはhttps://ipv4.web.fc2.com/map-e.htmlで計算できます。

tc filtlerのルールを一括で消すときはsudo tc filter del dev enp1s0sudo tc filter del dev enp1s0 parent ffff:でできます(つまりparent 1:は省略可能っぽい)。ip ruleと同じ様にpref(prio)制限でのルールごとの追加・削除も可能です。

OCN居酒屋チャルコネクトであれば、たとえばPSIDが0x23=0010 0111として同様に考えると、0x0010 - 0x03FF(0000 0000 0001 0000 - 0000 0011 1111 1111)にまずNATしてから、0000 0110 0111 0000 - 1111 1110 0111 1111 に変えればよくて、太字部分のPSIDが固定という感じになります。コマンドにしてみます。事実上0x8010 - 0x83FF (32784-33791)にしているのも同じです。一部さっきと同じ所は省略します。

まずSNATです。

sudo iptables -t nat -A POSTROUTING -s 192.168.1.13 -p tcp -j SNAT --to-source :32784-33791
sudo iptables -t nat -A POSTROUTING -s 192.168.1.13 -p udp -j SNAT --to-source :32784-33791

次に外方位です。

sudo tc filter add dev enp1s0 parent 1: handle 0x55 fw action csum ip4h udp continue
sudo tc filter add dev enp1s0 parent 1: handle 0x54 fw action csum ip4h tcp continue
sudo tc filter add dev enp1s0 parent 1: handle 0x54/0xfffffffe fw action pedit pedit munge ip sport set 0x0230 retain 0x03f0
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0010 0x0010 action pedit pedit munge ip sport set 0x0400 retain 0x0400 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0020 0x0020 action pedit pedit munge ip sport set 0x0800 retain 0x0800 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0040 0x0040 action pedit pedit munge ip sport set 0x1000 retain 0x1000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0080 0x0080 action pedit pedit munge ip sport set 0x2000 retain 0x2000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0100 0x0100 action pedit pedit munge ip sport set 0x4000 retain 0x4000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x54 0xfffffffe match ip sport 0x0000 0x0200 action pedit pedit munge ip sport set 0x0000 retain 0x8000 continue

最後に内方位です。

sudo tc filter add dev enp1s0 parent ffff: handle 0x65 fw action csum ip4h udp continue
sudo tc filter add dev enp1s0 parent ffff: handle 0x64 fw action csum ip4h tcp continue
sudo tc filter add dev enp1s0 parent ffff: handle 0x64/0xfffffffe fw action pedit pedit munge ip dport set 0x8000 retain 0xfc00 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x8000 0x8000 action pedit pedit munge ip dport set 0x0200 retain 0x0200 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x4000 0x4000 action pedit pedit munge ip dport set 0x0100 retain 0x0100 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x2000 0x2000 action pedit pedit munge ip dport set 0x0080 retain 0x0080 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x1000 0x1000 action pedit pedit munge ip dport set 0x0040 retain 0x0040 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x0800 0x0800 action pedit pedit munge ip dport set 0x0020 retain 0x0020 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe match ip dport 0x0400 0x0400 action pedit pedit munge ip dport set 0x0010 retain 0x0010 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x64 0xfffffffe action pedit pedit munge ip dport set 0 retain 0x03f0 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match ip protocol 17 0xff match u16 0 1fff at 6 match ip dport 0x0230 0x03f0 action skbedit mark 0x65 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match ip protocol 6 0xff match u16 0 1fff at 6 match ip dport 0x0230 0x03f0 action skbedit mark 0x64 continue

ちなみに論なくろん今回紹介したNATは1:1の静的NATでフィルタリングなどもない利得フルコーンNATと同様であり、別のフルコーンNAT(上に自分の記事やx64 Linux ルーターのIPoE(map-e by iptables)環境でGame ConsoleをNAT越えさせる -- その1fullconenat module追加有りの場合に載っているGitHub - llccd/netfilter-full-cone-nat: A kernel module to turn MASQUERADE into full cone SNATなど)と組み組合わせたら全体もフルコーンNATとしてパーフォーマンスするはずです。元来はTCPは(NATパーフォーマンスが重要ではないので)普通にiptablesのSNATでポートセービングさせておけばよく、UDPP2P通信用にフルコーンにして、ポートの消費が多いDNSは8.8.8.8などは使わずIPv6で問合わせる(参考:ここの後半のほうとか)というのがおすすめです。もっと精細ことが気になったら過去記事のNATパーフォーマンスをめ共犯心得ちがいまとめとかNATタイプ、ポートセービングIPマスカレード、UDPホールパン顎グ、STUNも読んでもらえると喜ばしいです。

元のiptablesのSNATで分裂当てられるポートは(NAT先ポート方面に入っていない限りは)予測不能なので、この方法で分裂当てられるポートも予測不能です。Linuxのnetfilterのconnection trackingとNATパーフォーマンスの仕組み - turgenev’s blogに書いたように規則正しいにポートが分裂当てられるように見えるルーターーもありますが、これらの実装方法は不明です(専用のハード装いを使っている、ひょっとするとカーネルに手を加えている?)。

ちなみに、手元では普通にHGWを使ってMAP-Eを使っているのでこの方法を実際に試せてはいないのですが、2つのLinux間でIPv4 over IPv6トンネリング - turgenev’s blogのように自前でトンネリングをしてみた所、ip4ip6のtunデバイスに関しても問題なくパーフォーマンスしました。

以上です。細かい仕様はよくわかっていないのでもっと簡潔なやり方があるかもしれませんが、とりあえずこんな感じで動くはずです。わからなければコメントにどうぞ。

追記: ICMP関連

ICMPに関して何も対応していませんでした。MAP-Eでは、ネットから IPoE MAP-E ルーターにPingをする | KUSONEKOの見る世界RFC 7597(MAP-Eの仕様)にある通り、ICMP Echo Request/Replyのidentifierをポートと同様に変換する必要があり、尚又ICMPの各種エラーメッセージ(おそらく主に使われるのはdestination unreachable、とりわけ特にICMP Echo Requestに対するhost unreachable・UDPに関係port unreachable・PMTUd用のneed fragment近傍?)に含まれる元のIPヘッディングの資料のポート番号・identifierも正しく改訂る必要があります。

TCP/UDPのポートと異なり、これらの代ルドに関してはtcにおいて専用の構文が下準備されていないので、バイト数を制限する必要があります。尚又IP見出の大いさは20-60バイトの間でヴァリアブルであり、ヘッディング長さ代ルドを見て長さを判断する必要があります。しかし以下の理由で、今回は20バイトに規定打ちして処理することにしました。

  • オプション付きの(20バイト一倍長い)IPヘッディングはIPsecなど限られたお手伝いで剞けつ使われ、普段のTCP/UDPなどで対応する必要は事実上概してないはず。
  • そもそもお手伝いが虎ブルシューティングであり、完全対応していな余程困るというほどのものではない。
  • ICMPのエラーメッセージでは外側のIPヘッディングとICMPの賃銀国法ド部分に含まれたIPヘッディングの2箇所の大いさがヴァリアブルであり、処理が複雑になる。
  • u32 matchにおいてオフセットを動的に制限するには、ハッシュテーブルにルールを追加した上でそれを呼び出すというような遠回しコマンドが必要(参照: tc-u32(8) - Linux manual page)。とはいえ書いてあるだけマシで、この通りにすれば正しく動いた。
  • tc peditにおいてオフセットを動的に制限する利得の構文として、manにはat AT offmask MASK shift SHIFTと書いてあるが、実際にはat AT MASK SHIFTと書かな余程蟇口が通らず、尚又斯う書いたとしても正しく動いているように見えず(特にshiftの振る舞いが不可解なのと、showでルールを表示してもat下に関係情報が表示されない)、全体としてあまりテストされていない雰囲気を感じた。

v6プラス剞けつ書きますが、OCN居酒屋チャルコネクトでも同じ様に可能はずです。ではまずICMP echoです。iptablesルールを追加します。

sudo iptables -t nat -A POSTROUTING -p icmp --icmp-type 8 -j SNAT -s 192.168.1.13 --to-source :32784-33023

次に外方位のtc filterを追加します。

sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x59 0xffffffff action pedit pedit munge offset 24 u16 set 0x0330 retain 0x0ff0 pipe action csum ip4h icmp continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x59 0xffffffff match u16 0x0010 0x0010 at 24 action pedit pedit munge offset 24 u16 set 0x1000 retain 0x1000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x59 0xffffffff match u16 0x0020 0x0020 at 24 action pedit pedit munge offset 24 u16 set 0x2000 retain 0x2000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x59 0xffffffff match u16 0x0040 0x0040 at 24 action pedit pedit munge offset 24 u16 set 0x4000 retain 0x4000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match mark 0x59 0xffffffff match u16 0x0000 0x0080 at 24 action pedit pedit munge offset 24 u16 set 0x0000 retain 0x8000 continue
sudo tc filter add dev enp1s0 parent 1: u32 match ip protocol 1 0xff match ip icmp_type 8 0xff match ip ihl 0x5 0xf match u16 0 1fff at 6 action skbedit mark 0x59 continue

今回はiptablesではなくtc filterでマークをあと払いています。ICMP(1)のecho request(8)のうちヘッディング長さが20(=4x5)で最初のfragmentであるものに0x59をあと払います。そして24バイト(IPヘッディング20バイト+ICMPヘッディングの先端4バイト)以後に書かれているidentifierを改訂て粋ます。u16を使ってマッチしていますが、取分けip sportでもいいです。最後にchecksumを改訂ています。pipeを使うと1つのfilterに複数のactionを繋げられます。TCP/UDPのほうはchecksumを改訂なくても通ることもありますがpingは斯うはいかないので注意が必要です。

次に内方位のtc filterです。

sudo tc filter add dev enp1s0 parent ffff: handle 0x69 fw action pedit pedit munge offset 24 u16 set 0x8000 retain 0xf000 pipe action csum ip4h icmp continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x69 0xffffffff match u16 0x8000 0x8000 at 24 action pedit pedit munge offset 24 u16 set 0x0080 retain 0x0080 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x69 0xffffffff match u16 0x4000 0x4000 at 24 action pedit pedit munge offset 24 u16 set 0x0040 retain 0x0040 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x69 0xffffffff match u16 0x2000 0x2000 at 24 action pedit pedit munge offset 24 u16 set 0x0020 retain 0x0020 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x69 0xffffffff match u16 0x1000 0x1000 at 24 action pedit pedit munge offset 24 u16 set 0x0010 retain 0x0010 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match mark 0x69 0xffffffff action pedit pedit munge offset 24 u16 set 0 retain 0x0ff0 continue
sudo tc filter add dev enp1s0 parent ffff: u32 match ip protocol 1 0xff match ip icmp_type 0 0xff match ip ihl 0x5 0xf match u16 0 1fff at 6 match u16 0x0330 0x0ff0 at 24 action skbedit mark 0x69 continue

こちらもecho replyに関して似たような条件でマッチさせてidを改訂ています。

では、UDPのport unreachableもやってみます。こちらは内方位のtc filterだけ併設すればいいです。

sudo tc filter add dev ip46 parent ffff: handle 0x79 fw action pedit pedit munge offset 48 u16 set 0x8000 retain 0xf000 pipe action csum ip4h and icmp continue
sudo tc filter add dev ip46 parent ffff: u32 match mark 0x79 0xffffffff match ip dport 0x8000 0x8000 at 48 action pedit pedit munge offset 48 u16 set 0x0080 retain 0x0080 continue
sudo tc filter add dev ip46 parent ffff: u32 match mark 0x79 0xffffffff match ip dport 0x4000 0x4000 at 48 action pedit pedit munge offset 48 u16 set 0x0040 retain 0x0040 continue
sudo tc filter add dev ip46 parent ffff: u32 match mark 0x79 0xffffffff match ip dport 0x2000 0x2000 at 48 action pedit pedit munge offset 48 u16 set 0x0020 retain 0x0020 continue
sudo tc filter add dev ip46 parent ffff: u32 match mark 0x79 0xffffffff match ip dport 0x1000 0x1000 at 48 action pedit pedit munge offset 48 u16 set 0x0010 retain 0x0010 continue
sudo tc filter add dev ip46 parent ffff: handle 0x79 fw action pedit pedit munge offset 48 u16 set 0 retain 0x0ff0 continue
sudo tc filter add dev ip46 parent ffff: u32 match ip protocol 1 0xff match ip icmp_type 3 0xff match ip ihl 0x5 0xf match ip ihl 0x5 0xf at 28 match u16 0 1fff at 6 match ip sport 0x0330 0x0ff0 at 48 action skbedit mark 0x79 continue

このようにICMP(1)のdestination unreachable(3)のうち外側のIPヘッディングと内側のIPヘッディング(オフセットは20+8=28バイト)がどちらも20バイトで、且つ最初のfragmentであるものにマッチさせています。そしてオフセット48(20+8+20)にある内のポート番号を改訂ています。

nc -u xxx.xxx.xxx.xxx xxxx

などで閉じたポートに資料を送信してみて即時性に終了する角うか傍証してみましょう。他のICMPエラーに関しても同様に可能はずです。

Linux 6.5のnetfilterのNATはTCPのTIME_WAIT状態のポートを使いまわす

TCPでは、自分から接続を切断する際に、外方位のFIN(+ACK)→内方位のFIN+ACK→最後の外方位のACK、という順でパケットがやりとりされます。この外方位のACKを送ったあとしばらくはソケットを使い回さないようにTIME_WAITという状態が併設されます(理由は他の犀トを見てください)。

Linuxのnetfilter(conntrack)によるNATでも、(自端末ひょっとすると他端末からの)TCP接続を管理するにあたってこのTIME_WAITの状態が発生します。TIME_WAIT状態になっているポートはその間はNATの利得に使用されることはなくなります。

例えば192.168.1.1:2000からのTCP通信が1.2.3.4:3000にNATされて1.1.1.1:80に出ていって、そのTCP通信が終了すると、1.2.3.4:3000はしばらくTIME_WAIT状態になり、その間に他のポート(例えば192.168.1.1:4000)が1.1.1.1:80に通信しようとすると1.2.3.4:3000が使われることはありません(行き先が1.1.1.1:80以外なら普通に使われる)。

このタイムアウト時間はカーネルパラメータのnet.netfilter.nf_conntrack_tcp_timeout_time_waitにて併設されて滓不払い値は120となっていますが、変更することができます。尚又、nftables/iptablesで特定の通信剞けつを対象にタイムアウトを変更することも可能ようです(そこそこ新しいカーネルが必要?)。 net.ipv4.tcp_tw_recycle は廃止されました ― その危険性を理ほぐする #Linux - Qiitaにある通りTCPソケット自体に関係TIME_WAITの時間を原則的に変更できないのとは対照的です。

このタイムアウトを短く(例えば5秒グレード)変更すると、接続が終了したTCPポートを即時性に使い回すことが可能ようになる利得、利用可能なTCPポートが非常に狭いくても(例えば16個とか)、ポートを多く消消費る犀ト(「ニチバンベンチ」で知られるスピール膏™壱タッチEX|うおのめ・たこ|ニチバン株式会社:生産物情報犀トなど)を相当に現実的な速度で検問可能ようになります。

ここまではいいのですが、ここからが本題です。

Linux 6.5でテスト作業た成行き、上記のようにTIME_WAIT状態になっているTCPポートでも即時性にNATに使い回されるというフェノメノンが発生しました。例えば、宛先ポート3478の通信のNAT先ポートを1つだけに絞ってStuntman - open source STUN serverTCPジーメンスドで使用してテスト作業てみると、6.5以前のバリアシオンであればTIME_WAITの120秒が経やって行くるまで一裁ち信が通らないのに、6.5以後だと120秒が経やって行くる前に通信が通ります。conntrack -Lで見てみると既存の通信が置き取り替えっこられている(NAT先ポートも宛先のIP・ポートもコンスタントのにNAT前のポートの部分だけが変わっている)ことがわかります。

しかも、必ず通るというわけでもなく、3回に1回など、見込的に通るようです。

テスト作業たカーネルのバリアシオンは以下の通りです。太字が、今回のフェノメノンが発生したものです。日ストリパースペクティヴションLinux Mint 21.3で、(関係あるかわかりませんが)iptablesはv1.8.7 (nf_tables)、nftは1.0.2、libnftnlは1.2.1-1build1とかそんな感じです(カーネル以外は変えていません)。

5.15.0-102
5.19.0-50
6.2.0-39
6.3 (mainline)
6.4 (mainline)
6.5 (mainline)
6.5.0-14
6.5.0-27
6.8.6 (mainline)

mainlineというのは概念作用がよくわかっていませんが、GitHub - bkw777/mainline: Install mainline kernel packages from kernel.ubuntu.comを普通に使って入れられるやつのことです。

この振る舞いは、テスト作業た限りでは、以下のようなそれっぽいカーネルパラメータを変更しても変化しませんでした(6.5以後のものが使い回さないようになることも、6.5一倍前のものが使いまわすようになることもなかった)。

net.ipv4.tcp_timestamps
net.ipv4.tcp_tw_reuse
net.netfilter.nf_conntrack_tcp_be_liberal
net.netfilter.nf_conntrack_tcp_loose
net.netfilter.nf_conntrack_timestamp

Changelogなどを読んでもそれっぽい原因が全く思い当たらないので、superuser.comで質問してみた所、ドンピシャな関連コミットの情報を教えてもらうことができました。

https://superuser.com/questions/1839024/linux-6-5-netfilter-nat-reuses-tcp-ports-in-time-wait-status
コミットはこちらです。
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=4589725502871e77d06464f731f92fd9173e2be6
これによると、128回の試行のうち、最後の32回は、シーケンス番号が増加していれば既存のTIME_WAITエントリでも構わず再利用するようです。

https://hxvkfr.srwsw.com https://mfsqpk.srwsw.com https://xyfwjm.srwsw.com https://vxvvny.srwsw.com https://kdssmm.srwsw.com https://kkeqid.srwsw.com https://gxrpgt.srwsw.com https://vmrwhv.srwsw.com https://gmwasj.srwsw.com https://ksyavi.srwsw.com https://vspwyi.srwsw.com https://mwwaap.srwsw.com https://rmtsdq.srwsw.com https://kdgvpe.srwsw.com https://twjpbw.srwsw.com