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