Populate DHCP-offered Options

Hi there,

I'm looking for the hooks where dnsmasq populates options populated by a dhcp-server, in particular I'm looking for a better way to use the dhcp-offered log-server than doing nmap --script broadcast-dhcp-discover 2>/dev/null | sed -n '/Log/ {s/^.*: *//; p}' 'cause that requires nmap-full and a second DHCP-Broadcast to obtain an information that is already somewhere avaiable. But where? Or does dnsmasq dump that option? The NTP-Option is similar and get populated via ubus, OpenWrt use it already, but what about other less popular options?

Best regards
Peter

Do you mean DHCP options OpenWrt receives when acting as DHCP client? There should be /etc/udhcpc.user for that. All received DHCP options are passed via env variables. Options recognized by udhcpd are passed by their name, others in the form opt123=value where 123 denotes the DHCP option number.

For the logic, see:

For recognized options, see:

I mean DHCP options that're recieved by OpenWrt in DHCP-Client mode. As far as I understand /etc/udhcpc.user is for overwriting things recieved by DHCP with local values? - the opposite of what I want.

I populate beside others the options ntp-server (option 42) and log-server (option 7) and want them to be recognised by the OpenWrt-Box acting as DHCP-Client. With ntp-server that's already implemented, but for the option log-server it's not. (I'm lazy, don't want to edit several files on several boxes if I can handle that by editing a single file...) So I tried to add a line into /lib/netifd/dhcp.script that it reads

[ -n "$timesrv" ]  && json_add_string timeserver "$timesrv"
[ -n "$logsrv" ]   && json_add_string logserver "$logsrv"
[ -n "$hostname" ] && json_add_string hostname "$hostname"

at the given position - but that doesn't work, same result with opt7 instead of logsrv, same result if I move the added line around. With nmap --script broadcast-dhcp-discover -e br-lan I get the log server on that box, so it's definetly populated by DHCP and can be recieved. As far as I understand the code the information should already be avaiable on the box, but where? How to get it populated to json? Where is my mistake?

Best regards
Peter

Are you saying that you want OpenWrt to request and use DHCP Options:

  • 42 (NTP Servers)
  • 7 (log server)

Usually you'd hard-configure that in a router-like device (otherwise a rogue DHCP server could compromise your network).

E.g.

If the ISP provided time servers via DHCP - how/does/would the OpenWrt request/honor/configure/use those NTP servers instead of 0.openwrt.pool.ntp.org, etc.

I think thats the OP's "request".

No, it's for implementing custom handling of DHCP received values. When you tried modifying /lib/netifd/dhcp.script, did you reboot the box / restart the netifd service?

You could also place a command like env | logger -t dhcp_variables in there to dump all known environment variables to the system log, so you can review them with logread.

It is also possible that udhcpc simply does not request option 7 (log server), at least it is not among the group of default-requested options. Try setting: uci set network.wan.reqopts="0x7" followed by ifup wan. The resulting udhcpc command line in ps w should then contain a -O 0x7

Yes, that's what I want.

In puncto Safety issue I'm unsure. If the log is somewhere else on the net I'm controlling it is with no doubt a big advantage 'cause an intruder's first goal in a compromised system is wiping it's footmarks - cleaning the logs - so I expect no real evidence in the log on the compromised device. On OpenWrt it's also a kind of ring buffer which holds only relatively short amount of data, it even get completely lost in a power cycle. If there is a copy somewhere on the local net the attacker needs to know that and if so he knows his next target. By reading out the config files he knows that one exist and where to find it. On the other hand if he supposes the presence of that feature placing an appropiate line for taking over the DHCP-System is as easy as nothing - if his first step is taking over the DHCP-System. But that is fixable if you only accept DHCP-Messages of a particular range of hosts - and configure your log server in a way that it throws a warning message if a device hasn't sent a regular message eg. the --- Mark --- message if it's turned on. There are others, that won't be so noticeable than the Mark though. That way you know that all is fine if all is silence - and get warned by a relative short delay if something goes south, that neen't be an attack, but includes it. The downside you get also warned if you "play around" with the that way supervised devices...

A restart of the network or reboot of the whole system doesn't bring up the variable, that's the first I tried, in the environment I can't find it either, and the uci command has no effect to the udhcp-line in the output of ps...

Weird, it works here:

root@er-x:~# ps w | grep udhcpc
 9001 root      1312 S    udhcpc -p /var/run/udhcpc-switch0.1.pid -s /lib/netifd/dhcp.script -f -t 0 -i switch0.1 -C -R -O 121
 9862 root      1312 S    grep udhcpc
root@er-x:~# uci set network.wan.reqopts="0x7"
root@er-x:~# ifup wan
root@er-x:~# ps w | grep udhcpc
10396 root      1312 S    udhcpc -p /var/run/udhcpc-switch0.1.pid -s /lib/netifd/dhcp.script -f -t 0 -i switch0.1 -C -R -O 0x7 -O 121
11020 root      1312 R    grep udhcpc
root@er-x:~# 

Got my error... Copy'n'paste - you think wan, i mean lan...

root@gateway:~# cat /etc/openwrt_release | grep 'DESC'
DISTRIB_DESCRIPTION='OpenWrt 22.03.0 r19685-512e76967f'
root@gateway:~# ps w | grep udhcpc
 1915 root      1308 S    udhcpc -p /var/run/udhcpc-br-lan.pid -s /lib/netifd/dhcp.script -f -t 0 -i br-lan -C -R -O 121
15773 root      1308 S    grep udhcpc
root@gateway:~# uci set network.wan.reqopts="0x7"
root@gateway:~# ifup wan
root@gateway:~# ps w | grep udhcpc
 1915 root      1308 S    udhcpc -p /var/run/udhcpc-br-lan.pid -s /lib/netifd/dhcp.script -f -t 0 -i br-lan -C -R -O 121
15813 root      1308 S    grep udhcpc
root@gateway:~# ifup lan
root@gateway:~# ps w | grep udhcpc
16306 root      1308 S    udhcpc -p /var/run/udhcpc-br-lan.pid -s /lib/netifd/dhcp.script -f -t 0 -i br-lan -C -R -O 121
16686 root      1308 S    grep udhcpc
root@gateway:~# uci set network.lan.reqopts="0x7"
root@gateway:~# ifup lan
root@gateway:~# ps w | grep udhcpc
17543 root      1308 S    udhcpc -p /var/run/udhcpc-br-lan.pid -s /lib/netifd/dhcp.script -f -t 0 -i br-lan -C -R -O 0x7 -O 121
17931 root      1308 S    grep udhcpc

Now opt7 is recognised in hex notation within /lib/netifd/dhcp.script, a change of 0x7 to 7 has no effect, but at least it's there... Now I have only to make it survive a reboot - and write the appropiate start scripts...

Thanks!

Well you can still use /etc/udhcpc.user which is sourced by the netifd dhcp.script and inherits the same environment. This way you do not need to modify the original file.

To make the reqopts thing persistent, issue a uci commit network

For the adventurous here is a patch that enables this feature:

diff -ru a/etc/init.d/log b/etc/init.d/log
--- a/etc/init.d/log    2023-03-30 14:49:52.382027565 +0200
+++ b/etc/init.d/log    2023-03-30 14:49:52.378694231 +0200
@@ -20,7 +20,9 @@
                'log_port:port:514' \
                'log_proto:or("tcp", "udp"):udp' \
                'log_trailer_null:bool:0' \
-               'log_prefix:string'
+               'log_prefix:string' \
+               'log_use_dhcp:bool:1' \
+               'log_dhcp_interface:list(string):lan'
 }

 validate_log_daemon()
@@ -65,8 +67,31 @@

 start_service_remote()
 {
-       PIDCOUNT="$(( ${PIDCOUNT} + 1))"
-       local pid_file="/var/run/logread.${PIDCOUNT}.pid"
+       # If someone really wants to automatically search all interfaces
+       # aka placing an asterisk in the log_dhcp_interface list
+       [ "$log_dhcp_interface" = "*" ] && {
+               echo "Searching for all interfaces! That's a security risk."
+               log_dhcp_interface="$(ubus call network.interface dump | \
+                       jsonfilter -e "@.interface[@]['interface']")"
+       }
+
+       # There could be more than one server be published via DHCP, catch 'em all.
+       [ "$log_remote" -ne 0 -a "$log_use_dhcp" -ne 0 -a -z "$log_ip" ] && {
+               for entry in $log_dhcp_interface; do
+                       log_ip="$log_ip $(ubus call network.interface."$entry" status | \
+                               jsonfilter -e "@.data['logserver']")"
+               done
+       }
+
+       # To remove duplicate IP adresses
+       _log_ip=$log_ip; log_ip=''
+       for entry in $_log_ip; do
+               local duplicate=0
+               for log_entry in $log_ip; do
+                       [ "$entry" = "$log_entry" ] && duplicate=1
+               done
+               [ "$duplicate" = 0 ] && log_ip="$log_ip $entry"
+       done

        [ "$2" = 0 ] || {
                echo "validation failed"
@@ -76,14 +101,19 @@
        [ -z "${log_ip}" ] && return
        [ -z "${log_hostname}" ] && log_hostname=$(cat /proc/sys/kernel/hostname)

-       procd_open_instance logremote
-       procd_set_param command "$PROG" -f -h "$log_hostname" -r "$log_ip" "${log_port}" -p "$pid_file"
-       case "${log_proto}" in
-               "udp") procd_append_param command -u;;
-               "tcp") [ "${log_trailer_null}" -eq 1 ] && procd_append_param command -0;;
-       esac
-       [ -z "${log_prefix}" ] || procd_append_param command -P "${log_prefix}"
-       procd_close_instance
+       for _log_ip in $log_ip; do
+               PIDCOUNT="$(( ${PIDCOUNT} + 1))"
+               local pid_file="/var/run/logread.${PIDCOUNT}.pid"
+
+               procd_open_instance logremote."$PIDCOUNT"
+               procd_set_param command "$PROG" -f -h "$log_hostname" -r "$_log_ip" "${log_port}" -p "$pid_file"
+               case "${log_proto}" in
+                       "udp") procd_append_param command -u;;
+                       "tcp") [ "${log_trailer_null}" -eq 1 ] && procd_append_param command -0;;
+               esac
+               [ -z "${log_prefix}" ] || procd_append_param command -P "${log_prefix}"
+               procd_close_instance
+       done
 }

 register_mount_trigger()
