Page MenuHomeVyOS Platform

Conditional DHCP option
Closed, ResolvedPublicFEATURE REQUEST

Description

In a Mikrotik environment, I use a conditional DHCP server where, for each Yealink IP phone model, a provisioning link is used
In a setup with multiple different IP phone models, Mikrotik’s ‘matcher’ feature is brilliant
Because using option 66 for an entire subnet isn’t suitable (even if the number of models isn’t huge – usually 3–4 models)
I believe Kea DHCP allows for this sort of thing, but it doesn’t seem to have been implemented via the CLI yet.
Is that correct?
Could this be possible in a future version of VyOS?
Here’s an example of a Mikrotik configuration that works very well

ip dhcp-server add interface=ether4 name="DHCP Server P" address-pool=DHCP_z172_P lease-time=7d add-arp=no
ip dhcp-server network add address=172.31.34.0/24 dns-server=1.1.1.1,1.0.0.1 gateway=172.31.34.1 netmask=24
ip dhcp-server option add name=Yealink_T42S code=66 value=0x68747470733a2f2f7777772e6578616d706c652e62652f646d732f5965616C696E6B543432532F comment="https://ccdms.proximus.be/dms/YealinkT42S/"
ip dhcp-server option add name=Yealink_T54W code=66 value=0x68747470733a2f2f7777772e6578616d706c652e62652f646d732f5965616C696E6B543534572F comment="https://ccdms.proximus.be/dms/YealinkT54W/"
ip dhcp-server option add name=Yealink_T73U code=66 value=0x68747470733a2f2f7777772e6578616d706c652e62652f646d732f5965616C696E6B543733552F comment="https://ccdms.proximus.be/dms/YealinkT73U/"
ip dhcp-server option add name=Yealink_T74U code=66 value=0x68747470733a2f2f7777772e6578616d706c652e62652f646d732f5965616C696E6B543734552F comment="https://ccdms.proximus.be/dms/YealinkT74U/"
ip dhcp-server option add name=Yealink_W70B code=66 value=0x68747470733a2f2f7777772e6578616d706c652e62652f646d732f5965616C696E6B573730422F comment="https://ccdms.proximus.be/dms/YealinkW70B/"
ip dhcp-server option sets add name=set_Yealink_T42S options=Yealink_T42S
ip dhcp-server option sets add name=set_Yealink_T54W options=Yealink_T54W
ip dhcp-server option sets add name=set_Yealink_T73U options=Yealink_T73U
ip dhcp-server option sets add name=set_Yealink_T74U options=Yealink_T74U
ip dhcp-server option sets add name=set_Yealink_W70B options=Yealink_W70B
ip dhcp-server matcher add server="DHCP Server P" address-pool=DHCP_z172_P name=match_Yealink_T42S code=12 matching-type=substring value=T42S option-set=set_Yealink_T42S
ip dhcp-server matcher add server="DHCP Server P" address-pool=DHCP_z172_P name=match_Yealink_T54W code=12 matching-type=substring value=T54W option-set=set_Yealink_T54W
ip dhcp-server matcher add server="DHCP Server P" address-pool=DHCP_z172_P name=match_Yealink_T73U code=12 matching-type=substring value=T73U option-set=set_Yealink_T73U
ip dhcp-server matcher add server="DHCP Server P" address-pool=DHCP_z172_P name=match_Yealink_T74U code=12 matching-type=substring value=T74U option-set=set_Yealink_T74U
ip dhcp-server matcher add server="DHCP Server P" address-pool=DHCP_z172_P name=match_Yealink_W70B code=12 matching-type=substring value=W70B option-set=set_Yealink_W70B

Thanks

Details

Version
1.5
Is it a breaking change?
Unspecified (possibly destroys the router)
Issue type
Feature (new functionality)

Event Timeline

DuxOv8 created this object in space S1 VyOS Public.

Sorry, I forgot to say hello

Implemented conditional DHCP options via client classification, extending the existing client-class framework (previously limited to Option 82 relay-agent matching):

  • client-class <name> hostname value|substring ... — match on the client's DHCP hostname (Option 12)
  • client-class <name> vendor-class-id value|substring ... — match on the vendor class identifier (Option 60)
  • A client-class can now deliver its own options directly (reusing the existing subnet/range option syntax)

    This covers the reported use case directly: multiple device models sharing one subnet can each get a different Option 66 (or any other option) based on a substring of their hostname, without splitting the subnet into per-model pools.

    PRs:
  • vyos-1x (implementation + smoketest): https://github.com/vyos/vyos-1x/pull/5436
  • vyos-documentation: https://github.com/vyos/vyos-documentation/pull/2227

    Example config in the documentation PR shows the Yealink-style scenario from this ticket.
