Mini Web ServerをC言語で書いた

自作OSでopenとかreadのSystemCallを実装していましたが、何か面白いものを作りたくなったので、最近の自作OSでは一つの目標(?)とされているWebサーバを動かすことを目標とすることにしました。
とりあえず使用するシステムコールを探るためにC言語で超簡易版のWeb Serverを書いてみました。

使用するSystemCallを最小限にするためにfopenやmallocなどは使用せず、open/readなどのSystemCallに近い関数のみ使用するようにしました。本当はmallocは既にOS側でメモリ管理ができているので使いたかったのですがこの世のLinux向けのlibc実装はメモリを確保する際にbrkやmprotectを使用するらしく、現在の自分のOSでは紆余曲折あってLinux ABI互換で、これらの実装がやや面倒だったので(頑張ればできないことはないが)、とりあえず端折ることにしました。

コードは以下のとおりです。

#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

//#define DEBUG

#ifdef DEBUG
#include <arpa/inet.h>
#endif

#define HTTP_VERSION "HTTP/1.1"
#define GET_METHOD "GET"
#define SERVER_HEADER_ENTRY "Server: Mini Web Server"
#define HTTP_200 HTTP_VERSION " 200 OK"
#define HTTP_404 HTTP_VERSION " 404 Not Found"
#define HTTP_500 HTTP_VERSION " 500 Internal Server Error"

int main() {
  puts("Mini Web Server");

  int receive_socket = socket(AF_INET, SOCK_STREAM, 0);
  if (receive_socket < 0) {
    fprintf(stderr, "Failed to open the socket(ret: %d)\n", receive_socket);
    return 1;
  }

  struct sockaddr_in host;
  host.sin_family = AF_INET;
  host.sin_port = htons(8080);
  host.sin_addr.s_addr = INADDR_ANY;
  if (bind(receive_socket, (struct sockaddr *)&host, sizeof(host)) < 0) {
    fprintf(stderr, "Failed to bind\n");
    return 1;
  }

  if (listen(receive_socket, 10) < 0) {
    fprintf(stderr, "Failed to listen the socket.\n");
    return 1;
  }

  while (1) {
    struct sockaddr_in client_address;
    socklen_t client_address_length;
    int client_socket =
        accept(receive_socket, (struct sockaddr *)&client_address,
               &client_address_length);
#ifdef DEBUG
    printf("Client: {\"Address\": \"%s\", \"Port\": %d}\n",
           inet_ntoa(client_address.sin_addr), client_address.sin_port);
#endif
    char buffer[0x1000];
    buffer[sizeof(buffer) - 1] = '\0';

    int received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (received < 0) {
      fprintf(stderr, "Failed to receive data(ret: %d)\n", received);
      return 1;
    }

    /* Check method */
    if (received <= (sizeof(GET_METHOD) - 1) ||
        strncmp(buffer, GET_METHOD, sizeof(GET_METHOD) - 1) != 0) {
      close(client_socket);
      continue;
    }
    size_t pointer = sizeof(GET_METHOD) - 1;

    if (buffer[pointer] != ' ' || buffer[pointer + 1] != '/') {
      close(client_socket);
      continue;
    }
    pointer += 1;
    /* remove "/"(root) */
    pointer += 1;
    char *file_name = buffer + pointer;
    size_t file_name_size = 0;
    for (; (pointer + file_name_size) < received &&
           buffer[pointer + file_name_size] != ' ';
         file_name_size++)
      ;
    pointer += file_name_size + 1;

    if (received <= (pointer + sizeof(HTTP_VERSION) - 1) ||
        strncmp(buffer + pointer, HTTP_VERSION, sizeof(HTTP_VERSION) - 1) !=
            0) {
      close(client_socket);
      continue;
    }
    if (file_name_size == 0) {
      const char index_name[] = "index.htm";
      strcpy(file_name, index_name);
      file_name_size = sizeof(index_name) - 1;
    }
    file_name[file_name_size] = '\0';
#ifdef DEBUG
    printf("URL: %s\n", file_name);
#endif
    /* Create Response */
    int fd = open(file_name, O_RDONLY);
    if (fd < 0) {
      const char not_found_text[] =
          HTTP_404 "\r\n" SERVER_HEADER_ENTRY "\r\n\r\nFile is not found.";
      send(client_socket, not_found_text, sizeof(not_found_text) - 1, 0);
    } else {
      size_t size = lseek(fd, 0, SEEK_END);
      lseek(fd, 0, SEEK_SET);
      if (size > sizeof(buffer)) {
        const char error_text[] =
            HTTP_500 "\r\n" SERVER_HEADER_ENTRY
                     "\r\n\r\nFile size is exceeded the buffer size.";
        send(client_socket, error_text, sizeof(error_text) - 1, 0);
      } else {
        sprintf(buffer,
                HTTP_200 "\r\n" SERVER_HEADER_ENTRY
                         "\r\nContent-Length: %zu\r\n\r\n",
                size);
        send(client_socket, buffer, strlen(buffer), 0);
        read(fd, buffer, size);
        send(client_socket, buffer, size, 0);
      }
    }
    close(client_socket);
  }
  return 0;
}

straceで呼ばれているシステムコールを確認すると起動処理以外では、

  • wrtiev(puts, fprintf)
  • socket
  • bind
  • listen
  • accept
  • recvfrom
  • open
  • lseek
  • sendto
  • read
  • close

のみとなってます。

とりあえずLinuxでビルドしたところまあまあ動いているようのなのでこれが動くように頑張ります。

と言っても、まずはネットワークデバイスのドライバ書くところから始めないといけないのですが…

追記: 一応動きました。 https://twitter.com/PG_MANA_/status/1533812030582292481

