Write and Run

it's a simple way, but the only way.

自宅の潤沢な計算資源に対して一時的にグローバルIPが欲しいとき

みなさんが夏の訪れを感じるのはいつですか。 私は自宅サーバーのファンの音がうるさくなったときです。

私が普段自宅サーバーでサービスを公開するときは1つだけのグローバル IP アドレスを使って Virtual Host でやりくりしています。 (ロードバランサとして k8s 上の Contour(Envoy) を使っています。便利ですよ)

しかし、HTTP(s) ではないサービスをちょっと露出させたかったりするときに困ります。

かといって固定 IP アドレスをたくさんもらえる ISP 契約というのは一時的な用途のために契約するのにはちょっとお値段が。

今回はそんな自宅サーバーでグローバル IP アドレスをちょっと使いたいときに使えるテクを紹介します。

TL;DR

AWS の Elastic IP を EC2 につけて、IPIP6 でトンネルする

やりかた

前提として、対象の自宅サーバーは IPv6 でインターネットに出られるものとします。

IPv4 connectivity しかない場合は IPIP6(4-over-6) を IPIP(4-over-4) に読み替えればできる気がします。

IPIP ではなく IPIP6 にしている理由はフレッツだと IPv6 の方が圧倒的に速いからです。

VPC の用意

まずは AWS 側で IPv6 を有効にした VPC を用意します。

既存のやつを流用してもいいし、新規で作ってもいいと思います。

アドレスプールは Amazon のやつでいいです。

サブネットに IPv6 の CIDR を設定するのも忘れずに。

Elastic Network Interface と Elastic IP の用意

次に Elastic Network Interface(ENI) を作ります。

サブネットはさっき作った VPC のやつをちゃんと選びましょう。

IPv4 Private も IPv6 も Auto-Assign でいいです。

Security Group は用途に合わせてよしなに。

ENI ができたら Elastic IP をアロケートして Associate してやります。

複数付けたければ Manage IP Addresses で Private IP を足して Associate します。

ここで気をつけなければならないのは、ENI に紐付けられる IP アドレスの数は EC2 によって制限されていることです。

この上限はインスタンスタイプによってちがうのでよく確認しましょう。

また、1つのインスタンスに紐付けられる ENI の数にも制限があるため、ENI を分割しても限界があります。

たくさん欲しい場合はインスタンスタイプを変えたりインスタンスを増やしたりする必要があるでしょう。

docs.aws.amazon.com

EC2 インスタンスの用意

用意した ENI をくっつけたインスタンスを立ち上げます。

VPC とサブネットが ENI と食い違っていると作成できないので注意しましょう。

また、OS は Amazon Linux 2 を使っておくのがオススメです。

Amazon Linux 2 以外だと複数の IP アドレスをいい感じにする設定がダルいので。

Ubuntu だとダルいという例: Make Secondary Network Interface Work in Ubuntu EC2 Instance

インスタンスタイプは前述のとおり ENI 数や IP アドレス数に合わせて選びましょう。

IP パケットを右から左(または左から右)に流すだけなので CPU もメモリも問題になりません。一番安いやつを選べばいいと思います。

t2.nano でも 2ENI x 2IP で 4IP いけるのでだいたいのケースで十分だと思います。

ディスクもどうせほとんど使わんし超小さくていいです。

IPIP6 トンネルを掘る

EC2 が起動したらトンネルを掘っていきます。

以下のような構成を想定しています:

  • 自宅側のトンネル終端はルーター(私は IX2105 使ってます)
  • 自宅側のトンネル終端の IPv6 アドレス: 2001:DB8::789
  • 自宅サーバーの CIDR : 192.168.0.0/24
  • 対象の自宅サーバーの Private IP アドレス(自宅内): 192.168.0.1
  • VPC の CIDR : 10.123.0.0/16
  • ENI の Private IPv4 アドレス: 10.123.0.1
  • ENI の Public IPv4 アドレス: 203.0.113.1
  • ENI の IPv6 アドレス 2001:DB8::EC2

適宜読み替えてください。

自宅側

先にトンネルの自宅側の設定をしましょう。

ここでは NEC の IX 系ルーターの設定例だけを紹介します。

tunnel sourceIPv6 アドレスがあるインターフェースに、ip unnumbered自宅サーバーがあるサブネットに繋がってるインターフェースにするとよいです。

interface Tunnel0.0
  tunnel mode 4-over-6
  tunnel adjust-mtu auto
  tunnel destination 2001:DB8::EC2
  tunnel source GigaEthernet0
  ip unnumbered GigaEthernet1
  ip tcp adjust-mss auto
  no shutdown

トンネルの設定を入れたらルーティングの設定もします。

ポリシールーティングを使って自宅サーバーからのトラフィックをトンネルに流すことにします。

ip access-list aws-public permit ip src 192.168.0.0/16 dest any
route-map aws-public permit 10
  match ip address access-list aws-public
  set interface Tunnel0.0
interface GigaEthernet1
  ip address 192.168.0.254/24
  ip policy route-map aws-public
  no shutdown

ここでは CIDR でガバっとやってますが、サブネット内に EC2 に流したいサーバーと流したくないサーバーがいる場合は /32 とかで細かくやることになると思います。

私は EC2 経由で公開するサーバーを置く専用の VLAN を切ってるのでガバっとやっています。

EC2 側設定

コマンドは sudo とか付けずに書きますが必要に応じて root でやるとか sudo つけるとかしてください。

まずは Linuxルーターっぽいことをするときに必ず必要なやつ。

sysctl -w net.ipv4.ip_forward=1

次に IPIP6 トンネルを作成します。今の時代は ip コマンドでなんでもできて便利。

# ip -6 tunnel add ipip0 mode ipip6 local <ENI IPv6 アドレス> remote <自宅終端 IPv6 アドレス>
ip -6 tunnel add ipip0 mode ipip6 local 2001:DB8::1 remote 2001:DB8::789 encaplimit none
ip link set ipip0 up

トンネルが掘れたらルーティングを設定します。

ip route add 192.168.0.0/24 dev ipip0

これで自宅宛てのトラフィックがトンネルに潜って抜けていくようになります。

試しに自宅サーバーの IPv4 アドレス宛に ping 送って試したりするとよいです。

ping 192.168.0.1

これで返ってこなかったら何かがミスっているので確認しましょう。

正しくトンネルを掘れてパケットが行って帰ってこられるようになったら NAT の設定をします。

Public IPv4 アドレス1つにつき、行きと帰りで計2行ずつ設定が必要です。

欲張ってたくさん Elastic IP を貼り付けたひとはがんばって設定しましょう。

# iptables -t nat -A POSTROUTING -s <自宅サーバー Private IP アドレス(自宅内)> -j SNAT --to-source <ENI Private IPv4 アドレス>
# iptables -t nat -A PREROUTING -d <ENI Private IPv4 アドレス> -j DNAT --to-destination <自宅サーバー Private IP アドレス(自宅内)>
iptables -t nat -A POSTROUTING -s 192.168.0.1 -j SNAT --to-source 10.123.0.1
iptables -t nat -A PREROUTING -d 10.123.0.1 -j DNAT --to-destination 192.168.0.1

これでインターネットから 203.0.113.1(ENI の Public IPv4 アドレス) を使って自宅サーバーにアクセスできるはずです。

このままだと EC2 インスタンスを再起動したときに EC2 側のトンネル設定が吹き飛ぶのでいい感じに永続化してください。

自宅サーバー側の設定

ハマりポイントとして、自宅内の DNSゾルバを見るようになってて名前解決ができねぇ、みたいなことがあったりします(ハマった)。

DNSゾルバが同じブロードキャストドメインにいるなら大丈夫なんですが、そうでない場合はポリシールーティングで全部 EC2 にぶん投げちゃってるので見えなくなって困ります(困った)。

ゾルバの設定を DHCP で配ってたりすると面倒ですね(面倒だった)。まぁうまくやってください。

まとめ

Elastic IP は文字通り Elastic なので、パッと調達して取り付けることができて便利ですね。

私は ISUCON の練習用 VM をチームメイトに貸すときに使っています。

一時的にしか使わないのに3台分のグローバル IP アドレスを調達するのってめんどうですからねぇ。

ではよき自宅サーバーライフを!

Special Thanks

このアイデアは同僚の id:sora_h にもらいました。 最初は真面目に暗号化された VPN を張ろうとがんばってたんですが

てかインターネットに出たいトラフィックなら無暗号の ipip tunnelやGREで十分よ

との助言でこのような雑トンネルとなりました。いやー賢いね。

WASIを叩くWASMを手書きしてLucetでHello, world!する

WASI がなんだとか WASM がなんだとかは詳しく説明しません。各位やって。

まず Lucet を入れる。

github.com

はい

Hello, world! するには fd_write があればよさそう。

wasmtime/WASI-api.md at master · CraneStation/wasmtime · GitHub

引数はだいたい見りゃわかる。が、__wasi_ciovec_t っつーのが引っかかる。

とはいえこれも見りゃわかる。

wasmtime/WASI-api.md at master · CraneStation/wasmtime · GitHub

バッファへのポインタとバッファの長さを持ってる構造体。

で、fd_write にはそいつのポインタを渡してやればいいということがわかる。

というところまでわかったら WAT を書く。S 式。

(module
  (import "wasi_unstable" "fd_write" (func $__wasi_fd_write (param i32 i32 i32 i32) (result i32)))
  (func $_start
    i32.const 1
    i32.const 16
    i32.const 13
    i32.const 24
    call $__wasi_fd_write
    drop)
  (memory 1)
  (data (i32.const 0) "Hello, world!\00\00\00\00\00\00\00\0d\00\00\00")
  (export "_start" (func $_start)))

import ってやつでリンクをやってて、data でメモリの初期値を埋めてる。export はエントリポイントの公開。

で、data に埋まってる初期値が大切で、ここに

"Hello, world!"(13bytes) + padding[0x00 0x00 0x00](3bytes) + buf address[0x00 0x00 0x00 0x00] (4bytes = i32) + buf length[0x0d 0x00 0x00 0x00](4bytes = i32)

が埋まっている。

buf address と buf length ってのが __wasi_ciovec_t の中身。

ので、スタックに

1  ;; stdout の fd
16 ;; buf address へのアドレス
13 ;; "Hello, world!" のバイト数。buf length とは別にこれも必要らしい
24 ;; `nwritten` へのアドレスっぽい。戻り値はスタックに返ってくるんじゃねーのかよ(スタックにも返ってくるんだよな)

というかんじで積んでやって、call $__wasi_fd_write してやる。

最後、スタックに nwritten と思しき値が残ってしまうのでそいつを drop で消し飛ばせば完了。

hello.wat みたいな名前で保存してやって、以下のように実行できる。

lucetc-wasi -o hello.so hello.wat
lucet-wasi hello.so

楽しいね

デカいテレビとXbox One Xを買ってランボルギーニを乗り回す

f:id:koba789:20181216034813j:image

はい

 

サイバーマンデーにそそのかされてデカいテレビを買いました。

2018年も暮れなので、4K HDR な OLED です。ナウい

サイズは65インチ。デカい。

なお部屋は8畳1K。狭い。

 

で、テレビだけ買っても 4K HDR コンテンツを吐ける箱がなけりゃ意味なかろうということで、Xbox One X という箱を買いました。

Forza Motorsport 7 と Forza Horizon 4 というブーブーゲームが付いてるモデル。

 

私が暮らしている恵比寿(正確には違うが)という街は大変イカれた場所で、昼飯を食いに外に出るだけでランボルギーニを眺められるようなシティなのですが、デカいテレビと箱があるとなんと外に出ずとも部屋でランボルギーニを運転できます。

はえらい。

 

冒頭の画像はランボルギーニラカンで崖から飛び降りる瞬間を写したもの。

ゲームの世界では一軒家並みの値段の車で紐なしバンジーしても無料。

はえらい。

 

私は人間を歩かせたり武器を構えて何かを射るようなゲームが苦手なのですが、Forza は人間を歩かせたり武器を構えて何かを射たりということがないので助かっています。

 

ちなみに据え置きのゲーム機を手に入れるのはこれがやっと2台目で、ちなみに1台目は2ヶ月前に中古で買った Wii です。

携帯ゲーム機はというと DSi をバイト先の忘年会のビンゴ大会で当てたのが最後。