Viacheslav changed the task status from Open to In progress.Aug 31 2026, 12:07 PM
Viacheslav assigned this task to rherold.
Viacheslav triaged this task as Normal priority.

Hello all
Thanks for response but i have a bug

set service dhcp-server client-class Yealink_T42S hostname substring value 'T42S'

vyos@vyos# set service dhcp-server client-class Yealink_T42S hostname substring value 'T42S'

  Configuration path: service dhcp-server client-class Yealink_T42S [hostname] is not valid
  Set failed

[edit]
vyos@vyos#

And with tab

vyos@vyos# set service dhcp-server client-class Yealink_T42S 
Possible completions:
   disable              Disable instance
 > relay-agent-information
                        Match DHCP Option 82 (relay agent information)

      
[edit]
vyos@vyos# set service dhcp-server client-class Yealink_T42S 

vyos@vyos# run show version 
Version:          VyOS 2026.10.07-0712-rolling
Release train:    rolling
Release flavor:   generic

Built by:         autobuild@vyos.net
Built on:         Wed 07 Oct 2026 09:52 UTC
Build UUID:       dba9f656-4a45-4687-bc9e-5451fb086ab4
Build commit ID:  

Architecture:     x86_64
Boot via:         installed image
System type:      bare metal
Secure Boot:      n/a (BIOS)

Hardware vendor:  Dell Inc.
Hardware model:   OptiPlex 3040
Hardware S/N:     F28JXJ2
Hardware UUID:    4c4c4544-0032-3810-804a-c6c04f584a32

Copyright:        VyOS maintainers and contributors
[edit]
vyos@vyos#

Or maybe it hasn't been implemented in the code yet

@DuxOv8 as you could see on the PR link is it not merged yet.

Viacheslav moved this task from Need Triage to Completed on the VyOS Rolling board.
Viacheslav subscribed.

@DuxOv8 feel free to test/check it. It will be available in the next rolling release (after the 15th of October)

Great @Viacheslav ! I'll wait for the next rolling release to test this new feature. Thank you both (you and @rherold) and VyOS Team

Hi all

The test/labo is not ok but CLI is OK with hexa

Just to remind you, here's my Mikrotik setup OK

ip dhcp-server option add    name=Yealink_T54W        code=66   value=0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54353457            comment="https://provisionning.myserver.be/dms/Yealink/T54W"
ip dhcp-server option add    name=Yealink_T87W        code=66   value=0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54383757            comment="https://provisionning.myserver.be/dms/Yealink/T87W"
ip dhcp-server option sets add    name=set_Yealink_T54W    options=Yealink_T54W
ip dhcp-server option sets add    name=set_Yealink_T87W    options=Yealink_T87W
ip dhcp-server matcher add   server="DHCP Server Proximus"      address-pool=DHCP_z172_Proximus_A  name=match_Yealink_T54W  code=12   matching-type=substring  value=T54W               option-set=set_Yealink_T54W
ip dhcp-server matcher add   server="DHCP Server Proximus"      address-pool=DHCP_z172_Proximus_A  name=match_Yealink_T87W  code=12   matching-type=substring  value=T87W               option-set=set_Yealink_T87W

Option 12 hostname is a substring name because with wireshark, the real name is sip-t87w and sip-t54w
uppercase letter or lowercase letter is not a problem with Mikrotik (i think ...)

And this my VyOS config :

set service dhcp-server client-class Yealink_T54W hostname substring value 'T54W'
set service dhcp-server client-class Yealink_T87W hostname substring value 'T87W'
set service dhcp-server client-class Yealink_T54W option tftp-server-name '0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54353457'
set service dhcp-server client-class Yealink_T87W option tftp-server-name '0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54383757'
set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class Yealink_T54W
set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class Yealink_T87W
commit

Option 66 must be an URL

If I encode the CLI using the URL in ASCII instead of hex, i have an error

vyos@vyos# set service dhcp-server client-class Yealink_T87W option tftp-server-name 'https://provisionning.myserver.be/dms/Yealink/T54W'
  
  Error: https://provisionning.myserver.be/dms/Yealink/T54W is not a valid IPv4 address
  
  Invalid value
  Value validation failed
  Set failed

[edit]

Second problem: If I install a large number of Yealink phones of different models at a customer's site, I need to be able to set up multiple client classes on my DHCP server.
However, the CLI command set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class only accepts one.

VyOs only keeps the latest CLI
set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class Yealink_T87W
That makes sense

Unfortunately, with my setup,