diff -ru a/lib/netifd/dhcp.script b/lib/netifd/dhcp.script
--- a/lib/netifd/dhcp.script    2023-03-30 14:49:46.368694193 +0200
+++ b/lib/netifd/dhcp.script    2023-03-30 14:51:23.235361487 +0200
@@ -52,6 +52,15 @@
                echo "Environment variable 'timesvr' will be deprecated; use 'timesrv' instead."
        fi

+       if [ -n "$opt7" -a -z "$logsrv" ]; then
+               # IPv4 Hex to IP-notation conversion
+               [[ "$opt7" =~ ^[[:xdigit:]]{8}$ ]]  && \
+                       logsrv="$(printf "%d.%d.%d.%d\n" $(echo "$opt7" | sed 's/../0x& /g'))"
+               # IPv6 Hex to IP-Notation conversion #### UNTESTED. Always 64 bit? ####
+               [[ "$opt7" =~ ^[[:xdigit:]]{32}$ ]] && \
+                       logsrv="$(echo "$opt7" | sed 's/..../:&/g'))"
+       fi
+
        proto_add_data
        [ -n "$ZONE" ]     && json_add_string zone "$ZONE"
        [ -n "$ntpsrv" ]   && json_add_string ntpserver "$ntpsrv"
@@ -61,6 +70,7 @@
        [ -n "$timezone" ] && json_add_int timezone "$timezone"
        [ -n "$lease" ]    && json_add_int leasetime "$lease"
        [ -n "$serverid" ] && json_add_string dhcpserver "$serverid"
+       [ -n "$logsrv" ]   && json_add_string logserver "$logsrv"
        proto_close_data

        proto_send_update "$INTERFACE"

It introduces two new options to the uci system and enables the ability to send log messages to multiple IP addresses, so with it it's possible to set multiple values to the log_ip option in the simplest way, if you just want that you needn't care about the new options, just the patch.

The first added option is the boolean switch log_use_dhcp, it enables searching for DHCP provided log servers and is disabled by default.
To enable it issue uci set system.@system[0].log_use_dhcp='1'

The second one is optional, log_dhcp_interface, it holds a list of the interfaces where the log system shall search for dhcp provided log server IPs. It defaults to lan, by default the most trustworthy network in my opinion. You can alter and add interfaces here eg. with
uci set system.@system[0].log_dhcp_interface='lan'

At the end a uci set network.lan.reqopts="7" enables the obtainment of DHCP provided log server IP addresses on that particular interface.

With IPv4 it works here flawless tested with 22.03.0, 22.03.3 and Snapshot. IPv6 is untested, if someone will test it I'd be happy about a short response if it works. In the patch it expects a completely transmitted IP address, not a shortened one, should work, but I'm not shure.

Have fun with it

Found an issue:

During system boot the log system is brought up before the network system, so at startup there can't be a DHCP message recieved. We have to reload the log system when the network, in particular the interface we wanna recieve the messages from are brought up. That does a little scrip in the folder /etc/hotplug.d/iface/, I called it 12-log, here it is:

#!/bin/sh

. /lib/functions.sh

get_dhcp_interface() {
        config_get dhcp_interface "$1" log_dhcp_interface
}

[ "$ACTION" = ifup ] && {
        config_load system
        config_foreach get_dhcp_interface system
        [ -z "$dhcp_interface" ] && dhcp_interface=lan

        for entry in $dhcp_interface; do
                [ "$INTERFACE" = "$entry" ] && {
                        /etc/init.d/log reload
                        return
                }
        done
}