Skip to content

[Feature Request] mDNS Service Discovery #1284

Open

Description

@george-palmsens

I want to be able to do automatic service discovery using DNS-SD over mDNS.


I would like to be able to use a more advanced hook than xApplicationDNSQueryHook to be able to describe a more comprehensive set of DNS queries to provide responses to. Then DNS-SD could be supported on top of that.


For example:

/* Records necessary for DNS-SD */
const DNSRecordTable_t records[] = {
    {dnsTYPE_PTR, .name ="service._udp.local", .domain="myname.service._udp.local"},
    {dnsTYPE_SRV, .name ="myname.service._udp.local", .port = 5353, target="myname.local"},
    {dnsTYPE_HOST_A, .name = "myname.local"}
};

BaseType_t xApplicationDNSQueryHook(DNSRecordTable_t const ** out_records, size_t* out_records_len) {
    *out_records = records
    *out_records_len = LEN(records);
    return pdTRUE;
}

I am also happy to help deliver this feature.

This would serve LLMNR through the same mechanism.

Activity

  1. george-palmsens commented on Aug 6, 2025

    @george-palmsens
    ContributorAuthor

    I have a working proof of concept where the user facing API is as follows:

    static const DNSRecord_t s_records[] = {
        {.record_type = dnsTYPE_PTR, .name="\x7service\x4_udp\x5local", .data.ptr_record = "\x8hostname\x7service\x4_udp\x5local"},
        {.record_type = dnsTYPE_SRV, .name="\x8hostname\x7service\x4_udp\x5local", .data.srv_record = {.target="\x8hostname\x5local", .port=5353}},
        {.record_type = dnsTYPE_A_HOST, .name="\x8hostname\x5local"}
    };
    
    BaseType_t xApplicationDNSQueryHook( const DNSRecord_t ** outRecords, size_t * outLen ) {
        *outRecords = s_records;
        *outLen = 3;
        return pdTRUE;
    }

    Obviously defining record names with the path lengths pre-included is gross, but it was easy just for getting things moving. This then responds correctly to DNS-SD service resolvers (such as mdns-sd).

  2. david-sawatzke commented on Aug 6, 2025

    @david-sawatzke

    I'm also interested in this feature. As far as I can tell, there's no way to listen to multicast mDNS requests as a user of the FreeRTOS plus TCP stack (implementing it outside, without using the integrated mDNS feature).

  3. moninom1 commented on Aug 7, 2025

    @moninom1
    Contributor

    Thank you for raising this feature request. I’ll review it with the team and follow up soon.

  4. george-palmsens commented on Aug 7, 2025

    @george-palmsens
    ContributorAuthor

    I'm also interested in this feature. As far as I can tell, there's no way to listen to multicast mDNS requests as a user of the FreeRTOS plus TCP stack (implementing it outside, without using the integrated mDNS feature).

    This would require FreeRTOS to support multicast listeners, and raw sockets. That would be very cool, but a big change.

    I hope that this is a much smaller code difference.

  5. tony-josi-aws commented on Aug 8, 2025

    @tony-josi-aws
    Member

    @george-palmsens We have a multicast support PR that is in progress - #1019
    Since it's already in progress, I'm wondering if this feature can be implemented better with multicast support.

  6. george-palmsens commented on Aug 11, 2025

    @george-palmsens
    ContributorAuthor

    @george-palmsens We have a multicast support PR that is in progress - #1019 Since it's already in progress, I'm wondering if this feature can be implemented better with multicast support.

    That would certainly work - My proposition was mainly to keep the delta as small as possible, for the sake of implementation speed.

  7. htibosch commented on Aug 11, 2025

    @htibosch
    Contributor

    @george-palmsens, your proposal sounds good!

    @evpopov, what is your feeling about the above proposals? Any comments, recommendations?

      // Current signature
      BaseType_t xApplicationDNSQueryHook_Multi( NetworkEndPoint_t *pxEndPoint, const char *pcName )
      // Desired signature
      BaseType_t xApplicationDNSQueryHook( const DNSRecord_t ** outRecords, size_t * outLen )

    We used to have a application hook called xApplicationDNSQueryHook(), which is now renamed to xApplicationDNSQueryHook_Multi(), provided that ipconfigIPv4_BACKWARD_COMPATIBLE is not enabled.

    Can we have a new config macro that tells if the new DNSRecord_t feature is in use?

    I hope that after adding this feature none of the existing sources have to change.

  8. george-palmsens commented on Aug 13, 2025

    @george-palmsens
    ContributorAuthor

    @george-palmsens, you proposal sounds good!

    @evpopov, what is your feeling about the above proposals? Any comments, recommendations?

    // Current signature
    BaseType_t xApplicationDNSQueryHook_Multi( NetworkEndPoint_t *pxEndPoint, const char *pcName )
    // Desired signature
    BaseType_t xApplicationDNSQueryHook( const DNSRecord_t ** outRecords, size_t * outLen )

    We used to have a application hook called xApplicationDNSQueryHook(), which is now renamed to xApplicationDNSQueryHook_Multi(), provided that ipconfigIPv4_BACKWARD_COMPATIBLE is not enabled.

    Can we have a new config macro that tells if the new DNSRecord_t feature is in use?

    I hope that after adding this feature none of the existing sources have to change.

    Yeah we can do that. I only named it the same as a sort of comparison to what we have already.

    But it would be perfectly easy to name it something else and have it switched in via config.

  9. evpopov commented on Aug 13, 2025

    @evpopov
    Contributor

    @george-palmsens We have a multicast support PR that is in progress - #1019 Since it's already in progress, I'm wondering if this feature can be implemented better with multicast support.

    @tony-josi-aws I don't know exactly what @george-palmsens code looks like, but #1019 doesn't really do anything with mDNS or the APIs. There is only a small change to FreeRTOS_DNS_Parser.c that will probably not overlap with the proposal here. My personal opinion is that this proposal should not be attached or dependent on #1019 to avoid complications.

    @htibosch Sounds great to me. Sadly I can't speak from experience about DNS-SD, but I also have general interest in the subject, so yeah, service discovery support sounds great.

    I'm personally very curious to see the proposed code and maybe give it a try and learn more about service discovery.
    @george-palmsens could you please share your proof-of-concept so we could try it out? Whether it's in the form of a draft PR or just a link to your repo. I'd like to get a better feel for the complexity that this adds to xApplicationDNSQueryHook_Multi()

  10. george-palmsens commented on Aug 14, 2025

    @george-palmsens
    ContributorAuthor

    Yep, I'll make it available today.

    Do note that I didn't keep xApplicationDNSQueryHook_Multi, I replaced it with a new hook in the manner mentioned in the Issue description. But I hope it's still useful to see.

  11. george-palmsens commented on Aug 14, 2025

    @george-palmsens
    ContributorAuthor

    Okay, you can find my fork at https://github.com/george-palmsens/FreeRTOS-Plus-TCP/tree/dns_sd_support

    It is very rough, just to prove the idea. It is compiled with ipconfigUSE_LLMNR and ipconfigUSE_MDNS.

    It also uses ipconfigIPv4_BACKWARD_COMPATIBLE, because that's what I was using in the project I demo'd this in.

    It changes the signature of the DNS hook to:

    BaseType_t xApplicationDNSQueryHook( const DNSRecord_t ** outRecords, size_t * outLen )

    so the application code must implement something like the following:

    static const DNSRecord_t s_records[] = {
        {.record_type = dnsTYPE_PTR, .name="\x9myservice\x4_udp\x5local", .data.ptr_record = "\x6myname\x9myservice\x4_udp\x5local"},
        {.record_type = dnsTYPE_SRV, .name="\x6myname\x9myservice\x4_udp\x5local", .data.srv_record = {.target="\x6myname\x5local", .port=5353}},
        {.record_type = dnsTYPE_A_HOST, .name="\x6myname\x5local"}
    };
    
    
    BaseType_t xApplicationDNSQueryHook( const DNSRecord_t ** outRecords, size_t * outLen ) {
        *outRecords = s_records;
        *outLen = 3;
        return pdTRUE;
    }

    I hope that's enough to get the idea across! Let me know if I can clear anything up.

  12. evpopov commented on Aug 18, 2025

    @evpopov
    Contributor

    @george-palmsens I tried your code but couldn't get it to work. The only way I know to query for services is to use avahi-browse --all --terminate and that seems to generate the two-question query from the attached wireshark trace
    dns-sd-two-questions.zip
    DNS_ParseDNSReply() failed after parsing the first question.
    I limited the questions loop to only parse the first question and finally got the xApplicationDNSQueryHook() to get called.
    I used your sample callback code but still couldn't see a response. I'm struggling because I do not know what should and should not happen in DNS_ParseDNSReply() due to my inexperience with DNS-SD

    I was finally able to get some response by modirying my records definition to

    	static const DNSRecord_t s_records[] = {
    		{.record_type = dnsTYPE_PTR, .name="\xa_myservice\x4_udp\x5local", .data.ptr_record = "\x6myname\x9myservice\x4_udp\x5local"},
    		{.record_type = dnsTYPE_SRV, .name="\x7_myname\x9myservice\x4_udp\x5local", .data.srv_record = {.target="\x6myname\x5local", .port=5353}},
    		{.record_type = dnsTYPE_A_HOST, .name="\x7_myname\x5local"}
    	};
    
    

    and running avahi-browse _myservices._udp --terminate
    however I don't think avahi liked the response because the above did not produce any output. I think DNS_ParseDNSReply() is not capable of sending more than 1 response record.
    here's the wireshark capture that I finally got:

    dns-sd-response-1.zip

    It would be nice if you could get this proof of concept a little better implemented with maybe some general service discovery information and sample Windows/Linux usage commands. If I was implementing a printer, I'd surely need this, but other than that, what are other examples of services that can be discovered on the network? I can envision this being used as the basis to "Discover" device IPs and their services without writing custom discovery tools as we often have to do, but I don't see any built-in Windows or Linux commands to tap into this potential. Can you elaborate a bit on sample usage?
    I see that printers advertise printing services and configuration web pages which sounds intriguing, but how do you "browse" for these resources with common user tools?

  13. george-palmsens commented on Aug 19, 2025

    @george-palmsens
    ContributorAuthor

    @evpopov Thanks for giving it a go! I appreciate your persistence.

    I have personally been using the mdns rust library for my queries, but maybe I should try avahi.

    Let me dig in a little and see if I can work out some more about what you're seeing.


    It's true that there's not tons of user facing discovery tools. But the use case for us, for example, is to allow our software to find our hardware on the network automatically.

  14. george-palmsens commented on Aug 19, 2025

    @george-palmsens
    ContributorAuthor

    @evpopov Okay, think I've made some progress. Avahi seems to want the response to contain only answers, no questions.

    If I do that, avahi picks up the service. I'll do some more work to make sure I understand it fully but thanks for pointing this out :)

  15. george-palmsens commented on Aug 19, 2025

    @george-palmsens
    ContributorAuthor

    @evpopov Okay, I now have avahi working.

    $ avahi-browse _myservice._udp -r
    +   eth2 IPv4 instanceFRTCP                                 _myservice._udp      local
    =   eth2 IPv4 instanceFRTCP                                 _myservice._udp      local
       hostname = [instanceFRTCP.local]
       address = [192.168.0.26]
       port = [5353]
       txt = []
    

    Changes needed:

    • Service must begin with an underscore. _myservice._udp.local not myservice._udp.local
    • mDNS responses must not contain questions, only answers
    • Service must serve a TXT record at the service address (I thought this was optional)

    Additionally, Avahi (and DNS requests generally) may issue more than one question in a single DNS query. The FreeRTOS DNS reply system currently discards all questions except the last, which is a big problem. If Avahi asks for SRV+TXT, we need to respond with both.

    So the necessary work to get this going properly are:

    • Add TXT records as an option in the records table
    • Change DNS_ParseDNSReply to build a response to all questions in the query, not just the last
      • Oof, this will be quite a big change.
    • Change DNS_ParseDNSReply to remove the questions from responses
      • Basically a big memmove of the packet at the end, which is a bit sad. Maybe we can build up the response in a new buffer? I don't know how that works under the hood.
      • I also don't know if this would have implications for the existing mDNS and LLMNR responses. My guess is no...?

    I have added this to my fork in a very dirty way. Basically if the last DNS question matches any of our records, we serve all of our records.

    This is wrong and bad but it is easy to do, and hopefully helps you get it at least working to have a look.

    The records I am using for this are:

    static const DNSRecord_t s_records[] = {
        {.record_type = dnsTYPE_PTR, .name="\x9_services\x7_dns-sd\x4_udp\x5local", .data.ptr_record = "\xA_myservice\x4_udp\x5local"},
        {.record_type = dnsTYPE_PTR, .name="\xA_myservice\x4_udp\x5local", .data.ptr_record = "\xDinstanceFRTCP\xA_myservice\x4_udp\x5local"},
        {.record_type = dnsTYPE_SRV, .name="\xDinstanceFRTCP\xA_myservice\x4_udp\x5local", .data.srv_record = {.target="\xDinstanceFRTCP\x5local", .port=5353}},
        {.record_type = dnsTYPE_TXT, .name="\xDinstanceFRTCP\xA_myservice\x4_udp\x5local"},
        {.record_type = dnsTYPE_A_HOST, .name="\xDinstanceFRTCP\x5local"}
    };

    The first record (_services._dns_sd._udp.local) allows avahi-browse -a to find us. The TXT record satisfies Avahi's need for such a record, and makes everything work.

  16. evpopov commented on Aug 19, 2025

    @evpopov
    Contributor

    @george-palmsens

    I think you forgot to commit your changes to FreeRTOS_DNS_Globals.h because I'm missing the definition for dnsTYPE_TXT
    Also, I think you missed a break statement in case dnsTYPE_SRV of switch (record->record_type) causing us to report 6 answers but only encode the 5 entries in DNSRecord_t s_records[].

    Other than that, this is really cool. I'm not sure if or when I can use dns-sd, but this is definitely a cool feature. With those changes I'm getting avahi-browse to show the service as well. Great!

    If you have the time, it would be really cool to get this beyond the proof of concept stage and do a proper implementation. That will probably require a little more planning and research into the best way to modify the existing callbacks..... Or, here's an alternative idea: What if you had a new callback that is responsible for just service requests. The old callbacks could service the A and AAAA queries. I know DNS can have all sorts of queries in a single packet but just an idea.

    If you want to develop and maintain this maybe ask the +TCP maintainers if they are OK with putting this in a draft PR or if not, just leave it in your repo and keep the link here for people to find your work.
    Thanks for sharing this.

  17. george-palmsens commented on Aug 19, 2025

    @george-palmsens
    ContributorAuthor

    @evpopov

    I appreciate you taking the time to work around my crusty prototype code! Glad to hear you got it running.

    I will definitely bring this to a full PR, the only thing giving me pause at the moment is not knowing the details having not really contributed here before. But perhaps it's better just to get some of the way there, then at least we have something real to discuss.

    I was also planning to preserve the old callbacks. So in the case of the old callback style, we basically wrap them up so they produce results like the new "records collection" style. Then all the old application code stays working, and just plugs into the new DNS processing. I haven't fully fleshed that out in my mind, but I guess we're imagining similar things.


    What does asking the maintainers entail beyond what we've already done here? Are draft PRs a special thing in this repo?

  18. tony-josi-aws commented on Aug 20, 2025

    @tony-josi-aws
    Member

    @george-palmsens I took a quick look at the dns_sd_support branch in your fork; the proposal looks good to me other than some code style/formatting changes.

    Please feel free to create a PR once it's ready if you wish to.

  19. george-palmsens commented on Aug 20, 2025

    @george-palmsens
    ContributorAuthor

    @george-palmsens I took a quick look at the dns_sd_support branch in your fork; the proposal looks good to me other than some code style/formatting changes.

    Please feel free to create a PR once it's ready if you wish to.

    Thanks, will do! I'll PR when it's up to standard.

  20. htibosch commented on Aug 21, 2025

    @htibosch
    Contributor

    @evpopov wrote:

    Or, here's an alternative idea:

    The old callbacks could service the A and AAAA queries. I know DNS can have all sorts of queries in a single packet but just an idea.

    I'm not sure if the current implementation supports a mixed requests with both A and AAAA queries.

    As I have several private projects using the current DNS callback mechanism, I will be glad to help testing backward compatibility.

    A short history of DNS support:

    DNS in FreeRTOS+TCP started with the simplest possible query: looking up an IPv4 address using gethostbyname():

        uint32_t ulAddress = FreeRTOS_gethostbyname( "freertos.org" );
        if( ulAddress != 0U )
        {
            struct freertos_sockaddr addr;
            addr.sin_addr = ulAddress;
            addr.sin_port = HTTP_PORT;
            FreeRTOS_connect( xSocket, &addr, sizeof addr );
        }
    

    As a DNS query on the Internet may last seconds, it would be convenient to have a call-back. Either when the query succeeds, or when it times out, a user hook will be called.
    So beside FreeRTOS_gethostbyname(), we also made FreeRTOS_gethostbyname_a(), an "asynchronous" version.

    gethostbyname() had many limitations, getaddrinfo() replaced it. We needed it to handle AAAA (IPv6) queries.

    And then we started using mDNS and LLMNR in both directions. These are both used on a LAN, and they can be replied to by a FreeRTOS device. I still use these to locate an embedded website on a LAN, so I can surf to "https://my_sound_system", an URL without a dot, or using the postfix ".local".

    FreeRTOS+TCP rarely makes a change that breaks compilation. FreeRTOS_gethostbyname() still works in today's release.

    In FreeRTOS_IP.h, we have this section:

    #if ( ipconfigENABLE_BACKWARD_COMPATIBILITY == 1 )
        #define FOnConnected                  FOnConnected_t
        #define FOnTcpReceive                 FOnTCPReceive_t
        #define FOnTcpSent                    FOnTCPSent_t
        ...
    #endif
    

    When a name change is really important, this section can be useful to keep the code backward compatible, and to make upgrading less painful.

  21. evpopov commented on Aug 22, 2025

    @evpopov
    Contributor

    As I'm reading through RFC6736 and I'm examining the DNS-SD traffic in my office, I'm beginning to realize that DNS-SD is not a trivial thing. Printers for example will reply with a bunch of answer advertising not only their IPP service, but also A and AAAA records, maybe a web page, a scanner service, all sorts of stuff in one nice little DNS-SD response.
    I think properly handling DNS-SD with all of it's query possibilities will be very difficult with the current callbacks. I think modifying them will be a nightmare because of the need to keep backwards compatibility.

    Let me elaborate a bit on the callback idea...
    Imagine a completely new callback handler. One that passes to the user the end-point that the query came in on, and all the queries and answers that the incoming packet had. It then generates a response based on a list of queries and answers that the user provides in this new callback. A callback like that would be a bit too advanced for beginners to handle simple A, AAAA, and DNS-SD requests but nothing that can't be solved with a simple example in the documentation.
    The beauty of it is that is leaves it to the user to parse as much or as little as they want, it lets them form one or multiple answers, let's them decide which IP to respond from. The down side is that the parser would have to pass a variable list to the user thus requiring extra RAM and CPU.

    Here's a quick example of two printers with vastly different responses as well as a MacBookAir using multiple queries and answers to explore the network.

    dns-sd-examples.zip

    The only other way I see to handle all those versatile packets is to abandon the idea of a user callback, but instead provide an API for the user to register records with the network stack. One such registration might contain things like a record type, name, and a list of pointers to end-points on which the service is available. The DNS parser would then do the heavy lifting of parsing all the questions, digging through all registered records and forming a multi-answer response without any user callbacks at this point. This second proposal I think will be harder to implement and less versatile, but much easier to use by the end user.

  22. george-palmsens commented on Aug 25, 2025

    @george-palmsens
    ContributorAuthor

    @evpopov What you're describing in the second case is basically what I've proposed, right? That the user provides a list of records to serve and the DNS code builds up the answer.

    I don't think that is really so complicated to implement - it is not a million miles from the prototype code I've already shared.

  23. evpopov commented on Aug 25, 2025

    @evpopov
    Contributor

    @george-palmsens, My second case proposes not using callbacks at all. It Adds a "Register" function to FreeRTOS_TCP that the user uses to register all DNS records that they want. +TCP will have to allocate a list of these and keep them for later use. Later on when DNS-SD queries come in, +TCP will have to parse the list and form a reply.

    Even though I suggested this as an option, I'm not a very big fan of this approach because it is quite limiting. The user will not get fine control on what they put in the responses. For example +TCP will decide whether to put an A record in the response, an AAAA record or both and for which endpoints? Additionally, what hostname does +TCP place in the response? My device at work answers to 2-3 different hostnames.

    My first proposal in more in line with your current code. With that proposal I was trying to preserve backwards compatibility by keeping the current callbacks the way they are. Adding a new callback would allow for full freedom of what goes into a DNS-SD response. Maybe the new callback only handles PTR queries while the old calbacks can continue serving the A and AAAA queries. Your code does almost exactly that because it forms the response with all the records that the user provides. I just don't see a easy way to make your code work nicely with the current callbacks in a backwards-compatible manner. I may be wrong. Don't let me discourage you. Those are just suggestions.

  24. george-palmsens commented on Aug 25, 2025

    @george-palmsens
    ContributorAuthor

    I see, thank you for clarifying.

    Yeah when I get a chance, I'll play around with the code. I think there's a path forward.

  25. nicolasb565 commented on Sep 2, 2025

    @nicolasb565

    Hi, I am also interested in that feature. We want to discover our devices on a local network using bonjour/avahi. Thank you for your work.

  26. george-palmsens commented on Mar 11, 2026

    @george-palmsens
    ContributorAuthor

    There is now a PR open, that I think is sufficient for discussion @ #1326

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions