外部記憶装置

脳みそ小さすぎるからメモしとく

2020 年を雑に振り返る

みなさん、いかがお過ごしでしょうか? PiBVT です。

怒涛の 2020 年もあと数時間で終わるため、紅白を見ながら今年やったことについてまとめてみました。

今年あったこと

今年は公私ともに色々ありすぎて大変でした。 その中でも特に印象的だった以下の2つの事柄について取り上げます。

  • 未踏
  • 研究室・卒業研究

未踏

2019年6月から2020年2月までの期間中は未踏プロジェクトとして @nimdanaoto と @ushitora_anqou と一緒に取り組んでいました。 詳しいことは彼らの記録に任せますが、まぁ大変でした。

virtualsecureplatform.github.io

github.com

anqou.net

anqou.net

単純に成果を上げることだけでなく、成果をいかに伝えるかやアピールといった表現部分に大きな労力を費やしました。 また、全員がそのようなことは経験したことがないので、暗中模索するうちに意見が衝突することが多発し、精神的にも厳しいものがありました。

そんなチームが崩壊寸前だった中、僕らをつなぎとめてくれたのは「Factorio」というゲームの存在です。

とある惑星に墜落した宇宙船の船員が、原住民の土地を侵略し資源を略奪しながら脱出用のロケットや衛星を飛ばすというアメリカの開拓者を乗せたメイフラワー号顔負けの工場経営SLGです。

store.steampowered.com

未踏のミーティングが東京であった夜はホテルで反省会をした後に Factorioマルチプレイをすることで 互いの親睦を深め、「Factorio をするためにチームとして存在している」といっても過言ではない状態でした。

無事未踏のプロジェクト期間を終えた後は、成果を論文としてまとめる作業をしていました。 @nimdanaoto が筆頭で論文を書き、 @ushitora_anqou と僕が論文に必要な実装や計測を行っていました。 その中でも、文章表現をめぐり内紛が勃発することがありましたが、そんな日の夜はみんなで Factorio をして仲直り(?)をしていました。

その時書いていた論文は USENIX Security に採択され、来年はバンクーバーに行くのが楽しみです。(行けない可能性が高そうではあるけれど。)

www.usenix.org

来年以降も、このチームで少しずつ活動して SCO(Secure Computation Offloading) だけでなく他の分野にも応用できるように頑張っていきたいと思っています。

研究室・卒業研究

例年に1か月遅れて 4 月下旬に京都大学学術情報メディアセンター 岡部研究室に配属されました。

もともと自分自身の興味やアルバイトの担当分野がネットワーク(L2~L4) であったことからこの研究室を選びましたが、 結論としては自分にとって最高の研究室を選択したなぁと感じています。 研究室の先生だけでなく、修士の先輩と話していても面白い話や、かなり研究の専門的な部分に踏み込んだ相談であっても的確なアドバイスを頂けて とても楽しいです。ある程度の自由が効くので色々とやっていました。

pibvt.hateblo.jp

卒業研究に向けていろいろと研究しているのですが、先行研究を調べてまとめて内部のアーキテクチャを考えていると、 頭が爆発して一定期間何も手につかない状態になることがありました。

そんな時に僕の疲れた頭を癒してくれたのが「Factorio」というゲームです。 とある惑星に墜落した宇宙船の船員が、工場を建てまくって環境汚染をしたり、先住民の巣に核弾頭を叩き込んで先住民の反発を抑え込んだりする非常に友好的で健全な工場経営SLGです。

store.steampowered.com

日中は研究をして夜は2時間ほど未踏チームや他の友人と遊んでリフレッシュをしていました。 研究ばかりではなく、適度な息抜きが肝心であるということです。

卒業研究は卒論の執筆が若干遅れていますが、できる限り頑張って良い論文に仕上げて卒業したいです。

最後に

Factorio というゲームはあらゆる障壁を打ち破ることができるので、ぜひ遊んでください!

store.steampowered.com

2020年はお世話になりました。 それではみなさん、よいお年をお迎えください。

L3 技術で研究活動を快適にした話

これは CAMPHOR- Advent Calendar 2020 の12日目の記事です

advent.camph.net

はじめに

どうも、 PiBVT です。CAMPHOR- は11月ごろに初めて行ったばかりで、最近は研究が忙しくて行けてない状態です。

学生エンジニアの方が多く訪れる CAMPHOR- のアドベントカレンダーなので、技術的なことについて書いていきます。

免責事項

当記事の著者 PiBVT は当記事に基づいて行う一切の行為について、何らの責任を負うものではありません。

何をしたのか

研究室に配属された

今年からネットワーク関係の研究室に配属されたのですが、研究室に所属すると以下のメリットがあります

  • 大量の書籍
  • 自分専用のデスク
  • 自由に議論ができるスペース・ホワイトボード
  • やわらかいソファー

学部3回生までの生活とは一変し、研究室で生活できるのでは?と思うくらいに充実しています。

第1世代ネットワーク

院試に無事?合格したのち、研究室でお手伝いをするうちに研究室の VM サーバーを利用する権利を手に入れました。 研究室の VM サーバーでは結構強めで安定した VM を立てれるため、研究に関する開発の一切を研究室の VM 上で行うことにしました。

しかし、研究室の VM は学外からは専用の踏み台や VPN 経由で接続する必要があり、毎回接続するのは面倒です。 また、自宅サーバーに置いているデータを取り寄せるのも面倒です。

そこで、最初は VM と自宅のネットワーク間で VPN を張っていました。

f:id:PiBVT:20201210225228j:plain
第1世代ネットワーク

今回利用した VPN プロトコルが自宅ネットワークのコアルーターである RTX1200 に対応していなかったため、カプセリング用に別途 VyOS VM を用意しました。 自宅のルーターに VyOS を経由する研究室 VM への経路を静的経路として追加することで、自宅のネットワークと VM 間がシームレスに通信できるようになり、非常に快適になりました。

※図中の IP アドレスや AS 番号などはすべて仮のものです。図の通りに運用しているわけではありません。 「セキュリティがなってない!けしからん!」などの意見はご遠慮ください。書いてないだけなので。

※KUINS3 (京都大学の学内ネットワーク) では特定の VPN プロトコルで対外接続が可能です。

第2世代ネットワーク

せっかくネットワークの研究室に属しているのですから、けしからんことをしたくなるのが当然です。 先生や先輩と色々雑談しているうちに、以下の機材・権利を手に入れました。

  • Cisco の小型スイッチ・ルーター
  • 研究室内で割り当てられるプライベート v4 アドレスセグメント
  • (そこそこ自由に)研究室のネットワーク機器を操作する権利

弊研究室は、各個人にセパレーターで区切られた個人的なデスク・環境が割り当てられています。 機材を置くのに十分なスペースがあったため、デスクサイドに持ち込んだ Raspberry Pi などを利用して写真のような環境を構築しました。

f:id:PiBVT:20201210224957j:plain
デスクサイドの機材

機材を置けばそれらを家から操作したくなるのは当然のことなので、自分専用のオーバーレイネットワークは図のようになりました。

f:id:PiBVT:20201211021115j:plain
第2世代ネットワーク

デスクサイドと VM 間は L3 でトンネリングしていますが、VLAN 切り出せばよかったことに今気が付きました。 トンネリングをして、各ルーターに静的経路を設定すれば無事自宅からでもデスクサイドのネットワークに入れるようになりました。 研究室のデスクから自分のプライベートサブネットに接続すれば、自宅ネットワークにもそのままアクセス出来て快適です。

第3世代ネットワーク

便利なネットワークを構築できたことにより、研究も快適にできるようになりました(注:快適でも成果が出るわけではない)

しかし、ある日学内ネットワークの障害(一部対外接続の不通)により研究活動が停止してしまいました。 ネットワークの研究を行う学生として、ネットワークの障害で研究が止まるのはけしからん、 ということで学内のネットワークが死んだ場合でも大丈夫なネットワークへ改良することにしました。

研究室には学内ネットワークの KUINS 経由で外部に疎通する経路と、研究室独自の WIDE 経由で外部に疎通する経路(v6 のみ) があります。 そこで、 v6 をバックアップ経路として利用することにしました。

また、VPN で接続している自宅(S〇ftBank 回線)から大学の VM に到達する経路が東京を経由するというけしからんことが判明したため、 この問題にも対処することにしました。(SINET と直接接続している JPNAP と JPIX のトランジットポリシー?)

以下の図が最終的なネットワークの構成図となります。

f:id:PiBVT:20201210225541j:plain
第3世代ネットワーク

VyOS や FRRouting などのソフトウェアルーターを活用することで実費0円で構築することが出来ました。

対外接続時の経路切り替えは、各セグメントごとにプライベート AS 番号を割り当てて BGP を利用することで自動的に到達可能な経路になるようにしました。 デスクサイドと研究室VM の間で AS が異なるのはデスクサイドと VM 間が L3 でトンネルされていた影響です。 IGP は RIP を利用しています。簡単なネットワークなので。(フラグ)

自宅で契約している S〇ftBank 回線と SINET が東京でトランジットしている問題は、 さくらインターネットさんの大阪リージョンで VPS を借りてプライベートトランジットとして利用することで解決しています。

大阪にプライベートトランジットを置いた結果はグラフから確認できます。遅延がほぼ半分になり、大成功といえるでしょう

f:id:PiBVT:20201210225921p:plain
遅延の変化

しかし、さくらインターネットさんの VPS に大量のトラフィックを流すと迷惑なので、レイテンシが気になるもの(SSH など)や観測用の ICMP 以外は すべて大学・自宅間を直接結ぶ経路に流しています(Policy Based Routing)。大学・自宅間が不通になった場合はすべてのトラフィックVPS を流れますが。

f:id:PiBVT:20201210230006j:plain
PBR を適用