MouseProのLTEモジュール(Telit LN940)をLinuxで使う(ATコマンド編)

旅先や出先で大活躍のLTEモジュール付きノートパソコンですが、妙に電波のつかみが悪いので更に調査していたところ別の方法で認識させることができたので記録しておきます。

環境

  • MousePro-NB510HL
  • openSUSE Tumbleweed
  • KDE Plasma Desktop

注意

前回の方法(MouseProのLTEモジュール(Telit LN940)をLinuxで使う)をすでに適用されている場合は一旦取り消して再起動してください。
具体的には以下のコマンドを実行してください。

sudo rm /etc/udev/rules.d/15-lte.rules
sudo systemctl disable wwan-sleep-hook
sudo rm /etc/systemd/system/wwan-sleep-hook.service

また今回の方法はモジュールのファームウェアの設定を変更します。そのため場合によってはモジュールが動作不能になる恐れがあります。以下の内容は全て自己責任で行ってください。

手順

まずはlibqmiやmodemmanagerをインストールします。
ModemManagerは起動させずに停止させたままにします。
(sudo systemctl stop ModemManager)

次に/dev/ttyUSB2に接続します。(screenコマンドがない場合はインストールしてください)

sudo screen /dev/ttyUSB2 115200

接続したらATE1と入力し改行します。(アルファベットは大文字で)、リターンキーを押すまでエコーバックはないのでお気をつけください。この作業でエコーバックを有効にします。成功すればOKと返ってきます。

(ATE1と入力し改行)
OK

次にATIと入力し改行してみます。成功すれば各情報とOKが返ってきます。(改行)はリターンキーのことを指します。

ATI(改行)

Manufacturer: QUALCOMM INCORPORATED
Model: 4105
Revision: (ファームウェアのリビジョン番号)
+GCAP: +CGSM

OK

モデルが4105であることを確認し次の作業に進みます。
AT^SETMODE?で現在のファームウェアの状態を確認します。成功すれば現在のファームウェアのモードとOKが返ってきます。マニュアルによれば0がNormal Mode、1がExtension Mode、2がLegacy Modeだそうです。

AT^SETMODE?(改行)
^SETMODE: 0

OK

次にモードを1(Extension Mode)に変更します。
AT^SETMODE=1でモードを変更します。成功すればOKが返ってきます。

AT^SETMODE=1
OK

ここまで来たらCtrl+a, k, yの順に入力しscreenを終了し、最後に再起動します。