set service dhcp-server client-class Yealink_T54W hostname substring value 'T54W'
set service dhcp-server client-class Yealink_T87W hostname substring value 'T87W'
set service dhcp-server client-class Yealink_T54W option tftp-server-name '0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54353457'
set service dhcp-server client-class Yealink_T87W option tftp-server-name '0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54383757'
set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class Yealink_T54W
set service dhcp-server shared-network-name 172_PROXIMUS subnet 172.31.32.0/22 client-class Yealink_T87W
commit

The Yealink phone do not recognize the option 66 query, even though the hostname 12 query is recognized correctly.

Yealink write the 0x68747470733A2F2F70726F766973696F6E6E696E672E6D797365727665722E62652F646D732F5965616C696E6B2F54383757 in the field "DHCP Server URL" but not in the field "Server URL" (view image name VyOS.png)
With Mikrotik CLI, Yealink is OK (view image Mikrotik.png)

Sorry but thanks for help

I'd really love to see this feature on VyOS because, on Mikrotik, it's convenient to automatically set up multiple Yealink phones of different models using the DHCP options.
Especially when a client orders 5–6 different models for their company.
But I also understand that assigning different URLs to each phone might not be the best solution. Still, I don't know how to get our engineers to change that...

VyOS.png (636×1 px, 46 KB)

Mikrotik.png (634×1 px, 47 KB)

The default server in the yealink phone is normal and this is the default URL after a factory reset (https://updateyealink.lydis.com ....

In the screenshot, I used the actual URL
In my previous post, I included an example
But option 66 must be a URL at my company

Thanks for testing, @DuxOv8.

One client-class per subnet

Right, subnet client-class is a single value in the VyOS CLI, and it restricts the whole subnet to that class. To serve several models from one subnet today, put the class on a range and the options next to it. Each range becomes its own Kea pool with its own client-class and option-data:

set service dhcp-server client-class Yealink_T54W hostname substring value 'T54W'
set service dhcp-server client-class Yealink_T46U hostname substring value 'T46U'

set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 1 start 192.0.2.10
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 1 stop 192.0.2.49
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 1 client-class Yealink_T54W
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 1 option tftp-server-name <value>

set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 2 start 192.0.2.50
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 2 stop 192.0.2.99
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 2 client-class Yealink_T46U
set service dhcp-server shared-network-name LAN subnet 192.0.2.0/24 range 2 option tftp-server-name <value>

The catch is that every class needs its own address range, they cannot share one pool. That is a VyOS limit, not a Kea one: Kea can attach option-data to a client class itself, which would apply to all members regardless of the pool, but VyOS only renders the class name and its match expression today. Options on a client class (and possibly a list of classes per subnet) would be a new feature. I can open a separate task for that if you want it.

ASCII URL in tftp-server-name

This is separate from the client-class matching and was already like this before. The VyOS option only accepts an IPv4 address or an FQDN, so http://... is rejected. Kea itself does not require that, option 66 is a plain string there. I see the validator as too strict for how option 66 is used in practice (provisioning URLs). I will open a separate task for it and prepare a fix.

Yealink shows a hex value instead of the URL

Short version: the hex workaround cannot work with VyOS. I think this is a consequence of the previous point and not a bug in the matching. Since a URL is rejected, the hex-encoded URL was set as tftp-server-name. The fqdn validator only checks [A-Za-z0-9][-.A-Za-z0-9]*, so a string of hex digits (also with a leading 0x) is accepted as a "hostname". VyOS and Kea then send those characters as plain text. The phone gets the text 687474703a2f2f... in option 66 and shows exactly that. On the Mikrotik the same hex value is encoded as raw bytes, so the phone receives the real URL.

With a validator that allows URLs, the plain URL would be sent as the option 66 string. Could you confirm with a capture (tcpdump -vvv -ni <if> port 67 or port 68) that option 66 contains the hex digits as text? If so, the fix for the URL point is also the fix for the Yealink case.

Thanks @rherold

Options on a client class (and possibly a list of classes per subnet) would be a new feature. I can open a separate task for that if you want it.

If it would be of interest to the VyOS community, if there is demand from other IT professionals, and if my example is useful, I’d be happy to
I’m not forcing anyone 😉

That’s it for your tcpdump request

vyos@vyos:~$ tcpdump -vvv -ni eth3 port 67
tcpdump: listening on eth3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
13:58:02.190610 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 576)
    0.0.0.0.68 > 255.255.255.255.67: [udp sum ok] BOOTP/DHCP, Request from c4:fc:22:9d:fa:98, length 548, xid 0x8a22b869, secs 100, Flags [none] (0x0000)
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Discover
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            Unknown (125), length 41: 0.0.13.233.36.1.6.99.52.102.99.50.50.2.16.56.48.49.50.50.55.74.48.52.48.48.48.50.55.51.51.3.8.83.73.80.45.84.56.55.87
            MSZ (57), length 2: 576
            Parameter-Request (55), length 19: 
              Subnet-Mask (1), Time-Zone (2), Default-Gateway (3), Time-Server (4)
              Domain-Name-Server (6), LOG (7), Hostname (12), Domain-Name (15)
              BR (28), NTP (42), TFTP (66), BF (67)
              Vendor-Option (43), Unknown (252), POSIX-TZ (100), TZ-Name (101)
              Unknown (120), Unknown (132), Unknown (133)
            Hostname (12), length 8: "SIP-T87W"
            Vendor-Class (60), length 7: "yealink"
            END (255), length 0
            PAD (0), length 0, occurs 208
