4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-08-26

導入

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

リポジトリです

内容

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とか。ラズパイは持っておりますがそれ以外の機材が皆無ですので。
それは、まぁ、追々です。

4
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?