再起動後ModemManagerを起動するとKDEのネットワーク設定でLTEモジュールを認識していることを確認します。
あとはMouseProのLTEモジュール(Telit LN940)をLinuxで使うを参考にKDEの設定を行ってください。
まれに接続が成功してもDNSの名前解決ができない場合があります。(curl https://1.1.1.1/ は接続できるけど curl http://example.com はできないなど)
この際は、resolv.confにPublic DNSを追記してやると接続できるようになる場合があります。

sudo sh -c "echo nameserver 1.1.1.1 >> /etc/resolv.conf"

以上の方法でusb_switchを使う場合よりより安定してつながるようになりました。
もし、不具合が発生した場合は上記の手順をAT^SETMODE=0に変更して行えば元に戻るはずです。

あけましておめでとうございます

今年もあけてしまったらしいです。新型コロナウイルス感染症もあってか2020から時の進みが速い気がします。

去年は東京に戻って引っ越しをしたり、またまた実家に帰ったり、お仕事を頂いたり、本の謝辞に載せていただいたりと色々なことがありました。とても貴重な体験でした。それでもオフラインの勉強会やイベントに参加することはかないませんでしたが…

初日の出を見て写真を撮り、飛行機で東京に戻るところです。元旦は妙に飛行機が安くて助かります(?)

年越しはOS自作をやっておりました。今年少しずつ進めていければと思います。

今年もよろしくおねがいします。

Linuxディストリビューションの比較

なかなかWordPressが完璧に動かないで悩んでいたら、いつの間にか動くようになっていて気がついたら、8月が終わってしまいました。
何かあるたびにログ見て悩むのはめんどくさいですが楽しくもありますよね。

今回はとりあえず、今まで扱ってきたことのあるLinuxディストリビューションについてざっと特徴をまとめてみたいと思います。
注意してもらいたいことは、Linuxディストリビューションとデスクトップ環境(GNOMEやKDE)というのは別物であり、それぞれのディストリビューションで対応していたりしていなかったりするのでご注意ください。
2021/04/06 追記: 最近アクセス数が増えておりますが、内容はあくまで個人的な意見ということをご留意ください。

2017/09/30:編集
2018/09/07:編集
2020/01/31:編集
2020/10/25:編集
2021/04/06:編集

Debian系

Debian

これはUbutnuを使用する前後とラズベリーパイ(Raspbian)で使っていたのですが、GNUの精神の完全自由を目指すコミュニティベースのディストロです。

昔からあるディストリビューションですので、情報はかなり豊富です。パッケージリポジトリにあるパッケージ(aptなどでインストールできるソフトウェア)も豊富でわざわざビルドしなくて良いことが多いです。
ただし、自由の定義が他のディストロより厳格でオープンソースであってもDebian Projectが自由でないと判断したソフトはNon-Freeリポジトリに取り入れられる、名称が変わるなどされるため、混乱することがあるかもしれません。(FirefoxとIceweaselの関係がよく知られた例です。)デスクトップ環境環境はGNOME3やKDE Plasma Desktopなどおおよそのものは揃っています。
安定版のパッケージリポジトリから降ってくるソフトウェアのバージョンは古いです。バージョンが古いのが気になる方は、安定版ではなくtesting(次期リリース予定)か、”sid”と呼ばれる不安定版を利用されると良いと思います。この場合はバグに遭遇する可能性はありますが、testingはそんなに困る状況に陥ることは少ないと思います。パッケージメンテナーの方には日本人も多くいらっしゃり、OSCなどでお会いして話すこともあるため、そこで情報交換ができるかもしれません。

Ubuntu

Debianから派生した大規模なディストロです。Linux入門としてWindowsからの乗り換え先として有名で、以前はWindowsのサポートが切れると「Ubuntuへの乗り換える方法」なんて本が出ていました。

Unityというちょっと(名前と動作が)ややこしいUIを持っています。Windowsから来た人がこのUIに慣れるかでその後のLinux人生が決まる…かもしれませんGNOME3に移行しました。

ネットを漁れば情報は山ほど出てきます。初心者に優しく、GUIツールが充実しているのでCUIを知らなくてもなんとかなると思います。
パッケージのバージョンはそこそこ古いですが、Debianのsidと同じバージョンのパッケージが多いです。Debian同様安定性重視だと思われるので、問題が発生するのを極力避けたい場合には最適です。すぐに新しいパッケージが使いたい場合は開発版を使うか他のディストリビューションを利用しましょう。
デスクトップ環境はGNOME3以外にもLXDEやKDE Plasma Desktop、MATEなどをサポートしたフレーバーが存在しており、Kubuntuなどの名前がついています。
初心者の方にはおおよそおすすめできますが、チューニングが必要なPC・環境などでは別のディストリビューションを利用したほうが良いでしょう。

Linux Mint

Ubuntuに不満がある人が駆け込んでるイメージがあるディストリビューションです(個人の感想です)。上記2つのディストロより、非オープンソースに対して寛大で一部の音声コーディックなどが標準で入っています。標準デスクトップ環境のCinnamonは、GNOME3と違ってWindowsライクなUIで安心する人が多いようです。日本語環境も昔よりかなり充実しており、初心者の方も戸惑うことなく安心して使えると思います。GUIツールも豊富で大抵の設定はできると思います。おおよその知識はUbuntuやDebianの技術を流用できると思いますし、Linux Mint Japanのフォーラムもあるので、そこで質問しながら勉強していくと良いと思います。

Zorin OS

WindowsとそっくりのUIとMacとそっくりのUIを持つディストリビューションです。爽やかなUIが好感が持てます。ただし、有料版があるので、無料版に若干の機能制限がかかってます。日本語対応はバージョン9でかなり良くなりました。隠れたLinux入門の道と言うところでしょうか。
最近は使ってないのでもしかしたら変わっているかもしれませんが、Wine(Windowsアプリケーションを動かすためのライブラリ群)の調整がうまく行っており、3Dゲームも一発で動くことが多いです。

Red Hat系

CentOS

CentOS 8まではRHEL(有料)のソースコードから商標や商用パッケージが削除されたものを使用しているディストリビューションです(クローンではなく、CentOS独自の変更も存在します)。サーバー用途で使用されることが多く感じますがデスクトップ環境も利用できます。Linuxでサーバを構築したい場合には、サーバ関係の情報がネットに落ちているこのディストリビューションがやりやすいのではないでしょうか。
前述のディストリビューション同様安定重視ですが、バージョンはそれほど古くなく各アプリケーションのメインバージョンに近いものが降ってきます。
個人的な感想ではデスクトップ利用者はこちらより後述のFedoraの方が人気である感じがします。一方で大学などのラボではこれが使われている所を見ます。
2020年に方針転換があり、CentOSはCentOS StreamとなりFedoraとRHELの中間に位置する扱いになりました。サーバ用途では長期間の安定性を求める場合は別のディストリビューションに移行する必要があるかもしれません。

Fedora

かつて存在していたRed Hat Linux(現在はRHEL)の後継のような存在です。Red Hat社の支援を受けたコミュニティが開発しており、かなり活発に開発されています。パッケージ管理システムもDebian系列のdpkgと違いRPMを用いています。個人的にはUbuntuとFedoraがLinuxディストリビューションの双璧を成している感じがします。6ヶ月ごとにメジャーアップデートが行われ、パッケージのバージョンも新しいのが多いです。ここで変更によって得られた改善などがRHELに還元されることもあります。仕様変更も発生し、最近ではパッケージマネージャーがyumからDNFに変更されました。昔使っていたときより安定性が高くなってると聞きますが最近使ってないのでわかりません。昔自分が使っていた時、パッケージ依存関係がぶっ壊れたり起動しなくなったりと苦い思い出があるディストリビューションですが、パッケージマネージャも置き換えられましたし、ユーザも増えたので現在はかなり良くなっていると思います。通常のリリースに対してRawhideという不安定版があります。ただしこれは名前の通り結構な不安定で仮想マシンで動かしていたときは不具合が発生することもありました。Linuxの最新が知りたいという人には良いディストロだと思いますし、ある程度の知識が身についた後に長く愛用するディストリビューションとしては最適かもしれません。

Vine Linux

国産のディストリビューションであり、これが好感されることも多いです。フォーラムでも気軽に日本語で質問できますし、日本語環境はしっかり整っています。ただし、すべてのエラーメッセージが日本語であることではなく、もちろん英語のエラーメッセージも存在します。パッケージマネージャはAPT-RPMというdpkgのフロントエンドとして用いられるAPTをFedoraで使われているRPMに移植したものを使っています。開発はかなりゆっくりでメジャーアップデートも多くないのですが、最近は特に開発が停滞しているような気がします。Firefoxのバージョンも古いのでセキュリティ的な不安は残ります。
かつては人に勧めていましたが、今は勧めづらいです。

Berry Linux

軽量なディストリビューションです。描画が綺麗で、起動時の猫の写真はかわいく映ります。あまりつかったことがないので詳しい事はわかりませんが、シンプルで良さそうです。

Slackware系

openSUSE

現在サーバで使用しているディストリビューションです。
公式リポから降ってくるソフトは少し古いですがDebianほどではありません。
日本ではシェアが少ないですが、海外では多いため有名なソフトではopenSUSEでのインストール方法が書かれています。SlackやChromeなどのrpmパッケージはおおよそ流用できますが、Fedoraなどとパッケージ命名が違うこともあるので依存パッケージが多いと依存関係を手動で解決する必要があります。
Arch Linuxと同じくローリングリリースのTumbleweedがあり、最新のパッケージが落ちてきます。最新と言っても不安定版が降ってくることはあまりないのでおおよそ安定して使うことができます。デスクトップ環境は初期インストールではGNOME3かKDE Plasma Desktopの二択ですが、あとで他のデスクトップ環境に切り替えられます。デスクトップではTumbleweedで新しいパッケージを、サーバーは安定したLeapを使い、openSUSEで固めることでディストロ間の設定の仕方の違いで悩むことをなくすこともできます。

Yastがコントロールパネルのように強くまとまっています。
弱点としては上記の通り日本でのシェアが低いため、情報が見つけにくい、Slackwareの血を引いているので設定が若干難しい、などが挙げられます。
時期によってはパッケージの更新が滞っていたり、削除される(nginx-modulesとandorid-toolsが消えたのは痛かったです)などがあり、自分でビルドすることも多々あります。特にVLCの公式ページに置かれているopenSUSE用のパッケージが長らく更新されない時期がありました。
日本のコミュニティもありOSCなどにも参加されているので連携しながら対応していくのも手かと思います。
2021/04現在このサーバーはopenSUSEで動いてますが、特に問題はないです。

Puppy Linux(Ver.5以前)

起動時にワンワンと大音量でなってびっくりした思い出があるディストリビューションです。バージョン5以降はいろいろあってubuntu系になったそうですが、使っていないのでここでは述べません。
メモリ128MBでも動く軽さです。XP時代のPCでもなんなく動かすことができ、CDから起動するときは起動時にファイルをRAMに転送するようなので起動後にCDを取り出しても作業を続行することができます。ただし、最新のデバイスには対応していないので、そのような状況下では正常動作しません。自分はデータ救出用として重宝しています。

独立系

Arch Linux

Linuxについて調べていると必ず遭遇するArch Wikiでよく知られているディストリビューションです。組み立て型OSというべきディストリビューションでデベロッパーフレンドリーを掲げています。インストールはGUIインストーラが存在せず、CUI環境下で行います。インストール作業はそこまで難解ではなくむしろ自由で、インストール時に特殊なドライバーを入れたい場合はとても柔軟に行えます。ローリングリリースモデルを採用しているのでメジャーアップデートがありません。GUIだけで全てが完結することはまずないと思いますので、コマンドあたりの知識は必須となります。公式リポジトリも最新版のパッケージが利用されています。一週間使用しないと1000件のアップデートが溜まっていることもしばしばあります。

デスクトップ環境もおおよそのものは用意されています。Xorg環境の整備に若干戸惑うことはあるかもしれませんが、良い勉強になるでしょう。AURという非公式のパッケージ群もありそこからChromeなども手に入ります。(余談ですが、知識が古いのでyaourtがなくなってyayが主流になっていたことを知らなかったです、と書いていたらyayも古くなったとか…)

デベロッパーフレンドリーを掲げているために細かい微調整が必要になる部分があるため、環境構築にはじっくり時間をかける必要がありますが、自分好みに調整しやすいでしょう。

Solus Linux

よくSolarisと聞き間違えられますが、「ソーラス」です。デスクトップ環境専用ディストリビューションとして開発されており、かなりすっきりにまとまっています。重複したパッケージを導入しないという目標を掲げているようですが、様々な選択肢があるのがLinuxエコシステムの特徴とも言えるので、これに関しては微妙な気持ちになっています。
日本で常用している人はほとんど見かけません(自分は現在メインで使っていますが)。パッケージ管理システムはeopkgという独自のシステムですが、GUIのパッケージマネージャーを使うことでChromeやSlackは導入できます。落ちてくるパッケージは概ね最新版で不満はない程度ですがopenSSLなど一部古いものもあります。Budgieというデスクトップ環境を開発しており、モダンなデザインで扱いやすいものになっています(Budgie自体は他のディストリビューションでも利用できます)。

ibus-mozcが公式リポジトリに存在しないため、スクリプトを書いてパッケージリクエストを行いましたが途中で返信がなくなっているためリポジトリには取り込まれていません。そのため日本語環境は整っているとは言い難いです。しかしながらGNOME3やKDE Plasma Desktop自体は日本語化は十分なされているので普段使いで困ることはないと思います。
追記(2020/10/25):mozcが追加されました。

(Pear OS)

今はなきディストリビューションで林檎じゃなくて梨です。要は林檎のそっくりデザインさんでして林檎社に食われて死んだという噂が流れています。軽くて快適でしたがアプリが案外少なかったと思います。ネットに情報が少なくどこの部類に入るか悩みますが、リポジトリがUbutnuのものだった気がするのでDebian系であると思います。消滅したディストリビューションですのでここでは一番下においてます。今でもインストールDVDは持っているのですが、UI部分のアップデートがなく不具合が起きそうなので使用しておりません。似たようなデザインのディストリビューションとして、elementary OSがあるので、使ってみてはいかがでしょうか。自分はelementary OSを使ったことがないのでここでは紹介しません。

まとめ

初心者におすすめしたいと思うのはLinux Mint、Ubuntuあたりです。
多くのOSでは安定志向が強いので古いパッケージが降ってきますが最初のうちはこだわらない限り気にならないでしょう。
紹介の中でパッケージが古いことを挙げていますが、安定して動くことを目的としており逆に最新パッケージを利用しているディストリビューションでは再起動すると起動してこなくなった…なんてこともありますので、利用者の方針に沿って利用してください。
なお、この記事は個人の意見なのであくまで参考程度で利用してください。
ほとんどのディストリビューションは無料ですので、慣れてきたら別のディストリビューションに挑戦するのも良いでしょう。

新サーバーとIPv6/IPv4ネットワーク構成とNginx暴走

この度引越しに伴い、実家で「引越し先で光回線使えるならこのクソデカサーバーとUPS持っていけ」と言われたのでサーバー機は高校同期に譲渡し(ついでにLinux沼に沈めて)、新しく作ることにしました。
念願のRyzenを使い、更にベアボーンキットを使って小型化しつつ、NVMeなSSD2つ使ってRAID1で組みました。
実家はBBIQだったのでIPv4環境だったわけですが、こちらではフレッツ光パワーでIPv6環境が手に入りました。
さてIPv6な環境が手に入った瞬間、「IPv4なんて知らねーウォォォォ」とIPv6以外でアクセスできないサーバを構築しそうになったわけですが、残念ながらそういうわけには行かないわけです。
別にIPv6でもPPPoEで通信すればグローバルなIPv6/IPv4アドレスが降ってくるわけですが、新しい物好きでネットは速いほうがいいのでIPoEで通信したくてIPv4 over IPv6してほしいわけです。この方式だとIPv6は半固定なグローバルIPアドレスが降ってくるのですが、IPv4の方はIPアドレスが共有されてしまい、ウェルノウンポートが使えなくなります。
CDNや中間サーバを挟んでポートフォワーディングしてやる方法も考えたのですが、スイッチングハブを間に挟みルータの外にサーバーを出してやることで解決しました。
この構成にするとサーバーでPPPoEを張らなければならず、rp-pppoeを導入しました。
pppoe-setupコマンドを叩いてIDとパスワードを入力し、ファイアーウォールはNONEを選択します。(後でfirewalldで設定します)
設定が終わると、/etc/sysconfig/network/ifcfg-ppp0 ができているはずなのでこれに
LINUX_PLUGIN=/usr/lib64/pppd/2.4.7/rp-pppoe.so
を追記します。
設定が終わった後に
sudo systemctl start pppoe
とでもしてあげれば接続が開始されppp0ができています。ファイアーウォールで適宜ポート開放して動くことが確認できたらenableで起動時に自動接続するようにします。
NginxやDovecotでppp0をlistenするようにしてDDNSにはIPv6アドレスとPPPで得られたIPv4アドレスを通知するようにします。

さて、このように設定してついでにマストドンのサーバも移行し「夜も遅いし、サーバー移動も終わったし、寝るかぁ」と最後にRyzenパワーを見ようとhtopを開いていくつかのページを閲覧したところ…

htopコマンドの様子

これを見て眠れるわけがない、顔面真っ青案件が広がってました。
最初はマストドンのSidekiqあたりが再試行で暴走しているのかと思って確認画面を見たが特に暴走している様子もなく、nginxが常に上に張り付きだし、そうこうしてたらswapperが出てきて忙しく動き出したので慌ててNginxを止めることになりました…。
NAXSIとマストドンの組み合わせが怪しいと考え、拡張機能をすべて切り離し、再現する条件を探していって結局、NAXSIではなくBrotliプラグインとproxy_passの組み合わせで再現することが判明しました。BrotliをOFFにして無事平穏を取り戻しました…
原因解明と行きたいですが、疲れたのでそれはまたの機会にします。

というわけでRyzen3 PROなCPUを搭載した新サーバーとIPv6環境でもアクセスできる新環境になりました。

ゼロからのOS自作入門の献本を頂いた

最近引っ越しましたPG_MANAです、家賃は25kです。
さて、その引っ越し作業中に人生初の献本をいただきました。
その名も「ゼロからのOS自作入門」です。

入れ替わりが激しいIT書の本棚に長らく重鎮として並んでいた「30日でできる! OS自作入門」の流れを汲んだ最新のPCで動くOSの自作本です。
というのも、「30日でできる! OS自作入門」(以後、30日OS自作本と呼ばせていただきます)の内容は古く、PS/2 マウス・キーボードや32bitプロテクトモードなど現在では使われてない機器や機能について取り扱っています。
これらの中にはすでに最新のPCでは搭載されてない機能も存在しており、本の内容を実機で試そうにも動かないなんてこともあります。それでも長く本棚に並んでいたのはプログラミング初心者にも分かりやすく、OSのことだけでなくプログラミングのエッセイも学べる大変優れた良書であるからだと思いますが、続編が出ていないのもまた事実でした。「作って理解するOS」という、これまた良書も出たのですが、やはりレガシーな内容を多く含んでいます。
話を戻すと「ゼロからのOS自作入門」の献本をいただいたのですが、実はこの本のテスト読者として参加しておりました。思い出せば一昨年の話ですが、著者のuchanさんからテスト読者としてのお誘いをいただきました。「OS自作本のテスト読者」というワードで二つ返事で了承したわけですが、その時に「献本がほしい」と言ったことを覚えています。そんな待ち望んだ献本を頂いたのですが、引っ越し作業の忙しさや本をもらった直後(約10分後)の祖父との最後のビデオ通話をして、その夜の訃報の哀愁感などで部屋の片隅でプチプチシートにくるまれて放置されており3日経ってやっとまじまじと見たところです。

ゼロからのOS自作入門の献本


表紙は30日OS自作本を想起させる緑色をベースとし、MikanOS(「ミカノス」と呼ぶようです)の「未完」と「蜜柑」をかけ合わせたことがわかる蜜柑の断面がデザインされております。
ちなみに、果汁飲料のパッケージでは果汁100%でないと果物の断面のイメージを使用してはいけないと公正取引委員会の規約で定められています。このことを踏まえるとこの表紙から「この本はMikan100%だよ!!つまり未完成率100%、本を読んでOSを作り上げ、完成させるのは君自身だ!!」という著者の熱い思いが読み取れますね(と深読みをしたつもりで得意げになってるPG_MANAであった。未完成率ってなんだろう)。
さらに「Intel 64モード」「UEFI BIOS起動」「USB3.0 ドライバ」などという現代の規格を表すワードが並んでいます。これはワクワクです。背表紙を見ると30日OS自作本の著者である川合さんからのメッセージや東工大教授である権藤先生のコメント、ツイッターg…吟遊詩人のヴァネロピさんのコメントがあり、どれも素晴らしいコメントです。

内容面では、この本はC++を使って記述されています。そのためクラスやnew演算子といったC言語にない用語が出てきます。C++をガチガチに理解していないとこの本を読み進めることはできない、ということでは決してありませんが完全の無知では少々厳しいかなと思います。C言語など手続き型言語しか触ったことがない、またはプログラミング初心者の方はC++の入門書やサイトで予習したり必要に応じて参照したりすれば良いでしょう。Javaなどの他言語でオブジェクト指向をなんとなくでも触った方は「C++ではこう書くのか」程度で対応できると思います。いずれにせよ、できる限りのフォローアップはされていますのでそれを読んで理解できないようでしたら適宜検索するか「そいうものもあるのか」と読み飛ばしても構わないと思います。
大変分厚い本ですが、1章から丁寧にステップアップ方式で書かれています。ここは30日OS自作本と同じで、これまたこの本も30日で読めるようになっています。とはいえ、30日で読み切る必要もなく自分のペースで読んで構わないと思います。
最初の数日はEDK2(UEFIの開発環境です)を用いてブートローダを作成しています。この部分はC言語で作成されています。ここではOS本体のELFのヘッダーを解析してメモリ上に展開しております。30日OS自作本ではフロッピーディスクのセクタ数を指定して読み込んでいたのでOSを改造して大きなバイナリを内蔵すると不具合が出てましたが、そのような心配はもうありません。
そこからはフレームバッファに縞模様を書き込んだりmakeを導入したりと30日OS自作本を読んだことがある方は懐かしく感じるような内容を実践していきます。
この後も30日OS自作本の流れを大まかに汲んだ進行となっています。後半になるとページングを用いたデマンドページングやコピーオンライトなど30日OS自作本にはない内容が出てきます。このような内容の濃い本となっておりますが、ステップアップで書かれているので一つ一つ丁寧に理解できると思います。

テスト読者として執筆途中の原稿を読んだとき、「もう完璧じゃん…」と思ったのを覚えております。しかし30日OS自作本を読んだときの記憶を思い出し、できる限りのレビューをさせていただきました。思い出せば、30日OS自作本を読んでいたときはちょうどLinuxインストールなどにも手を出したところでBIOSの設定をいじり起動しなくなったことがあり、OS自作ではなるべくQEMUを使うようになったなどの覚えがあります。OS自作初心者の段階で自作OS用PCの購入はやはり躊躇するでしょうし、普段使いのPCのBIOSの設定は慎重に行うように促したほうが良いのではないか、という意見をさせていただいたところ恐縮にも原稿に反映してくださりました。すごく嬉しい。さらに謝辞にも載せていただきました、本当に光栄です。

長々と書きましたが、要約するとすっごい良書です。一ヶ月も読める本が4kで買えます。家賃5日分で30日分の本が買えます、これはお買い得です。
ぜひお手にとっていただければと思います。

Solus Linuxでカーネルのアップデートが適用されない

Solus Linuxを使っていてある時からカーネルのアップデートが適用されなくなり、VirtualBoxなどが動作しなくなり困ったことになりました。
一回修正しようと適当に/boot以下をいじったら無事起動しなくなりLiveUSBのお世話になったので、今回はよく調べてから対応することに。

Solus LinuxではブートシステムとしてClearLinuxのclr-boot-managerというのを使っており、UEFI環境下ではバックエンドとしてsystemd-bootを利用しているらしいです。
というわけでまずはsystemd-bootの状態を確認するために
bootctl statusを実行すると

$ bootctl status
System:
     Firmware: UEFI 2.50 (American Megatrends 5.12)
  Secure Boot: disabled
   Setup Mode: user
 Boot into FW: supported

Current Boot Loader:
      Product: systemd-boot 246
     Features: ✓ Boot counting
               ✓ Menu timeout control
               ✓ One-shot menu timeout control
               ✓ Default entry control
               ✓ One-shot entry control
               ✓ Support for XBOOTLDR partition
               ✓ Support for passing random seed to OS
               ✓ Boot loader sets ESP partition information
          ESP: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/SYSTEMD/SYSTEMD-BOOTX64.EFI

Random Seed:
 Passed to OS: yes
 System Token: set
       Exists: yes

Available Boot Loaders on ESP:
          ESP: /boot/efi (/dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b)
         File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 246)
         File: └─/EFI/BOOT/BOOTX64.EFI (systemd-boot 246)

