TCPでの接続確認には「openssl s_client」の話

UNIX関連エンジニアが最も良くやっているトラブル対応ってネットワーク接続の確認だと思うんですよ。

## 接続確認には「`openssl s_client`」を使いましょう。

まず結論です。以下のコマンドを使いましょう。
```sh
$ openssl s_client -connect <サーバーのIPアドレス>:<ポート番号>
CONNECTED(00000003)
(この後は何でもよし)
```

「CONNECTED」が出たら基本的に接続OKです。接続できてるってことです。

----
今回も簡単なUNIXでのコマンドの紹介です。

## 課題

今回のことの発端は、最近流行りの「Gen AI」氏に聞いてみたんです。
「サーバーへのTCPの接続確認に使うコマンドは何？」
って。

### 「Gen AI」氏の回答

> サーバーへのTCP接続を確認するためには、いくつかのコマンドがあります。以下のコマンドが一般的に使用されます：
> 
> 1. **telnet**: 特定のポートへの接続を確認するために使用されます。
>    ```sh
>    telnet <サーバーのIPアドレス> <ポート番号>
>    ```
>    例: `telnet 192.168.1.1 80`
>    
> 2. **nc (netcat)**: telnetの代替として使用されることが多いです。
>    ```sh
>    nc -zv <サーバーのIPアドレス> <ポート番号>
>    ```
>    例: `nc -zv 192.168.1.1 80`
> 
> 3. **curl**: HTTP/HTTPSの接続確認に使用されます。
>    ```sh
>    curl -v telnet://<サーバーのIPアドレス>:<ポート番号>
>    ```
>    例: `curl -v telnet://192.168.1.1:80`
> 
> これらのコマンドを使用して、サーバーへのTCP接続を確認することができます。どのコマンドを使用するかは、確認したいプロトコルやポートによって異なります。何か他にお手伝いできることがあれば教えてください。

だそうです。

### 「Gen AI」氏の回答に対する著者の認識（課題のポイント）

数千のトラブル対応で、そのサーバー上の有りものだけで解決をしてきた筆者の経験から「Gen AI」氏の回答は、嘘ではないが、それではないのだ。


ポイント：**トラブル時、システムに導入されていない無いコマンドは使えない。**

#### 1. `telnet`について

「オープンした接続をクローズするときには、「^]」っていれるのだよ。」という補足はさて置きまして。
典型的な答えです。20年前ならば「普通」といったところです。でも、今の時代は違います。
もうさ、「`telnet`」コマンドが導入されてない場合の方が多い…。


#### 2. `nc`について