13:58:02.192619 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 413)
    172.31.34.1.67 > 172.31.32.1.68: [udp sum ok] BOOTP/DHCP, Reply, length 385, xid 0x8a22b869, Flags [none] (0x0000)
          Your-IP 172.31.32.1
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Offer
            Subnet-Mask (1), length 4: 255.255.252.0
            Default-Gateway (3), length 4: 172.31.34.1
            Domain-Name-Server (6), length 8: 1.1.1.1,1.0.0.1
            Hostname (12), length 8: "sip-t87w"
            Lease-Time (51), length 4: 75786
            Server-ID (54), length 4: 172.31.34.1
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            TFTP (66), length 86: "0x68747470733A2F2F6363646D732E70726F78696D75732E62652F646D732F5965616C696E6B2F54383757"
            END (255), length 0
13:58:02.216779 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 576)
    0.0.0.0.68 > 255.255.255.255.67: [udp sum ok] BOOTP/DHCP, Request from c4:fc:22:9d:fa:98, length 548, xid 0x8a22b869, secs 100, Flags [none] (0x0000)
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Request
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            Requested-IP (50), length 4: 172.31.32.1
            Server-ID (54), length 4: 172.31.34.1
            Unknown (125), length 41: 0.0.13.233.36.1.6.99.52.102.99.50.50.2.16.56.48.49.50.50.55.74.48.52.48.48.48.50.55.51.51.3.8.83.73.80.45.84.56.55.87
            Parameter-Request (55), length 19: 
              Subnet-Mask (1), Time-Zone (2), Default-Gateway (3), Time-Server (4)
              Domain-Name-Server (6), LOG (7), Hostname (12), Domain-Name (15)
              BR (28), NTP (42), TFTP (66), BF (67)
              Vendor-Option (43), Unknown (252), POSIX-TZ (100), TZ-Name (101)
              Unknown (120), Unknown (132), Unknown (133)
            Hostname (12), length 8: "SIP-T87W"
            Vendor-Class (60), length 7: "yealink"
            END (255), length 0
            PAD (0), length 0, occurs 200
13:58:02.218520 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 413)
    172.31.34.1.67 > 172.31.32.1.68: [udp sum ok] BOOTP/DHCP, Reply, length 385, xid 0x8a22b869, Flags [none] (0x0000)
          Your-IP 172.31.32.1
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: ACK
            Subnet-Mask (1), length 4: 255.255.252.0
            Default-Gateway (3), length 4: 172.31.34.1
            Domain-Name-Server (6), length 8: 1.1.1.1,1.0.0.1
            Hostname (12), length 8: "sip-t87w"
            Lease-Time (51), length 4: 75786
            Server-ID (54), length 4: 172.31.34.1
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            TFTP (66), length 86: "0x68747470733A2F2F6363646D732E70726F78696D75732E62652F646D732F5965616C696E6B2F54383757"
            END (255), length 0
q^H^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel
vyos@vyos:~$ tcpdump -vvv -ni eth3 port 68
tcpdump: listening on eth3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
14:00:47.613327 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 576)
    0.0.0.0.68 > 255.255.255.255.67: [udp sum ok] BOOTP/DHCP, Request from c4:fc:22:9d:fa:98, length 548, xid 0x98789a53, secs 100, Flags [none] (0x0000)
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Discover
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            Unknown (125), length 41: 0.0.13.233.36.1.6.99.52.102.99.50.50.2.16.56.48.49.50.50.55.74.48.52.48.48.48.50.55.51.51.3.8.83.73.80.45.84.56.55.87
            MSZ (57), length 2: 576
            Parameter-Request (55), length 19: 
              Subnet-Mask (1), Time-Zone (2), Default-Gateway (3), Time-Server (4)
              Domain-Name-Server (6), LOG (7), Hostname (12), Domain-Name (15)
              BR (28), NTP (42), TFTP (66), BF (67)
              Vendor-Option (43), Unknown (252), POSIX-TZ (100), TZ-Name (101)
              Unknown (120), Unknown (132), Unknown (133)
            Hostname (12), length 8: "SIP-T87W"
            Vendor-Class (60), length 7: "yealink"
            END (255), length 0
            PAD (0), length 0, occurs 208