この構成にしてから何度か発生した学内ネットワークの障害も乗り越え、研究活動を継続できる超絶快適な環境が完成しました(注:環境が快適でも成果は(ry)

AS?BGP?

AS は Autonomous System の略で、管理のまとまりの単位となります。

www.nic.ad.jp

AS 間でお互いに BGP で経路情報を交換すること(ピアリング)により、互いのネットワークへの疎通が取れるようになります。 つまり、インターネットとは複数の AS 間のピアリングによって構成されているということです。

f:id:PiBVT:20201211134354j:plain
AS 間のピアリング

しかし、すべてのAS(一般に ISP が多い)がお互いにピアリングをするとなると、回線や管理費がかかりすぎて大変です。 Tire1 ISPと呼ばれる、全世界規模にネットワークを張り巡らした最上位のプロバイダが 国内の Tier2, Tier3 のISP や事業者をトランジット(ピアしている相手のトラフィックを交換する)ことで全世界のネットワークに到達することが出来るようになります。 国内では NTT Communications の GIN(Global IP Network) が Tier1 ISP として有名です。

また、トランジットサービスを様々な事業者に提供している組織を IX(Internet eXchange) といいますが、国内では JPIX, JPNAP, BBIX が有名です。

なぜ東京経由?

しかし、トランジット自体もお金がかかるので、事業者がかならずしもすべての IX と接続されているわけではありません。 そのため、 京都大学の上流ネットワークである SINET では、商用 IX としては JPIX と JPNAP としか接続されていません。

www.sinet.ad.jp

www.peeringdb.com

S〇ftbank のIP網からのトラフィックは JPIX か JPNAP を経由する必要がありますが、SINET という学術ネットワークに関するトラフィックを わざわざお金がかかる大阪(堂島)でトランジットするわけでもなく東京(大手町)でまとめてトランジットしていると考えるのが妥当でしょう。

f:id:PiBVT:20201211013013j:plain

一方で、SINET からさくらインターネット大阪リージョンの VPS に対するトラフィックは JPIX OSAKA 経由で、S〇ftbank からは直接到達できるようになっているため、 非常に低遅延で到達することができます。

プライベートトランジットサーバー

ここまでの話を整理すると

となります。

つまり、さくらインターネット大阪リージョンの VPS を経由すればトラフィックの経路が関西圏内に収まり、 遅延をさらに減らすことができると期待できます。

f:id:PiBVT:20201211133839j:plain
大阪でトランジット

そのためには、VPS 上でトラフィックを交換する必要がありますが、ごく簡単に言えば単なるルーティングでしかないので 実証自体は容易にできます。

しかし今回の目的は「研究環境を快適にする」ことなので、動的経路制御をするとなると プライベート AS 番号を割り当てて BGP でピアを張ること必要があるわけです。 そのため、前述のような研究室と自宅を結ぶには大掛かりなネットワークになりました。

Docker で実験

ここまでつらつらと僕自身の体験や経験を書いてきましたが、せっかくなので手軽に似たことを実験してみました。

有志の方によって VyOS の Docker コンテナが作られているため、このコンテナを利用して簡易的な実験ネットワークを組みました。

github.com

ネットワークはこの図のような構成になっています。

f:id:PiBVT:20201211153442j:plain
構築するネットワーク

インターネットを通じて両端の client-65001 と client-65002 が互いに疎通できるようになっています。 しかし、今回の経験を踏まえ、 vyos-65001, vyos-65002 間の経路には計 16ms の遅延が発生するようになっています。(値は大阪・東京間で計測した結果から雰囲気で決めました)

各 VyOS のコンフィグには以下の設定が入るようになっています。

# vyos-internet の設定ファイル
#!/bin/vbash
source /opt/vyatta/etc/functions/script-template
configure

# configure hostname
set system host-name vyos-internet

# configure VLAN interface
set interfaces ethernet eth0 vif 100 address 192.168.1.1/30
set interfaces ethernet eth0 vif 200 address 192.168.1.5/30

# configure latency emulation policy
set traffic-policy network-emulator DELAY-8 network-delay 8
set interfaces ethernet eth0 vif 100 traffic-policy out DELAY-8
set interfaces ethernet eth0 vif 200 traffic-policy out DELAY-8

# configure static route to each vyos
set protocols static route 192.168.100.0/24 next-hop 192.168.1.2
set protocols static route 192.168.200.0/24 next-hop 192.168.1.6

# 環境によって変えること
set protocols static route 0.0.0.0/0 next-hop 172.19.0.1

# configure nat
set nat source rule 1001 outbound-interface eth0
set nat source rule 1001 source address 192.168.100.0/24
set nat source rule 1001 translation address masquerade
set nat source rule 1002 outbound-interface eth0
set nat source rule 1002 source address 192.168.200.0/24
set nat source rule 1002 translation address masquerade

commit
exit
# vyos-65001 の設定ファイル
#!/bin/vbash
ip route del default

source /opt/vyatta/etc/functions/script-template
configure

set system host-name vyos-65001

# configure loopback
set interfaces loopback lo address 192.168.100.1/32

# configure VLAN interface
set interfaces ethernet eth0 vif 100 address 192.168.1.2/30
set interfaces ethernet eth0 vif 150 address 192.168.100.1/25

commit

# configure static route
set protocols static route 0.0.0.0/0 next-hop 192.168.1.1

commit
exit
# vyos-65002 の設定ファイル
#!/bin/vbash
ip route del default

source /opt/vyatta/etc/functions/script-template
configure

set system host-name vyos-65002

# configure loopback
set interfaces loopback lo address 192.168.200.1/32

# configure VLAN interface
set interfaces ethernet eth0 vif 200 address 192.168.1.6/30
set interfaces ethernet eth0 vif 250 address 192.168.200.1/25

# configure static route
set protocols static route 0.0.0.0/0 next-hop 192.168.1.5

commit
exit

各クライアントは起動時に VLAN を設定しています。

# client-65001 の設定
#!/bin/bash

# delete default route via docker network
ip route del default

# configure vlan
ip link add link eth0 name eth0.150 type vlan id 150
ip a add 192.168.100.2/25 dev eth0.150
ip link set dev eth0.150 up

# configure default route via vyos
ip route add default via 192.168.100.1

# wait for tty
while true
do
  sleep 1
done
# client-65002 の設定
#!/bin/bash

# delete default route via docker network
ip route del default

# configure vlan
ip link add link eth0 name eth0.250 type vlan id 250
ip a add 192.168.200.2/25 dev eth0.250
ip link set dev eth0.250 up

# configure default route via vyos
ip route add default via 192.168.200.1

# wait for tty
while true
do
  sleep 1
done

簡単に説明すると、各 VyOS は ISP (vyos-internet) の保有するセグメントの IP アドレスを割り振られているため、それを Interface に対して設定します。 vyos-6500x と vyos-internet 間のリンクは Docker ブリッジを毎回作るのが面倒なので VLAN を切っています。 そのため、まず指定された vlan id の vif (vlan interface) を作成し、それから vif に対してアドレスを割り当てます。

vyos-internet は 65001 と 65002 の経路情報が必要なため、static route でそれぞれのネットワークについて設定しています。 vyos-6500x では、デフォルトルートについて、 vyos-internet が外界に出れる GW となるので vyos-internet の IP アドレスを指定しています。

クライアントについても同様に、各ネットワークの vyos-6500x と VLAN で接続してデフォルトルートに向けるようにしています。

これで2拠点間を結ぶネットワークができました。疎通が取れていることも分かりますが、16ms の遅延があることが分かります。

f:id:PiBVT:20201211153547p:plain
ネットワーク間の疎通確認

では、プライベートトランジットとして vyos-65003 を追加してみましょう。

ネットワークの構成はこの図のようになっています

f:id:PiBVT:20201211171205j:plain
プライベートトランジットを利用するネットワーク

設定ファイルは以下の通りです。

# vyos-65001 の追加設定
set interfaces ethernet eth0 vif 103 address 192.168.250.2/30

# configure BGP
set protocols bgp 65001 address-family ipv4-unicast network 192.168.100.0/24

# configure BGP neighbor
set protocols bgp 65001 neighbor 192.168.250.1 remote-as 65003
# vyos-65002 の追加設定
set interfaces ethernet eth0 vif 203 address 192.168.250.6/30

# configure BGP
set protocols bgp 65002 address-family ipv4-unicast network 192.168.200.0/24

# configure BGP neighbor
set protocols bgp 65002 neighbor 192.168.250.5 remote-as 65003
# vyos-65003 の設定
#!/bin/vbash
ip route del default

source /opt/vyatta/etc/functions/script-template
configure

set system host-name vyos-65003

# configure loopback
set interfaces loopback lo address 192.168.250.1/32

# configure VLAN interface
set interfaces ethernet eth0 vif 103 address 192.168.250.1/30
set interfaces ethernet eth0 vif 203 address 192.168.250.5/30

# configure latency emulation policy
set traffic-policy network-emulator DELAY-3 network-delay 3
set interfaces ethernet eth0 vif 103 traffic-policy out DELAY-3
set interfaces ethernet eth0 vif 203 traffic-policy out DELAY-3

# configure BGP
set protocols bgp 65003 address-family ipv4-unicast network 192.168.250.0/24

# configure BGP neighbor
set protocols bgp 65003 neighbor 192.168.250.2 remote-as 65001
set protocols bgp 65003 neighbor 192.168.250.2 address-family ipv4-unicast nexthop-self

set protocols bgp 65003 neighbor 192.168.250.6 remote-as 65002
set protocols bgp 65003 neighbor 192.168.250.6 address-family ipv4-unicast nexthop-self

commit
exit

vyos-65001,vyos-65002 では vyos-65003 とピアリングするための BGP の設定を追加しています。 vyos-65003 はトランジットするために、各vyos-6500x から受け取った経路の nexthop を自分自身に設定した上で他のピアに対して再広報しています。 本来は route-map などで各ピアごとに広報する経路を制限するのが当然なのですが、今回は実験的なネットワークなので省略します。

設定ができたので実際に動かしてみます。 vyos-65001,vyos-65002 で vyos-65003 を介してそれぞれの経路が広報されていることが分かります。

f:id:PiBVT:20201211170734p:plain
vyos-65001 に BGP で広報された経路情報

traceroute などでも、広報された経路に基づいて経路選択が行われていることが分かります。 さらに、遅延は vyos-internet を介していた時よりも大きく減少していることも分かるかと思います。

f:id:PiBVT:20201211171411p:plain
プライベートトランジットを介する場合の経路と遅延

これで無事、プライベートトランジットを介した通信をすることが出来ました。

最後に

今回は L3 の技術を活用することで研究活動を快適にする環境を構築しました。 普通に開発をしているだけではこういった L2, L3 に触れる機会は少ないため、いい経験になりました。

最近は Docker などのコンテナで簡単に実験環境が構築出来て便利な一方、 現実の環境と異なり、理想的な環境しか提供してくれません。(カオスエンジニアリングのように障害等を注入することはもちろん可能ですが。)

今回は経路や障害といった現実の環境ならではの制約のもとでしたが、 一筋縄ではいかないために試行錯誤する過程は非常に楽しかったです。 少なからぬ研究の時間が吸い取られたことは事実ですが、 今後の研究に役立つかもしれない経験をできたこともまた事実です。 卒業研究が終わるまでは現環境で使い続けますが、春休みには研究室のインフラ刷新に合わせて改良するつもりです。

ここまで読んでくださった方の何かしらの役に立てば幸いです。それでは。

大学生活4年間を振り返るポエム

この記事は、Kyoto University Advent Calendar

adventar.org

の4日目の記事です。

はじめに

お久しぶりです。PiBVT です。 今年は未踏やら研究室配属やら濃厚な1年でした。

今年度で4回生となり、うまくことが進めば学部を卒業できるはずです。 せっかくなので、4年間の大学生活を振り返ってみたいと思います。

注意

圧倒的ポエムなので、こういった記事が嫌いな方は申し訳ありませんがブラウザバックの方をお願いします。

時系列順に振り返る大学生活

適当に1年ずつ振り返っていきます。

1回生の前期が怒涛すぎて2回生以降は手抜きです。

大学 0 回生

高校生の時は地方の県立高校で部活(柔道)をしつつ家では勉強、外に遊びに行くことはほとんどない(行く場所もない) ような生活でした。 高校生までは地元の古くからの友人も多く、それは楽しい毎日を送っていました。 自宅から高校まで往復で 25km ある道のりを毎日自転車で通学するのは大変でしたが...

無事前期試験で合格し、当時は何の具体的なプラン・ビジョンもなく、 「大学に行けば自分の好きなことを学べる!」という希望だけを胸に大学へと通い始めました。 そして、新学期早々、現実を突き付けられることになりました。

大学 1 回生

終わらぬ課題・加速する焦り

前期が始まり弊学科では、一つの講義が 90 分、多い日では1限(8:45~)から5限(~18:00) までありました。 当然各講義では課題が出されるわけですが、講義の内容すらよく理解できぬまま課題を解く羽目になり、朝までかかることもしばしばでした。 課題の量的に、京都大学工学部情報学科の学生にとって1回生の前期が最も辛いのではないかと思います。

さらに、物理学実験なる(実験自体は楽しいが)レポートの量がえげつない講義を受講していたため、 これも提出当日の朝までかかることが良くありました。

1回生前期は1週間のほとんどを課題に費やし好きなことを勉強できる時間はほとんどなく、 おまけに睡眠時間すらままならぬ状況でした。

また、家庭の事情で京都に下宿できず大阪から通学していたため、往復3時間かけて慣れない電車通学に体力を削られていきました。

孤独な闘い・削られる命

睡眠もままならぬ状態でそのような生活をしていて何も起こらないわけがありません。 半年で体重は 10kg 以上減り、精神的にも突然泣き始める、自殺願望などの異常が出てきました。

さらに、大阪からの通学という点で下宿をしている人に比べて気軽に相談できるような友人を持つことも出来ていない状態でした。

そんなこんなで、半年間は命を削りながら耐える綱渡り状態が続きました。

COVID-19 でリモート講義となっている1回生の方たちが辛い思いをしているのは痛いほど分かります。

終わる地獄

前期試験を終え、夏休みとなり地獄は終わりを迎えました。 夏休み中は反動で2か月間ずっとただダラダラ過ごす毎日が続きました。

夏休み期間中に自分の人生についていろいろと考え、 それまで学業を重視する考えを投げ捨て、自分のやりたいことをやることにしました。

もちろん、まったく勉強をしないわけではなく、無理をしない範囲で頑張りつつ自分のやりたいことを優先するという方針です。 高校生までは京都大学に行くためにやりたいことを押し殺して学業最優先で取り組んでいたわけであり、当時の自分としては大きな転換でした。

何事もなかった後期

夏休みで考え方を切り替え、できることをやりつつ自分をやりたいことをやっていました。

実のところ、いろいろと手を出していたので何をしていたのかはそれぞれ覚えていません。 唯一所属していたサークルの「機械研究会」にちょこちょこ顔を出すようになり、NF で作品を展示していたような気がします。

そんなこんなで1回生後期は追いつめられることもなく平和に過ごすことができました。

大学 2 回生

怒涛の1回生を終え、計算機コースに配属されました。 2回生では、それとなく講義を受けつつ色々とやりたいことをやっていました。 具体的には「アルバイト」と「ロボコン」です。

両親から食費として一定額を毎月もらっていたのですが、好きなことに使おうとすると自分で稼ぐ必要があります。 4月ごろに生協のアルバイト紹介で百万遍の近くにある会社がエンジニアのアルバイトを募集していたため、勉強になるかもと応募してみました。 面接をして無事採用されたのですが、初めての賃金労働で最初は分からないことばかりで大変でした。

サークルの方で「NHK学生ロボコン」という国内の学生ロボコンでは最大の大会に参加することになりました。 サークル内でメンバーの募集がかかった時、「なんとなく楽しそう」という楽観的な考えでメンバーとなりましたが、 実際にはかなり大変でした

学業は「上回履修」なる制度を利用して3回生配当科目でおもしろそうな講義を受講してたりしました。

大学 3 回生

やりたいことをやりつくした2回生は自分の人生においても大きな転換点でした。 3回生は講義自体が少なく、自由な時間が多いため何かしたいなぁと考えていたところ、 学生ロボコンのメンバーの一人から「未踏」なるプロジェクトに応募しようと声がかかったので グループで応募したところ、採択されたため、その開発に取り組んでいました。

専門性の高い講義や 計算機コース内では不評な計算機科学実験も楽しみながら受講していました。

大学 4 回生

無事そこそこの成績を収めていたため、第一志望の研究室に配属されました。 研究室ではIoT ネットワークのセキュリティについて研究しています。

研究自体も楽しいのですが、研究室に所属することで、 「研究室の計算資源を(ほぼ)自由に利用する」、 「研究室の機材を使って(ほぼ)自由に遊べる」 などの権利を獲得したため、存分に権利を活用して遊んでいます。

イベントごとに振り返る大学生活

アルバイト

2回生の4月から同じバイトを続けているのですが、アルバイト先の方の影響で 自分がやりたい研究分野が決まったり、 それまで知らなかった世界を知ることができたりなど、かなり良い影響を受けました。

仕事内容も研究っぽいことをして対外発表をする機会を頂くなど、大学に通っているだけでは出来ない経験もさせてもらいました。

色々と書きたいことはあるのですが、契約上書いてもいいか不明瞭なので今回はパスとします。

ロボコン

機械研究会というサークルで2回生の夏休みから3回生の夏休みの約1年間、NHK学生ロボコンに取り組んでいました。

機械研が学生ロボコンに出場するのは約15年ぶりで、技術的な資産がないだけでなく練習をする場所の確保すらままならない状態でした。 出場するために各メンバーが奔走して場所の確保やロボットの製作資金をかき集めるなど苦難の連続でした。

製作資金は OB の方からの寄付を頂き、感謝の限りです。 結局練習場所は、「ルネ」の横にあるサークル棟の屋上となり、1~4 月は寒空の下で夜通しロボットを動かしながらみんなでデバッグをしていました。

f:id:PiBVT:20201203224523j:plain

f:id:PiBVT:20201203224931j:plain

事前審査であるビデオ審査の締め切り直前では、 全員が限界まで知恵を絞って試行錯誤し、なんとか15年ぶりに本選出場が決まりました。

大会当日は「1回でも勝てれば万々歳」と言っていたのですが、実際の試合ではあれよあれよと勝ち進み、 訳が分からない状態で優勝しました。

NHK学生ロボコンでは優勝したチームが ABU ロボコンという世界大会(アジア・太平洋圏)に進出するのですが、 全く想定していなかった事態で翌日からドタバタと準備することになりました。

ロボットの改修だけでなく、メンバー全員分の旅費やその他手続きなどあれやこれやてんてこ舞いになりながらなんとか処理していました。

f:id:PiBVT:20201203225939j:plain

書き始めるときりがないので適当にはしょりますが、大会では 8 位となり、世界を相手にした貴重な経験が出来ました。 負けた試合の原因が僕の担当範囲の動作不良で自分の詰めの甘さを痛感しました。(試合前後の記憶があまりない)

海外のチーム(特に中国の代表チーム)の方たちとは自主的にホテルで意見交換会をしたりする中で 海外の学生のレベル・向上心の高さを目の当たりにし、身の引き締まる思いでした。

未踏

2019年度の未踏事業に応募し、採択されました。

www.ipa.go.jp

詳しい体験記はほかのメンバーの記事があります。

anqou.net

anqou.net

きっかけは、2019年の2月末に学生ロボコンで同じメンバーだった @nimdanaoto と機械研の工作室で 雑談をしていたところ「おもろいアイデアあるで。」となり、ちょうど未踏事業の応募期間だったため応募することにしました。

イデアを出すうちに「独自 ISA のコンパイラとかツールチェインを書ける人が欲しい」と なったので、偶然Cコンパイラを書ける深海魚 @ushitora_anqou がいたため チームに引き込み、3人で応募することになりました。

f:id:PiBVT:20201203232827p:plain

その後、採択の通知が届き、未踏事業として約9か月間取り組んでいました。 取り組んでいた内容は各種発表などで詳細にあるので割愛します。

github.com

相応の成果を求められながら独自のアイデアで開発を行う経験は初めてで、 期間中はメンバーとアイデアや実装方針を巡って口論になることが多々あり、 メンバーのうち1人は「もうあんな経験は二度としたくない」というほどにはお互いにギスギスすることもありました。

最後はうまくまとめ上げることが出来てよかったものの、一つ間違えば大変なことになっていたと思います。 その後、ありがたいことにスーパークリエーターに認定していただいたり、VSP に関する論文が USENIX Security '21 に採択されたりしました。 成果がいろいろな形で評価され、開発者としてはうれしい限りです。

www.usenix.org

最後に

1回生のころは一人で呆然としていたのですが、2回生以降は友人達のおかげで非常に濃厚な日々を過ごすことができとても感謝しています。 気軽に話せる友人がいると、いろいろと意見やアイデアの交換ができ一人では無理なことも出来るということを感じることが多々ありました。 大学生活4年間でそういったことを実際に体験できたのは貴重な経験だと思っています。 学部卒業後は現在所属している研究室で引き続き修士学生として研究に取り組んでいきます。

長々と駄文を垂れ流しましたが、読者の方に楽しんでいただけたのなら幸いです。

5日目は高林さんです。どのような記事を書かれるのか楽しみです。 それでは。

MBR を雑にダンプしてみた

動機

MBRを触ったことがあまりなかったので、勉強がてらに雑に読み出してダンプしてみました。

MBRの構造

MBRはディスクの先頭セクタ(512byte)に存在します。

Wikipediaによると、 構造は以下のようになっています。

ja.wikipedia.org

オフセット サイズ(byte) 内容
0x000 446 ブートストラップローダ
0x1BE 16 第1パーティションテーブル
0x1CE 16 第2パーティションテーブル
0x1DE 16 第3パーティションテーブル
0x1FE 16 第4パーティションテーブル
0x1FE 2 ブートシグネチャ(0xAA55)

今回は、ブートシグネチャパーティションテーブルを読み取ることにしました。 ブートシグネチャはリトルエンディアンで 0xAA55 で、パーティションテーブルは以下のようになっています。

オフセット サイズ(byte) 内容
0x0 1 ブートグラグ(0x80:ブート可 0x00:ブート不可)
0x1 3 パーティション
最初のセクタ
(CHS方式)
ヘッド
シリンダの上位2ビットと
セクタ
シリンダの下位8ビット
0x4 1 パーティション識別子
0x5 3 パーティション
最後のセクタ
(CHS方式)
ヘッド
シリンダの上位2ビットと
セクタ
シリンダの下位8ビット
0x8 4 パーティションの最初のセクタ(LBA方式)
0xC 4 パーティションの全セクタ数

CHS方式は、シリンダ・ヘッド・セクタを利用した表現で、LBA方式はセクタ(512byte)を単位とした表現です。 CHS方式は最大24bit, LBA方式は最大32bit のフィールドしか割り当てられていないため、 扱えるディスクサイズには限界があります。 それが 528MB, 8.4GB, 137GB の壁として問題になりました。 詳細はこの方のページが非常に参考になりました。 caspar.hazymoon.jp

MBRをダンプする

初めて触るGo言語でダンプツールを作成しました。 まだ触り始めなので、もっといい書き方があるに違いないのですが、この程度でも十分に機能してくれます。

処理の流れとしては、

だけです。それぞれのデータのオフセットは固定なので簡単にダンプすることが出来ます。 さらに、Linuxではブロックデバイスファイルを open して read することでディスクの中身を簡単に読み出すことが出来るので楽です。

実際のコードがこんな感じです。

package main

import (
    "fmt"
    "os"
    "encoding/binary"
)

func printMBRPartitionInfo(partitionName string, buf []byte) {
    fmt.Println(partitionName)
    fmt.Printf("- Type: %02X\n", buf[0x04])
    fmt.Printf("- BootFlag: %t\n", buf[0x00] == 0x80)

    fmt.Printf("- Sector Num: %d\n", binary.LittleEndian.Uint32(buf[0x0C:]))
    fmt.Printf("- First Sector(CHS): %02X %02X %02X\n", buf[0x01], buf[0x02], buf[0x03])
    fmt.Printf("- Last  Sector(CHS): %02X %02X %02X\n", buf[0x05], buf[0x06], buf[0x07])
    fmt.Printf("- First Sector(LBA): 0x%08X\n", binary.LittleEndian.Uint32(buf[0x08:]))
}

func main(){
    // ブロックデバイスを開く
    file, err := os.Open("/dev/sdb")
    if err != nil {
        panic(err)
    }
    defer file.Close()

    // LBA 0 を読み込む
    buf := make([]byte, 512)
    file.ReadAt(buf, 0)

    // ブートシグネチャの読み取り
    if (buf[0x1FE] != 0x55 || buf[0x1FF] != 0xAA){
        return
    }
    printMBRPartitionInfo("1st Partition", buf[0x1BE:])
    printMBRPartitionInfo("2nd Partition", buf[0x1CE:])
    printMBRPartitionInfo("3rd Partition", buf[0x1DE:])
    printMBRPartitionInfo("4th Partition", buf[0x1EE:])
}

Lubuntu のインストラーが入ったUSBメモリに対して実行したときの結果は以下のようになりました。

1st Partition
- Type: 00
- BootFlag: true
- Sector Num: 3475360
- First Sector(CHS): 00 01 00
- Last  Sector(CHS): 6A E0 F6
- First Sector(LBA): 0x00000000
2nd Partition
- Type: EF
- BootFlag: false
- Sector Num: 8000
- First Sector(CHS): FE FF FF
- Last  Sector(CHS): FE FF FF
- First Sector(LBA): 0x00349820
3rd Partition
- Type: 83
- BootFlag: false
- Sector Num: 12140544
- First Sector(CHS): 76 1F D8
- Last  Sector(CHS): 2D E1 CC
- First Sector(LBA): 0x00351000
4th Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000

第1パーティションは Type 00 の空のパーティションですが、パーティションが確保されていることから何らかの専用パーティションと考えられます。 ブートフラグが立っているので、ここからブートします。

第2パーティションは Type EF の EFIパーティションです。UEFI用の bootx64.efi などが配置されています。

第3パーティションは Type 83 の Linux ファイルシステムです。今回はwritableな領域として、ログの保存等に利用されています。

第4パーティションは存在しません。

第1パーティションについて

さすがに気になるので詳細を調べてみたところ、 Lubuntuのディスクイメージは Mac を含むあらゆるシステムで動かすために、CDのファイルシステムである ISO 9660 と Apple Partition Map を含むようです。

askubuntu.com

実際に parted で調べたところ、 Apple フォーマットのパーティションとして認識されました。

$ sudo parted /dev/sdb p
警告: ドライバは物理ブロックサイズが 2048 バイトであると言っていますが、Linux は 512 バイトだと言っています。
無視(I)/Ignore/取消(C)/Cancel? I                                          
モデル: BUFFALO USB Flash Disk (scsi)
ディスク /dev/sdb: 32.0GB
セクタサイズ (論理/物理): 2048B/512B
パーティションテーブル: mac
ディスクフラグ: 

番号  開始    終了    サイズ  ファイルシステム  名前   フラグ
 1    2048B   6143B   4096B                     Apple
 2    1765MB  1769MB  4096kB                    EFI

GPT なディスクを読んでみた

GPT 形式のディスクも読み込んでみました。

1st Partition
- Type: EE
- BootFlag: false
- Sector Num: 1000215215
- First Sector(CHS): 00 01 00
- Last  Sector(CHS): FE FF FF
- First Sector(LBA): 0x00000001
2nd Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000
3rd Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000
4th Partition
- Type: 00
- BootFlag: false
- Sector Num: 0
- First Sector(CHS): 00 00 00
- Last  Sector(CHS): 00 00 00
- First Sector(LBA): 0x00000000

第1パーティションは Type EE となり、GPT形式のディスクであることを示しています。

このように、GPT形式のディスクでも LBA 0 にMBRが存在し、GPT形式であることを示しています。

まとめ

MBR のダンプ自体は非常に簡単で、ディスクに対する知見を得ることが出来ました。 せっかくなので、GPT形式のディスクについてもダンプを取りたいと思っています。

RustでMBRダンプをしてみた(2020/03/28 追記)

Rustにも興味があったのでRustを使ってダンプしてみました。

use std::io::prelude::*;
use std::fs::File;

// ここらへんもう少しなんとかならんかな
fn convert_bytes_to_u32_le(bytes:&[u8]) -> Result<u32, ()>{
    // サイズが違うときはエラーとする
    if bytes.len() != 4 {
        return Err(());
    }

    let mut u32_raw:[u8; 4] = [0; 4];
    u32_raw.clone_from_slice(&bytes[0..4]);
    let _u32 = unsafe { std::mem::transmute::<[u8; 4], u32>(u32_raw) }.to_le();
    return Ok(_u32);
}

fn print_mbr_partition_info(part_name:&str, buffer:&[u8]){
    // サイズが違うときは何も表示しない
    if buffer.len() != 0x10 {
        return;
    }

    println!("{}", part_name);
    println!("- Type: 0x{:X}", buffer[0x04]);
    println!("- BootFlag: {}", buffer[0x00] == 0x80);
    println!("- Sector Num: {}", convert_bytes_to_u32_le(&buffer[0x0C..0x10]).unwrap());
    println!("- First Sector(CHS): {:02X} {:02X} {:02X}", buffer[0x1], buffer[0x02], buffer[0x03]);
    println!("- Last  Sector(CHS): {:02X} {:02X} {:02X}", buffer[0x5], buffer[0x06], buffer[0x07]);
    println!("- First Sector(LBA): 0x{:08X}", convert_bytes_to_u32_le(&buffer[0x08..0x0C]).unwrap());
}

fn main() {
    let mut f = File::open("/dev/sdb").unwrap();

    let mut buffer:[u8; 512] = [0; 512];
    f.read(&mut buffer).unwrap();

    // シグネチャがマッチしなければ終了する
    if (buffer[0x1FE] != 0x55) || (buffer[0x1FF] != 0xAA){
        return;
    }

    print_mbr_partition_info("1st Partition", &buffer[0x1BE..0x1CE]);
    print_mbr_partition_info("2nd Partition", &buffer[0x1CE..0x1DE]);
    print_mbr_partition_info("3rd Partition", &buffer[0x1DE..0x1EE]);
    print_mbr_partition_info("4th Partition", &buffer[0x1EE..0x1FE]);
}

2019年を振り返って

ポエムです

2019年は色々あったので自分の中での振り返りを含めて適当に書いていきます

1月

年末年始は大学の実験で、論理ゲートを組み合わせて何かを作る課題でプロセッサを作ってました。 手でカルノー図を解いて一つ一つのゲートをKicadみたいな回路CAD上で配置して手でつなげる作業は苦行でした。

中旬・下旬はインフルエンザになりレポート・試験をインフルエンザとともに過ごす地獄でした。

2月

大学は春休みなのでほとんど毎日サークルに行ってロボコンの作業をやってた気がします。 それ以外の記憶がないだけですが。

ロボコンでの担当はLinux・ROS・通信まわりの担当でした。 UARTを利用したマイコン間の簡易的な通信プロトコルをスクラッチしたり、 EthernetでROSのデータを流し込んでマイコンを暴走させたりしてました。

3月

春休み後半もロボコンの作業をやってました。

中旬頃に作業をしながら、「未踏にこんなアイデアのやつ出したいんよね〜」 とか話してたら偶然メンバーの友人と話が盛り上がり、 締切10日前から資料を一気に作って応募しました。

4月

ロボコンの2次ビデオ審査の締切があり、 人が限界を超えると何が起こるのか目の当たりにしてました。 端的に辛かったです。

2次審査をパスし本戦出場が決まったときは本当に嬉しかったです。

5月

大学の実験やらロボコンの作業をしてたような気がします。

プロセッサ実験では、徹夜で実装したりマルチコア化してセマフォの制御でやらかしたりしてました。

実験内のソート速度コンテストでシングルコア歴代最速を叩き出したのですが、 ほとんどは一緒に作業していた相方の超高性能ソートアルゴリズムのおかげです。 シミュレーターやアセンブラも書いてくれたので本当に感謝しかないです。

ロボコン本番はまさか優勝するとは思ってなかったので 嬉しいというより、何が起きたのか分からんという困惑の方が大きかったように思います。

ABUでモンゴルに行くことにはなったけれどお金も場所も何もない状態で途方にくれてたような...

あと、未踏に採択されました。てんてこ舞い。

6月

ほとんど記憶がないのですが、おそらくロボコンと未踏の開発をやってたはずです。

ロボコンはLiDAR・ROSのデータを制御に反映する部分の改良をしたりしてましたが、 ほとんどの時間はひたすら走らせて問題ないか確認してました。

7月

ほとんど記憶がないのですが、おそらくロボコンと未踏の開発をやってたはずです。

ロボットの発送と試験と未踏の用事が重なって大変だったことは覚えてます。

8月

ABUロボコンでした。

大会中は色々トラブルがあり終始ピリピリしてしまい、チームメンバーには迷惑をかけてしまいました。

大会についてはNHKの放送を見て下さい。文章化は書くことが多すぎて大変なので省略しますが、 僕自身の甘さを痛感する大会でした。

9月

ほとんど記憶がないのですが、ロボコンの後片付けと未踏の開発をやってたはずです。

未踏は提案アイデアのPoCに成功して「あ〜うごいた〜」とか言ってました。

10月

未踏の開発を中心にやってました。

あと、NHKロボコン2020に向けて新チームで開発が始まりました。

11月

未踏の開発とロボコンの開発のサポートをしてました。

NF(11月祭)の展示を手伝ったり、大学の実験で初めてまともなWebアプリケーションを組んだりしてました。

12月

未踏の開発とロボコンの開発のサポートをしてました。

GPGPU無限に分からんとか無限に計算資源欲しいとか言ってました。

2019年総括

色々あって充実した楽しい1年でした。

NHK・ABUロボコンは喜怒哀楽すべてを詰め込んだ時間を過ごすことができました。

僕自身の甘さや未熟さ、一人では何も出来ないことを痛感する1年でもありました。 ロボコンや未踏のチームメンバー全員が人として本当に素晴らしい人ばかりで、 僕はそれに甘えたり迷惑をかけたりすることが多々ありました。 メンバーや友人には本当に感謝です。ありがとうございました。

2019年も残りわずかですが、みなさんよい年をお迎えください。

蛇足

そういや自作OSとかFPGAで何かするやつ全くしてない....

Zen言語でTCP/IPプロトコルスタックをスクラッチしたかった

この記事は 自作 OS アドベントカレンダー 2019 の 8 日目の記事です

adventar.org

近況報告

こんにちは。VTb(@PiBVT)です。最近はtwitterで発言する機会が少なくなり表舞台からは消えてますが、一応生きてます。 このブログも去年のアドカレ以来の更新になります...放置しすぎた。

今年はロボコンやら未踏やらなんやらかんやらで、てんてこ舞いになり自作OS界隈から離れてしまっていたため自作OSとはあまり関係のない内容になります。ご了承ください。

ちなみに、未踏ではプログラムを暗号化された状態で実行するプラットフォームを3人のチームで開発してます。 専用のISA,CPU,コンパイラも自作してるので、低レイヤネタとしてプロジェクト終了後にまた記事にしたいと考えています。

Zen言語とは

Zen言語は京都にある株式会社コネクトフリーで開発されているプログラミング言語です。 組み込みなどのベアメタル環境においてもメモリー安全かつ確実なエラーハンドリング、HashMapなどの一般的なコレクション(Zen言語ではcontainer)群が提供されています。 zen-lang.org

僕も京都の大学に通学してる都合上、 何度か会社の方にお邪魔させてもらったことがありました。 そういったつながりでZen言語を使ってみたいと思っていたので、Zen言語でプロトコルスタック(一部)を書いてみました。 (インターン等には行ってないのでZen言語に関してはど素人です)

今回作ったもの

TAPデバイス経由で流れてきたパケットを処理してpingに応答するソフトウェアです。 f:id:PiBVT:20191208012451p:plain

github.com

本当は level-ip のようにソケットライクなAPIを実装してガンガンTCPパケット流したかったのですが、いかんせん時間がなかったので断念しました。無念。 github.com

※注: ベアメタル開発はペリフェラルドライバ開発にかなり時間を使うため、今回はLinux上でのシステムプログラミングをしてみました。 おそらく、この分野はZen言語ではあまり想定されていない(主眼を置いていない)と思われるので、 ベアメタル開発とは開発の勝手がかなり違う可能性があります。

できること

L2パケットを処理してpingに対して応答する。 f:id:PiBVT:20191208012451p:plain

以上です。

全体の構成

プロトコルとしてはEthernet,ARP,IPv4,ICMPv4で、pingに対して応答できる最低限の実装になってます。

色々とひどいのでソースコードは全く参考にならないと思います。

TAPデバイス

TAPはL2デバイスをエミュレートした仮想デバイスです。(TUNはL3) OpenVPNとかでよく用いられているアレです。

TAPを使うことで仮想NICを作成したことになるので、今回は仮想NICにつながる先の部分を書いたことになります。

似たようなことをするときにRaw Socketも使えるのですが、これは既存のNICに生のEthernetフレームを流すものなのでTAPデバイスとは全く異なるものです。

ちなみに、Raw Socketを作成するのにはroot権限が必要です。一方でpingはRaw Socketでパケットを流していますが、root権限なしでも利用出来ます。 この仕組みについては、Linux Capability を調べると分かります。(常識?)

TAPデバイスの初期化部分はLinuxシステムプログラミングの領域なので、C言語と相性の良いZen言語なら楽にかけます(もちろんlibcはリンクしてます)

pub fn create_netdev() NetDevError!std.os.fd_t {
    const sock = try std.os.openC(c"/dev/net/tun", std.os.O_RDWR, 0);
    errdefer {
        std.os.close(sock);
        std.debug.warn("Failed to open /dev/net/tun\n");
    }

    const ifr_addr = tun.ifreq_addr{
        .ifr_flags = tun.IFF_TAP | tun.IFF_NO_PI,
    };

    const zero = ([1]u8{0}) ** 12;

    var ifr = tun.ifreq{
        .ifr_name = "test" ++ zero,
        .addr = ifr_addr,
    };

    const err = tun.ioctl(sock, tun.get_tunsetiff(), &ifr);
    if (err < 0) {
        std.debug.warn("{}", err);
        return NetDeviceError.FailedToCreateTUN;
    }
    std.debug.warn("Successfully Created TUN Device:{}\n", ifr.ifr_name);
    _ = tun.system(c"ip a add 10.0.0.2/24 dev test");
    _ = tun.system(c"ip link set test up");
    return sock;
}

こんな感じになります。TUNSETIFFマクロだけはZen言語単体では無理だったので、Cから呼び出してます。 TUNデバイスを作成したらIPv4アドレスを割り当ててリンクを上げます。(エラー処理してませんが。) これでTUNデバイスを使えるようになったので、あとはwrite,readでパケット投げ放題です。

TUNSETIFFマクロも追いかけてみると結構楽しかったのですが、話が脱線するので割愛します。

ARP(arp.zen)

RFC 826 を参考に実装しました tools.ietf.org

pub const pkt_hdr = packed struct {
    hwtype: u16,
    protype: u16,
    hwsize: u8,
    prosize: u8,
    op: u16,
    sha: u48,
    spa: u32,
    tha: u48,
    tpa: u32,
};

今回はpingに反応するだけなので、ARP Request に対して ARP Reply で応答すると同時にARPテーブルを更新するだけの処理になっています。

本来はパケット送信時にARP Requestを投げたり、テーブルのキャッシュクリア等をする必要があるのですが、pingへの応答には必要なかったので実装しませんでした(バッサリ)

C言語ではsha,tha等はuint8_t[6]で実装するのですが、Zen言語では自由にビット幅を設定できるのでu48一発でいけます。 (むしろ[6]u8使うと謎にバグることが多くて辛かった)

IPv4(ipv4.zen)

RFC 791 を参考に実装しました tools.ietf.org

pub const pkt_hdr = packed struct {
    header_length: u4,
    version: u4,
    tos: u8,
    total_len: u16,
    identification: u16,
    frags: u16,
    ttl: u8,
    protocol: u8,
    chksum: u16,
    src_addr: u32,
    dst_addr: u32,
    payload: [*]u8,
};

単純にIPv4ヘッダーを読んでプロトコルを判断するだけです。 フラグメント,デフォルトゲートウェイ等は一切実装していません。気にしたら負けです。

RFC上では、version, header_length となっているのですが、エンディアンの関係か、逆の順番でないと正常に動作しませんでした。 ココらへんをC言語で書こうとすると

struct header {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    uint8_t len :4;
    uint8_t ver  :4;
#elif __BYTE_ORDER == __BIG_ENDIAN
    uint8_t ver  :4;
    uint8_t len :4;
#endif
} __attribute__((packed));

こんな感じで対応できていたので、Zen言語でも対応できると嬉しいです。

ICMP(icmp.zen)

RFC 792 を参考に実装しました tools.ietf.org

Echo Request に対して Echo Reply を返すだけです。

ちなみに、pingに応答するためにはペイロードを丸々コピーしないといけないようです。本当はどうなのか分かりませんが、とりあえず動いてるのいいでしょう。(おい)

Zen言語を触ってみて

良いところ

  • Cと相性がいい。何も考えずにリンクできる
  • ビルドシステム
  • テストフレームワーク
  • 自由なビット幅整数型
  • エラーが日本語

C言語と何も考えずに普通にリンクできるので、少しずつZenで書き直していくという戦略は有効に感じました。が、C言語と同じ書き方は出来ないことを理解しておく必要はあります。

ビルドシステムやテストフレームワークは、まだ十分に使ってないのでその力を引き出せてはいないのですが、C言語のときは毎回悩みの種だったので予め標準で備わっているのは非常に強い点です。

個人的には自由なビット幅整数型が便利に感じました。エンディアンには気をつける必要があるのですが、 そこさえ気をつければ今までuint8_t配列で処理していたものをまとめて固めることができるので有効に使っていきたいです。

エラーが日本語なだけで安心感が段違いです。(英語だと読まない人がいるので...)

微妙なところ

  • 配列とポインタの関係が分かりにくい
  • ドキュメントがところどころ不十分
  • Zenコンパイラがたまにクラッシュする
  • コミュニティ,相談場所が存在しない

配列とポインタの関係については、C言語ほど単純なものではないので概要を理解しておく必要があります。 プログラムを書いてる時間の大半を配列とポインタ周りのバグでこじらせてたので... 逆に言うと、配列とポインタの組み合わせはすべきではない。ということかもしれません(今気づいた)

ドキュメントの不十分さやZenコンパイラのクラッシュは、公開されて半年もしていないので当然のことなのですが.. 標準ライブラリの実装が参考になりました。 一般的に使われるようになるにはまだ時間がかかりそうですが、これからに期待です。

個人的に一番困ったのが、相談できる場所がないことです。 クラッシュ,バグ報告をしようとしても、それらの再現コードの貼付ける場所が存在しないため 現状では積極的に改善要求を出すことがつらい状況にあるように感じました。 せめてGitHubリポジトリが存在すれば、再現コードを貼ってissueを投げることが出来たのですが....

まとめ

パケットの処理系をZen言語で書いてみた一番の印象は、Zen言語はC言語との相性はいいけれど、C言語では決してないと感じました。

Zen言語ではベアメタル開発においても容易で安全なプログラミング環境を提供する方針で言語仕様自体やライブラリが整備されており、 遊びでC言語を書くノリで書こうとすると安全でないコードになるので、コンパイラが自動的に弾いたり実行時にエラーを吐いてくれます。 慣れないうちはモヤモヤするのですが、慣れれば快適に書けるようになると思います。

僕自身は15時間ほどしか書いてないので全く慣れてないのですが、今後も少しずつ書いていきたいとは思っています。

蛇足・独り言

本当はQCOW2フォーマットのディスクをブロックデバイスとして直に認識させるLinuxカーネルモジュールを書くつもりだったのですが、時間の都合上断念しました。 nbdを利用したものとのパフォーマンスの違いを計測したかったんですよね...

パケット処理系ネタはもともとは2018年Seccamp後にNICデバドラを実装してガバガバTCP処理系を実装したのが最初でした。 その後、今年の3月にはSTM32F767上にデバドラからUDP処理系までを書いてみたりしていたのですが、それ以降は全くやっていなかったので今回ネタとして取り上げてみました。 いい加減ガッツリ、バッファリング周りから全部設計して常用できるTCP/IPスタックを書きたいところではあります。

ロボコンではそこらへんの担当(マイコンの通信プログラム,Ethernet,PCの管理)をしてたのですが、まさか世界大会に行くことになるとは。人生何があるか分からんもんです。

Android-x86でLinux Kernelのドライバにパッチを当てた

この記事は、自作OS Advent Calendar 2018

adventar.org

の 12/20 の記事として書かれました。

はじめに

怒涛の2018年もあと残すところ10日ほどになりました。皆様いかがお過ごしでしょうか?

僕は10月から大学の講義が始まって以来、講義やサークルのロボット製作などが忙しく、自作OSの方はxv6をx86-64ネイティブに完全移植する作業を開始した事ぐらいしか進捗はありません。

今回の自作OS Advent Calendar 2018では、xv6を64bitUEFIで起動したことやNICのデバドラ・プロトコルスタックの実装をしたことを書いても良かったのですが、どれも中途半端な進展なので あえてアドカレに書くために新しくネタを見つけてきました。

Android-x86とは?

Android-x86とは、その名前の通りAndroidx86のプロセッサを積んだPCで動かそうというプロジェクトです。

www.android-x86.org

Android自体はオープンソースなプロジェクトでかつ、Linux Kernelを改造したものが用いられているため、 x86で動かすこと自体は困難ではないようです。 が、x86とはいっても主に使われるハードウェアがWindowsを搭載したタブレットや2in1ノートのような、 ハードウェア構成が特殊なものが多いため全ての機能を完動させることは難しいようです。

今回利用したタブレットも中国製のWindows搭載のタブレットCube iWork10(いわゆる中華Winタブ)で、タッチパネルのドライバに問題がありました。 折角の機会なので、今回はこのタッチパネルを正常に動作させるためのパッチを作成しました。

今回の環境

今回の環境は以下のとおりです。

  • ハード: Cube iWork10
  • OS: Android-x86 8.1-rc2
  • Kernel: Linux Kernel 4.18.14

問題の症状

今回発生したタッチパネルの問題とは、タッチ位置と認識される位置が反転していることです。 横方向をx,縦方向をyとすると、x座標が反転している感じです。

f:id:PiBVT:20181220144704p:plain
問題の症状

問題となるドライバの調査

まず、タッチパネルに使われているドライバ(kernel module)を探すことにします。

幸いなことに、Android-x86にはターミナルエミュレータがあり、各種コマンドでタッチパネルのドライバを調査しました。

f:id:PiBVT:20181220124959p:plain
lsmod
f:id:PiBVT:20181220124949p:plain
modinfo goodix
以上より、タッチパネルのドライバはgoodixであるようなので、goodix.cにパッチをあてることにします。

linux/drivers/input/touchscreen/goodix.cの140行目付近にある

/*
 * Those tablets have their coordinates origin at the bottom right
 * of the tablet, as if rotated 180 degrees
 */
static const struct dmi_system_id rotated_screen[] = {
#if defined(CONFIG_DMI) && defined(CONFIG_X86)
    {
        .ident = "WinBook TW100",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
            DMI_MATCH(DMI_PRODUCT_NAME, "TW100")
        }
    },
    {
        .ident = "WinBook TW700",
        .matches = {
            DMI_MATCH(DMI_SYS_VENDOR, "WinBook"),
            DMI_MATCH(DMI_PRODUCT_NAME, "TW700")
        },
    },
#endif
    {}
};

