micropython / micropython-esp32

Old port of MicroPython to the ESP32 -- new port is at https://github.com/micropython/micropython
MIT License
673 stars 216 forks source link

RFC: mDNS support implementation #201

Closed MrSurly closed 4 years ago

MrSurly commented 6 years ago

I have current projects (e.g. RS232 <--> WiFi adapter) that could certainly benefit from mDNS -- hard-coding IPs, or scanning ports sucks. I know the IDF has support, but I haven't tried it.

Since I'm not working on cache invalidation, this means the problem is naming things.

Would having a top-level network.mDNS object make a sensible starting point?

nickzoic commented 6 years ago

mDNS has been on my list for ages and is definitely a requirement for me for LinuxConf ... There's two ways to do this: either wrap the IDF mDNS code in a Python class (network.mdns, etc) or make the required multicast UDP available in uPy and then implement mDNS in uPython. I'm lean towards the latter, because it would make the mDNS function shared across multiple ports, which would be nice.  But the former might be more expedient.

MrSurly commented 6 years ago

Maybe both? Use the IDF for the esp32 port, multicast UDP for others?

But I don't know enough about the inner workings of any of this to have a firm opinion.

nickzoic commented 6 years ago

Yeah, so long as the API is identical it shouldn't matter, philosophically speaking :-)

dpgeorge commented 6 years ago

I was looking in to this just a few days ago, but didn't get very far. I'd suggest making a pure-uPy implementation for both the client and server, to see what's required, then optimise for a specific port if needed. It should just need multicast UDP support.

nickzoic commented 6 years ago

Yeah, last time I looked at it the multicast UDP subscription support wasn't there, but it looks like it was added in 56f186028 ... in which case a Python implementation should be super easy.

MrSurly commented 6 years ago

Wouldn't pure-uPy require that it be in your main loop?

dpgeorge commented 6 years ago

Wouldn't pure-uPy require that it be in your main loop?

For a server, yes. But I don't think so for a client. The idea with a uPy solution is to 1) see what's involved; 2) have a reference implementation that works and is easy to debug/change; 3) have something that will run on all ports even if they don't have built-in mDNS.

nickzoic commented 6 years ago

Problem being, I suspect most of the interest in mDNS is in being a "server", eg: a device waiting to advertise its existence to the local network. You can do that by select over your multicast listening socket, but it is kind of inconvenient to do that in a library. This might not be the place for it, but how would you feel about a socket.set_recv_callback() method?

dpgeorge commented 6 years ago

how would you feel about a socket.set_recv_callback() method?

This feature (but under a different name, via ioctl) already exists in the esp8266 implementation of socket, and is used for webrepl so it can act as a "daemon" in the background. And it's the reason why webrepl is not easy to port to the esp32 because we don't have such callbacks available here. In general such callbacks are not easy/possible to port to other platforms so best to avoid if at all possible.

MrSurly commented 6 years ago

Maybe import _thread ? Though I've never used it in µPy

nickzoic commented 6 years ago

Ah, right, I thought I'd seen it somewhere, although I never quite got to the bottom of WebREPL and my rrepl works quite differently. It is quite unpythonic but then again I'm not totally convinced that MicroPython shouldn't be pinching ideas from other languages (in this case, JavaScript's event queue) where they are applicable.

dpgeorge commented 6 years ago

Note that recent version of lwIP (like v2+) have an mDNS client and server builtin, but esp32's version (v1.5) doesn't.

julienfr112 commented 6 years ago

This works, and respond to every dns requests the local address


from _thread import start_new_thread
import socket

ip = bytes([192, 168, 0, 1])

def response(packet):
    return b''.join([
        packet[:2],             # Query identifier
        b'\x85\x80',            # Flags and codes
        packet[4:6],            # Query question count
        b'\x00\x01',            # Answer record count
        b'\x00\x00',            # Authority record count
        b'\x00\x00',            # Additional record count
        packet[12:],            # Query question
        b'\xc0\x0c',            # Answer name as pointer
        b'\x00\x01',            # Answer type A
        b'\x00\x01',            # Answer class IN
        b'\x00\x00\x00\x1E',    # Answer TTL 30 secondes
        b'\x00\x04',            # Answer data length
        ip])                    # Answer data

def run():
    dnsserver = socket.socket(socket.AF_INET,
                              socket.SOCK_DGRAM,
                              socket.IPPROTO_UDP)
    dnsserver.setsockopt(socket.SOL_SOCKET,
                         socket.SO_REUSEADDR,
                         1)
    dnsserver.bind(('0.0.0.0', 53))
    # dnsserver.setblocking(True)
    while True:
        packet, cliAddr = dnsserver.recvfrom(256)
        packet = response(packet)
        if packet:
            dnsserver.sendto(packet, cliAddr)

start_new_thread(run, ())

I have set ifconfig like that :

import network

ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid="Setup")
ap.ifconfig(('192.168.0.1', '255.255.255.0', '192.168.0.1', '192.168.0.1'))
nickovs commented 6 years ago

I've just released version 0.1.0 of slimDNS, a Simple, Lightweight Implementation of Multicast DNS. It's designed for MicroPython and I've tested it on the ESP32 and ESP8266 ports so far.

slimDNS tries to be standard-complient wherever possible but if the RFC says that we SHOULD do something and it will take a lot of memory to do it (e.g. maintaining a cache) then it errs on the side of smaller memory footprint. As it stands if you use it as a frozen module it takes about about 1260 bytes when in use (assuming you were already using the network, socket and select modules, because without an application using these advertising your address is a little pointless).

This is very much "version 0.1.0" code quality. It works for the use cases that I needed and the trivial extensions to those that immediately came to mind. There is fundamental support for multicast DNS service discovery but at the moment you have to roll the PTR, SRV and TXT record bodies by hand. I will be adding support for this, either in the main module (if it doesn't expand the memory footprint of the frozen module too much) or as an optional separate module.

Right now I'd very much welcome any feedback (positive or otherwise) on the code, the API, the feature set and/or directions for improvement. In the mean time I hope it is of use to the MicroPython community.

MrSurly commented 6 years ago

@nickovs Thanks for this =) JFYI, this repo has been merged back into mainline MP.

nickovs commented 6 years ago

@MrSurly, you're welcome! I did see that the ESP32 support has been merged into the mainline and in fact I'm using it regularly! I posted here because this seemed to be the most comprehensive discussion thread of MicroPython mDNS support on GitHub. I also posted to the MicroPython forum about this in case anyone there was interested.

MrSurly commented 6 years ago

@nickovs You're right, this is probably the right place to announce. Just we get a lot of issues/PRs here from people who aren't aware of the merge.

I have some upcoming client work where I might incorporate this, even just to try out =)

goatchurchprime commented 5 years ago

For the use of MQTT the ability to look up ipnumbers using mDNS messages is more useful that being able to broadcast the name of the device onto mDNS.

It's really important for the device to be able to find the MQTT broker and then it can post things to its channel (including its ipnumber, if it wants to).

I've managed to hack down a very minimal 60 line implementation of the mDNS here from the SLIMdns that gets to the nub of the implementation. (a short binary packed sequence goes out to 224.0.0.251:5353 and another short binary packed sequence of records comes back with the ipnumber, and you've got to find the right record and pick lift the 4-byte ipnumber.)

https://github.com/goatchurchprime/jupyter_micropython_developer_notebooks/blob/master/basicsockets/minimalmdns.py

dpgeorge commented 4 years ago

In addition to slimDNS, mDNS support was added to the esp32 upstream in commit https://github.com/micropython/micropython/commit/2ccf030fd1ee7ddf7c015217c7fbc9996c735fd1