[IBMさんも推奨](https://www.ibm.com/support/pages/using-netcat-ascertain-if-particular-port-open-between-sender-and-receiver)のncコマンド。呼び名は「netcatコマンド」なのに「`nc`」がコマンドです。
現在は、telnetよりマシかも知れない。でも導入されているかは怪しい。

#### 3. `curl`について

curlにURLとして「telnet:」を使うとは、ちょっと驚きました。まぁ、こちらも同様です。現在は、telnetよりマシかも知れない。でも導入されているかは怪しい。wgetも同じく。


## 解説

### 現代どこにでもあるコマンドは`ssh`と`openssl`

「トラブル時、システムに導入されていない無いコマンドは使えない」が鉄則です。では、入っているコマンドってなんでしょう？

ログインするときに、`ssh`コマンド使ってますよね？（普通は…）
ということは、普通は、sshクライアントくらいは入ってます。または、sshクラアントの前提ライブラリである`openssl`ライブラリが入っているのです。

### コンテナベースのシステムでsshしないでログインしてますよ？

では、そのアプリケーションシステムは、どこともTLSで通信しないのでしょうか？
昨今、通信は当然で、その上に暗号化も当然の時代です。サーバーを実現するにせよクライアントを実現するにせよ、SSL(TLS)のライブラリ実装であるopensslが入っているのが普通でしょう。

### openssl s_clientコマンド

本来は、`telnet`の非暗号化のTCP接続に対する、SSL/TLSの暗号化を伴う接続を同じようにできるユーティリティなコマンドです。なので「暗号化を伴う接続」にしか使わない認識があるかもしれません。
ですが、いいのです。トラブル時に大事なことは「接続確認ができさえすればいい」のです。
「CONNECTED(00000003)」が出たらSSLのハンドシェークやら何やらはさて置き、TCPレベルではつながっています。

#### メリット

- 上記に説明してきたとおり、「`openssl`」コマンドは、かなり存在可能性が高いと言えます（主観）。
- 「CONNECTED(00000003)」が出たらTCP的にはつながっている
- SSL/TLS接続上の課題もチェックできる

#### デメリット

- コマンドがちょっと長い：他のコマンドのケースに比べて長いんですよね。だから覚える必要があります。丸暗記です

```shell
$ openssl s_client -connect hostname:443
CONNECTED(00000003)
```
上記は、いつでも打てるようにしておきましょう。試験に出ます。


- [openssl11が必要なケース](https://oji-cloud.net/2024/03/09/post-8215/)が有るとか

### sshコマンド

sshコマンドが入っているのに、opensslコマンドが使えないなんてこともあったりもします。
そんな時は、こんな感じです。

```shell
$ ssh -p <ポート番号> <サーバーのIPアドレス|ホスト名>
kex_exchange_identification: banner line contains invalid characters
banner exchange: Connection to 123.123.123.123 port 123: invalid format
$
```
「あれ？エラーですね？」
「ええ、エラーです。」
エラーがでてますけど良いんです。エラーの中身を理解してください。sshの接続を試みようとして、「banner line contains invalid characters」とは「間違った文字が返ってきた。」と言っているのですから「接続できた」証拠です。

繋がらないときは、以下みたいに「だんまり」になるので、「^C」するしかありません。

```shell
$ ssh -p 1080 google.com
^C
$
```
または、もっとあからさまな「接続できんかった」エラーがでるでしょう。

```shell
$ ssh -p 443 1.1.1.1
kex_exchange_identification: Connection closed by remote host
Connection closed by 1.1.1.1 port 443
$ ssh -p 443 127.0.0.1
ssh: connect to host 127.0.0.1 port 443: Connection refused
$
```

## 応用

### Server Name Indicationを使った接続チェック

先にも述べた通り、現代は通信もTLSでの暗号化が当たり前になりました。
私がWebサーバーに関わりを始めたのは、大学生の頃で1996年…。その頃には、[Server Name Indication(SNI)](https://ja.wikipedia.org/wiki/Server_Name_Indication)なんてものはなかったんですが、昨今はコンテナでの仮想化が当然になり、SNIが必須になりました。

詳細はSNIの解説リンク先でも読んで欲しいですが…大事なことは、
**「IPアドレスとポートの接続だけではなく、宛先の仮想ホスト名も入れないと接続確認できなくなってる」**
です。

以下、サンプルです。
```sh
# SNIを指定してない場合
$ openssl s_client -connect <サーバーのIPアドレス>:<ポート番号>
CONNECTED(00000003)
(この接続は、代表アドレスが、とりあえず接続を受け取っているだけ。)
```

下のようにしましょう。
```sh
# SNIを指定した場合
$ openssl s_client -connect <サーバーのIPアドレス>:<ポート番号> -servername <仮想ホスト名>
CONNECTED(00000003)
(この場所にでる内容(証明書のSubject等)が仮想ホストに順次た内容ならOK)
```

もう、SNIまで来ると、`telnet`や`nc`では対応できず、最低でも`openssl`コマンドや`curl`等での対処となります。

### 接続チェックの際の補助手段

`netstat`コマンドでの接続ステータスを確認しましょう。可能ならば複数のターミナルを開いて、さらに可能ならば相手先側のマシン上でもターミナルを開いて、以下のコマンドで接続をリアルタイムでモニターしましょう。

以下は、192.168.0.100さんに接続できない1080ポートで接続を試した例ですけども「SYN_ACKが来ねーよ」で止まっていることがわかります。
```sh
$ while true
> do
> netstat -an |grep 1080 # 1080がチェックしたいポート番号
> sleep 1
> done # <--- ここまで入力したら、このターミナルは放って置き、別ターミナルから接続を試行
tcp        0      1 172.19.56.162:45304     192.168.0.100:1080      SYN_SENT
tcp        0      1 172.19.56.162:45304     192.168.0.100:1080      SYN_SENT
tcp        0      1 172.19.56.162:33726     192.168.0.100:1080      SYN_SENT
tcp        0      1 172.19.56.162:33726     192.168.0.100:1080      SYN_SENT
tcp        0      1 172.19.56.162:33726     192.168.0.100:1080      SYN_SENT
^C     # Ctrl-Cで終了
$ 
```
この繋ぎ側が「SYN_SENT」状態は、かなり普通の「繋がらない状態」です。繋ぎ先には何も出ません。
ただ、時々、繋先側で「SYN_RECEIVED」状態はでるものの「ESTABLISHED」にならないなんてときもあります。「SYN/ACK」を返したのに行方不明になっているときです。間にいるロードバランサーやファイヤウォール等が犯人でしょう。

トラブルの際に「全箇所でパケットキャプチャしないとわからん」…って、そんな無理難題をネットワーク技術者が言い出しても推論でなんとかなる問題だってあります。


## おわりに

「Gen AI」氏が残念回答をだしてきたので「まぁ、そんなもんだろうな」と思いつつも、小手先技でも発信しなきければ、学習データにも入らんし…と思って記事を書いてみました。

SEは、沢山の仕事があります。些細なトラブルの調査で時間を取らないよう、こんな記事でも参考にして頑張ってほしいと思います。


## 背景
今日はトラブル時の接続チェックというちっさな話の紹介をしました。

プログラムを書いて、世の中の役に立つ製品でも作って生きたいなと思ってIBMって会社へ入社したらSE(当時の呼ばれ方、システムズエンジニア)という職種になって25年です。なったもんは仕方ないので、SEという職種で一人前になってやろうと言うことで、今にいたりますが。世の中の発展は早すぎて、何がSEの一人前がわからないくらいの状態ですし、技術に追いつくのがやっとこさです。とはいえ、世の中は25年前とあんまり変わらないテクノロジー(Linux)が存続していて、ローテクのシェル芸も私と同じく現役で役に立ちます。

現場の作業の実態を見ていると、どうにも非効率な状況が惜しい知識不足だったりするため、この記事を書くことにしました。

皆様のお仕事がスムーズ・安全に進むよう願っております。