ろうとるがPythonを扱う、、（その６：ネットワーク内の機器検索）

# サブネット内の機器検索
Python上でnmapを用い、サブネット内の機器を検索する。複数ネットワークI/Fにも対応。

## nmap
オプション「-sn」（ARP）により、同一サブネット内の機器を検索する。コマンド的には下記となる。
- nmap -sn 192.168.1.0/24

パケット的には、ARPブロードキャストを下限から上限まで実施している。
![ARP.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/302a7529-ab65-68b8-2374-ebf466f19dc5.png)

### Pythonでのnmap
下記サイトを参照。
- [python-nmap 0.7.1](https://pypi.org/project/python-nmap/)
- [【Python×nmap】基礎と使い方](https://euniclus.com/article/python-nmap-usage/)

## ソースコード

### サブネットマスクをビット数に変換
ネットワークI/Fごとに得られるサブネット情報（後述）が「255.255.255.0」となっており、nmapコマンドに引数として渡す時に、「/24」のように指定する必要がある。サイト「[PythonでCIDR表記のネットマスクをドット付き十進表記に変換](https://qiita.com/quenhulu/items/3f6f717ab9d01cb552aa)」をそのまま利用した。
```python
import socket
import struct

def mask2cidr(mask):
    return bin(struct.unpack('!L', socket.inet_pton(socket.AF_INET, mask))[0])[2:].index('0')
```
例えば、「255.255.255.0」を「24」に変換する。

### ネットワークI/F名の取得
```python
import netifaces
for iface in netifaces.interfaces():
```
「iface」として、Windowsでは「{362E1251-D728-413E-897B-0A4BB1DCAB97}」のようなアウトプットとなる。Linuxなどでは「eth0」となる。

### ネットワークI/F情報の取得
```python
    conf = netifaces.ifaddresses(iface)
```
例えば、次のような結果が得られる。
```
{-1000: [{'addr': '4c:bb:58:rr:kk:82'}],
 2: [{'addr': '192.168.10.109', 'netmask': '255.255.255.0', 'broadcast': '192.168.10.255'}]}
```
Key'2'にI/FのIPアドレスなどが格納されている。

### nmapを実行
```python
    if 2 in conf:
        adr = str(conf[2][0]['addr'])
        if adr == '127.0.0.1' or '169.254' in adr:
            continue
        search = adr + '/' + str(mask2cidr(conf[2][0]['netmask']))
        print(search)
        nm = nmap.PortScanner()
        output = nm.scan(hosts=search, arguments='-sn')
        dic = output['scan']
```
ループバックアドレスやリンクアドレスが付与されているI/Fを除いた全I/Fで、nmapによる機器検索（-snオプション）を実施する。「scan」の結果（上記の変数'dic'）例は下記となる。
```
'192.168.10.1': {'hostnames': [{'name': 'aterm.me', 'type': 'PTR'}], 'addresses': {'ipv4': '192.168.10.1', 'mac': '80:22:A7:ZZ:SS:52'}, 'vendor': {'80:22:A7:ZZ:SS:52': 'NEC Platforms'}, 'status': {'state': 'up', 'reason': 'arp-response'}},
'192.168.10.109': {'hostnames': [{'name': '', 'type': ''}], 'addresses': {'ipv4': '192.168.10.109'}, 'vendor': {}, 'status': {'state': 'up', 'reason': 'localhost-response'}}}
```

### IPアドレスとMacアドレスの取得
```python
        for key in dic:
            ip = dic[key]['addresses']['ipv4']
            if 'mac' in dic[key]['addresses']:
                m = dic[key]['addresses']['mac']
            else:
                m = ''
            print(' IP address: %s\tMac address: %s' % (ip, m))
```
「scan」の結果からそれぞれを抽出。

### コード全体
```python
import netifaces
import nmap
import socket
import struct

def mask2cidr(mask):
    """Convert netmask from Dotted address to CIDR."""
    return bin(struct.unpack('!L', socket.inet_pton(socket.AF_INET, mask))[0])[2:].index('0')

for iface in netifaces.interfaces():
    conf = netifaces.ifaddresses(iface)
    if 2 in conf:
        adr = str(conf[2][0]['addr'])
        if adr == '127.0.0.1' or '169.254' in adr:
            continue
        search = adr + '/' + str(mask2cidr(conf[2][0]['netmask']))
        print(search)
        nm = nmap.PortScanner()
        output = nm.scan(hosts=search, arguments='-sn')
        dic = output['scan']
        for key in dic:
            ip = dic[key]['addresses']['ipv4']
            if 'mac' in dic[key]['addresses']:
                m = dic[key]['addresses']['mac']
            else:
                m = ''
            print(' IP address: %s\tMac address: %s' % (ip, m))
```

## 結果
```
192.168.2.16/24
 IP address: 192.168.2.1        Mac address: AE:87:A3:xx:yy:64
 IP address: 192.168.2.16       Mac address:
192.168.56.1/24
 IP address: 192.168.56.1       Mac address:
192.168.219.1/24
 IP address: 192.168.219.1      Mac address:
192.168.137.1/24
 IP address: 192.168.137.1      Mac address:
192.168.42.1/24
 IP address: 192.168.42.254     Mac address: 00:50:56:zz:bb:EE
 IP address: 192.168.42.1       Mac address:
192.168.59.1/24
 IP address: 192.168.59.254     Mac address: 00:50:56:rr:ww:0E
 IP address: 192.168.59.1       Mac address:
192.168.10.109/24
 IP address: 192.168.10.1       Mac address: 80:22:A7:qq:pp:52
 IP address: 192.168.10.105     Mac address: 6C:40:08:kk:vv:44
 IP address: 192.168.10.109     Mac address:

```

このPythonのモジュールでは、自身のI/FのMac Addressは取得されないようだ。また、他の機器のMac Addressは取得されないケースもあった。このあたりの詳細は未調査。

EOF