2
0
mirror of https://github.com/xcat2/confluent.git synced 2026-08-26 16:46:43 +00:00
Files
confluent/misc/getipsfromswitchport
T
Markus Hilger 644843b892 Remove unused imports and pointless f-string prefixes (F401, F541, E713)
Entirely mechanical, produced by `ruff check --fix --select F401,F541,E713`
and reviewed rather than taken on faith: deleting an import is only safe if
nothing imports it for its side effects or re-exports it.  None of the 19
removed names is referenced anywhere in its file, none appears in any string
literal, and none of the touched files uses eval, exec, globals() or
__import__, so there is no dynamic lookup that could reach them.
2026-08-10 05:32:00 +02:00

206 lines
6.5 KiB
Python

#!/usr/bin/python
import asyncio
import re
import sys
try:
import confluent.asynclient as asynclient
except ImportError:
sys.path.append('/opt/confluent/lib/python')
import confluent.asynclient as asynclient
_whitelistnames = (
# 3com
re.compile(r'^RMON Port (\d+) on unit \d+'),
# Dell
re.compile(r'^Unit \d+ Port (\d+)\Z'),
)
_blacklistnames = (
re.compile(r'vl'),
re.compile(r'Nu'),
re.compile(r'RMON'),
re.compile(r'onsole'),
re.compile(r'Stack'),
re.compile(r'Trunk'),
re.compile(r'po\d'),
re.compile(r'XGE'),
re.compile(r'LAG'),
re.compile(r'CPU'),
re.compile(r'Management'),
)
def mac2lla(mac):
"""Convert MAC address to IPv6 link-local address."""
# Remove colons from MAC address
mac_clean = mac.replace(':', '')
# Insert fe80:: prefix and convert to lowercase
# Split MAC into first 3 octets and last 3 octets
first_half = mac_clean[:6]
second_half = mac_clean[6:]
# Flip the universal/local bit in the first octet
first_octet = int(first_half[:2], 16)
first_octet ^= 0x02
first_half = f'{first_octet:02x}' + first_half[2:]
# Format as IPv6 link-local address
lla = f'fe80::{first_half[0:4]}:{first_half[4:]}ff:fe{second_half[0:2]}:{second_half[2:6]}'
return lla
async def is_reachable(address):
try:
await asyncio.wait_for(asyncio.open_connection(address, 22), timeout=0.5)
return True
except (asyncio.TimeoutError, OSError):
return False
async def get_nics():
proc = await asyncio.create_subprocess_exec(
'ip', '-json', 'link',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await proc.communicate()
import json
links = json.loads(stdout)
for link in links:
if 'LOOPBACK' in link.get('flags', []):
continue
if 'LOWER_UP' not in link.get('flags', []):
continue
yield link['ifname']
async def add_zone(lla):
if '%' in lla:
return lla
tocheck = []
async for link in get_nics():
tocheck.append(link)
tasks = []
for link in tocheck:
zone_addr = lla + '%' + link
tasks.append(is_reachable(zone_addr))
results = await asyncio.gather(*tasks)
for link, reachable in zip(tocheck, results):
if reachable:
return lla + '%' + link
return lla
def _namesmatch(switchdesc, userdesc):
if switchdesc is None:
return False
if switchdesc == userdesc:
return True
try:
portnum = int(userdesc)
except ValueError:
portnum = None
if portnum is not None:
for exp in _whitelistnames:
match = exp.match(switchdesc)
if match:
snum = int(match.groups()[0])
if snum == portnum:
return True
anymatch = re.search(r'[^0123456789]' + userdesc + r'(\.0)?\Z', switchdesc)
if anymatch:
for blexp in _blacklistnames:
if blexp.match(switchdesc):
return False
return True
return False
async def ping_everywhere():
tasks = []
async for link in get_nics():
tasks.append(asyncio.create_subprocess_exec(
'ping', '-I', link, 'ff02::1', '-c', '2',
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
))
for task in tasks:
proc = await task
await proc.communicate()
async def main(switch, port):
portname = None
client = asynclient.Command()
async for rsp in client.read(f'/networking/neighbors/by-switch/{switch}/by-port/'):
portcandidate = rsp.get('item', {}).get('href')[:-1]
if _namesmatch(portcandidate, port):
if portname:
sys.stderr.write(f"Multiple matches found for port {port} on switch {switch}\n")
portname = None
else:
portname = portcandidate
peerid = None
if portname:
async for rsp in client.read(f'/networking/neighbors/by-switch/{switch}/by-port/{portname}/by-peerid/'):
peerid = rsp.get('item', {}).get('href')
fe80found = False
if peerid:
maybemac = None
async for rsp in client.read(f'/networking/neighbors/by-switch/{switch}/by-port/{portname}/by-peerid/{peerid}'):
if 'peeraddresses' in rsp:
for addr in rsp['peeraddresses']:
if addr.startswith('fe80::'):
fe80found = True
addr = await add_zone(addr)
print(addr)
if 'peerchassisid' in rsp:
maybemac = rsp['peerchassisid']
if not fe80found and maybemac:
try:
maybella = mac2lla(maybemac)
maybella = await add_zone(maybella)
fe80found = True
print(maybella)
except Exception as e:
pass
if not fe80found:
if not portname:
async for rsp in client.read(f'/networking/macs/by-switch/{switch}/by-port/'):
portcandidate = rsp.get('item', {}).get('href')[:-1]
if _namesmatch(portcandidate, port):
if portname:
sys.stderr.write(f"Multiple matches found for port {port} on switch {switch}\n")
portname = None
else:
portname = portcandidate
if not portname:
await ping_everywhere()
async for rsp in client.update('/networking/macs/rescan', {'rescan': 'start'}):
pass
scanning = True
while scanning:
async for rsp in client.read('/networking/macs/rescan'):
if 'scanning' in rsp:
scanning = rsp['scanning']
if scanning:
await asyncio.sleep(1)
async for rsp in client.read(f'/networking/macs/by-switch/{switch}/by-port/{portname}/by-mac/'):
mac = rsp.get('item', {}).get('href').replace('-', ':')
if mac:
try:
maybella = mac2lla(mac)
maybella = await add_zone(maybella)
fe80found = True
print(maybella)
except Exception as e:
pass
if __name__ == '__main__':
if len(sys.argv) < 3:
sys.stderr.write(f"Usage: {sys.argv[0]} <switch> <port>\n")
sys.exit(1)
switch = sys.argv[1]
port = sys.argv[2]
asyncio.run(main(switch, port))