繋がらないを解決するステップ

# はじめに

普段アプリケーションの開発をしていると、何かしらに繋がらなくて困るといった場面は少なくないと思います。

- 立ち上げたはずのwebアプリにアクセスできない
- アプリとDBが疎通できない
- 〇〇のサーバにSSHできない

現在リンクアンドモチベーションのSREとして、そして過去インフラエンジニア/システム管理者としての経験もある筆者がこのような時にどうやって調査し解決しているかを紹介します。
特に若手の開発者やインフラエンジニアの方々に参考になれば幸いです。
(あくまで個人の考えのため、もしより良い方法などありましたらぜひコメントで教えてください!)

# L4レベルで疎通できるか確認

役に立つコマンド

- nc
- telnet
- curl

OSI参照モデルにおけるL4、トランスポート層での通信ができるかどうかをまず確認します。
誤解を恐れずにいうと具体的にはIPアドレスとポートの組み合わせが許可されているかを確認します。
この確認にはnc/telnet等のコマンドが活用できます。
※ ncコマンドのバージョンが古いと `-z`オプションが使えないので注意

```bash
ubuntu@gratified-gunnel:~$ nc -zv 192.168.64.3 80
Connection to 192.168.64.3 80 port [tcp/http] succeeded!
ubuntu@gratified-gunnel:~$
```

余談ですがコンテナなどのncもtelnetもインストールされていない環境の場合curlでも疎通確認できます。

```bash
ubuntu@gratified-gunnel:~$ curl -v telnet://192.168.64.3:22
*   Trying 192.168.64.3:22...
* TCP_NODELAY set
* Connected to 192.168.64.3 (192.168.64.3) port 22 (#0)
SSH-2.0-OpenSSH_8.2p1 Ubuntu-4ubuntu0.3
^C
ubuntu@gratified-gunnel:~$
```

## L4で通信できない場合

通信できなかった場合には下記を確かめていきましょう。

### Firewall/Security Group

役に立つコマンド

- ip
- ifconfig
- hostname
- curl

疎通先サーバのfirewallや、AWSの場合Security Groupが適切に設定されているかを確認しましょう。
また疎通元のIPアドレスを今一度確認して、フィルター設定が適切か見直しましょう。

プライベートIPの場合はip/hostname等のコマンドで確認できます。

```bash
ubuntu@gratified-gunnel:~$ ip a s
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp0s2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether f6:39:81:a7:32:ac brd ff:ff:ff:ff:ff:ff
    inet 192.168.64.2/24 brd 192.168.64.255 scope global dynamic enp0s2
       valid_lft 80544sec preferred_lft 80544sec
    inet6 fe80::f439:81ff:fea7:32ac/64 scope link
       valid_lft forever preferred_lft forever
ubuntu@gratified-gunnel:~$ hostname -I
192.168.64.2
ubuntu@gratified-gunnel:~$
```

グローバルIPの場合は `checkip.amazonaws.com`等のIPを確認できるサイトを利用しましょう。

```bash
ubuntu@gratified-gunnel:~$ curl checkip.amazonaws.com
203.0.113.1
ubuntu@gratified-gunnel:~$
```

また `ifconfig.io`の場合はipv6のIPアドレスを優先的に返してくれるので、ipv4のつもりが想定外にipv6でアクセスしていないかを確認することもできます。

```bash
ubuntu@gratified-gunnel:~$ curl ifconfig.io
2001:db8::1
ubuntu@gratified-gunnel:~$
```

### 名前解決

役に立つコマンド

- dig
- nslookup
- ping

疎通先がIPアドレスではなくドメイン名やホスト名の場合名前解決の不具合を疑いましょう
この確認にはdig/nslookup/ping等のコマンドが活用できます。

```bash
ubuntu@gratified-gunnel:~$ dig +short example.com
93.184.216.34
ubuntu@gratified-gunnel:~$

ubuntu@gratified-gunnel:~$ ping -c 1 example.com
PING example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=54 time=110 ms

--- example.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 109.553/109.553/109.553/0.000 ms
ubuntu@gratified-gunnel:~$
```

もしも `example.com`等の一般的なドメインの名前解決が失敗する場合にはローカルのresolverの設定を確認しましょう。
特定の宛先のみ名前解決が失敗する場合には参照しているDNSの設定を確認しましょう。またローカルの `/etc/hosts`に余計なエントリーが入っていないかも確認すると良いです。

### ルーティング

役に立つコマンド

- ip
- netstat
- traceroute
- tracepath

`no route to host`のようなログの場合にはルーティング設定を確認しましょう。
ipコマンドやnetstatなどでサーバのルートテーブルを確認できます。
AWSの場合にはVPCのルートテーブルの設定を確認しましょう。

