
If you’ve ever tried to sync MAC addresses from your network devices into NetBox, you’ve probably hit a wall. What seems like it should be simple – “just put the MAC address on the interface” – turns into a frustrating puzzle that breaks your automation scripts.
You’re not alone. This is one of the most common stumbling blocks for teams starting their NetBox journey.
When you discover a device on your network, you get information like this:
switch-01Ethernet1AA:BB:CC:DD:EE:FF192.168.1.10Your first instinct? Put the MAC address directly on the interface in NetBox. Most people try something like this and wonder why it doesn’t work properly:
# Get first device as an example or get specific device with .get()
device = list(nb.dcim.devices.all())[0]
# Step 2: Create dummy interface for that device
interface = nb.dcim.interfaces.create({
"device": device.id,
"name": "Ethernet99",
"type": "1000base-t"
})
# This appears to work, but it doesn't
interface.mac_address = "AA:BB:CC:DD:EE:FF"
interface.save()
# Reponse: True
Here’s the thing: NetBox has a more sophisticated way of handling MAC addresses that most people don’t discover until they’re deep into a project.
NetBox treats MAC addresses like a inventory system. Think of it this way:
This dual system exists because in real networks:
Instead of just setting interface.mac_address, you need to:
Here’s the correct approach:
import pynetbox
# Initialize NetBox connection
nb = pynetbox.api(
url='http://netbox_url',
token='api_token'
)
# Get first device as an example or get specific device with .get()
device = list(nb.dcim.devices.all())[0]
# Step 1: Create MAC address in Devices > MAC Addresses repository
mac_record = nb.dcim.mac_addresses.create({
"mac_address": "AA:BB:CC:DD:EE:99"
})
# Step 2: Create dummy interface for that device
interface = nb.dcim.interfaces.create({
"device": device.id,
"name": "Ethernet94",
"type": "1000base-t"
})
# Step 3: Assign MAC to interface in MAC table
mac_record.assigned_object_type = "dcim.interface"
mac_record.assigned_object_id = interface.id
mac_record.save()
# Step 4: Set as primary MAC on Device's interface
interface.primary_mac_address = mac_record.id
interface.save()
# That's the process
print(f"MAC {mac_record.mac_address}\n- assigned to {device.name}\n- int {interface.name} with mac {interface.mac_address}")
# Response:
# MAC AA:BB:CC:DD:EE:99
# - assigned to router01n01
# - int Ethernet94 with mac AA:BB:CC:DD:EE:99Production networks often have duplicate MAC addresses (think shared VIPs). This breaks the simple approach:
# ❌ This fails if there are duplicates
mac_record = nb.dcim.mac_addresses.get(mac_address=mac)
# Error: get() returned more than one result
# ✅ This handles duplicates gracefully
mac_records = list(nb.dcim.mac_addresses.filter(mac_address=mac))
mac_record = mac_records[0] if mac_records else None
When you assign MAC addresses programmatically, NetBox expects specific format for the content type:
# ❌ Wrong - numeric content type
mac_record.assigned_object_type = 35
# ✅ Correct - string content type
mac_record.assigned_object_type = "dcim.interface"
Your discovery tools might return empty or invalid MAC addresses that will break your sync:
# ✅ Always validate before processing
def is_valid_mac(mac_address):
if not mac_address or mac_address == "00:00:00:00:00:00":
return False
return True
Here’s a production-ready function that handles all the edge cases:
"""NetBox MAC address sync - proper 3-step process"""
import pynetbox
# Configuration
NETBOX_URL = "http://netbox_url/"
NETBOX_TOKEN = "api_token"
def sync_mac_to_interface(nb, device_name, interface_name, mac_address):
"""Sync MAC address to NetBox interface using proper 3-step process."""
# Validate MAC
if not mac_address or mac_address == "00:00:00:00:00:00":
print(f"❌ Invalid MAC: {mac_address}")
return False
# Get device and interface
device = nb.dcim.devices.get(name=device_name)
interface = nb.dcim.interfaces.get(device_id=device.id, name=interface_name)
if not device or not interface:
print(f"❌ Device or interface not found")
return False
mac_upper = mac_address.upper()
# Step 1: Create/find MAC in repository
mac_records = list(nb.dcim.mac_addresses.filter(mac_address=mac_upper))
if mac_records:
mac_record = mac_records[0]
else:
mac_record = nb.dcim.mac_addresses.create({"mac_address": mac_upper})
# Step 2: Assign MAC to interface
mac_record.assigned_object_type = "dcim.interface"
mac_record.assigned_object_id = interface.id
mac_record.save()
# Step 3: Set as primary MAC
interface.primary_mac_address = mac_record.id
interface.save()
print(f"✅ Synced {mac_upper} to {device_name}:{interface_name}")
return True
# Usage
nb = pynetbox.api(NETBOX_URL, token=NETBOX_TOKEN)
sync_mac_to_interface(nb, "router01n01", "Ethernet95", "AA:AA:CD:DD:EE:FF")Getting MAC address sync right isn’t just about clean data – it enables:
🔍 Network Troubleshooting: Quickly find which device has a specific MAC address
📊 Accurate Reporting: Real visibility into your network inventory
🤖 Reliable Automation: Scripts that don’t break on edge cases
🔒 Security Tracking: Monitor MAC address movements across your network
Before deploying to production, test with edge cases:
# Test cases that commonly break automation
test_cases = [
{"mac": "AA:BB:CC:DD:EE:FF", "description": "Normal MAC"},
{"mac": "00:00:00:00:00:00", "description": "Invalid MAC"},
{"mac": "", "description": "Empty MAC"},
{"mac": "aa:bb:cc:dd:ee:ff", "description": "Lowercase MAC"},
{"mac": None, "description": "None MAC"}
]
for test in test_cases:
result = sync_interface_mac(nb, "test-device", "eth0", test["mac"])
print(f"{test['description']}: {'✅' if result else '❌'}")
If you’re just starting with NetBox MAC address management:
NetBox’s MAC address system seems complex at first, but it’s designed to handle real-world network complexity. The key insights:
✅ Use the two-step process: Repository first, then assignment
✅ Handle duplicates: Always use filter() instead of get()
✅ Validate input: Check for invalid MAC addresses
✅ Test edge cases: Your automation will encounter weird data
Once you understand these principles, MAC address synchronization becomes a reliable part of your network automation toolkit instead of a source of frustration.
Related Resources: