Netcat(nc)でHTTPと仲良くなる

[PONOS Advent Calendar 2022](https://qiita.com/advent-calendar/2022/ponos)の5日目の記事です。
昨日は@Portierさんの「JIRAのステータス変更を自動化」でした。

## この記事で取り扱うこと

Netcatを使って、生のHTTP通信を体験してみます。
クライアントとしてリクエストする立場・サーバーとしてレスポンスを返す立場の両方を体験することで、ブラウザやhttpdへの感謝が深まること請け合いです。

:::note warn
この記事で取り扱うNetcatは、様々な用途に応用できるツールです。 [^1]
悪い人が使えば悪用できてしまうため、セキュリティソフトにマルウェア扱いされることもあります。
この記事の内容を検証される際は、利用する環境や実施内容に十分ご注意ください。
[^1]: ネットワークのスイスアーミーナイフと呼ばれることも。
:::


## Netcat(nc)とは

> Netcatは、Unix系OSコマンドラインアプリケーションの一つ。TCPやUDPのパケットを読み書きするバックエンドとして機能するツールで、ネットワークを扱う万能ツールとして知られる。オリジナル版より機能的に優位な派生・互換ツールが開発され、用いられている。
[Netcat](https://ja.wikipedia.org/wiki/Netcat)

ネットワーク越しに``cat``するツールです。
コマンドとしては``nc``で登録されていることが多いです。

- 標準入力(STDIN)に書き込んだ内容を通信相手に送る
- 通信相手から受け取った内容を標準出力(STDOUT)に書き出す

他にもサーバーモードやポートスキャン機能など、幅広くなんでもできます。

## 準備

Unix系のOSであれば、最初から入っていたり公式リポジトリに含まれていたり。
今回はDockerで動かす方法でやっていきます。

Docker DesktopなどのDocker実行環境が入っていればOKです。

```
$ docker run --rm subfuzion/netcat -h
OpenBSD netcat (Debian patchlevel 1.130)
usage: nc [-46CDdFhklNnrStUuvZz] [-I length] [-i interval] [-O length]
	  [-P proxy_username] [-p source_port] [-q seconds] [-s source]
	  [-T toskeyword] [-V rtable] [-w timeout] [-X proxy_protocol]
	  [-x proxy_address[:port]] [destination] [port]
(略)
```

## クライアントとしてページを取得

`nc` を通してHTTPリクエストを送って、ページを取得してみます。

SSL化されていない方が分かりやすいので、みなさんご存知の [阿部 寛のホームページ](http://abehiroshi.la.coocan.jp/) にアクセスします。

```
$ docker run --rm -it subfuzion/netcat abehiroshi.la.coocan.jp 80
```

`abehiroshi.la.coocan.jp` のTCP80（HTTP）に接続しています。
するとこちらの入力を待ち受ける状態になるので、HTTPリクエストを送りましょう。

```
GET / HTTP/1.1
Host: abehiroshi.la.coocan.jp
```

`/` をGETしようとしています。すると……

```
HTTP/1.1 200 OK
Date: Mon, 05 Dec 2022 06:41:42 GMT
Content-Type: text/html
Content-Length: 538
Connection: keep-alive
Last-Modified: Thu, 27 Feb 2020 07:10:16 GMT
ETag: "21a-59f8969af8600"
Accept-Ranges: bytes
Server: Apache

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<meta name="GENERATOR" content="JustSystems Homepage Builder Version 20.0.6.0 for Windows">
<meta http-equiv="Content-Style-Type" content="text/css">
<title>�������̃z�[���y�[�W</title>
</head>
<frameset cols=18,82>
  <frame src="menu.htm" marginheight="0" marginwidth="0" scrolling="auto" name="left">
  <frame src="top.htm" marginheight="0" marginwidth="0" scrolling="auto" name="right">
  <noframes>
  <body></body>
  </noframes>
</frameset>
</html>
```

レスポンスが返ってきます。
HTTPレスポンスと、フレームの親ページですね。
タイトルが文字化けしているのはご愛嬌。（Shift_JISだから）


## サーバーとして待ち受け

次は`nc`の標準入力・標準出力をシェルに直結して、サーバーとして待ち受けてみます。

```
$ docker run --rm -it -p 8080:8080 subfuzion/netcat -l 8080
```

この例だと、8080ポートで待ち受ける状態になります。
届いたリクエストをターミナルに流して、逆に入力した内容はレスポンスとしてクライアントに送られます。

この状態でブラウザから http://localhost:8080/ にアクセスすると、ブラウザはレスポンスを待ち続ける状態になります。
と同時に、ターミナルにはブラウザからのリクエストが流れてきます。

```
GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
sec-ch-ua: "Google Chrome";v="107", "Chromium";v="107", "Not=A?Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "macOS"
(以下略)
```

この状態でターミナルからレスポンスを返してあげると、ちゃんとブラウザ側にも表示されます。

```手入力 or コピペする
HTTP/1.1 200 OK
Server: honeniq
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>netcat http server</title>
</head>
<body>
手入力で返すテストです。
</body>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2492/23b8ab01-fdad-ab70-cbc8-fd02c9d19ef1.png)

注目してほしいのは、HTMLだけじゃなくてHTTPレスポンスも同時に返しているところです。
いつもならWebサーバーがやってくれているところを、自分でやっているわけですね。


### サーバーとして待ち受け（ファイル使用）

上記の変形として、レスポンス内容を書いたファイルを標準入力に流してあげれば、レスポンスとして定型文を返すことができます。

```
$ docker run --rm -i -p 8080:8080 subfuzion/netcat -l 8080 < response.txt
```

`< response.txt` で標準入力にテキストファイルを渡しています。
この状態でブラウザから http://localhost:8080/ にアクセスすると、 `response.txt` に書かれたHTMLが返ってきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2492/72e3fea8-5a0e-0cac-2672-61aabf1747e2.png)

`response.txt` の中身はこんな感じ。（さっきターミナルから返した内容とほぼ同じ）

``` response.txt
HTTP/1.1 200 OK
Server: honeniq
Content-Type: text/html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>netcat http server</title>
</head>
<body>
ファイルで返すテストです。
</body>
```

## まとめ

普段はブラウザやサーバーが隠蔽してくれていますが、実際に試してみると理解が深まりますね！
特に、HTTPといっても普通のテキストをやり取りしているだけなのが面白いところでした。
単純といえば単純だけど、このシンプルさがHTTPの強さなのかもしれない。

明日は @nissy_gp さんです。お楽しみに！