20260102_Magic_DNS.md (8574B)
1 Topics: rust esp32 eculocate 2 Subject: Magic DNS 3 Date: Fri Jan 2 10:58:45 2026 4 5 It's all very 2026 around here, isn't it? I am reminded by 6 7 * [The Case for Blogging in the Ruins](https://www.joanwestenberg.com/the-case-for-blogging-in-the-ruins/) and 8 * [A website to destroy all websites](https://henry.codes/writing/a-website-to-destroy-all-websites/) 9 10 to jot down some of what I've been doing in the past month or so. The 11 tl;dr is "making a thing I can plug into my motorbike ECU to log the 12 data (rpm, speed, throttle position, temperatures etc etc) it 13 produces". For reasons mostly of ramifying the learning opportunities, 14 I decided the best way would be to get a cheap ESP-32 device (it's 15 RISC-V - isn't that cool?) and hook it up to a level converter, and 16 then write a program for it in Rust (Rust learning opportunity ahoy) 17 to twiddle the serial line appropriately and send the data over the 18 network to the mobile phone which sits on my handlebars. 19 20 It turns out that I spent _way_ less time getting the serial interface 21 to the [Honda K-line ECU signal](https://gonzos.net/projects/ctx-obd/) 22 to reveal its secrets than on the "why don't you just ..." part where 23 I want to stream the data over wifi to another device. So this post is 24 actually not at all about hardware hacking. 25 26 The constraints I have imposed on myself here are 27 28 * I do not want to hardcode my wifi ssid and password into the device 29 (actually, I have at least two wifi networks I may want to use it with) 30 31 * the (yet to be written) data collection app should be able to find 32 the device without hardcoding its IP address or requiring me to type it 33 in - as the device is getting its address from DHCP, we don't even 34 know what address it will get 35 36 These are both _in principle_ solved problems. 37 38 There's a convention for [provisioning wifi on these devices](https://randomnerdtutorials.com/esp32-wi-fi-provisioning-ble-arduino/) 39 which involves using a mobile phone app to connect to it using BLE 40 then sending the ssid/password of the chosen access point. In fact 41 there's even a prebuilt Android app which we can use and an esp32 42 arduino library which we can't (because we have elected to make our 43 lives difficult and use Rust instead). But I am led to believe that 44 "rewrite everything in Rust" is idiomatic for Rust programmers anyway. 45 I haven't done this yet. 46 47 And for the "what's my IP address" problem there is a standard way, by 48 combining [Multicast DNS](https://datatracker.ietf.org/doc/html/rfc6762) 49 and [DNS-based Service Discovery](https://datatracker.ietf.org/doc/html/rfc6763), for 50 computers to publish their services on the LAN. When I say 51 "computers": if this household is typical, mostly they're set-top 52 boxes, printers, light bulbs, smart speakers and thermostats rather 53 than general-purpose computing devices. I've mostly done this bit. 54 55 ## Terms 56 57 **Multicast DNS** is DNS, but peer-to-peer: it reuses mostly the same packet formats 58 but instead of requiring a centralised server which knows all the 59 names, every device listens on a multicast address 60 for DNS queries for its _own_ name. 61 62 **DNS-SD** is a convention for which records you can query/need to send in 63 order to advertise what kind of services you have and where they are. 64 Because sending an A record alone is not sufficient for anyone with a 65 Mac and a fancy-schmancy service browser to know what _kind_ of service 66 is on offer at that address. Is it a printer? A dishwasher? An 67 IoT air fryer? 68 69 The RFCs for each (which are, by the way, much easier reads than a lot 70 of RFCs and contain no EBNF at all) go to great lengths to point out 71 that each is independent of the other. But they stack well. 72 73 ## DNS-SD, 3048 metre view 74 75 DNS-SD is based on a paradigm of "services" and "service instances". A 76 "service" is the general "kind" of thing on offer and is named 77 something like `_http._tcp.local` - it will always end in `_tcp.local` 78 if it is TCP or `_udp.local` if it is anything other than TCP. For our 79 ECU project we chose the service name `_keihin._udp.local` after the 80 manufacturer of the ECUs that the device knows how to talk to. A 81 service instance might be something like 82 `WiserHeat05AB12._http._tcp.local`. Service names aren't usually 83 hierarchical but there are a few with a second level like 84 `_printer._sub._http._tcp` 85 86 87 The minimum/usual set of records you need to publish for DNS-SD is 88 this (pseudocode) 89 90 ``` 91 myinstancename._theservicename._udp.local SRV, data: (target: myhostname.local, port: nnnn) 92 myhostname.local A, data: a.b.c.d 93 myinstancename._theservicename._udp.local TXT, data: "txtvers=1" 94 _theservicename._udp.local PTR, data: myinstancename._theservicename._udp.local 95 _services._udp.local PTR, data: _theservicename._udp.local 96 ``` 97 98 Your service instance needs a SRV and a TXT, then there's a PTR 99 connecting the service instance to the service for people who are 100 browsing the service - think about e.g. an "Add a printer" dialog box, 101 then there's a PTR from `_services._udp.local` to the service name PTR 102 for people who are running `avahi-browse -a` or its moral equivalent 103 in GUI-land. And not forgetting there's an A record matching the one in the 104 SRV record data. 105 106 * Note: `_services._udp.local` is the right name for discovery 107 even if your service is TCP - there is no `_services._tcp.local` 108 109 * Note: I am assuming `.local` is the suffix, which is likely true for 110 MDNS but probably not if you are using DNS-SD with regular DNS 111 112 ## MDNS 113 114 The single biggest problem when implementing MDNS is the lack of 115 tooling to test it against. In my experience: 116 117 * dig: historically, you used to be able to `dig @224.0.0.251 -p 5353 name.local` but it largely worked by accident and [now it 118 doesn't](https://gitlab.isc.org/isc-projects/bind9/-/issues/3428). Note 119 that even when it _did_ work it wasn't sending the same packets as a 120 real MDNS query. 121 122 * avahi-browse: e.g. `avahi-browse -v -a` shows all the services on 123 the LAN. Note that is is a frontend to the persistent `avahi-daemon` 124 and there is caching happening in there somewhere, so if it didn't 125 work then and you made a change and restarted your service ... is 126 your service still broken or did it not reissue the query? shrug-emoji.gif 127 128 * `mquery` from Jeremie Miller's mdnsd: note that this has been forked 129 into a million pieces. The one I'm using is 130 [https://github.com/troglobit/mdnsd](https://github.com/troglobit/mdnsd). 131 This works for querying the `_services._udp.local` discovery name 132 but if you run e.g. `mquery _scanner._tcp.local` it appears to send 133 the query and then sit there silently ignoring the responses. *But*: 134 it doesn't cache as avahi-browse does, so that's good. 135 136 * wireshark, of course. Wireshark is pretty good, but note that it 137 will display your replies as "Unsolicited" because the query id for 138 MDNS is (per the standard) 0, so there is no way for it to correlate 139 them with requests. 140 141 * [mdns-debugger](https://github.com/andrewbonney/mdns-debugger) was 142 handy for pointing out my TTLs (and a lot of other TTLs) were wrong. 143 It didn't point out that my PTR record data was incorrectly encoded 144 and was therefore naming a nonexistent A record, which was a source 145 of much hair tearing. 146 147 * there are a couple of Android apps I also used, mostly to see what 148 they'd do when nothing was working (see "hair tearing" above) and I 149 wa sout of ideas. "mDNS Discovery" (`com.mdns_discovery.app`) and 150 "Service Browser" (com.druk.servicebrowser). The first one is 151 prettier, the second one _very_ helpfully rendered the errant PTR as 152 with a backslash as `eculogical\.local` and so led me to the said 153 encoding error. 154 155 156 ## Where are we now? 157 158 I believe that it now does everything an mdns responder SHOULD(sic) do 159 except 160 161 * compress labels in response data 162 * ignore queries for records where the record we'd send is already in the answers section of the query message 163 * NSEC 164 165 and I can't decide, in the context of this being a program that 166 probably nobody else in the world will ever use and even I will only 167 use on one single piece of hardware (I only have one motorbike) 168 whether implementing those things is a good and laudable decision 169 because spec compliance is important, or just a way of further putting 170 off the inevitable next step which involves writing the Android app 171 to collect the data. 172 173 It also could do with being extracted into its own module/crate/thing 174 to be more modular. I'd say "to aid reuse" but I don't think anyone 175 really wants to (or should want to) reuse my [novice-level Rust code](https://gti.telent.net/dan/eculogical/src/branch/main/ecu-esp32/src/wifi.rs). 176 Learning in public.