Unique MAC address for each VLAN interface

Before I go digging into the code to figure out where this is done and doing some hacky patch...

I have a situation where I need my VLAN interfaces to have unique MAC addresses. Has anyone ever dealt with this before?

The device I am dealing with uses a switch between the single SoC network interface and it's external/physical ports. The result is that both ports have the same MAC because they are eth0.1, eth02, and so on. I need a VRRP-like setup to a pair of redundant uplink switches.

This works fine on my RPi4.

config interface 'lan'
	option proto 'static'
	option netmask ''
	option ipaddr ''
	option ip6assign '60'
	option force_link '0'
	option ip6ifaceid '::1'
	option ifname 'eth0.4'
	list dns ''
	list dns ''

config interface 'iot'
	option proto 'static'
	option netmask ''
	option macaddr 'EE:EE:EE:EE:EE:EE'
	option ipaddr ''
	option ifname 'eth0.3'
	option force_link '0'
	option ip6ifaceid 'eui64'
	option ip6assign '64'
	option ip6hint '30'
	option type 'bridge'

config interface 'guest'
	option proto 'static'
	option netmask ''
	option macaddr 'FF:FF:FF:FF:FF:FF'
	option ipaddr ''
	option ip6ifaceid 'random'
	option ifname 'eth0.2'
	option force_link '0'
	option ip6assign '64'
	option ip6hint '17'
	option ip6class 'wan6'

lan is using the default MAC.


I would not have posted in the dev section if I needed user-level help. Manual configuration isn't desirable. I will have about 30 of these going out into the field.

Write script to have a combination of vlanid and default Mac.

Or whatever algorithm you wish.

The previous post was very informative

1 Like

Use /etc/uci-defaults to pre-configure them on first boot?


When building from source - custom files can be placed in <buildroot dir>/files/ . [1]

Before building the image - include a hot plug script e.g. In <buildroot dir>/files/etc/hotplug.d/iface/21-lan. In there, you could create the VLAN interfaces, assign custom addresses to each, etc. Without knowing your use case I couldn't provide anything specific, but an example could be as shown below.

P.S. Below example relies on arrays and needs bash as ash doesn’t support arrays.
P.S.S The example is for DSA devices; the ones with swconfig would need to be configured differently, though the concept remains the same.

$ cat /etc/hotplug.d/iface/21-lan


[ "$INTERFACE" = lan ] && [ "$ACTION" = ifup ] || exit 0

physical_interfaces=('eth0' 'eth1' 'eth2' 'eth3' 'eth4')
iface_vlans=('10' '100' '105' '150' '200')

for (( i=0; i<"$number_of_vlans"; i++ )); do

  ip link add link "${physical_interfaces["$i"]}" name \
    "${physical_interfaces["$i"]}"."${iface_vlans["$i"]}" \
  type vlan id "${iface_vlans["$i"]}"
  ip link set dev "${physical_interfaces["$i"]}"."${iface_vlans["$i"]}" \
    address ff:ff:ff:ff:ff:f"$i"

This example presumes there is one VLAN per interface, but can be modified to your needs. Testing the output - works as expected. Hope this helps!

$ cat ~/Documents/simple_vlan_setup.sh

#!/usr/bin/env bash

physical_interfaces=('eth0' 'eth1' 'eth2' 'eth3' 'eth4')
iface_vlans=('10' '100' '105' '150' '200')

for ((i = 0; i < "$number_of_vlans"; i++)); do
  printf %b\\n "
ip link add link ${physical_interfaces[$i]} name \
${physical_interfaces["$i"]}.${iface_vlans["$i"]} \
type vlan id ${iface_vlans["$i"]}
ip link set dev ${physical_interfaces["$i"]}.${iface_vlans["$i"]} \
address ff:ff:ff:ff:ff:f$i

$ bash ~/Documents/simple_vlan_setup.sh

ip link add link eth0 name eth0.10 type vlan id 10
ip link set dev eth0.10 address ff:ff:ff:ff:ff:f0

ip link add link eth1 name eth1.100 type vlan id 100
ip link set dev eth1.100 address ff:ff:ff:ff:ff:f1

ip link add link eth2 name eth2.105 type vlan id 105
ip link set dev eth2.105 address ff:ff:ff:ff:ff:f2

ip link add link eth3 name eth3.150 type vlan id 150
ip link set dev eth3.150 address ff:ff:ff:ff:ff:f3

ip link add link eth4 name eth4.200 type vlan id 200
ip link set dev eth4.200 address ff:ff:ff:ff:ff:f4

[1]. https://openwrt.org/docs/guide-developer/build-system/use-buildsystem

1 Like

This is a great way to go, since it can do anything you can do with a standard BASH script, runs, then deletes itself so it doesn't remain on the device.

It is a part of the image, so it stays on the device under /rom. They are marked as deleted, but they are still there.

In case anyone comes along and reads this in the future:

hotplug.d isn't a terrible idea, but won't work in many situations. It's non-blocking and netifd has already started by that time, so the interface could already be in use by a DHCP client, hostapd, or something else. This is why some devices which have to fix their MAC addresses use a script in /lib/preinit. Preinit gets run even before the overlay gets mounted, so forget about anything not built into the image itself. This is also why changing MAC addresses, especially for 802.11 interfaces, is usually not possible in configuration.

uci-defaults isn't a bad idea but I also think it gets run too late, but I'm not sure. I'll look into that.

As a temporary solution we wrote a kernel patch that assures unique MACs for new interfaces. In the long term we will need to do something else. The ideal solution is to fix the device config, probably in the DTS, to give each unique hardware interface it's own MAC address.

It runs after the first boot to set everything up before any interfaces are brought up. It only runs once and then the files are deleted. They are executed by /etc/init.d/boot, which is starting before the network.