や、700行目付近の

if (dmi_check_system(rotated_screen)) {
        ts->prop.invert_x = true;
        ts->prop.invert_y = true;
        dev_dbg(&ts->client->dev,
            "Applying '180 degrees rotated screen' quirk\n");
}

あたりが参考になりそうです。これは、タッチパネルを180°回転させるための処理のようなので、この処理を参考にx座標だけを反転させる処理を追加することにします。

パッチの方針

以下の方針でパッチを適用することにしました。

  1. rotated_screenと同様にDMI(SMBIOS)を利用した端末認識を行う
  2. invert_x=true,invert_y=falseの処理をrotated_screenと同様の処理で行う。

DMI(SMBIOS)の確認

DMI(SMBIOS)はハードのベンダーや型番などの固有情報をもつ領域らしいです。

Linuxから確認する場合はdmidecodeが使えるようなのですが、Android-x86にはインストールされていなかったので、Windows 10側から確認することにします。

Windowsの場合は、DirectX診断ツール(dxdiag)から確認できます。 スタートメニューの検索欄で「dxdiag」と検索することで起動できます。

f:id:PiBVT:20181220125517p:plain
DirectX診断ツール
このように各種情報が取得できました。今回必要な情報である、製造元は「cube」,型番は「i15-T」であることが分かります。

