systemctl disable sshd.service したら sshd を起動できなくなった話

Linux の勉強を体系的にしようと思って, LPIC の勉強をしてます。
systemctl の勉強中, 実際にいくつかコマンドを実行して結果を見ようと思い, systemctl disable sshd.service を実行したら, sshd.service の Unit ファイルが消えて操作できなくなりました

※このツイート時点では検証できてないので, ツイート中には不正確な推測が入ってます

正しく復旧するには systemd Unit ファイルの知識が必要で, 原因がちょっとコーナーケースじみていておもしろかったので記録しておきます。

なお, 動作環境は VirtualBox 上の Ubuntu 18.04.4 LTS です。

kangetsu@ubuntu18:~$ uname -a
Linux ubuntu18 4.15.0-99-generic #100-Ubuntu SMP Wed Apr 22 20:32:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
kangetsu@ubuntu18:~$
  • systemctl disable と enable の機能
  • systemctl disable で何が起きたか
    • systemctl enable で元に戻そうとしたら戻らない
  • オリジナルの Unit ファイルを確認する
  • 真相: Unit ファイルの Alias
  • 結論
  • おまけ
続きを読む

ShellScript Tips --関数--

 ShellScript の基本についての記事を, 備忘を兼ねて最近まとめています。

今回の記事では, 関数 について書きます。
なお, これまで明記してませんでしたが, 基本的には bash を前提 として書いています。

  • ShellScript の関数
    • 基本: 関数定義と実行
    • 引数
    • 変数のスコープ:local によるローカル変数宣言
    • 戻り値
  • まとめ
  • おまけ:bash における関数定義の仕様
続きを読む

ShellScript Tips --条件分岐--

 前回の記事 では, ShellScript の変数についての基本をいくつか書きました。今回の記事では, 条件分岐 について書きます。

新しいLinuxの教科書

新しいLinuxの教科書

新しいシェルプログラミングの教科書

新しいシェルプログラミングの教科書

ShellScript の条件分岐

基本: if 句と test コマンド

 プログラミング言語によって条件分岐の記法は微妙に異なりますが, ShellScript の条件分岐では, if, then, else, fi を使います。
また, 条件文を判定するために, test コマンド, または [ ] コマンドなどを使います。

#!/bin/bash

if [ ${#} -eq 0 ]
then
    echo "the number of argument is 0"
else
    echo "the number of argument is more than 0"
    if [ "${1}" = "hoge" ]
    then
        echo "test starts"
    fi
fi

サンプルスクリプト中の ${#} は引数の数を表す特殊変数, ${1} は 1番目の引数を表す特殊変数です。
また, サンプルでは if を入れ子にしています。

上のサンプルのように, if に続ける条件式は, ShellScript では test コマンド ([) を使って評価します。
test コマンドは, 引数として渡した EXPRESSION の評価をするコマンドです。
利用可能な比較演算子には種類が多くありますが, サンプルでは左辺と右辺の数値が等しいかを評価する -eq, 左辺と右辺の文字列が等しいかを評価する = を使っています*1
なお, test コマンドは [ とも表すことができ, [ を使った場合は条件式の終わりを示す目的で ] も必要になります
この表記は以下の通り man にも書いてあります。

TEST(1)                                                             User Commands                                                            TEST(1)

NAME
       test - check file types and compare values

SYNOPSIS
       test EXPRESSION
       test

       [ EXPRESSION ]
       [ ]
       [ OPTION
[...]

test[ が同じものを指すことは, 以下のようにそれぞれの mandiff がないことからもわかります。

[kangetsu@ubuntu16 ~ Tue Mar 31 15:50:25]
$ diff <(man test) <(man [)
[kangetsu@ubuntu16 ~ Tue Mar 31 15:50:28]

[ はいわゆる括弧などの記号ではなく立派なコマンド なので, スペースを空けて引数を渡してやる必要があります。
よくあるミスとして, [ を単なる記号と勘違いして if [a = b] などのように書くと, 以下のようにエラーになります

[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:26]
$ a=1
[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:29]
$ b=2
[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:30]
$ [a = b]
[a: command not found  <- スペースが空いていないので `[a` で 1つのコマンドと解釈されている
[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:34]
$ [ a = b ]
[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:38]
$ echo $?
1  <- 1 != 2 なのでちゃんと評価されて exit code 1 が返っている
[kangetsu@ubuntu16 ~ Tue Mar 31 16:08:43]

少し脱線しましたが, 以下が冒頭のサンプルの実行結果です。

#!/bin/bash

if [ ${#} -eq 0 ]
then
    echo "the number of argument is 0"
else
    echo "the number of argument is more than 0"
    if [ "${1}" = "hoge" ]
    then
        echo "test starts"
    fi
fi
[kangetsu@ubuntu16 shell_tips Tue Mar 31 16:21:37]
$ bash if_test.sh
the number of argument is 0
[kangetsu@ubuntu16 shell_tips Tue Mar 31 16:22:07]
$ bash if_test.sh aaa
the number of argument is more than 0
[kangetsu@ubuntu16 shell_tips Tue Mar 31 16:22:15]
$ bash if_test.sh hoge
the number of argument is more than 0
test starts
[kangetsu@ubuntu16 shell_tips Tue Mar 31 16:22:19]

引数の数, 第一引数が hoge か否かによって結果が分岐していることが確認できます。

test コマンドでよく使う評価演算子

 上のサンプルでは, test コマンドの比較演算子として -eq, = を使っています。
他にも種類があり, 特に私がよく使うものを挙げておきます。
意外といろいろあるので, この他にも man test を見ると便利なものを見つけられるかもしれません。

  1. -a-o
    • AND 演算子と OR 演算子
  2. -n-z
    • 文字列長が is nonzerois zero
    • 変数が空値か否かの判定によく使う
  3. -ge, -gt, -le, -lt
    • (greater|less) (equal|than), よくあるやつ
    • ShellScript では <, >, <=, >= は使えないのでこちらを使う
  4. -d-f
    • 続く引数が存在し, かつ directory か, regular file
    • 存在チェックに使える
  5. -nt-ot
    • 左辺のファイルが右辺のファイルより newer thanolder than

if に「成否を評価したいコマンド」を渡す

 こちらもよく使います。
よく「あるコマンドを実行し, 失敗したら return 1exit 1 してエラーメッセージを出す」みたいなことをします*2
if 文では, 評価したいコマンドを渡すと, その exit code が 0 か否かで true, false のような評価をしてくれます。

このため, 次のような記述ができます。

#!/bin/bash

if ! ssh "${HOST}" echo "This is test" > /home/workdir/test.txt
then
    echo "test failed!"
    return 1
fi

このサンプルでは, ${HOST}ssh して /home/workdir/test.txt を作成しようとしています*3
if の直後の ! は否定演算子なので, ssh から始まる一連のコマンドが失敗したら, "test failed!" という文字列を出力して exit code 1 を返すようにしています。

exit code について

exit code は, コマンドの実行結果を表す数値です。
コマンド実行が正常終了すると 0, 異常時はそれ以外の数値を返します*4
また, 直前に実行したコマンドの exit code は特殊変数 $? に格納されます。

コマンドラインツールの試験時は, よく次のように exit code を echo して確認したりしています。

[kangetsu@ubuntu16 coreutils Thu Apr 02 20:35:52]
$ cat hoge
cat: hoge: No such file or directory
[kangetsu@ubuntu16 coreutils Thu Apr 02 21:51:11]
$ echo $?
1  <- 失敗しているので exit code 1
[kangetsu@ubuntu16 coreutils Thu Apr 02 21:51:14]

これを利用して, 当初は次のようなコードを ShellScript に書いていました。

#!/bin/bash

ssh "${HOST}" echo "This is test" > /home/workdir/test.txt

if [ "${?}" -ne 0 ]
then
    echo "test failed!"
    return 1
fi

しかし, 上で述べたように if 句で直接コマンドを評価してしまえば, 多少記述を省略できます。
可読性も特に損なわないので, 今は if で直接コマンド評価をする方を使っています。

まとめ

 今回のまとめです。
基本的なことばかりですが, exit code はよく参照するので, 覚えておいて損はないです。

ShellScript における条件分岐基本
  1. if 句の後に test コマンド ([, ]) で条件評価する
  2. [, ] コマンドと一緒に使える比較演算子には便利なものがあるので man test(1) を見てみよう
  3. コマンド実行成否判定なら test を使わず if の後にコマンドを続ければよい
  4. exit code は割と使う, echo $? で見られるので覚えておきたい

おまけ: true と false

 exit code の話が出たのでおまけに。
SHellScript を読んでいて, もしかしたら true, false という記述に出会うかもしれません。
ところが ShellScript には原則 データ型はない ので, boolean 型の値というわけでもありません*5

これは何かというと, man もある, 歴としたコマンドです。

TRUE(1)                                                                                                     User Commands                                                                                                    TRUE(1)

NAME
       true - do nothing, successfully

SYNOPSIS
       true [ignored command line arguments]
       true OPTION

DESCRIPTION
       Exit with a status code indicating success.
[...]
FALSE(1)                                                                                                    User Commands                                                                                                   FALSE(1)

NAME
       false - do nothing, unsuccessfully

SYNOPSIS
       false [ignored command line arguments]
       false OPTION

DESCRIPTION
       Exit with a status code indicating failure.
[...]

以下のように, 正常終了, 異常終了を示す exit code をそれぞれ返すだけのコマンドです。

[kangetsu@ubuntu16 coreutils Thu Apr 02 22:09:39]
$ true; echo $?
0
[kangetsu@ubuntu16 coreutils Thu Apr 02 22:09:50]
$ false; echo $?
1
[kangetsu@ubuntu16 coreutils Thu Apr 02 22:09:52]

while true みたいな感じで出会うことがもしかしたらあるかもしれないです*6
私は初見で「boolean 型がある? -> データ型がある?」と勘違いしたので, お気をつけて。

true のソースの main はこんな感じでした*7
help, version という唯一受け取れる引数の判定, 処理以外で見ると, return EXIT_STATUS してるだけのようです。

[...]
/* Act like "true" by default; false.c overrides this.  */
#ifndef EXIT_STATUS
# define EXIT_STATUS EXIT_SUCCESS
#endif
[...]
int
main (int argc, char **argv)
{
  /* Recognize --help or --version only if it's the only command-line
     argument.  */
  if (argc == 2)
    {
      initialize_main (&argc, &argv);
      set_program_name (argv[0]);
      setlocale (LC_ALL, "");
      bindtextdomain (PACKAGE, LOCALEDIR);
      textdomain (PACKAGE);

      /* Note true(1) will return EXIT_FAILURE in the
         edge case where writes fail with GNU specific options.  */
      atexit (close_stdout);

      if (STREQ (argv[1], "--help"))
        usage (EXIT_STATUS);

      if (STREQ (argv[1], "--version"))
        version_etc (stdout, PROGRAM_NAME, PACKAGE_NAME, Version, AUTHORS,
                     (char *) NULL);
    }

  return EXIT_STATUS;
}

falsetrueEXIT_STATUS の定義を反転させただけの 2行のソースでした。

#define EXIT_STATUS EXIT_FAILURE
#include "true.c"

*1:数値も = で評価できますが, 数値を評価している, という意味を表すために -eq を使った方が可読性はよいと思います

*2:return, exit の後の 1 は exit code です, すぐ後で出てきます

*3:このサンプルに特に実用性はありません

*4:詳しくは こちらの記事 が参考になりました。原文は こちら

*5:bash や zsh で declare, typeset で宣言した場合は定義できるみたいですね

*6:while : で同じことができて, こちらの方が簡便なのでほとんど見ることはないかも

*7:一部省略

ShellScript Tips --変数--

 現職ではインフラ系の QA をしています。扱うものはコマンドラインツールがほとんどなので, 自動試験なども ShellScript で組むことが多いです。現職からまともに触るようになったのですが, いろいろと不思議な挙動に悩まされ, また無知ゆえの無駄な記述もいろいろしてきました。その中で溜まった知見を整理しておきます。

今回は表題の通り, 主に変数について書きます。

  • ShellScript の変数
    • $ のみ
    • ${}
      • 配列参照時は {} は必須
      • まとめ: 原則変数は {} で囲む
    • "${}"
      • 配列を "" で囲む必要性
      • glob 展開を防ぐために "" で囲む必要性
      • まとめ: 変数は原則 "" で囲む
  • "" と '' の使い分け
  • まとめ

新しいLinuxの教科書

新しいLinuxの教科書

続きを読む

論文所感: An Analysis of Performance Evolution of Linux Core Operations

 大学院で一緒だった友人と CS 系の論文も読んでみよう, という話になり, 大体 4年振りに論文を読みました。
気になる論文を探すのも一苦労だったのですが, 今回は "An Analysis of Performance Evolution of Linux Core Operations" (Ren, X., Rodrigues, K., Chen, L., Vega, C., Stumm, M., and Yuan, D., 2019) です*1

さすがに理解できていない部分もあるのですが, 気になった点をまとめておきます。
読み違えている部分もあると思うので, お気づきの際はご指摘いただけると助かります。

  • 概要
  • 測定
  • 結果
    • パフォーマンス低下の主要因
  • 所感
続きを読む

VirtualBox ネットワークモードまとめ

本記事作成のきっかけ

 以前読んだ『サーバ/インフラを支える技術』を読み直しており, その中で持った疑問を手を動かしながら解消したいと思いました。
第一章は冗長化なので, VM を複数立ててフェイルオーバなどをシミュレートしたいと考えたのですが, VirtualBox で作成した VM 間の通信などを実現するにはどうするのが良いか, ちゃんと理解していなかったので調べようと思ったのが本記事のきっかけです。

今回の目的は「Web サーバのフェイルオーバのシミュレーション」なので, VM 間通信ができること, VM の外からの通信を受け付けられること*1, の 2点の達成が必要です。

このためにまずは, VM の NW を決定する主要因である, VirtualBox のそれぞれの NW アダプタモードで何ができるのか を, VirtualBox のマニュアルを*2改めて読んで整理しました。

なお, 本記事では基本的な NW の説明は省略しているので, 理解できない点がある場合は『マスタリング TCP/IP 入門編』*3や, 『ネットワークはなぜつながるのか』を読まれることをお勧めします。

なお, 本記事執筆時に利用している VirtualBox のバージョンは 6.0.14 です。

  • 本記事作成のきっかけ
  • VM の NW 設定とは
  • なぜ NW アダプタモードの理解が必要か
  • VM の NW アダプタモード
    • 1. NAT (Network Address Translation) モード
      • NAT モードでの VM -> インターネット通信時のプロセスの様子
      • NAT モードで外から VM への通信を実現するポートフォワーディング
    • 2. NAT Service (NAT ネットワーク)
    • 3. Bridged Networking (ブリッジアダプター)
    • 4. Internal Networking (内部ネットワーク)
    • 5. Host-Only Networking (ホストオンリーアダプタ―)
  • 6. 結論

*1:Web サーバのシミュレーションなら理想的にはインターネットからの通信を可能にしたいですが, フェイルオーバが焦点なので今回はホストからの通信でも十分と考えました

*2:https://www.virtualbox.org/manual/ch06.html

*3:最近新版が出ましたね, 私は最近読んだばかりなので嬉しいけど悲しい

続きを読む

OpenSSH 公式による scp 非推奨宣言を受け, scp, sftp, rsync を比較してみた (2020/5/25 rsync の計測結果について注記追加)

2020/5/26 再検証記事追加追記

Twitter でのご指摘を受けて再検証しました, 転送先のファイルを削除していないために差分転送になっていた点を考慮したものとなっています。
rsync の速度については結果が変わっています。

www.kangetsu121.work

TL;DR

  • scp はセキュリティ, 今後の開発優先度を考えて公式で非推奨宣言している
  • 転送速度は (1GB のファイル転送の計測では) rsync >> scp > sftp
    • Twitter でコメントをいただき, 転送ファイルの削除を都度していないので, rsync が差分転送になっているとのご指摘をいただきました。
      ただいま検証中ですので, rsync の速度比較結果については判断をお待ちください。 -> 再検証しました, 画面上部の再検証記事をご確認ください
  • rsync は多機能 かつ速い ので rsync を使っていくのがよさそう

OpenSSH 公式が scp 非推奨宣言

 今年の 5/3 に, こんなツイートを自分でしていました。

ssh, scp, sftp などのパッケージを含むソフトウェアである OpenSSH ですが, その公式自身が「scp を使うのは非推奨」と宣言していました。
自分はこれまで何の気なしに, 使い慣れておりかつよく見るコマンドである scp をリモートファイル転送に使っていましたが, 改めなければ, とこの時思っていました。

しかしすっかりそんなことは忘れて scp を使い続けていたので, この機にこの件をもう少し調べて, sftp と rsync のより使いやすい方に移行しようと考え, この記事を書きました。

  • 2020/5/26 再検証記事追加追記
  • TL;DR
  • OpenSSH 公式が scp 非推奨宣言
  • OpenSSH 8.0/8.0p1 (2019-04-17) Release Notes より
  • 各コマンドの特徴
  • 転送速度計測
    • 条件
    • SCP
    • SFTP
    • RSYNC
  • 結果発表
続きを読む

Filesystem Hierarchy Standard (FHS) かんたんなまとめ

TL;DR

  • FHS は UNIX 系 OS のファイル・ディレクトリ配置のガイドライン
    • FHS によりソフトウェアやユーザーはファイル, ディレクトリの位置などを推測できる
    • OS のディストリビューションやバージョンによって一部仕様は異なる
  • 各種ファイル配置などを考える上で開発者は読んでおいた方が良さそう
  • ユーザーも /etc が設定ファイル, /var がログなど動的に更新される可変データ, /usr が read-only の第二の階層, /usr/local がホスト固有のデータを格納する第三の階層, というくらいは覚えておいてよい
  • 正直原典の PDF は後で読めばよい, 先に wiki なり RedHat の記事なり LPIC で勉強するなりがお勧め

Introduction

 Linux を触り始めて、まず戸惑うことの一つが、「どこに何があるかわからない」ではないでしょうか。
私はそうでした。というか恥ずかしながらちょっと前までそうでした。
 初めのうちは、業務で触るファイル類は決まったものが多く、それらの場所さえ覚えておけば意外と何とかなります。
しかし、より少し大きめのソフトウェアなり開発なりに携わるようになると、そうも言ってられなくなります。
障害調査やデバッグなどで, OS のログやファイル類を追うことも珍しくありません。

そんなとき, Linux のディレクトリ階層がどうなっているかを知っていると, どこを調べればよいかの見当がつきやすく, 非常に役立ちます。

そこで, 今回 UNIX 系 OS のファイル・ディレクトリ階層の標準を定義している, Filesystem Hierarchy Standard (FHS) を読んでみました。
2019/10/28 現在の最新版は 2015/6/3 にリリースされた FHS 3.0 です。

重要と思った点や雑感なんかを書き留めておきます。

Linux教科書 LPICレベル1 Version5.0対応

Linux教科書 LPICレベル1 Version5.0対応

[試して理解]Linuxのしくみ ?実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ?実験と図解で学ぶOSとハードウェアの基礎知識

続きを読む

読書記録: ふしぎなキリスト教

 「イエス・キリストとは何者なのか」「ユダヤ教とキリスト教はどう違うのか」「絶対神が世界を創ったのならば、なぜ世界に悪があるのか」「楽園追放、ソドムとゴモラ、ノアの箱舟のエピソードなどに見られる『後悔する神』は本当に絶対的なのか」。
聖書を少しでも知る人は、こうした疑問が浮かんだことはないでしょうか。本書は、こうした基本的かつ重要な疑問に、二人の社会学者の対談を通して解釈を導くものです。
念のため書いておくと、本書は特定の宗教の価値観を伝えるための作品ではなく、むしろフラットな立場で、挑戦的にユダヤ教やキリスト教への疑問を投げかける作品となっています。

※以下本書および遠藤周作の『沈黙』の内容に触れています、ネタバレを極度に気にされる場合はご注意ください。

書籍情報

  • 橋爪 大三郎・大澤 真幸 著
  • 2011年5月20日発刊

ふしぎなキリスト教 (講談社現代新書)

ふしぎなキリスト教 (講談社現代新書)

ふしぎなキリスト教 (講談社現代新書)

ふしぎなキリスト教 (講談社現代新書)

続きを読む

strace と netstat で socket の様子を見てみる

 『ネットワークはなぜつながるのか』第二章では, アプリから OS プロトコルスタックにデータ送信の依頼を出し, 実際に socket を作成してデータを送信するところまでの流れを概観していました。

www.kangetsu121.work

ここでは, その流れに出てくる登場人物のひとつ, socket の作成とその動作を, systemcall*1 を追いかけて表示する strace と, socket の状態を表示する netstat を使って簡単に確認してみます。

環境情報は変わらず VirtualBox 上の Ubuntu 16。

[kangetsu@ubuntu16 ~ Thu Aug 22 21:13:15]
$ uname -a
Linux ubuntu16 4.4.0-157-generic #185-Ubuntu SMP Tue Jul 23 09:17:01 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
[kangetsu@ubuntu16 ~ Thu Aug 22 21:35:52]

strace で systemcall を追う

 まず, strace コマンドで, Web サーバに HTTP リクエストを送ったときにどんな systemcall が呼び出されているのか, 実際に確認してみいます。
ここでは, コマンドラインで HTTP リクエストを送れる curl コマンドを使います。
また, そのままだと表示量が膨大になるため, strace-e trace=network オプションでネットワーク系の systemcall のみに, curl-sI オプションで表示を絞ります。
例によって, www.google.com に GET リクエストを投げ, その時の様子を観察してみます。

[kangetsu@ubuntu16 ~ Thu Aug 22 21:38:11]
$ strace -e trace=network curl -sI https://www.google.com
socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP) = 4
socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 4
setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
setsockopt(4, SOL_TCP, TCP_KEEPIDLE, [60], 4) = 0
setsockopt(4, SOL_TCP, TCP_KEEPINTVL, [60], 4) = 0
connect(4, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("172.217.161.68")}, 16) = -1 EINPROGRESS (Operation now in progress)
getsockopt(4, SOL_SOCKET, SO_ERROR, [0], [4]) = 0
getpeername(4, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("172.217.161.68")}, [16]) = 0
getsockname(4, {sa_family=AF_INET, sin_port=htons(57654), sin_addr=inet_addr("10.0.2.15")}, [16]) = 0
sendto(4, "\26\3\1\0\351\1\0\0\345\3\3]^\215]\6cu1\241?g\0\367\237\206M\357\256\225\215?"..., 238, MSG_NOSIGNAL, NULL, 0) = 238
recvfrom(4, 0x5600887b73ab, 5, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(4, "\26\3\3\0N", 5, 0, NULL, NULL) = 5
recvfrom(4, "\2\0\0J\3\3]^\215@}\3628je]\6\v\207\374=v\26f\\\326\21\227\362\2DO"..., 78, 0, NULL, NULL) = 78
recvfrom(4, "\26\3\3\10@", 5, 0, NULL, NULL) = 5
recvfrom(4, "\v\0\10<\0\0109\0\3\3230\202\3\3170\202\2\267\240\3\2\1\2\2\20L\2\205\376d\323\212"..., 2112, 0, NULL, NULL) = 2112
recvfrom(4, "\26\3\3\0\223", 5, 0, NULL, NULL) = 5
recvfrom(4, "\f\0\0\217\3\0\27A\0040U\364\225\5\315\330\337G\33\352\201\344\17\252\4Q#\201\30<\26\24"..., 147, 0, NULL, NULL) = 147
recvfrom(4, "\26\3\3\0\4", 5, 0, NULL, NULL) = 5
recvfrom(4, "\16\0\0\0", 4, 0, NULL, NULL) = 4
sendto(4, "\26\3\3\0F\20\0\0BA\4@(\364z\323\255\251U\270\321\177\270c\217y\"8\2760\213Q"..., 75, MSG_NOSIGNAL, NULL, 0) = 75
sendto(4, "\24\3\3\0\1\1", 6, MSG_NOSIGNAL, NULL, 0) = 6
sendto(4, "\26\3\3\0(\0\0\0\0\0\0\0\0{\245\366\277'\353\1\265d0Y\255TQ7^\273\25\245"..., 45, MSG_NOSIGNAL, NULL, 0) = 45
recvfrom(4, 0x560089766563, 5, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(4, "\26\3\3\0\344", 5, 0, NULL, NULL) = 5
recvfrom(4, "\4\0\0\340\0\1\211\300\0\332\0\260t*YO]\26\\SK\337\352gP`\352\r\363\325\210\257"..., 228, 0, NULL, NULL) = 228
recvfrom(4, "\24\3\3\0\1", 5, 0, NULL, NULL) = 5
recvfrom(4, "\1", 1, 0, NULL, NULL)     = 1
recvfrom(4, "\26\3\3\0(", 5, 0, NULL, NULL) = 5
recvfrom(4, "\0\0\0\0\0\0\0\0\177\10(\304\367\\\354S`\251<\275\261\345*\334\346D\241\32\21\203\275\376"..., 40, 0, NULL, NULL) = 40
sendto(4, "\27\3\3\0g\0\0\0\0\0\0\0\1\303\7\253\246\0\246\345\r\232$%\334\307\327\207SSN\361"..., 108, MSG_NOSIGNAL, NULL, 0) = 108
recvfrom(4, "\27\3\3\3\34", 5, 0, NULL, NULL) = 5
recvfrom(4, "\0\0\0\0\0\0\0\1\372\270\350\0317\234\0274\267\340e\330\r\2\354\26\236\256\371}I\275|C"..., 796, 0, NULL, NULL) = 796
HTTP/1.1 200 OK
...
Vary: Accept-Encoding

sendto(4, "\25\3\3\0\32\0\0\0\0\0\0\0\2\217\210\22bI\261\366\210i\213k\217\257h\20\305\200\226", 31, MSG_NOSIGNAL, NULL, 0) = 31
recvfrom(4, 0x560089766563, 5, 0, NULL, NULL) = -1 EAGAIN (Resource temporarily unavailable)
+++ exited with 0 +++
[kangetsu@ubuntu16 ~ Thu Aug 22 21:40:32]

 色々出てきました。悲しいことに, 正直私はちゃんと読み解く地力が今はないので, ざっくりと流れを追うにとどめます。
なお, HTTP レスポンスとして返ってきている HTTP ヘッダは頭と末尾以外省略しています。

呼ばれている systemcall の種類

 curl で HTTP リクエストを投げた時, 次の systemcall が呼ばれています。

  • socket: socket を作成する*2
  • setsockopt: socket にオプションを設定
  • connect: socket をつないで connection 生成
  • getsockopt: socket のオプションの取得
  • getpeername: socket に接続している相手のアドレスを取得
  • getsockname: socket に接続しているローカルアドレスを取得
  • sendto: 接続先 socket にメッセージを送る
  • recvfrom: socket からメッセージを受け取る

これら systemcall は Linux に実装されているものなので, man コマンドでマニュアルを見ることができます (systemcall は OS の機能でした)*3
また, その実体は C 言語のプログラムで, 例えば man socket を見ると

... SYNOPSIS #include <sys/types.h> / See NOTES / #include <sys/socket.h>

  int socket(int domain, int type, int protocol);

...

とあり, 例えば私の環境では /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/x86_64-linux-gnu/sys/socket.h にありました。

sendto に関しては, man send に "send は flag がなければ write と等価, sendtosockaddr, socklen_t がなければ send と等価" のように書かれています。

... The only difference between send() and write(2) is the presence of flags. With a zero flags argument, send() is equivalent to write(2). Also, the following call

send(sockfd, buf, len, flags);

is equivalent to

sendto(sockfd, buf, len, flags, NULL, 0);

...

『ネットワークはなぜつながるか』では, write を例示していましたが, 機能としてはほぼ同じなので writesendto に読み替えればよさそうです。
同じように, recvfromread と読み替えればよさそうですね。

上から順に実行されているので, 大体

  1. socket を作成し
  2. socket にオプションを設定し
  3. socket をつなぎ
  4. メッセージを送り
  5. メッセージを受け取る

という感じのようです。

socket が主役: ファイルディスクリプタ

 上で示した systemcall の流れを見てみると, おもしろいことに気づきます。
それは, socket 以後, つまり socket を作成した後に呼ばれている systemcall では全て第一引数に 4 を渡していることです。

これが実は, socket の識別子として説明されていた, ファイルディスクリプタ です。
man socket を見ると,

... SYNOPSIS #include <sys/types.h> / See NOTES / #include <sys/socket.h>

  int socket(int domain, int type, int protocol);

DESCRIPTION socket() creates an endpoint for communication and returns a descriptor. ...

とあります。
すなわち, socket は返り値として, int 型のファイルディスクリプタを返す, と書かれています。
systemcall を追うことで, 確かにファイルディスクリプタによって socket を識別して動作している様子が確認できました。
socket を通じてデータ送受信をしているので, socket 作成後は毎回このディスクリプタで識別した socket, すなわち通信相手と接続できた socket を使っていますね。

netstat でsocket 利用の様子を見る

 次は, netstat コマンドで socket の利用状況を見てみます。
一旦 firefox で https*//www.google.com にリクエストを送った状態 (Google のページが表示されている状態) にして, netstat を叩きます。
今回見たいもの以外は不要なので, grep で表示するものを絞ってます。

[kangetsu@ubuntu16 ~ Thu Aug 22 22:49:33]
$ sudo netstat -ntp | head -n 2; sudo netstat -ntp | grep -v localhost | grep firefox | grep -w tcp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 10.0.2.15:37144         52.34.225.215:443       ESTABLISHED 26312/firefox
[kangetsu@ubuntu16 ~ Thu Aug 22 22:51:10]

-n オプションを付けたので名前解決していません。
これで, 今動作中の firefox のプロセスでは, ローカルの 37144 ポートと, 52.34.225.215 (おそらく Google) の 443 ポート (HTTPS) で socket を接続していることが分かります。

......ちなみに, 前回 dig を使って www.google.com の名前解決をしたときは, こんな IP アドレスではありませんでした。

www.kangetsu121.work

ちょっと気になったので, また dig を使って, このアドレスを今度は逆引きしてみました*4

[kangetsu@ubuntu16 ~ Thu Aug 22 22:43:41]
$ for i in $(netstat -ant | grep ESTABLISHED | grep -w tcp | grep -v "10.0.2.2" | awk '{print $5}' | cut -f 1 -d ':'); do echo $i; dig -x $i | grep "ANSWER SECTION" -A1; done
52.34.225.215
;; ANSWER SECTION:
215.225.34.52.in-addr.arpa. 300 IN      PTR     ec2-52-34-225-215.us-west-2.compute.amazonaws.com.
[kangetsu@ubuntu16 ~ Thu Aug 22 22:56:58]

え, www.google.com って AWS の EC2 で動いてるの......?
などと混乱しましたが, 色々調べてみると, どうやら CDN のようでした (おそらく)。
結構海外のサイトでも, 「なんで自分のマシンが何もしてないのに EC2 と通信してるの?」みたいな質問がありました。
みんな同じように悩んでて少し安心。

おわり

 今回は以上になります。
わかる人からは突っ込みどころ満載かもしれません, 何か気づかれた方は後学のためにご教授ください。
こんな風に Linux では systemcall を追ったり, 詳細を自分で観察できるので楽しいですね。

まだまだ知識不足ではありますが, こんな感じで少しずつでも手を動かしていきたいです*5

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

ネットワークはなぜつながるのか 第2版 知っておきたいTCP/IP、LAN、光ファイバの基礎知識

*1:ざっくり言うと, アプリケーションが OS の持つ機能を利用する仕組み......でいいはず。第一章で「データ送受信機能はアプリそれぞれで実装するとごちゃごちゃになるので共通基盤である OS の持つ機能を利用する」みたいなことが書いてあったと思いますが, 今回の例だとブラウザというアプリが OS のデータ送受信機能を利用すること, という感じです。次から見ていくように, 一口にデータ送受信機能と言っても様々な機能の集合から成っています

*2:2回呼ばれてるのは, 1回目が IPv6 で試み, 2回目で IPv4 で成功している, ように見えます。実際, curl で IPv4 を明示的に利用する -4 オプションを付けて実行したときは初回の PF_INET6 を利用する socket systemcall は呼び出されませんでした

*3:C を読める人, 強い人, 頑張りたい人は man で systemcall を追うときっと強くなれます。私は一旦後にします......

*4:無駄に for 回してますが気にしないでください。ESTABLISHED な connection 全部見てみたかったのでこう書いてました

*5:API, ABI, systemcall について詳しく知りたい方は, 武内覚さんの Qiita のエントリが詳しそう。私はこれで「なるほど」と思える地力はまだありません, 精進