mirror of
https://github.com/opnsense/core.git
synced 2026-02-03 20:39:42 -05:00
netaddr / mac vendor mapping - replace with simple local implementation, closes https://github.com/opnsense/core/issues/9187
This commit is contained in:
parent
f1c48f4699
commit
48f43cb04b
6 changed files with 38080 additions and 81 deletions
37990
contrib/ieee/oui.csv
Normal file
37990
contrib/ieee/oui.csv
Normal file
File diff suppressed because it is too large
Load diff
55
src/opnsense/scripts/interfaces/lib/__init__.py
Normal file
55
src/opnsense/scripts/interfaces/lib/__init__.py
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
"""
|
||||
Copyright (c) 2025 Ad Schellevis <ad@opnsense.org>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
|
||||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
import csv
|
||||
import os
|
||||
|
||||
DB_FILE = '/usr/local/opnsense/contrib/ieee/oui.csv'
|
||||
|
||||
class OUI:
|
||||
_db = None
|
||||
def __init__(self):
|
||||
# init database with csv file when not populated yet
|
||||
if OUI._db is None:
|
||||
OUI._db = {}
|
||||
with open(DB_FILE, newline='') as f_oui:
|
||||
for record in csv.reader(f_oui, delimiter=','):
|
||||
if len(record) > 2:
|
||||
OUI._db[record[1]] = record[2]
|
||||
|
||||
def get_vendor(self, mac, default=''):
|
||||
key = mac.replace(':', '').replace('-', '')[:6].upper()
|
||||
if key in OUI._db:
|
||||
return OUI._db[key]
|
||||
return default
|
||||
|
||||
def get_db(self):
|
||||
return OUI._db
|
||||
|
||||
@property
|
||||
def st_time(self):
|
||||
if os.path.isfile(DB_FILE):
|
||||
return os.stat(DB_FILE).st_mtime
|
||||
|
|
@ -29,25 +29,17 @@
|
|||
list arp table
|
||||
"""
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import ujson
|
||||
import netaddr
|
||||
sys.path.insert(0, "/usr/local/opnsense/site-python")
|
||||
import watchers.dhcpd
|
||||
from lib import OUI
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# do we use reverse DNS lookup ?
|
||||
arp_arg = '-a' if '-r' in sys.argv else '-an'
|
||||
|
||||
# index mac database (shipped with netaddr)
|
||||
macdb = dict()
|
||||
with open("%s/eui/oui.txt" % os.path.dirname(netaddr.__file__)) as fh_macdb:
|
||||
for line in fh_macdb:
|
||||
if line[11:].startswith('(hex)'):
|
||||
macprefix = line[0:8].replace('-', ':').lower()
|
||||
macdb[macprefix] = line[18:].strip()
|
||||
|
||||
result = []
|
||||
|
||||
# import dhcp_leases (index by ip address)
|
||||
|
|
@ -73,11 +65,9 @@ if __name__ == '__main__':
|
|||
'expires': src_record['expires'] if 'expires' in src_record else -1,
|
||||
'permanent': src_record['permanent'] if 'permanent' in src_record else False,
|
||||
'type': src_record['type'],
|
||||
'manufacturer': '',
|
||||
'manufacturer': OUI().get_vendor(src_record['mac-address'], ''),
|
||||
'hostname': src_record['hostname'] if src_record['hostname'] != '?' else ''
|
||||
}
|
||||
if record['mac'][0:8] in macdb:
|
||||
record['manufacturer'] = macdb[record['mac'][0:8]]
|
||||
if record['ip'] in dhcp_leases:
|
||||
record['hostname'] = dhcp_leases[record['ip']]['hostname']
|
||||
result.append(record)
|
||||
|
|
|
|||
|
|
@ -32,34 +32,27 @@
|
|||
import os.path
|
||||
import sys
|
||||
import ujson
|
||||
import importlib.util
|
||||
from lib import OUI
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
oui_registry_file = "%s/eui/oui.txt" % os.path.dirname(importlib.util.find_spec('netaddr').origin)
|
||||
cache_file = '/tmp/oui.txt.json'
|
||||
result = {}
|
||||
if os.path.isfile(oui_registry_file):
|
||||
oui_mtime = os.stat(oui_registry_file).st_mtime
|
||||
if os.path.isfile(cache_file) and os.stat(cache_file).st_mtime == os.stat(oui_registry_file).st_mtime:
|
||||
try:
|
||||
result = ujson.loads(open(cache_file).read())
|
||||
except ValueError:
|
||||
result = {}
|
||||
oui_mtime = OUI().st_time
|
||||
if os.path.isfile(cache_file) and os.stat(cache_file).st_mtime == oui_mtime:
|
||||
try:
|
||||
result = ujson.loads(open(cache_file).read())
|
||||
except ValueError:
|
||||
result = {}
|
||||
|
||||
if len(result) == 0:
|
||||
for line in open(oui_registry_file, 'rb'):
|
||||
line = line.decode()
|
||||
if line.find('(base 16)') > -1:
|
||||
parts=line.split('(base 16)')
|
||||
if len(parts) >= 2:
|
||||
result[parts[0].strip()] = parts[1].strip()
|
||||
if len(result) == 0:
|
||||
result = OUI().get_db()
|
||||
json_payload = ujson.dumps(result)
|
||||
open(cache_file, 'w').write(json_payload)
|
||||
os.chmod(cache_file, 0o444)
|
||||
os.utime(cache_file, (oui_mtime, oui_mtime))
|
||||
|
||||
json_payload = ujson.dumps(result)
|
||||
open(cache_file, 'w').write(json_payload)
|
||||
os.chmod(cache_file, 0o444)
|
||||
os.utime(cache_file, (oui_mtime, oui_mtime))
|
||||
|
||||
print(json_payload)
|
||||
sys.exit(0)
|
||||
print(json_payload)
|
||||
sys.exit(0)
|
||||
|
||||
print(ujson.dumps(result))
|
||||
|
|
|
|||
|
|
@ -32,17 +32,9 @@ import subprocess
|
|||
import os
|
||||
import sys
|
||||
import ujson
|
||||
import netaddr
|
||||
from lib import OUI
|
||||
|
||||
if __name__ == '__main__':
|
||||
# index mac database (shipped with netaddr)
|
||||
macdb = dict()
|
||||
with open("%s/eui/oui.txt" % os.path.dirname(netaddr.__file__)) as fh_macdb:
|
||||
for line in fh_macdb:
|
||||
if line[11:].startswith('(hex)'):
|
||||
macprefix = line[0:8].replace('-', ':').lower()
|
||||
macdb[macprefix] = line[18:].strip()
|
||||
|
||||
result = []
|
||||
# parse ndp output
|
||||
sp = subprocess.run(['/usr/sbin/ndp', '-an'], capture_output=True, text=True)
|
||||
|
|
@ -52,10 +44,8 @@ if __name__ == '__main__':
|
|||
record = {'mac': line_parts[1],
|
||||
'ip': line_parts[0],
|
||||
'intf': line_parts[2],
|
||||
'manufacturer': ''
|
||||
'manufacturer': OUI().get_vendor(line_parts[1], ''),
|
||||
}
|
||||
if record['mac'][0:8] in macdb:
|
||||
record['manufacturer'] = macdb[record['mac'][0:8]]
|
||||
result.append(record)
|
||||
|
||||
# handle command line argument (type selection)
|
||||
|
|
|
|||
|
|
@ -28,14 +28,9 @@
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import glob
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import ujson
|
||||
import netaddr
|
||||
import netaddr.core
|
||||
from datetime import datetime
|
||||
from lib import OUI
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
@ -44,32 +39,18 @@ if __name__ == '__main__':
|
|||
parser.add_argument('mac', help='mac address')
|
||||
cmd_args = parser.parse_args()
|
||||
|
||||
mac = None
|
||||
try:
|
||||
mac = netaddr.EUI(cmd_args.mac)
|
||||
except netaddr.core.AddrFormatError:
|
||||
result['status'] = 'failed'
|
||||
result['message'] = 'invalid mac'
|
||||
|
||||
if mac:
|
||||
result['status'] = 'ok'
|
||||
result['ip'] = []
|
||||
result['ip6'] = []
|
||||
result['org'] = "***"
|
||||
with open("%s/eui/oui.txt" % os.path.dirname(netaddr.__file__)) as fh_macdb:
|
||||
for line in fh_macdb:
|
||||
if line.startswith(str(mac)[0:8]):
|
||||
result['org'] = line[18:].strip()
|
||||
break
|
||||
|
||||
for cmd in ['/usr/sbin/arp', '/usr/sbin/ndp']:
|
||||
args = [cmd, '-na']
|
||||
for line in subprocess.run(args, capture_output=True, text=True).stdout.split('\n'):
|
||||
if line.upper().replace(':', '-').find(str(mac)) > -1:
|
||||
parts = line.split()
|
||||
if cmd.endswith('ndp'):
|
||||
result['ip6'].append(parts[0])
|
||||
else:
|
||||
result['ip'].append(parts[1].strip('()'))
|
||||
result['status'] = 'ok'
|
||||
result['ip'] = []
|
||||
result['ip6'] = []
|
||||
result['org'] = OUI().get_vendor(cmd_args.mac, '***')
|
||||
for cmd in ['/usr/sbin/arp', '/usr/sbin/ndp']:
|
||||
args = [cmd, '-na']
|
||||
for line in subprocess.run(args, capture_output=True, text=True).stdout.split('\n'):
|
||||
if line.find(cmd_args.mac) > -1:
|
||||
parts = line.split()
|
||||
if cmd.endswith('ndp'):
|
||||
result['ip6'].append(parts[0])
|
||||
else:
|
||||
result['ip'].append(parts[1].strip('()'))
|
||||
|
||||
print (ujson.dumps(result))
|
||||
|
|
|
|||
Loading…
Reference in a new issue