ゲーム機ってゲームできてすごいですね。

 

現実世界に置いてあるテレビの方は、現在こたつの上に曖昧に乗せてあるだけという状態で邪魔だしデカいしデカいし邪魔なので、いろいろして壁掛けにする算段です。

これもいい感じにシュッとしたら記事にしたい。

 

なお、テレビの登場によって今まで使っていたプロジェクターは失職となりました。

WXGA のくせに今までよく頑張ったと思います。解像度はともかくとしてランプがクソ明るかったのはよかった。

 

最後は崖からゴロゴロと転がってボコボコになり、リアスポイラーがバイバイしてしまった WRX STI で締めようと思います。

f:id:koba789:20181216040808j:image

ちなみに耳も取れてる👂

OBS Studioで使えるHDMIキャプチャを5000円で手に入れる

ヤァヤァ、KOBA789 であるぞ。

今回はおもしろハードウェア活用術のご紹介です。

安い HDMI キャプチャデバイスが欲しい

欲しい。欲しいじゃないですか。

ゲームにしろカメラの映像にしろ、なんらかの映像をオンラインで生配信したいと思ったとき、真っ先に必要になる機材が HDMI キャプチャデバイスです。
日常的にパソコンやってると USB Audio IF とか、ウェブカムとか、そういうのは案外持ってるもんですが、HDMI キャプチャは持ってないがち。

しかし HDMI キャプチャデバイスというのはあまり一般的なデバイスではないので案外高い。
いくら安くても1万は切れないことが多くて、まぁ2〜3万は覚悟が必要になるわけです。

映像ソースが1本しかないのであれば、まぁ買い切りだし1個くらい買ってもいいか、となるもんですが、これが2本、3本となると話は別です。
ちょっときつい。
ロスレスなんて言わないから、なんなら60fpsも諦めるから、安いやつが欲しい、そういうケースはあります。

たとえば勉強会の配信とか。
スライドなんて数十秒に1回しか切り替わらんし、演台の人間もほとんど動かないので60fpsは要らない。
大したディティールも要らんのでキャプチャデバイスロスレスだろうとロッシーだろうと、配信を見てる人にはほとんど差がわからない。

あと、登壇者のマシンとカメラが離れてるとか、そういうこともあって映像を数十メールくらい伝送する必要があったりします。
20m の HDMI のケーブルとか結構いいお値段するし、太くて取り回しはだるいし、かといって安いケーブルだと信号がロスってちゃんと伝送できないこともあります。

こういう願いを全部叶えてくれる、ナイスなデバイスないかな〜〜と思ってたら、あったんです。

LKV373

自称「120m HDMI extender over CAT5/5e/6」
自称というか、ちゃんと HDMI 延長器として銘を受けて生まれてきたデバイスです。本来は送信側と受信側をペアにして使います。

ただこのデバイス、over CAT5/5/6 とか言いながら、実は LAN ケーブルに律儀に IP を流しています。
中に流れているパケットの正体は細切れになった JPEG の断片を含む独自プロトコルUDP のパケットなので、受信側を外して代わりに一般的な NIC を搭載したコンピュータを繋ぐと映像を容易に受信することができます。
nJPEG に圧縮されているので原理的にはロッシーですが、言うほど劣化は目立たなくて、許せるレベル。

当然 CAT5 とかのいわゆるふつーの LAN ケーブルを使えるわけので、数十メール伝送するくらい楽勝ですし、ケーブルも安いしどこでも買える。

詳しいことはこの記事に書いてあるので、興味のある人はぜひ。

Reverse engineering Lenkeng HDMI over IP extender | danman's blog

一般的な NIC で容易に受信可能とはいえ、中身は独自のプロトコルなので、既存のソフトウェアにそのまま食わせることはできません。
生配信用のデファクトスタンダードである OBS Studio なんかに食わせられると最高なんですが、もうひと手間必要です。

OBS Studio 用のプラグインを書く

OBS Studio にはプラグイン機構があり、仕様に沿った動的リンクライブラリを用意してやると機能を拡張することができます。
拡張できる機能にもエンコーダとか映像入力とか、いくらか種類があります。

