### Description
Clients expecting `Option 67` / `bootfile-name` in DHCP can anticipate either a URL including the protocol, IP address or FQDN, port, and path to a file (ex: `http://192.0.2.1:8080/path/to/bootfile.py`), a file path (ex: `/path/to/bootfile.py`), or a simple filename (ex: `bootfile.py`, sometimes paired with `Option 66` / `tftp-server-name`).
Currently, the `dhcp-server` configuration mode interface in VyOS only accepts values matching the pattern `[-_a-zA-Z0-9./]+`. This limitation notably rejects values with a `:` character, effectively disallowing URLs that specify schema or port numbers. This issue hinders the use of VyOS's DHCP server for tasks like zero-touch provisioning of network devices, which rely on `Option 67` to fetch their boot instructions.
ISC DHCP's [definition](https://kb.isc.org/docs/isc-dhcp-44-manual-pages-dhcp-options):
> **option bootfile-name** //text//**;** This option is used to identify a bootstrap file. If supported by the client, it should have the same effect as the **filename** declaration. BOOTP clients are unlikely to support this option. Some DHCP clients will support it, and others actually require it.
ISC DHCP's manual mentions that in this context, //text// is any valid ASCII string:
>The **text** data type specifies an NVT ASCII string, which must be enclosed in double quotes - for example, to specify a root-path option, the syntax would be
>
>option root-path "10.0.1.4:/var/tmp/rootfs";
And is additionally corroborated by the following references in ISC DHCP's source:
- [tables.c#L165](https://github.com/isc-projects/dhcp/blob/572032cb0e514606559de3784e3f7ca8e1539d17/common/tables.c#L165)
- [parse.c#L5042](https://github.com/isc-projects/dhcp/blob/572032cb0e514606559de3784e3f7ca8e1539d17/common/parse.c#L5042)
The current issue seems to have originated from [T782](https://vyos.dev/T782), as indicated in [this commit](https://github.com/vyos/vyos-1x/commit/32df4dc8fbd28115c0c3fa7f529db9377fdfe8b5#diff-25875f6ea3f5c6b53aa0ab12fc93f5a79a0befa12b7e6e4abb586caeec992bbfR138).
### Impact
This bug affects DHCP server configurations where a `bootfile-name` needs to be specified, particularly when the name is a full URL or includes a `:` character. In its current state, the system may reject valid configurations, causing booting issues for clients that rely on these settings. This limitation particularly hampers the usage of VyOS's DHCP server for functions like zero-touch provisioning of network devices, where clients expect `Option 67` to fetch their boot instructions.
The significance of maintaining the functionality of these DHCP options is highlighted in another [issue (T1129)](https://vyos.dev/T1129#30188), where a user stresses the importance of these options for provisioning phones and other network devices.
This bug also prevents all `service dhcp-server` config with an "invalid" `bootfile-name` parameter present (from configs prior to the regex being implement) from being migrated during VyOS upgrade.
### Workaround limitations
The possible workaround of using `subnet-parameters` is also blocked due to a constraint on the usage of the double quote (") character in the value string:
```
eric@vyos# set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 subnet-parameters 'option bootfile-name "http://example.com/path/to/file.py";'
Cannot use the double quote (") character in a value string
Value validation failed
Set failed
```
### Proposed solution
Change the constraint regex pattern to: `[-_a-zA-Z0-9.:/~#@!$&*+,;=~%\\\\]+`
This adjustment expands the existing pattern to accommodate full URLs, file paths, and bare filenames. It also adds a subset of [RFC 3886](https://www.rfc-editor.org/rfc/rfc3986) "[reserved](https://www.rfc-editor.org/rfc/rfc3986#section-2.2)" characters (gen-delims and sub-delims), excluding `[]` `'` `()` , due to their rarity and negative interactions with the command line. Additionally, it adds simple handling of "[% encoding](https://www.rfc-editor.org/rfc/rfc3986#section-2.1)".
Finally, the double-escaped `\` enables support for Windows paths like those used for [Windows Deployment Services](https://web.archive.org/web/20160910100740/https://technet.microsoft.com/en-us/library/cc766320.aspx) (ex: `boot\x64\wdsnbp.com`). These land in `dhcpd.conf` as `boot\\x64\\wdsnbp.com`, which passes `dhcpd -t`. An explanation of `string` handling in ISC DHCP's [documentation](https://kb.isc.org/v1/docs/isc-dhcp-44-manual-pages-dhcp-eval) suggests that this is th correct format. See also: [PXE Boot from WDS using ISC DHCPd (r/networking)](https://old.reddit.com/r/networking/comments/3f4z6u/pxe_boot_from_wds_using_isc_dhcpd/).
### Test commands
The following commands test the proposed pattern against input validation and against `dhcpd -t` upon commit:
```
configure
cp /opt/vyatta/share/vyatta-cfg/templates/service/dhcp-server/shared-network-name/node.tag/subnet/node.tag/bootfile-name/node.def /tmp/bootfile-name-node.def.bak
# manually edit the regex in node.def to [-_a-zA-Z0-9./:?#@!$&*+,;=~%\\\\]+
sudo vi /opt/vyatta/share/vyatta-cfg/templates/service/dhcp-server/shared-network-name/node.tag/subnet/node.tag/bootfile-name/node.def
sudo systemctl restart vyos-configd
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 range 0 start 203.0.113.2
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 range 0 stop 203.0.113.254
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 bootfile-name "http://example.com/path/to/file.py"
commit
grep "shared-network TESTNET" -A9 /run/dhcp-server/dhcpd.conf
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 bootfile-name "https://192.0.2.1/path/to/file.py"
commit
grep "shared-network TESTNET" -A9 /run/dhcp-server/dhcpd.conf
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 bootfile-name "/pxelinux.0"
commit
grep "shared-network TESTNET" -A9 /run/dhcp-server/dhcpd.conf
set service dhcp-server shared-network-name TESTNET subnet 203.0.113.0/24 bootfile-name "boot\x64\wdsnbp.com"
commit
grep "shared-network TESTNET" -A9 /run/dhcp-server/dhcpd.conf
delete service dhcp-server shared-network-name TESTNET
commit
sudo mv /tmp/bootfile-name-node.def.bak /opt/vyatta/share/vyatta-cfg/templates/service/dhcp-server/shared-network-name/node.tag/subnet/node.tag/bootfile-name/node.def
sudo systemctl restart vyos-configd
exit
```