Files
macvendors/macvendorapi.py

122 lines
3.6 KiB
Python

from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
import re
import os
import httpx
from urllib.parse import unquote
app = FastAPI(title="MAC Address Vendor Lookup API")
def normalize_mac(mac_address: str) -> str:
mac_clean = unquote(mac_address)
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
if len(mac_clean) < 6:
return None
return '-'.join([mac_clean[i:i+2].upper() for i in range(0, len(mac_clean), 2)])
def is_valid_mac(mac_address: str) -> bool:
mac_clean = unquote(mac_address)
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
return len(mac_clean) >= 6 and len(mac_clean) <= 12 and all(c in '0123456789abcdefABCDEF' for c in mac_clean)
def get_mac_prefix(mac_address: str) -> str:
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_address)
return mac_clean[:6].upper()
def lookup_local_file(mac_prefix: str) -> str:
if not os.path.exists('mac_list.txt'):
return None
try:
with open('mac_list.txt', 'r') as f:
for line in f:
line = line.strip()
if line and ' ' in line:
prefix, vendor = line.split(' ', 1)
if prefix.upper() == mac_prefix:
return vendor
except Exception:
pass
return None
async def lookup_remote_gist(mac_prefix: str) -> str:
url = "https://gist.githubusercontent.com/aallan/b4bb86db86079509e6159810ae9bd3e4/raw/846ae1b646ab0f4d646af9115e47365f4118e5f6/mac-vendor.txt"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=5.0)
if response.status_code == 200:
for line in response.text.split('\n'):
line = line.strip()
if line and ' ' in line:
prefix, vendor = line.split(' ', 1)
if prefix.upper() == mac_prefix:
return vendor
except Exception:
pass
return None
async def lookup_remote_api(normalized_mac: str) -> str:
mac_for_api = normalized_mac
if len(mac_for_api.replace('-', '')) == 6:
mac_for_api = mac_for_api + '-00-00-00'
url = f"https://api.macvendors.com/{mac_for_api}"
try:
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=5.0)
if response.status_code == 200:
return response.text.strip()
except Exception:
pass
return None
@app.get("/lookup/{mac_address}")
async def lookup_mac_vendor(mac_address: str):
if not is_valid_mac(mac_address):
return JSONResponse(
status_code=400,
content={
"mac_address": mac_address,
"vendor": None,
"detail": "Invalid MAC address format"
}
)
normalized_mac = normalize_mac(mac_address)
mac_prefix = get_mac_prefix(mac_address)
vendor = lookup_local_file(mac_prefix)
if not vendor:
vendor = await lookup_remote_gist(mac_prefix)
if not vendor:
vendor = await lookup_remote_api(normalized_mac)
if vendor:
return {
"mac_address": normalized_mac,
"vendor": vendor,
"detail": None
}
else:
return JSONResponse(
status_code=404,
content={
"mac_address": normalized_mac,
"vendor": None,
"detail": "Vendor not found"
}
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)