Stable Thunderbolt network interface names

Hi folks,

  1. I’m facing an issue with configuring Thunderbolt network interfaces. Specifically, the following configuration works initially:
networking.interfaces = {
  thunderbolt0.ipv4.addresses = [
    {
      address = "10.0.1.1";
      prefixLength = 24;
    }
  ];
  thunderbolt1.ipv4.addresses = [
    {
      address = "10.0.2.1";
      prefixLength = 24;
    }
  ];
};

However, when I plug just the 2nd interface (thunderbolt1 in the above config), it shows up in the system as thunderbolt0, and thus has a mismatched IP wrt my client that’s connected to it over Thunderbolt.

How could I create stable network names for these?

I should note that initially, they show up in my system as such:

readlink -f /sys/class/net/thunderbolt0/device
/sys/devices/pci0000:00/0000:00:1b.4/0000:03:00.0/0000:04:00.0/0000:05:00.0/domain0/0-0/0-3/0-3.0

readlink -f /sys/class/net/thunderbolt1/device
/sys/devices/pci0000:00/0000:00:1b.4/0000:03:00.0/0000:04:00.0/0000:05:00.0/domain0/0-0/0-1/0-1.0

and if I plug just one of them, those paths remain stable.

I tried playing with services.udev.extraRules and systemd.network.links, but didn’t get far. Any advice on these or any other approaches truly appreciated!

  1. Alternatively, I tried creating a network bridge using
networking.bridges.thunderbolt.interfaces = ["thunderbolt0" "thunderbolt1"];
networking.interfaces.thunderbolt.ipv4.addresses = [
  {
      address = "10.0.1.1";
      prefixLength = 24;
    }
 ];

And that works great (and avoids the problem of relying on stable underlying interface names). However, when I plug in just one of Thunderbolt clients, it shows up as thunderbolt0, and then thunderbolt bridge fails to be brought up, because thunderbolt1 is missing. Is it possible to avoid its hard reliance on both underlying interfaces being present?

Thank you!

You should assign stable names using the MAC address of the interface, see NixOS Manual

1 Like

Thank you - I applied systemd.network.links, but still getting the one with physical MAC in 40-thudnerbolt1 rule named as thunderbolt0 when I only plug in that one…

OK, thx to your tip I was able to finally solve this using:

  systemd.network.links = {
    "40-thunderbolt0".enable = false;
    "40-thunderbolt1".enable = false;
    "41-thunderbolt0" = {
      matchConfig.PermanentMACAddress = "xx:xx:xx:xx:xx:xx";
      linkConfig.Name = "thunderbolt0";
    };
    "41-thunderbolt1" = {
      matchConfig.PermanentMACAddress = "yy:yy:yy:yy:yy:yy";
      linkConfig.Name = "thunderbolt1";
    };
  };

It turns out 40-thunderbolt0 and 40-thunderbolt1 links are created by NixOS by default, resulting in the following message in systemd-udevd.service logs:

thunderbolt0: Config file /etc/systemd/network/40-thunderbolt0.link is applied to device based on potentially unpredictable interface name.

I tried adjusting 40-thunderbolt0 and 40-thunderbolt1 directly as I did above for 41-thunderbolt0 and 41-thunderbolt1, but that doesn’t result in rename. So I had to actually disable those old links and create new ones, it seems.

Now, after I plug the 2nd client without the 1st, I see the following in sudo dmesg -f:

[  760.318808] thunderbolt 0-1: new host found, vendor=0x1 device=0x1
[  760.318820] thunderbolt 0-1: Intel Corp. PROART
[  760.332075] thunderbolt-net 0-1.0 thunderbolt1: renamed from thunderbolt0

so the renaming works properly.

The only other issue I noticed is I have to manually run

sudo systemctl restart network-addresses-thunderbolt1.service

each time I re-plug the cable, but I’m assuming it’s a separate issue. Perhaps this could be automated with udev too.

EDIT: actually solved this last issue as well:

services.udev.extraRules = ''
    SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="xx:xx:xx:xx:xx:xx", TAG+="systemd", ENV{SYSTEMD_WANTS}="network-addresses-thunderbolt0.service"
    SUBSYSTEM=="net", ACTION=="add", ATTR{address}=="yy:yy:yy:yy:yy:yy", TAG+="systemd", ENV{SYSTEMD_WANTS}="network-addresses-thunderbolt1.service"
  '';

Not sure if there’s a more elegant way, but that’s good enough.

EDIT2: I spoke too soon, looks like those last udev rules restart network-addresses-thunderbolt0.service but not network-addresses-thunderbolt1.service after rename. The logs for network-addresses-thunderbolt1.service say

Mar 19 22:11:13 homelab systemd[1]: Stopping Address configuration of thunderbolt1...
Mar 19 22:11:13 homelab network-addresses-thunderbolt1-pre-stop[9550]: deleting address 10.0.2.1/24... failed
Mar 19 22:11:13 homelab systemd[1]: network-addresses-thunderbolt1.service: Deactivated successfully.
Mar 19 22:11:13 homelab systemd[1]: Stopped Address configuration of thunderbolt1.
Mar 19 22:16:21 homelab systemd[1]: Dependency failed for Address configuration of thunderbolt1.
Mar 19 22:16:21 homelab systemd[1]: network-addresses-thunderbolt1.service: Job network-addresses-thunderbolt1.service/start failed with result 'dependency'.

OK, I finally figured it out - had to read the wiki page here: systemd-networkd - NixOS Wiki

{...}: let
  iface = {
    thunderbolt0 = {
      mac = "xx:xx:xx:xx:xx:xx";
      addr = "10.0.1.1/24";
    };
    thunderbolt1 = {
      mac = "yy:yy:yy:yy:yy:yy";
      addr = "10.0.2.1/24";
    };
  };
in {
  systemd.network = {
    enable = true;
    networks = let
      network = name: {
        matchConfig.PermanentMACAddress = iface.${name}.mac;
        networkConfig.Address = iface.${name}.addr;
      };
    in {
      "41-thunderbolt0" = network "thunderbolt0";
      "41-thunderbolt1" = network "thunderbolt1";
    };
    links = let
      link = name: {
        matchConfig.PermanentMACAddress = iface.${name}.mac;
        linkConfig.Name = name;
      };
    in {
      "40-thunderbolt0".enable = false;
      "40-thunderbolt1".enable = false;
      "41-thunderbolt0" = link "thunderbolt0";
      "41-thunderbolt1" = link "thunderbolt1";
    };
  };
}
1 Like