今回は映像を入力したいので、Source という機能を持つプラグインを実装しました。

実装の方法は至って簡単で、 libobs/obs-module.h というヘッダファイルのインターフェースに従って必要な関数を実装、export するだけです。
だいたいのことはこのページを読めばわかります。

Plugins — OBS Studio 20.1.0 documentation

普通なら実装言語は当然C言語ですが、2018年も末になって新規でC言語を書きたくなかったので、Rust で書くことにしました。

Rust は C++ などと同様、C言語の呼び出し規約と互換性のある動的リンクライブラリを生成することができるので、better C として使うことができます。

まずは libobs とリンクする必要があるため、Rust のビルドシステムである cargo に、libobs のビルドシステムを組み込んでやる必要があります。
libobs はビルドシステムとして cmake を利用しているのですが、cmake という crate を使うとシュッと連携させることができます。ほんと便利。
さらに、バインディングは頑張って手で書かなくても bindgen という crate を使うことでヘッダファイルから自動生成できます。マジで優秀。
というわけで、だいたい全部自動で準備が整います。

動画の JPEG フレームをデコードするため、同様の方法で、libjpeg-turbo もリンクしてやります。
Pure Rust な実装の JPEG デコーダもあるのですが、インターフェースの兼ね合いなどもあって断念しました。
libjpeg-turbo は SIMD 命令などを活用することで JPEG の高速な圧縮と伸張ができるライブラリです。
このライブラリを選んだ一番の理由は、出力形式として libobs と互換性のある BGRX フォーマットが使えたことです。
ピクセルエンディアンJPEG デコーダと libobs でズレていると、毎フレームごとにフォーマットを揃える必要があり、実行速度上のペナルティが大きくなります。
動画のフレームのデコードは速度が命なので、これはとても大切でした。

で、できあがったものがこちらです。

github.com

今は macOS でしかビルドを確認していませんが、ちょっと編集すれば Linux でもビルドできるんではないかと思っています。

使い方

  1. ビルドできたライブラリを、OBS Studio のプラグインディレクトリに入れる
  2. PC に NIC を刺す
  3. NIC の IP アドレスを 192.168.168.0/24 のうちの任意に設定
  4. OBS を起動
  5. 入力ソースとして "LENKENG" を追加
  6. Interface Address に3で設定した IP アドレスを設定
  7. たぶんこの時点では動かないので OBS を再起動
  8. 動く

7 でちゃんと動かないのは、俺が実装をサボったからです。 誰か source_info.update をちゃんと実装して Pull Request を投げてください。

おわり

2018年に個人でウェブサービスを開発するときの気持ち

  • (ウェブ系の)最近の技術のトレンドは組織のスケールにフォーカスしている気がする
  • 企業などのデカい開発組織で開発をするための技術(ツールや方法論)が充実してきた
  • 従来は企業での開発もあまり規模が大きくなかったので、個人での開発と同じような技術を利用していた気がする
  • 今では企業と個人では開発の規模に大きな隔たりがある気がする(昔も当然あったが、それはどんどん拡大している)
  • 規模が違うと適切な方法論やツールも違う
  • たとえばマイクロサービス化
  • 個人の規模でやっても不幸になりがち
  • サービスを分割したからといって自分以外のだれかがパラレルで開発をやってくれるわけではない
  • 運用・管理コストが自分に跳ね返ってくるだけ
  • シングルスレッド CPU でたくさんスレッド走らせたときの不毛さに近い
  • コンテナ技術とかもそう
  • デカい組織での便利な使い方と、個人での便利な使い方は違う
  • うっかり個人で企業の真似すると不幸
  • いずれにせよ便利な技術だけど、適切な使い方は違う
  • kubernetes とかもむずかしい
  • そもそも運用だるいがち
  • 組織だと運用コストよりスケーラビリティのメリットのほうが大きいかもしれない
  • 個人だとだいたい運用コストのほうがでかい(運用してるけどやはりつらい。家に帰った後の時間で面倒見られる規模じゃないと思う)
  • でも kubernetes 前提の便利ツールとかはたくさんある
  • 便利ツール使いたい
  • 個人開発でもコミュニティの恩恵を受けたい
  • でも無理
  • 以上、お気持ち