ろうとるがPythonを扱う、、（その７：nmapとmultiping）

# ポートOpen/Close確認やマルチpingの実行
pythonのnmapモジュールおよびICMPLibを使って、ポートOpen/Closeなどの確認やマルチpingの実行を、tkinterによりGUIプログラム化する。

## ポートOpen/Closeなどの確認
ある特定のIPアドレスおよびポート番号にて、nmapのいくつかのオプション結果を同時に確認したかった（あまりそのようなものを見たことがなかった）ので、作ってみることにした。下記スキャンの結果を同時に表示する。
- -sS：TCP Synスキャン
- -sA：TCP Ackスキャン
- -sN：TCP Nullスキャン
- -sU：UDPスキャン

## 同一サブネット内のIPアドレス複数にping
例えば、「192.168.1.42/29」の場合、下記のIPアドレスにpingを行い、リプライがあったIPアドレスを表示する。
- 192.168.1.41
- 192.168.1.42
- 192.168.1.43
- 192.168.1.44
- 192.168.1.45
- 192.168.1.46

## 実行結果例
### nmap
![nmap結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/2c26feaf-858c-9c27-4d32-e856671f3362.png)
### マルチping
![MultiPing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/441ec095-e0e5-fb89-71af-085b137a0338.png)

## ソースコード
### nmap
```python
import nmap

def ExecNmap(ip, num, arg, out):
    nm = nmap.PortScanner()
    scan_result = nm.scan(hosts = ip, ports = num, arguments = arg)
    out = out + nm.command_line() + '\n'
    #print(scan_result['scan'])
    target = next(iter(scan_result['scan'])) # for domain name ('ip' cannot be used.)
    if arg == '-sU':
        detail = scan_result['scan'][target]['udp']
    else:
        detail = scan_result['scan'][target]['tcp']
    first = next(iter(detail)) # 'num' does not work
    out = out + ' state: ' + detail[first]['state'] \
        + '\t\t\treason: ' + detail[first]['reason'] + '\n\n'
    return out
```
- out：結果を格納するバッファ
- nm.scan()：nmap実行
- nm.command_line()：コマンドラインをバッファに格納
- scan_result['scan']：nmap実行結果（下記はサンプル）
{'74.6.143.26': {'hostnames': [{'name': 'yahoo.com', 'type': 'user'}, {'name': 'media-router-fp74.prod.media.vip.bf1.yahoo.com', 'type': 'PTR'}], 'addresses': {'ipv4': '74.6.143.26'}, 'vendor': {}, 'status': {'state': 'up', 'reason': 'echo-reply'}, 'tcp': {80: {'state': 'open', 'reason': 'syn-ack', 'name': 'http', 'product': '', 'version': '', 'extrainfo': '', 'conf': '3', 'cpe': ''}}}}
- target = next(iter(scan_result['scan']))：結果の最初の要素であるIPアドレスを取得（対象IPアドレスをドメイン名で指定したときにも対応するため）
- TCPスキャンかUDPスキャンかで取得の仕方を変える
- first = next(iter(detail))：ポート番号を文字列化
- 「state」および「reason」の結果をバッファに格納

### multiPing
```python
from netaddr import *
from icmplib import *

def ExecPing(arg):
    ipNet = IPNetwork(arg)
    ipList=list(ipNet)
    Len = len(ipList)
    target = []
    for ip in ipList[1:Len-1]:
        target.append(str(ip))
    hosts = multiping(target, count=3)
    return hosts
```
- netaddr（IPNetwork()）により同一サブネット内のIPアドレス群を取得
- 最初（ネットワークアドレス）および最後（ブロードキャストアドレス）を除いたIPアドレスのリスト化（target）
- マルチping実行

### OKボタンクリック
```python
def ok_clk(frm, arg):
    ip = frm.entry1.get()
    if arg == 0:
        num = frm.entry2.get()
        out = ''
        out = ExecNmap(ip, num, '-sS', out)
        out = ExecNmap(ip, num, '-sA', out)
        out = ExecNmap(ip, num, '-sN', out)
        out = ExecNmap(ip, num, '-sU', out)
    else:
        hosts = ExecPing(ip)
        out = 'ping to ' + ip + '\n'
        for host in hosts:
            if host.is_alive:
                out = out + ' ' + host.address + '\tis up' + '\n'
    frm.text.config(state='normal')   # editable
    frm.text.insert(tk.END, out)
    frm.text.config(state='disabled') # not editable
    frm.text.see('end')
    return
```
- Entryボックスに入力された内容の取得
- nmap実行関数またはマルチping実行関数の呼び出し
- マルチpingの場合は、結果の加工
- テキストボックスへ結果をアウトプット

