devd: device state change daemon
Introduction
devd(8) is a tool included in the FreeBSD base system. It provides a way of running userland tools when a specific kernel event happens. For example, if plugging a smartphone configured for USB tethering, we could have a devd(8) rule that runs dhclient(8) for that interface name.
Contents
devd.conf configuration
devd.conf(5) is the configuration file for defining devd(8) rules. There's already one in the base system: /etc/devd.conf, which can be left unmodified in most situations. Instead, let's use the /usr/local/etc/devd directory for files with the .conf extension.
If a port or package installed a file there, that directory should already exist; otherwise, create it:
# mkdir -p /usr/local/etc/devd
The configuration consists of two features: statements and comments. All statements end with a semicolon. Many statements have substatements, that also end with a semicolon.
statement priority { substatement "value"; ... substatement "value"; };
Statements can occur in any order in the configuration file and can be repeated as often as necessary.
Each statement has a priority, except the options (see below). The priority is an arbitrary number, where zero is the lowest priority. If two or more statements match, only the statement with the greatest priority is processed.
The currently supported statements are as follows:
attach: When a device is plugged in and matches, perform an action.
detach: Reverse of the above. When a device is unplugged and matches, perform an action.
nomatch: This statement is triggered when no device driver currently loaded in the kernel claims a new device.
notify: This statement is very useful in many cases, since it allows us to perform actions when the kernel sends an event to userland. Normally, we can see this type of messages in /var/log/messages or by executing dmesg(8).
options: This sets various options and parameters that we can use throughout the statements we create.
One way to understand the statements is to directly read /etc/devd.conf and review the above mentioned. It might also be useful to read /etc/devd.conf and verify if an installed port or package we have installed created /usr/local/etc/devd; that is a good starting point.
Sub-Statements
options
directory "/some/path";: Adds /some/path to the directories to read *.conf files from. The default configuration file /etc/devd.conf already has two of these sub-statements in options to add directories /etc/devd and /usr/local/etc/devd.
pid-file "/var/run/devd.pid";: Specifies the file where the process ID is stored. Again, /etc/devd.conf already has one.
set regexp-name "(some|regexp)";: Create a regular expression that can be used in sub-statements later in the file. If it begins with !, it matches if "(some|regexp)" doesn't match. All regular expressions have an implicit $^ around them. In /etc/devd.conf we can find some pre-defined regular expressions, such as scsi-controller-regex and wifi-driver-regex. To use a regular expression in a sub-statement, the $ sign can be used (e.g.: device-name "$wifi-driver-regex";).
attach and detach
action "command";: Execute a command. For example: /etc/pccard_ether $device-name start.
class "string";: Shortcut for match "class" "string".
device-name "string";: Shortcut for match "device-name" "string";. A variable called `$device-name is set if the sub-statement matches.
match "variable" "value";: Matches the content of value against variable. For example, match "device-name" "$wifi-driver-regex"; must match with the device name according to $wifi-driver-regex.
media-type "string";: For network devices, media-type matches devices with the following types (devd.conf(5) in 12.3 and 13.1 still lists Tokenring and FDDI, which are no longer allowed):
Ethernet
802.11
ATM
unknown
subdevice "string";: Shortcut to match "subdevice" "string";.
nomatch
The following sub-statements have the same effect that attach and detach, so they will be mentioned without a description.
action "command";
match "variable" "value";
match
The following sub-statements have the same effect that the above sub-statement, so they will be mentioned without a description. The exception is action and match. The first is to offer an useful example and the second is because match is something different.
action "command";: Execute a command. For example, action "service dhclient quietstart $subsystem". Other useful example is when we need to use the $notify variable that is relative to the system and subsystem that triggered the event.
match "system | subsystem | type | notify" "value";: An arbitrary number of match sub-statements can exists in a match statement and as other match sub-statements, value can be a fixed value or a regular expression.
media-type "string";
Variables and the notify event
There are a lot of variables, systems, subsystems, types and notifications that can be used in statements that include a match sub-statement, but this article need not cover them because they can be found in devd.conf(5). See sections Variables that can be used with the match statement and Notify matching in devd.conf(5).
Comments
The comments are trivial to C/C++ or Shell/Perl programmers.
C-style comments start with the two characters ‘/*’ (slash, star) and end with ‘*/’ (star, slash). This style can cover one line or multiple lines but cannot be nested.
C++-style comments start with the two characters ‘//’ (slash, slash) and continue to the end of the physical line. If we need to cover more than one line, is necessary to put again this type of comments in the next line.
The above may be written using perl-style comments:
Notes on Variable Expansion
To avoid problems with shell special characters in action sub-statements, here's what happens to, say, $foo:
The characters $' are inserted.
The string $foo is removed.
- The value of the foo variable is inserted into the buffer with all single quote characters prefixed by a backslash.
See sh(1) for what this construct means. In any context, these conditions are safe, but single quotes can cause unwanted effects. Suppose foo=meta and bar=var:
action "echo '$foo $bar'";
They will be present in the shell through the system(3) function:
echo '$'meta' $'var''
Which produces the following output:
echo $meta $var
Is this the result we want? Probably not. To produce a correct result, it is necessary to rewrite the rule as follows:
action "echo $foo' '$bar"
Be careful.
devd(8) in debug mode
By default devd(8) runs as a service:
# service devd status devd is running as pid 2310.
To understand what devd(8) does, we need to run it in the foreground instead of the background. Likewise, it is highly recommended to read /var/log/messages and the output of dmesg(8) while writing and testing rules.
# service devd stop Stopping devd. Waiting for PIDS: 2310. # devd -d Parsing /etc/devd.conf setting scsi-controller-regex=(aac|aacraid|ahc|ahd|amr|ciss|esp|ida|iir|ips|isp|mlx|mly|mpr|mps|mpt|sym|trm)[0-9]+ setting wifi-driver-regex=(ath|bwi|bwn|ipw|iwi|iwm|iwn|malo|mwl|otus|ral|rsu|rtwn|rum|run|uath|upgt|ural|urtw|wi|wpi|wtap|zyd)[0-9]+ Parsing files in /etc/devd Parsing /etc/devd/uath.conf Parsing /etc/devd/hyperv.conf Parsing /etc/devd/zfs.conf Parsing /etc/devd/iwmbtfw.conf Parsing /etc/devd/asus.conf Parsing /etc/devd/devmatch.conf Parsing /etc/devd/ulpt.conf Parsing files in /usr/local/etc/devd Parsing /usr/local/etc/devd/cups.conf
The output is very descriptive. We see what devd(8) parses, which we can use for troubleshooting.
As mentioned, dmesg(8) and /var/log/messages can be our best friends. This is the output of dmesg(8) when a USB key is connected:
umass0 on uhub0 umass0: <SanDisk Cruzer Blade, class 0/0, rev 2.00/1.27, addr 2> on usbus4 umass0: SCSI over Bulk-Only; quirks = 0x8100 umass0:2:0: Attached to scbus2 da0 at umass-sim0 bus 0 scbus2 target 0 lun 0 da0: <SanDisk Cruzer Blade 1.27> Removable Direct Access SPC-4 SCSI device da0: Serial Number 200515364102BCF2AC69 da0: 40.000MB/s transfers da0: 7633MB (15633408 512 byte sectors) da0: quirks=0x2<NO_6_BYTE>
We have enough information to create statements. We will create usb_attach.conf in /usr/local/etc/devd as an example.
/usr/local/etc/devd/usb_attach.conf:
attach 100 { device-name "umass0"; action "logger $device-name is plugged in"; }; detach 100 { device-name "umass0"; action "logger $device-name is unplugged"; };
When a device matches umass0, its name is printed using logger(1). We can see the message in /var/log/messages:
Jul 11 15:57:01 dtxdf-laptop dtxdf-fbsd[4832]: umass0 is unplugged Jul 11 15:57:01 dtxdf-laptop kernel: ugen4.2: <SanDisk Cruzer Blade> at usbus4 (disconnected) Jul 11 15:57:01 dtxdf-laptop kernel: umass0: at uhub0, port 2, addr 2 (disconnected) Jul 11 15:57:01 dtxdf-laptop kernel: da0 at umass-sim0 bus 0 scbus2 target 0 lun 0 Jul 11 15:57:01 dtxdf-laptop kernel: da0: <SanDisk Cruzer Blade 1.27> s/n 200515364102BCF2AC69 detached Jul 11 15:57:01 dtxdf-laptop kernel: (da0:umass-sim0:0:0:0): Periph destroyed Jul 11 15:57:01 dtxdf-laptop kernel: umass0: detached Jul 11 15:57:07 dtxdf-laptop dtxdf-fbsd[4836]: umass0 is plugged in Jul 11 15:57:07 dtxdf-laptop kernel: ugen4.2: <SanDisk Cruzer Blade> at usbus4 Jul 11 15:57:07 dtxdf-laptop kernel: umass0 on uhub0 Jul 11 15:57:07 dtxdf-laptop kernel: umass0: <SanDisk Cruzer Blade, class 0/0, rev 2.00/1.27, addr 2> on usbus4 Jul 11 15:57:07 dtxdf-laptop kernel: umass0: SCSI over Bulk-Only; quirks = 0x8100 Jul 11 15:57:07 dtxdf-laptop kernel: umass0:2:0: Attached to scbus2 Jul 11 15:57:08 dtxdf-laptop kernel: da0 at umass-sim0 bus 0 scbus2 target 0 lun 0 Jul 11 15:57:08 dtxdf-laptop kernel: da0: <SanDisk Cruzer Blade 1.27> Removable Direct Access SPC-4 SCSI device Jul 11 15:57:08 dtxdf-laptop kernel: da0: Serial Number 200515364102BCF2AC69 Jul 11 15:57:08 dtxdf-laptop kernel: da0: 40.000MB/s transfers Jul 11 15:57:08 dtxdf-laptop kernel: da0: 7633MB (15633408 512 byte sectors) Jul 11 15:57:08 dtxdf-laptop kernel: da0: quirks=0x2<NO_6_BYTE>
This example can be extended, for example, to mount a partition when a device is plugged in. To avoid problems with device names /dev/da[0-9], it is strongly recommended to partition and label them. To simplify this task, we can use diskinfo(8) to get the serial number of our device to use as a unique label in our inventory.
$ diskinfo -s da0 200515364102BCF2AC69
The serial number is very large, but the final characters can be used. To finish the label, we could concatenate a logical abbreviation of the purpose of the partition and the final five characters. The end result is:
rt2AC69
We are ready to partition:
# gpart create -s gpt da0 da0 created # gpart add -t freebsd-ufs -l rt2AC69 da0 da0p1 added # newfs -j /dev/gpt/rt2AC69 /dev/gpt/rt2AC69: 7633.5MB (15633328 sectors) block size 32768, fragment size 4096 using 13 cylinder groups of 625.22MB, 20007 blks, 80128 inodes. with soft updates super-block backups (for fsck_ffs -b #) at: 192, 1280640, 2561088, 3841536, 5121984, 6402432, 7682880, 8963328, 10243776, 11524224, 12804672, 14085120, 15365568 Using inode 4 in cg 0 for 33554432 byte journal newfs: soft updates journaling set # mount /dev/gpt/rt2AC69 /mnt # echo "Hello!" > /mnt/hello.txt # cat /mnt/hello.txt Hello! # umount /mnt
Here the da0 device with GPT is used and a single partition is added. That partition is formatted and mounted on /mnt to write a file called hello.txt. Last, the partition is unmounted.
Unplug the device and plug it back in. The label is also useful for debugging, as it will be printed in the devd(8) output.
Processing event '!system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69' Pushing table setting *=!system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69 setting _=system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69 setting timestamp=1657571585.404812 setting system=GEOM setting subsystem=DEV setting type=CREATE setting cdev=gpt/rt2AC69 Processing notify event Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^ETHERNET$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^HYPERV_NIC_VF$, invert=0 Testing system=GEOM against ^ETHERNET$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^ZFS$, invert=0 Testing system=GEOM against ^IFNET$, invert=0 Testing system=GEOM against ^IFNET$, invert=0 Testing system=GEOM against ^IFNET$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Testing system=GEOM against ^ACPI$, invert=0 Popping table
When we plug in our USB key, devd(8) output is very large, so using a tool like screen(1) or tmux(1) is highly recommended, as both of them have a feature called Copy Mode that is useful for searching the output buffer. However, traditional tools like script(1) with a pager like less(1) or more(1) can be used for this purpose.
The idea is to search devd(8) output to get the exact point of the event we need. When we've done that, we need to check whether the parameters that devd(8) is using are the required ones. In this example, the following parameters are relevant:
setting system=GEOM setting subsystem=DEV setting type=CREATE setting cdev=gpt/rt2AC69
See "Variables and the notify event" for more details on those parameters.
We need to modify usb_attach.conf with the parameters described above:
notify 100 { match "system" "GEOM"; match "subsystem" "DEV"; match "type" "CREATE"; match "cdev" "gpt/rt2AC69"; action "sleep 2 && /usr/local/bin/scripts/automount.sh $cdev"; }; notify 100 { match "system" "GEOM"; match "subsystem" "DEV"; match "type" "DESTROY"; match "cdev" "gpt/rt2AC69"; action "sleep 2 && /usr/local/bin/scripts/autoumount.sh $cdev"; };
In many cases we need to do something and, in turn, its counterpart. In this case, we need two scripts: the first is to create a directory and mount its partition, and the second is to delete the directory and forcibly unmount it. Forcibly unmounting the file system seems a bad idea, but since the device was unplugged without unmounting its partition, it's necessary.
automount.sh:
set -e : ${RTDIR:=/mnt} dev=$1 if [ -z "${dev}" ]; then echo "usage: automount.sh device" >&2 exit 1 fi _dev=`basename "${dev}"` mntdir="${RTDIR}/${_dev}" fsck -p "/dev/${dev}" mkdir -p "${mntdir}" mount "/dev/${dev}" "${mntdir}"
autoumount.sh:
: ${RTDIR:=/mnt} dev=$1 if [ -z "${dev}" ]; then echo "usage: autoumount.sh device" >&2 exit 1 fi _dev=`basename "${dev}"` mntdir="${RTDIR}/${_dev}" umount -f "${mntdir}" 2> /dev/null rmdir "${mntdir}"
When the device is plugged in, devd(8) automatically mounts its partition.
Processing event '!system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69' Pushing table setting *=!system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69 setting _=system=GEOM subsystem=DEV type=CREATE cdev=gpt/rt2AC69 setting timestamp=1657575727.806329 setting system=GEOM setting subsystem=DEV setting type=CREATE setting cdev=gpt/rt2AC69 Processing notify event Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^DEVFS$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^USB$, invert=0 Testing system=GEOM against ^GEOM$, invert=0 Testing subsystem=DEV against ^DEV$, invert=0 Testing type=CREATE against ^CREATE$, invert=0 Testing cdev=gpt/rt2AC69 against ^gpt/rt2AC69$, invert=0 Executing 'sleep 2 && /usr/local/bin/scripts/automount.sh $'gpt/rt2AC69'' /dev/gpt/rt2AC69: FILE SYSTEM CLEAN; SKIPPING CHECKS /dev/gpt/rt2AC69: clean, 1880626 free (26 frags, 235075 blocks, 0.0% fragmentation) Popping table
cat /mnt/rt2AC69/hello.txt Hello!
There is no need to reinvent the wheel
There is an implementation in the FreeBSD base system that is better than our automounter, called automountd(8), but for teaching purposes it was a perfect way to understand devd(8).
However, No need to reinvent the wheel can be applied to the new rules for devd(8). Some rules that we can find in /etc/devd can be changed a bit to suit our needs.
An example is when using USB tethering from an Android device which creates an urndis(8) interface, which in turn can be configured using dhclient(8).
If we connect our Android device to our machine, we will see useful information:
Pushing table setting *=!system=IFNET subsystem=ue0 type=ATTACH setting _=system=IFNET subsystem=ue0 type=ATTACH setting timestamp=1657576470.672852 setting system=IFNET setting subsystem=ue0 setting type=ATTACH Processing notify event Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^USB$, invert=0 Testing system=IFNET against ^GEOM$, invert=0 Testing system=IFNET against ^GEOM$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^ACPI$, invert=0 Testing system=IFNET against ^ACPI$, invert=0 Testing system=IFNET against ^ACPI$, invert=0 Testing system=IFNET against ^ACPI$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^DEVFS$, invert=0 Testing system=IFNET against ^HYPERV_NIC_VF$, invert=0 Testing system=IFNET against ^ETHERNET$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^ZFS$, invert=0 Testing system=IFNET against ^IFNET$, invert=0 Testing subsystem=ue0 against ^(usbus|wlan)[0-9]+$, invert=1 Testing type=ATTACH against ^ATTACH$, invert=0 Executing '/etc/pccard_ether $'ue0' start' Starting Network: ue0. ue0: flags=8802<BROADCAST,SIMPLEX,MULTICAST> metric 0 mtu 1500 ether 00:00:00:00:00:00 nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> Popping table Processing event '!system=ETHERNET subsystem=ue0 type=IFATTACH' Pushing table setting *=!system=ETHERNET subsystem=ue0 type=IFATTACH setting _=system=ETHERNET subsystem=ue0 type=IFATTACH setting timestamp=1657576471.170142 setting system=ETHERNET setting subsystem=ue0 setting type=IFATTACH Processing notify event Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^GEOM$, invert=0 Testing system=ETHERNET against ^GEOM$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^ACPI$, invert=0 Testing system=ETHERNET against ^ACPI$, invert=0 Testing system=ETHERNET against ^ACPI$, invert=0 Testing system=ETHERNET against ^ACPI$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^HYPERV_NIC_VF$, invert=0 Testing system=ETHERNET against ^ETHERNET$, invert=0 Testing type=IFATTACH against ^IFATTACH$, invert=0 Executing '/usr/libexec/hyperv/hyperv_vfattach $'ue0' 0' Popping table
There is a rule that matches and runs the /usr/libexec/hyperv/hyper_vfattach tool. If we want to know what file is using that tool, we can use grep(1):
$ egrep -r '/usr/libexec/hyperv/hyperv_vfattach' /etc/devd /etc/devd/hyperv.conf: action "/usr/libexec/hyperv/hyperv_vfattach $subsystem 0";
We must remember that /etc/devd is not the only directory used for devd(8), so it is important to search all directories used by the options statement.
/etc/devd/hyperv.conf:
notify 10 { match "system" "ETHERNET"; match "type" "IFATTACH"; action "/usr/libexec/hyperv/hyperv_vfattach $subsystem 0"; };
Now it's trivial to add a new rule:
/usr/local/etc/devd/usb_tethering.conf:
notify 20 { match "system" "ETHERNET"; match "type" "IFATTACH"; match "subsystem" "ue[0-9]+"; action "/sbin/dhclient $subsystem"; };
We must increase the priority and add the subsystem for urndis(4). We also set dhclient(8) to action and when our device is plugged in, we should have an Internet connection automatically.
Processing event '!system=ETHERNET subsystem=ue0 type=IFATTACH' Pushing table setting *=!system=ETHERNET subsystem=ue0 type=IFATTACH setting _=system=ETHERNET subsystem=ue0 type=IFATTACH setting timestamp=1657577216.269419 setting system=ETHERNET setting subsystem=ue0 setting type=IFATTACH Processing notify event Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^DEVFS$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^USB$, invert=0 Testing system=ETHERNET against ^GEOM$, invert=0 Testing system=ETHERNET against ^GEOM$, invert=0 Testing system=ETHERNET against ^ETHERNET$, invert=0 Testing type=IFATTACH against ^IFATTACH$, invert=0 Testing subsystem=ue0 against ^ue[0-9]+$, invert=0 Executing '/sbin/dhclient $'ue0'' DHCPDISCOVER on ue0 to 255.255.255.255 port 67 interval 8 DHCPOFFER from 192.168.42.129 DHCPREQUEST on ue0 to 255.255.255.255 port 67 DHCPACK from 192.168.42.129 bound to 192.168.42.2 -- renewal in 1800 seconds. Popping table
Afterword
Now we can use devd(8) to exploit our dynamic hardware control, but remember that it is not a substitute for man pages, so please read also devd(8), devd.conf(5) and devctl(8).