Boot Loaders Listed in EFI Variables:
        Title: Linux Boot Manager
           ID: 0x0002
       Status: active, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/systemd/systemd-bootx64.efi

        Title: Linux Boot Manager
           ID: 0x0000
       Status: active, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/SYSTEMD/SYSTEMD-BOOTX64.EFI

        Title: UEFI OS
           ID: 0x0001
       Status: inactive, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/BOOT/BOOTX64.EFI

Boot Loader Entries:
        $BOOT: /boot/efi (/dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b)

Default Boot Loader Entry:
        title: Solus 4.1 Fortitude
           id: Solus-current-5.10.9-169.conf
       source: /boot/efi/loader/entries/Solus-current-5.10.9-169.conf
        linux: /EFI/com.solus-project/kernel-com.solus-project.current.5.10.9-169
       initrd: /EFI/com.solus-project/initrd-com.solus-project.current.5.10.9-169
      options: root=PARTUUID=782cda64-d615-4b92-b362-7580b073ca62 quiet loglevel=3 ...

これを見るとESP(UEFIの起動パーティション)は/boot/efiになっており、「Default Boot Loader Entry」はLinux 5.10.9-169が選択されています。
続いて、clr-boot-managerの様子を見てみます。

$ sudo clr-boot-manager list-kernels
* com.solus-project.current.5.10.12-171
  com.solus-project.current.5.10.9-169