### タブ作成
```python
def create_content(frm, arg):
    frm1 = tk.Frame(frm)
    frm2 = tk.Frame(frm)
    frm1.pack()
    frm2.pack()
    frm.entry1 = ttk.Entry(frm1, width=20)
    frm.entry1.grid(row=0, column=1)
    if arg == 0:
        label = ttk.Label(frm1, text='Enter IP address and Port Number')
        frm.entry2 = ttk.Entry(frm1, width=10)
        frm.entry2.grid(row=0, column=2)
    else:
        label = ttk.Label(frm1, text='Enter Network (ex 192.168.1.0/24)')
    label.grid(row=0, column=0)
    okBtn = ttk.Button(frm1, text='OK', command=lambda:ok_clk(frm, arg))
    okBtn.grid(row=0, column=3)
    frm.text = tk.Text(frm2, width=80, height=40)
    ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=frm.text.yview)
    frm.text["yscrollcommand"] = ysc.set
    ysc.pack(side=tk.RIGHT, fill="y")
    frm.text.config(state='disabled') # not editable
    frm.text.pack()
    return
```
ここは、[その５](https://qiita.com/infinite1oop/items/f798699c7058314b308c)あたりを参照されたし。

### メインプログラム
```python
# root main window
root = tk.Tk()
root.title("Port Check & Multi Ping")
root.geometry("600x400")

# Create an instance of ttk style
fgcolor = "lightskyblue2"
bgcolor = "gray80"
style = ttk.Style()
style.theme_create("style1", parent="alt", settings={
        "TNotebook.Tab": {
            "configure": {"background": bgcolor },
            "map":       {"background": [("selected", fgcolor)],
                          } } } )
style.theme_use("style1")

# Create Notebook Widget
note = ttk.Notebook(root)

# Create tab
tab0 = tk.Frame(note)
tab1 = tk.Frame(note)

# Add tab
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])

# Create content of tab
create_content(tab0, 0)
create_content(tab1, 1)

# Locate tab
note.pack(expand=True, fill='both')

# main loop
root.mainloop()
```
ここも、[その５](https://qiita.com/infinite1oop/items/f798699c7058314b308c)を参照されたし。

### 全体
```python
import nmap
from netaddr import *
from icmplib import *
import tkinter as tk
import tkinter.ttk as ttk

# command to execute
tab_list = [' nmap: -sS, -sA, -sN, -sU ', ' ping for subnet ']

# execute nmap
def ExecNmap(ip, num, arg, out):
    nm = nmap.PortScanner()
    scan_result = nm.scan(hosts = ip, ports = num, arguments = arg)
    out = out + nm.command_line() + '\n'
    #print(scan_result['scan'])
    target = next(iter(scan_result['scan'])) # for domain name ('ip' cannot be used.)
    if arg == '-sU':
        detail = scan_result['scan'][target]['udp']
    else:
        detail = scan_result['scan'][target]['tcp']
    first = next(iter(detail)) # 'num' does not work
    out = out + ' state: ' + detail[first]['state'] \
        + '\t\t\treason: ' + detail[first]['reason'] + '\n\n'
    return out

# execute multi-ping
def ExecPing(arg):
    ipNet = IPNetwork(arg)
    ipList=list(ipNet)
    Len = len(ipList)
    target = []
    for ip in ipList[1:Len-1]:
        target.append(str(ip))
    hosts = multiping(target, count=3)
    return hosts

# Function for click OK
def ok_clk(frm, arg):
    ip = frm.entry1.get()
    if arg == 0:
        num = frm.entry2.get()
        out = ''
        out = ExecNmap(ip, num, '-sS', out)
        out = ExecNmap(ip, num, '-sA', out)
        out = ExecNmap(ip, num, '-sN', out)
        out = ExecNmap(ip, num, '-sU', out)
    else:
        hosts = ExecPing(ip)
        out = 'ping to ' + ip + '\n'
        for host in hosts:
            if host.is_alive:
                out = out + ' ' + host.address + '\tis up' + '\n'
    frm.text.config(state='normal')   # editable
    frm.text.insert(tk.END, out)
    frm.text.config(state='disabled') # not editable
    frm.text.see('end')
    return

# Create content of tab
def create_content(frm, arg):
    frm1 = tk.Frame(frm)
    frm2 = tk.Frame(frm)
    frm1.pack()
    frm2.pack()
    frm.entry1 = ttk.Entry(frm1, width=20)
    frm.entry1.grid(row=0, column=1)
    if arg == 0:
        label = ttk.Label(frm1, text='Enter IP address and Port Number')
        frm.entry2 = ttk.Entry(frm1, width=10)
        frm.entry2.grid(row=0, column=2)
    else:
        label = ttk.Label(frm1, text='Enter Network (ex 192.168.1.0/24)')
    label.grid(row=0, column=0)
    okBtn = ttk.Button(frm1, text='OK', command=lambda:ok_clk(frm, arg))
    okBtn.grid(row=0, column=3)
    frm.text = tk.Text(frm2, width=80, height=40)
    ysc = tk.Scrollbar(frm2, orient=tk.VERTICAL, command=frm.text.yview)
    frm.text["yscrollcommand"] = ysc.set
    ysc.pack(side=tk.RIGHT, fill="y")
    frm.text.config(state='disabled') # not editable
    frm.text.pack()
    return

# Start of main program
# root main window
root = tk.Tk()
root.title("Port Check & Multi Ping")
root.geometry("600x400")

# Create an instance of ttk style
fgcolor = "lightskyblue2"
bgcolor = "gray80"
style = ttk.Style()
style.theme_create("style1", parent="alt", settings={
        "TNotebook.Tab": {
            "configure": {"background": bgcolor },
            "map":       {"background": [("selected", fgcolor)],
                          } } } )
style.theme_use("style1")

# Create Notebook Widget
note = ttk.Notebook(root)

# Create tab
tab0 = tk.Frame(note)
tab1 = tk.Frame(note)

# Add tab
note.add(tab0, text=tab_list[0])
note.add(tab1, text=tab_list[1])

# Create content of tab
create_content(tab0, 0)
create_content(tab1, 1)

# Locate tab
note.pack(expand=True, fill='both')

# main loop
root.mainloop()
```

EOF