14:00:47.615037 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 413)
    172.31.34.1.67 > 172.31.32.1.68: [udp sum ok] BOOTP/DHCP, Reply, length 385, xid 0x98789a53, Flags [none] (0x0000)
          Your-IP 172.31.32.1
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Offer
            Subnet-Mask (1), length 4: 255.255.252.0
            Default-Gateway (3), length 4: 172.31.34.1
            Domain-Name-Server (6), length 8: 1.1.1.1,1.0.0.1
            Hostname (12), length 8: "sip-t87w"
            Lease-Time (51), length 4: 75621
            Server-ID (54), length 4: 172.31.34.1
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            TFTP (66), length 86: "0x68747470733A2F2F6363646D732E70726F78696D75732E62652F646D732F5965616C696E6B2F54383757"
            END (255), length 0
14:00:47.634254 IP (tos 0x0, ttl 64, id 0, offset 0, flags [none], proto UDP (17), length 576)
    0.0.0.0.68 > 255.255.255.255.67: [udp sum ok] BOOTP/DHCP, Request from c4:fc:22:9d:fa:98, length 548, xid 0x98789a53, secs 100, Flags [none] (0x0000)
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: Request
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            Requested-IP (50), length 4: 172.31.32.1
            Server-ID (54), length 4: 172.31.34.1
            Unknown (125), length 41: 0.0.13.233.36.1.6.99.52.102.99.50.50.2.16.56.48.49.50.50.55.74.48.52.48.48.48.50.55.51.51.3.8.83.73.80.45.84.56.55.87
            Parameter-Request (55), length 19: 
              Subnet-Mask (1), Time-Zone (2), Default-Gateway (3), Time-Server (4)
              Domain-Name-Server (6), LOG (7), Hostname (12), Domain-Name (15)
              BR (28), NTP (42), TFTP (66), BF (67)
              Vendor-Option (43), Unknown (252), POSIX-TZ (100), TZ-Name (101)
              Unknown (120), Unknown (132), Unknown (133)
            Hostname (12), length 8: "SIP-T87W"
            Vendor-Class (60), length 7: "yealink"
            END (255), length 0
            PAD (0), length 0, occurs 200
14:00:47.635970 IP (tos 0x10, ttl 128, id 0, offset 0, flags [DF], proto UDP (17), length 413)
    172.31.34.1.67 > 172.31.32.1.68: [udp sum ok] BOOTP/DHCP, Reply, length 385, xid 0x98789a53, Flags [none] (0x0000)
          Your-IP 172.31.32.1
          Client-Ethernet-Address c4:fc:22:9d:fa:98
          Vendor-rfc1048 Extensions
            Magic Cookie 0x63825363
            DHCP-Message (53), length 1: ACK
            Subnet-Mask (1), length 4: 255.255.252.0
            Default-Gateway (3), length 4: 172.31.34.1
            Domain-Name-Server (6), length 8: 1.1.1.1,1.0.0.1
            Hostname (12), length 8: "sip-t87w"
            Lease-Time (51), length 4: 75621
            Server-ID (54), length 4: 172.31.34.1
            Client-ID (61), length 7: ether c4:fc:22:9d:fa:98
            TFTP (66), length 86: "0x68747470733A2F2F6363646D732E70726F78696D75732E62652F646D732F5965616C696E6B2F54383757"
            END (255), length 0
^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel
vyos@vyos:~$

Thanks for the capture, that settles it.

The hostname match works (the OFFER/ACK go out for SIP-T87W), and option 66 carries the hex string as plain ASCII text, so the Yealink shows it literally. The hex workaround cannot work with VyOS because the value is sent as characters, not decoded to bytes.

The fix is in T9467 / https://github.com/vyos/vyos-1x/pull/5563: tftp-server-name will accept http, https, ftp, ftps and tftp URLs. It is not merged yet. You can test it once it is in a rolling image.

Until then, the only option for a URL is a different DHCP server, sorry. For several models keep one range per model, each with its own client-class.

Options inside a client-class would be a new feature. If you want it, please open a separate task. A real use case like yours helps there.