Linux 5.10.12-171が既定のカーネルとして設定されています。
どうやら、clr-boot-managerとsystem-bootの連携がうまくいってないみたいです。
次に/boot以下を見ると、efiディレクトリとloaderディレクトリがあり、efiディレクトリの中にはこれまたloaderディレクトリがあり、中の構成もそっくりです。もしやと思い/boot/loaderを削除してclr-boot-manager updateを叩いてみると

$ sudo rm -rf /boot/loader/
$ sudo clr-boot-manager update
[FATAL] cbm (../src/bootloaders/systemd-class.c:L290): Failed to create loader entry for: ///usr/lib/kernel/com.solus-project.current.5.10.9-169 [No such file or directory]
[ERROR] cbm (../src/bootman/update.c:L218): Failed to repair running kernel
[FATAL] cbm (../src/bootloaders/systemd-class.c:L290): Failed to create loader entry for: ///usr/lib/kernel/com.solus-project.current.5.10.12-171 [No such file or directory]
[FATAL] cbm (../src/bootman/update.c:L250): Failed to install default-current kernel: ///usr/lib/kernel/com.solus-project.current.5.10.12-17

どうやらclr-boot-managerはESPの場所を確認せず/bootに書き込んでいるようで、結果実際に使用されている/boot/efi/loader/loader.confが更新されず、昔のカーネルが起動されるみたいです。

