Elixirでポートスキャナを作って遊ぶ

# 導入
ElixirでIPスイーパーとポートスキャナを作りました。
プレフィックス長は/24のみ対応です。
解説もいたしますのでご覧ください。

リポジトリです

https://github.com/BlackTanuTanuNinjas1035/tanu_port

# 内容
```elixir
defmodule TanuPort do

  @doc """
  指定したネットワーク内のホストのアドレスを検出
  """
  def ip_sweep(network_ip) do
    sweeper(self(), network_ip)

    receive do
      {:done, results} ->
        results
    end
  end

  defp sweeper(pid, network_ip) do
    results = []

    Enum.each(1..254, fn ip ->
      spawn(fn ->
        case System.cmd("ping", ["-c", "1", "#{network_ip}.#{ip}"]) do
          {_, 0}  ->  send(pid, {:child, '#{network_ip}.#{ip}'})
          _       ->  {}
        end
      end)
    end)

    loop(pid, results)
  end

  defp loop(pid, results) do
    receive do
      {:child, address} ->
        new_results = [address | results]
        loop(pid, new_results)
    after
        5000 -> send(pid, {:done, results})
    end
  end

  @doc """
  ホストが指定したポートを開放されているか検出します。
  ipは「''」文字リストにすること、portは整数で
  """
  def port_scanner(empty_ip_list, _) when empty_ip_list == [] do
    IO.puts("IP list is empty.")
  end

  def port_scanner(ip_list, port_list) when is_list(ip_list) and is_list(port_list) do
    Enum.each(ip_list, fn ip ->
      Enum.each(port_list, fn port ->
        scanner(ip, port)
      end)
    end)
  end

  def port_scanner(ip_list, port) when is_list(ip_list) do
    Enum.each(ip_list, fn ip ->
      scanner(ip, port)
    end)
  end

  def port_scanner(ip, port) do
    scanner(ip, port)
  end

  defp scanner(ip, port) do
    case :gen_tcp.connect(ip, port, [:binary, active: false]) do
      {:ok, socket} ->
        IO.puts("#{ip}\t: Opened port #{port}")
        :gen_tcp.close(socket)
      {:error, _} ->
        IO.puts("#{ip}\t: Closed port on #{port}")
    end
  end

end

```

# IPスイーパー
TanuPort.ip_sweepを実行してネットワーク内のホストを調べられます。
実行する際には引数に24ビットまでのネットワークアドレスを指定してください。

例`TanuPort.ip_sweep("192.168.0")`※文字列("")でも文字リスト('')でも構いません。

その後以下のように処理が実行されます。

__1__. 現在のプロセスID(`iex -S mix`のプロセスID)と一緒にsweeperに渡します。
    - `sweeper(self(), network_ip)`
__2__. ネットワークアドレスに1~254をくっつけて、1回だけpingする処理を行います
    - このとき子プロセスを生成してます。子プロセスはpingに成功すると、親プロセスにメッセージ(:child)とホストのIPアドレスを送信します。
    - `{_, 0}  ->  send(pid, {:child, '#{network_ip}.#{ip}'})`
    - 処理の最後にloopという関数を呼び出します。親プロセスIDと空のresultsというリストを渡します。
__3__. loopという関数は:childというメッセージを受信後、pingに成功したアドレスをresultsの頭にくっつけていきます。
    - 以後再帰します。最後にメッセージが送られてから5秒経過すると、親プロセスに:doneとresultsの値を渡します。
__4__. ip_sweepが:doneをいうメッセージを受け取ったらresultsを出力して終了です。
    - これで子プロセスの処理の結果をリスト化することができました。出力は次のport_scannerに渡すことができます。

# ポートスキャナー
TanuPort.port_scannerを実行して、ホストが指定したポートを開放しているか確認することができます。
事項する際は
    `'対象のホストのアドレス'or'先程ご紹介したIPスイーパーの戻り値`
    と
    `ポート番号(整数)orポート番号のリスト`を指定してください。

以下の処理が行われます。

__1__. port_scannerに渡されたホストのアドレスとポート番号をscannerに渡します。
    - この際にガード句を使用して引数の型や値によって処理を変えています。
__2__. scannerにて指定したホストのポートに接続します。接続するとソケットが生成されます。
    - `:gen_tcp.connect(ip, port, [:binary, active: false])`
    - `activate: false`が意味するのは、ソケットで受信したデータに何もしないパッシブモードを指定しています。
    - 処理の最後にソケットを閉じております。`:gen_tcp.close(socket)`
__3__. scannerが指定したホストでポートを開放しているか表示して終了です。


# あとがき
最近PhoenixやらLiveView尽くしの生活でございました。Elixir本来の味わい(プロセスとメッセージの受信、:gen_tcp(Erlangだけど)、ガード句)を堪能することができました。やはりElixirは最高でございます。
もっと進んだこともしてみたいですね。Nervesとか。ラズパイは持っておりますがそれ以外の機材が皆無ですので。
それは、まぁ、追々です。