How do DUID/IAIDs work? (DHCP)

Does anyone know how a DHCP IAID is generated for a DUID-UUID?

The spec says it needs to be consistent across reboots and suggests that it either be stored persistently or algorithmically generated but doesn’t provide guidance on how to do that.

I assume in practice this means hashing the hardware MAC address of the interface, possibly along with some other stuff, but can’t find specific mention of it and I’m not savvy enough to find it in the code…

I want to manually generate a type 4 DUID. The only part I haven’t been able to figure out is the IAID section.

I suspect that NetworkManager, networkd, etc may generate these differently. Either/both/all implementations would be fine. I just want to do something that is already in practice.

Looks like it happens here (in networkd):

        if (name)
                id = siphash24(name, strlen(name), HASH_KEY.bytes);

If I had to guess, name is probably the systemd persistent interface naming (enps0 for instance) but I don’t know what these do:

#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09)


It’s also interesting to me that the DUID spec includes a header indicating the DUID type. Why does the DHCP server need to know about the DUID type at all? Each type should be unique and persistent which is all that the server should care about, right?

@PhaseLockedLoop you ever look into this?

Another relevant link:

Side quest (although I think I can work through this):

What is the correct way to convert the octal form of the DUID into hex?

I found this, but it doesn’t seem to work?

[email protected]:~$ cat /var/lib/dhcp/dhclient.eth2.leases | cut -d '"' -f 2
\000\001\000\001)\302\037A\010\000'\015\246\220
[email protected]:~$ cat /var/lib/dhcp/dhclient.eth2.leases | cut -d '"' -f 2 | hexdump -e '14/1 "%02x " "\n"' | sed 's/ /:/g'5c:30:30:30:5c:30:30:31:5c:30:30:30:5c:30
30:31:29:5c:33:30:32:5c:30:33:37:41:5c:30
31:30:5c:30:30:30:27:5c:30:31:35:5c:32:34
36:5c:32:32:30:0a::::::::::::::::::::::::

Yes your DHCP server has to know the DUID type because it changes the DUID length. Are you trying to locate it for your machine?

1 Like

Never mind saw above. What do you want to know? Its very essentially just a unique identifier that was supposed to replace the MAC address on ip6

I’m grabbing food right now so I’m on mobile atm

Your dhcp system is your firewall right? What software?

Also you should be able to get the DUID from var lib dhcpd in arch and rhel systems. I’m not entirely sure on BSD bare that in mind here

1 Like

I do know that the lengths are different, but I thought the original DUID spec said “up to” 128 bytes but maybe I’m wrong. I guess it would need to know the actual length regardless… or would it? Isn’t it basically a standardized client-identifier option?

I basically have 2 questions.

  1. Is there a generally accepted method for generating the IAID on Linux. If not or it really doesn’t matter, I’ll just hash the mac address.

  2. What is the correct/best way to convert between the hexadecimal and octal formats (both ways)?

Octal format being what’s stored in /var/lib/dhcp and hexadecimal format being the pairs of hex digits separated by colons which AFAIK is what you’d use for static assignments in dhcpd.conf.


The underlying issue I’m having is that I’m using DUIDs across my Linux hosts (even in IPv4 DHCP), but there’s no way to configure this in OpenBSD as far as I can tell. So I’d like to generate one myself and just pass it manually as the client identifier.

I guess this is my 3rd question. Is there any difference between configuring a dhcp client to use DUID and manually passing the DUID via option dhcp-client-identifier DUID?

1 Like

The difference is simply manual assignment. When I get home do you want me to show you how I did it? I feel its best if I don’t answer on my phone

1 Like

For sure, no rush.

Here we go:

2 Likes

Yes that will do it

I copied my script off of my linux systems

printf "$(cat /var/lib/dhclient/dhclient6.leases | awk -F\" '{print $2}')" | hexdump -e '14/1 "%02x " "\n"' | sed 's/ /:/g'

It combines the various threads into one thing it works on arch and rocky. Im not sure if its distro specific on the location of those lease files. The next thing I have set in OPNsense is I discovered I dont actually need to do that.

OPNsense automatically lets you know because most DHCP servers should have a leases functionality that lists DHCPv6 leases like this

@oO.o if you want to explore the topic indepth. RHEL if your subscribed to any kind of support or have an IN i heard has really good subscription documentation for it. Otherwise simple docs are available on fedoras docs and Arch wiki with RFCs referenced

https://wiki.archlinux.org/title/dhcpcd#DHCP_Client_Identifier

There are known bugs on Fedora and Rocky because the bug is inherent to RHEL 8+ based ecosystems

https://bugzilla.redhat.com/show_bug.cgi?id=1269093

https://bugzilla.redhat.com/show_bug.cgi?id=1154200#c25

https://bugzilla.redhat.com/show_bug.cgi?id=1154200#c53

They discuss these in their docs/wiki

https://fedoraproject.org/wiki/QA/Networking/Configuration#IPv6_configuration_procedure

and the reason for their change over RFC compliance

https://fedoraproject.org/wiki/Changes/DUID_UUID

My personal complaint is the lack of public knowledge documentation on it. This isnt an easy topic to dig for

2 Likes

Ah yes, you have to wrap it in printf or it doesn’t work.

Now htf am I going to do this in Ansible? I can’t figure out how to make it handle the escaped octal…

how is ansible setup? Im not familair but maybe another pair of eyes on the logic would help

1 Like

I can’t figure out a way to get it to understand the escaped octal version of the DUID in the dhcp lease file. The rest of it I could do. It’s Python under the hood, but the ord function is not available, so I can’t replicate this:

print(":".join("{:02x}".format(ord(x)) for x in duid ))