特に/boot/efiにこだわってはいないのでESPの位置を/bootに変更することにしました。
一旦/boot/efiをumountし、/etc/fstabを編集して/boot/efiを/bootに変更します。

$ sudo umount /boot/efi
$ sudo rm -rf /boot/loader/ # すでに実行した場合は不要
$ sudo vi /etc/fstab # /boot/efiを/boot/に変更
$ sudo mount /boot

これでbootctl installとclr-boot-manager updateを叩くと

$ sudo bootctl install
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/systemd/systemd-bootx64.efi".
Copied "/usr/lib/systemd/boot/efi/systemd-bootx64.efi" to "/boot/EFI/BOOT/BOOTX64.EFI".
Random seed file /boot/loader/random-seed successfully written (512 bytes).
Created EFI boot entry "Linux Boot Manager".
$ sudo clr-boot-manager update
[FATAL] cbm (../src/bootman/kernel.c:L646): Failed to install kernel /boot/EFI/com.solus-project/kernel-com.solus-project.current.5.10.9-169: No such file or directory
[ERROR] cbm (../src/bootman/update.c:L218): Failed to repair running kernel
[FATAL] cbm (../src/bootman/kernel.c:L646): Failed to install kernel /boot/EFI/com.solus-project/kernel-com.solus-project.current.5.10.12-171: No such file or directory
[FATAL] cbm (../src/bootman/update.c:L250): Failed to install default-current kernel: ///usr/lib/kernel/com.solus-project.current.5.10.12-171
[ERROR] cbm (../src/bootman/bootman.c:L942): Error opening /boot//EFI/com.solus-project: No such file or directory
[ERROR] cbm (../src/bootman/update.c:L371): Failed to remove old freestanding initrd