```bash
ubuntu@gratified-gunnel:~$ ip r s
default via 192.168.64.1 dev enp0s2 proto dhcp src 192.168.64.2 metric 100
192.168.64.0/24 dev enp0s2 proto kernel scope link src 192.168.64.2
192.168.64.1 dev enp0s2 proto dhcp scope link src 192.168.64.2 metric 100
ubuntu@gratified-gunnel:~$
```

traceroute/tracepath等のコマンドで実際の経路を確認することもできますが、宛先がicmpを許可していない場合にはブロックされてしまうので気をつけましょう。

```bash
ubuntu@gratified-gunnel:~$ tracepath example.com
 1?: [LOCALHOST]                      pmtu 1500
 1:  _gateway                                              0.171ms
 1:  _gateway                                              0.232ms
 2:  hogehoge.(実際にはipアドレス等が入ります)                    5.669ms
 3:  fugafuga                                              7.505ms
 4:  foobarbaz.                                            22.078ms
```


## L4で通信できている場合

L4(IPアドレス/ポート)で疎通できる場合は大きく下記の2パターンに分けて調べましょう。

### HTTP(S)アクセス

役に立つコマンド

- curl

基本的にはアクセスした際に返却されるステータスコードを確認しましょう。
curlに `-I`オプションを付けるとステータスコードが確認できます。

```bash
ubuntu@gratified-gunnel:~$ curl -I http://example.com/nothing
HTTP/1.1 404 Not Found
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 07 Dec 2021 08:15:38 GMT
Expires: Tue, 14 Dec 2021 08:15:38 GMT
Server: EOS (vny/0453)
Vary: Accept-Encoding
Content-Length: 1256

ubuntu@gratified-gunnel:~$
```

出力の一番上にステータスコード(上記だと404)が表示されるので確認しましょう。
https://developer.mozilla.org/ja/docs/Web/HTTP/Status
基本的には4xxの場合にはパスが正しいか、認証が正しくできているかあたりを確認します。
5xxの場合はwebサーバ側が正しい状態になっているか、負荷が以上にかかっていないかなどが原因として考えられます。

またHTTPSの場合はSSLの処理が正しく行われているかを確認しましょう。

```bash
ubuntu@gratified-gunnel:~$ curl -v https://example.com
(省略)
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=Los Angeles; O=Internet Corporation for Assigned Names and Numbers; CN=www.example.org
*  start date: Nov 24 00:00:00 2020 GMT
*  expire date: Dec 25 23:59:59 2021 GMT
*  subjectAltName: host "example.com" matched cert's "example.com"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS RSA SHA256 2020 CA1
*  SSL certificate verify ok.
(省略)
ubuntu@gratified-gunnel:~$
```

上記の `SSL certificate verify ok.`が出力されなかった場合にはサーバ側のSSL証明書失効や、設定が不適切である可能性があります。
また、クライアント側のOSが古いと配置してあるCA証明書が古い可能性もあるのでそちらも確認しましょう。

### HTTPアクセス以外

HTTP以外の場合にはあまり有用なメッセージが出力されないケースもあります。(攻撃者に情報を与えてしまうため)
例えばSSHでログインできない場合、原因の候補はいろいろありますが基本的には同じメッセージしか出力されません。

```bash
(クライアント側)
ubuntu@effectual-caracal:~$ ssh ubunt@192.168.64.2
ubunt@192.168.64.2: Permission denied (publickey).
ubuntu@effectual-caracal:~$

(サーバ側のauth.log)
Dec  7 17:30:31 gratified-gunnel sshd[1325]: Connection closed by authenticating user ubuntu 192.168.64.3 port 36100 [preauth]
Dec  7 17:30:50 gratified-gunnel sshd[1327]: Invalid user ubunt from 192.168.64.3 port 36102
Dec  7 17:30:50 gratified-gunnel sshd[1327]: Connection closed by invalid user ubunt 192.168.64.3 port 36102 [preauth]
```

上記のようにユーザ名が間違っているという情報はクライアント側には伝わらないため、アクセスを受ける側から調査をした方が基本的には効率的です。
サービス側で出力された情報をもとにアプリのドキュメントを参照するのがいいと思います。もしくはエラーログを適当にコピペしてググると早いかもしれません。

# 上記を試しても解決できない場合

これらを試しても解決できない場合には上位者やシステム管理者へ問い合わせるのが良いかと思います。クライアント側からだと調査が難しくてもサービス側からであれば容易なケースも少なくないため。
問い合わせる際には下記のような情報があると調査する際に助かるかなと思います。

- アクセス元 (ローカル or 特定のサーバ)
- アクセス先
- プロトコル
- **エラーメッセージ/ステータスコード (全文コピペ or スクショ)**
- 試した日時 (ログ見るときに役立つ)