It’s fine, I’ll just have it run the hexdump command in the shell. It’s best practice to do data processing in Ansible, but it looks like it’s not possible unless I want to write my own filter which I don’t.


Actually, I think I’m just going to do this manually. Dealing with all these different implementations is more of a headache than just doing it myself.

Ok I think I did it…

  - debug:
      msg: "{{  ( '0004' + sys_sha256[:32] + iface_sha256[:8] )
                | regex_findall('..')
                | join(':') }}"
    vars:
      sys_sha256: "{{ ( ansible_system_vendor
                        + ansible_product_name
                        + ansible_product_uuid
                        + ansible_product_version
                        + ansible_product_serial )
                      | hash('sha256') }}"
    loop: "{{ phy_ifaces | map(attribute='hw_addr') | map('hash', 'sha256') }}"
    loop_control:
      loop_var: iface_sha256
TASK [debug] *********************************************************************************************************************************************************************************************************************************
ok: [debian11.hq.example.com] => (item=52e6d61e8f182f7cea9e8ba2aa889c93de932f2c03e7ea3a37fb63b9b8938af6) => {
    "msg": "00:04:d4:d6:5f:0d:ae:79:ea:72:b7:97:91:84:2d:d1:b4:c8:52:e6:d6:1e"
}
ok: [debian11.hq.example.com] => (item=4a4659b72901c41ede3ab5bff2c335a997e7dfc2607cae76a53dc93d64d60027) => {
    "msg": "00:04:d4:d6:5f:0d:ae:79:ea:72:b7:97:91:84:2d:d1:b4:c8:4a:46:59:b7"
}
ok: [debian11.hq.example.com] => (item=3d5d404f370b91d13915a14a21df2acf2de15757f81f18f5ff55c2b245fe5e8d) => {
    "msg": "00:04:d4:d6:5f:0d:ae:79:ea:72:b7:97:91:84:2d:d1:b4:c8:3d:5d:40:4f"
}

Am I right in thinking that the IAID is appended to the DUID?

Nope. IAID comes first, per your link earlier @PhaseLockedLoop

I’m actually not sure how to specify the client identifier type… let’s see.


   To send an RFC 3315-style binding identifier in a DHCPv4 'client
   identifier' option, the type of the 'client identifier' option is set
   to 255.  The type field is immediately followed by the IAID, which is
   an opaque 32-bit quantity.  The IAID is immediately followed by the
   DUID, which consumes the remaining contents of the 'client
   identifier' option.  The format of the 'client identifier' option is
   as follows:

       Code  Len  Type  IAID                DUID
       +----+----+-----+----+----+----+----+----+----+---
       | 61 | n  | 255 | i1 | i2 | i3 | i4 | d1 | d2 |...
       +----+----+-----+----+----+----+----+----+----+---

Nothing about a “type” for the client-identifier option in dhcp-options, do I literally put 255 at the beginning of the identifier (I’m assuming it should be ff unless it completely breaks with the rest of it)?

option dhcp-client-identifier ff:3d:5d:40:4f:00:04:d4:d6:5f:0d:ae:79:ea:72:b7:97:91:84:2d:d1:b4:c8;

ff will usually not result in breakage but you wont issue the right DUID. Issue your DUID in the client identifier option as it would be in your interface (IIRC) … im pretty sure the code Len and Type fields are already predefined in the DHCP packet and your placing just the IAID+DUID into the field to give it to the client.

TFW its been so long since youve messed with DHCPv6 (mostly set forget) that your relearning LOL

1 Like

That makes sense but does is the type field indicating that I’m using IAID/DUID? How does the dhcp client process understand that if I’m passing it in manually via the client-identifier option?

There’s some mention of the type field being different if you’re using say an fqdn, but definitely not clear if the dhcp client process is trying to parse it to figure this out or what.

Additionally, if the length field is there, then that brings me back to why we need to prefix on the DUID.

Oh this is a hard question. Your busting through my lower level of understanding of it… hmmm

Okay first so you have it: (CTRL F to STANDARD DHCPV6 OPTIONS)
https://www.linux.org/docs/man5/dhcp-options.html

I wouldnt approach it this way I would have the devices handle it via send dhcp-client-identifier = hardware and heres why. You can disagree with this path but as I understand it you are trying to set the DUID via the DHCP server and by convention its supposed to be left to the hardware to handle autonomously and without our input.

Per the man pages:

       option dhcp6.client-id string;

         This option specifies the client's DUID identifier.  DUIDs are similar  but  different  from  DHCPv4  client
         identifiers - there are documented duid types:

         duid-llt

         duid-en

         duid-ll

         This  value  should not be configured, but rather is provided by clients and treated as an opaque identifier key blob by servers.

So if im misunderstanding what you are doing. Can you elaborate more for me

1 Like

So first thing, I am not using IPv6 at this point at all which adds a bit to this confusion. But as RFC 4361 points out, it’s a good idea to use IAID/DUID on IPv4 DHCP, and doing so should put me in a better position to adopt IPv6 later.

Within that, the issue is that as far as I can tell, OpenBSD does not support IAID/DUID on dhcp4. There’s no mention of it in the dhclient docs or anything. But since I’m using it across all of the Linux hosts, I wanted to standardize and use it on OpenBSD also. So I was like, hey, I can just generate it myself and pass it via client-identifier. Then I was having the issue getting the escaped octal values out of the lease files on Linux. So THEN I figured I would just do it manually across the board.

But I think you’re right, the dhcp client service needs to configure the DUID so that it can set the correct client-identifier type. BUT, does that really matter? I don’t understand why the server needs to know what kind of identifier it is in the first place. Just accept that it’s the identifier and use it…