とエラーが出てるので、とりあえず

$ sudo mkdir /boot/EFI/com.solus-project
$ sudo clr-boot-manager update

でうまくいったので良しとします…
再度bootctl statusを見ると

$ bootctl status
System:
     Firmware: UEFI 2.50 (American Megatrends 5.12)
  Secure Boot: disabled
   Setup Mode: user
 Boot into FW: supported

Current Boot Loader:
      Product: systemd-boot 246
     Features: ✓ Boot counting
               ✓ Menu timeout control
               ✓ One-shot menu timeout control
               ✓ Default entry control
               ✓ One-shot entry control
               ✓ Support for XBOOTLDR partition
               ✓ Support for passing random seed to OS
               ✓ Boot loader sets ESP partition information
          ESP: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/SYSTEMD/SYSTEMD-BOOTX64.EFI

Random Seed:
 Passed to OS: yes
 System Token: set
       Exists: yes

Available Boot Loaders on ESP:
          ESP: /boot (/dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b)
         File: └─/EFI/systemd/systemd-bootx64.efi (systemd-boot 246)
         File: └─/EFI/BOOT/BOOTX64.EFI (systemd-boot 246)

Boot Loaders Listed in EFI Variables:
        Title: Linux Boot Manager
           ID: 0x0002
       Status: active, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/systemd/systemd-bootx64.efi

        Title: Linux Boot Manager
           ID: 0x0000
       Status: active, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/SYSTEMD/SYSTEMD-BOOTX64.EFI

        Title: UEFI OS
           ID: 0x0001
       Status: inactive, boot-order
    Partition: /dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b
         File: └─/EFI/BOOT/BOOTX64.EFI