パッチ作成

rotated_screenを参考に以下のパッチを作成しました。

diff --git a/drivers/input/touchscreen/goodix.c b/drivers/input/touchscreen/goodix.c
index 6a4ffe800194..4f646607e3f7 100644
--- a/drivers/input/touchscreen/goodix.c
+++ b/drivers/input/touchscreen/goodix.c
@@ -145,6 +145,18 @@ static const struct dmi_system_id rotated_screen[] = {
        {}
 };
 
+static const struct dmi_system_id x_inverted_screen[] = {
+#if defined(CONFIG_DMI) && defined(CONFIG_X86)
+       {
+               .ident = "cube i15-T",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "cube"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "i15-T")
+               }
+       },
+#endif
+       {}
+};

@@ -707,6 +719,11 @@ static int goodix_configure_dev(struct goodix_ts_data *ts)
                ts->prop.invert_y = true;
                dev_dbg(&ts->client->dev,
                        "Applying '180 degrees rotated screen' quirk\n");
+       }else if (dmi_check_system(x_inverted_screen)) {
+               ts->prop.invert_x = true;
+               ts->prop.invert_y = false;
+               dev_dbg(&ts->client->dev,
+                       "Applying 'x-axsis mirrored  screen' quirk\n");
        }
 
        error = input_mt_init_slots(ts->input_dev, ts->max_touch_num,

処理の内容としては、DMIの製造元(DMI_SYS_VENDOR)がcube,型番(DMI_PRODUCT_NAME)がi15-Tに一致する場合は、タッチスクリーンのx座標を反転するという内容です。

Android-x86をビルドする

いよいよ、パッチをあてたLinux Kernelを含めたAndroid-x86をビルドします。 詳細な手順は

www.android-x86.org

ここにあります。

ビルド環境の構築

まずは、ビルド環境を構築します。 大量のソースコードコンパイルする必要があるので、今回は逸般の誤家庭には必ず1台あるラックサーバーを使うことにしました。

  • ハード:VMWare ESXi on FUJITSU RX300S7
  • CPU: Xeon E5-2630相当 10コア
  • Memory:24GB
  • HDD:200GB *OS: Ubuntu 18.10

ソースコードは約40GBほどあるので、余裕をみて200GBほどHDDを確保しておくことをおすすめします。 ビルドにあたり、以下のパッケージをインストールする必要があります。

repo
git
build-essential
libncurses5
m4
curl
openjdk-8-jdk
bison
libc6:i386
libncurses5:i386
libstdc++6:i386
bison:i386
libssl-dev
python-mako
libxml2-utils
isohybrid

インストールします。

$ sudo dpkg --add-architecture i386
$ sudo apt update
$ sudo apt upgrade
$ sudo apt install repo git build-essential libncurses5 m4 curl openjdk-8-jdk bison libssl-dev python-mako libxml2-utils isohybrid
$ sudo apt install libc6:i386 libncurses5:i386 libstdc++6:i386 bison:i386

以上でビルド環境の構築は終了です。

ソースコードのダウンロード

公式の手順に従って、ソースコードをダウンロードします

$ mkdir android-x86
$ cd android-x86
$ repo init -u git://git.osdn.net/gitroot/android-x86/manifest -b $branch
$ repo sync --no-tags --no-clone-bundle

約30〜40GBほどダウンロードするので、3時間程かかります。就寝前など時間があるときにダウンロードしましょう。

パッチの適用

Linux Kernelは、android_x86/kernelにあるので、android_x86/kernel/drivers/input/touchscreen/goodix.c に先程のパッチを適用します。

ビルド

Android-x86をビルドします。 ビルドオプションには、eng,user,userdebugの3種類ありますが、今回はuserdebugでビルドすることにします。当初はuserでビルドしたのですが、永久に再起動をする症状が出てしまいました。

$ . build/envsetup.sh
$ lunch android_x86_64-userdebug
$ m -j10 iso_img

f:id:PiBVT:20181220010306p:plain
ビルド開始
これでビルドが始まります。これも大体3.5時間かかるので、時間があるときに行いましょう。

ビルドしたイメージを実機で動かす

f:id:PiBVT:20181220144714p:plain
ビルド完了
ビルドが完了すると、完成したイメージはandroid_x86/out/target/product/x86_64/android_x86_64.isoに保存されます。 このイメージを適当にdd等でUSBメモリに書き込み、タブレットで起動してみましょう。

正常にタッチパネルが動作するようになりました!

まとめ

自作OSについて何か書くつもりだったのですが、Linuxの話になってしまいました。 自作OSも楽しいのですが、既存のOSを改造する(今回はドライバでしたが)というのも楽しいもので、勉強になります。 来年はネットワークスタックを書き上げてHTTPサーバーを動作させたり、ファイルシステムの実装をしたいと思っています。 2018年も残すところわずかとなりましたが、年末の休みを利用してまた何か楽しいことをやるつもりです。