Boot Loader Entries:
        $BOOT: /boot (/dev/disk/by-partuuid/1026dfb7-66be-40ae-96d0-7de4a03c1d0b)

Default Boot Loader Entry:
        title: Solus 4.1 Fortitude (Solus-current-5.10.12-171.conf)
           id: Solus-current-5.10.12-171.conf
       source: /boot/loader/entries/Solus-current-5.10.12-171.conf
        linux: /EFI/com.solus-project/kernel-com.solus-project.current.5.10.12-171
       initrd: /EFI/com.solus-project/initrd-com.solus-project.current.5.10.12-171
      options: root=PARTUUID=782cda64-d615-4b92-b362-7580b073ca62 quiet loglevel=3 ...

無事更新されていているみたいなので完了です。

あけましておめでとうございます。

(今年は雲が多くてうまく見れなかった)初日の出を見て、家で過ごしてるところです。
去年は実家に閉じこもっており、夏場に鉄道旅行をした以外は何もしてないですね…
プログラムの進捗にも波があってOSでのマルチコアのサポートなどをしてました。
今年もよろしくお願いします。

セキュリティ・ネクストキャンプ2020参加記(チューター)

ドッタンバッタン大騒ぎして結局今までろくにまとめられなかったセキュリティキャンプの参加記。
感想も交えて詳しく書けたらいいなと思っています。

セキュリティキャンプ2017参加記

こんなことを書いて早3年、今度はセキュリティ・ネクストキャンプ2020にチューターとして参加させていただくことができました。というわけで前回と同じようにまとめたいと思います。
セキュリティキャンプの様子を知りたい方は以下のリンクをご覧ください。
セキュリティキャンプ2017参加記
セキュリティキャンプ2019参加記(チューター)
セキュリティキャンプ – カテゴリーアーカイブ

今年のセキュリティ・キャンプとセキュリティ・ネクストキャンプは史上初のオンライン開催となりました。
そのため、時系列でまとめるのは難しいですが、全体的な感想をまとめておきます。

全体的な進行形式

開会式と閉会式はYoutube Liveで行われ、講義自体はGoogle Meetで行われました。ブラウザから参加できるので、特別な設定無しで良かったです。(特にZoomは不調続きで大学で使用していてイライラしており使ってほしくなかったので、Meetを使ってもらえて良かったです。)
MeetのアドレスはKintoneで管理されており、講義ごとのスレッドが立っていてその講義に関する質問もできるようになっています。

良かった点

  • オンライン開催ながら講義の質はとても高く、チュータの自分でも多くのことを学べた(それでいいのかチューターよ)
  • Kintoneでの事前準備の情報確認はよく行われており、理解しやすかったと思う
  • オンライン開催だから、どこでも参加できた(普段は実家のある福岡で参加して、一回だけ旅行先の金沢の東横インから参加しました)
  • 録画が公開されており、わからないところは振り返ってみることができたのは助かりました。ただ、自分が担当していた講義以外は権限の調整が上手く行っていないところもあるらしくそこら辺は要改善かなと思います

反省点

  • 今回のチュータ業務は自分の専門外の分野も含んでいたため、事前準備をしてもサポートしにくい分野もあって力不足を感じた…
  • カメラをオンにするとCPU使用率が上がってファンがうるさくなるためつけられなかったが、できればオンにして参加したかった
  • オンラインコミュニケーションはやはり難しく、受講者同士の会話が少ないように思えました(モブプログラミングの回ではかなり活発になっていましたが)
  • オンラインで話すと割り込まれることが少ないのでついつい喋りすぎたと反省…

まとめ

オンラインで行っても質の高い講義があったのはさすがセキュリティキャンプだなと思いました。
ただ、例年の美味しいご飯と朝から晩までとことん打ち込める環境はセキュリティ・キャンプの醍醐味かなと思います。いつかまた現地で開催できるといいなと願っています。

自作OSとマルチコア

この記事は自作OS Advent Calendar 2020の9日目の記事です。

最近は大学の課題や様々な用事、精神的事情が重なってなかなかプログラムがかけてませんがアドベントカレンダーの枠が空いていたので短いですが急遽この記事を書いてみました。

最近、私の自作OS – Methylenixでマルチコア(SMP)対応を行いました。
https://github.com/PG-MANA/Methylenix

このOSはx86_64用に開発されているわけですが、x86_64では通常起動直後はBSP(BootStrap Processor)のみが動作しておりそれ以外のコアは停止しています。そのためにBSPがAP(Application Processor)を適切に初期化してあげる必要があり、これにはUEFIのMpServiceというものを使用するか(よく知らないので詳細はここでは説明できない)、LocalAPICのプロセッサ間通信を利用します。
Methylenixでは後者を選択しており、
https://github.com/PG-MANA/Methylenix/blob/11239235352ac46a051999edd47ae0590ba5f6c7/src/arch/x86_64/init.rs#L269で実装されています。
この方式ではAPが16bitリアルモードで立ち上がってきてしまうので、メモリアドレスが1MB以下の場所のメモリの確保やブートコードの転送などいろいろなことをしています…(ここらへんの設計が地味に辛かった…)

一方RISC-VなどのRISC系のCPUは起動と同時に全てのコアが一斉にエントリポイントを実行することが多く、この場合はコアIDを見ながらセマフォなどの排他処理を最初から行わないとスタックの競合やUARTの出力がダブるなど(HeHello,o, wwoorrldld!!となるなど)するので注意が必要です。RISC-VボードのSDKなどでif(core_id == 0){…}などあるのはこのためです。

x86_64でのマルチコア対応などをまとめてしっかりとした文章にしたいな…