Presenting MDNS support for SoapyRemote Wed, Feb 28 2018 AM
In addition to the existing SSDP discovery mechanisms, support has been added for MDNS on both the client and server side of SoapyRemote. The implementation ties into the existing MDNS daemons: Bonjour on OS and Avahi on linux (and others OSs). On the server side, the MDNS daemon advertises the services to the local network. While on the client side, the daemon maintains a cache of available services.
A little background
There is a cacophony of multi-cast traffic buzzing around your local area network for the purposes of sharing and discovering services. Printers, routers, phones, PCs: they are all doing it. Lets take a peek at some of this traffic in wireshark:
SSDP
SSDP - or simple service discovery protocol is a HTTP based protocol on UDP port 1900. The HTTP fields encode service advertisements and network search requests.
The SSDP server's job
The gist of the protocol is that servers can advertise a service under a USN (unique service name) by sending periodic multicast "ssdp:alive" packets. The server also responds to search packets when prompted. And when a service is shutdown (nicely), the server can announce its removal from the network with "ssdp:byebye" packets.
The SSDP client's job
The client's job is to sit and listen to the alive and byebye packets to keep track of the list of available USNs and their locations on the network. A client just coming onto the network can also send a search packet to get a recent head count of all of the services on the network.
For more detail...
For a better break-down of the protocol, see section 9: https://embeddedinn.wordpress.com/tutorials/upnp-device-architecture/
MDNS
MDNS - or multicast domain name system is also a UDP oriented protocol based on encoding service information in the existing DNS packet format. The service discovery part, known as DNS-SD, is a particular use of MDNS to share information about services using the existing TXT and other record fields in the DNS packet structure.
The SSDP implementation
For the SSDP protocol, I actually rolled my own implementation embedded in a single source in SoapyRemote (see SoapySSDPEndpoint.cpp). I reasoned that I didn't want to burden SoapyRemote with any new dependencies, or for that matter take on any additional development challenges at the time.
The major downside is that the client side of SoapySSDPEndpoint isn't a daemon, it doesn't sit around and cache discovered servers. So when I call SoapySDRUtil --find
or Device::enumerate()
, the discovery routine has to query the network and wait for a response.
That delay can be unreliable: So once consequence is missing a device on the network due to random network latencies or throttling. Note that the 'remote:timeout' device argument can help with this. The other consequence is that the server must respond ASAP to search requests, which is technically incorrect -- the server is supposed to randomly delay responses to avoid possible denial of service attacks.
The MDNS implementation
So the major difference this time around is that the MDNS implementation is based on the system's available MDNS daemon. The support for MDNS is optional based on discovered development headers/libs; so this way SoapyRemote has no new required dependencies. The result of using the system's existing daemon is that server discovery is very fast and reliable.
Avahi
Installing avahi development files is fairly strait forward on debian style distros (sudo apt-get install libavahi-client-dev) and red-hat style distros (sudo yum install libavahi-devel). In this case the client in avahi-client does not mean client of the MDNS service, it means client of the avahi daemon. Both SoapyRemote client and server sides use avahi-client development files.
Bonjour
On OSX, there is a very similar API for DNS-SD located in the dns_sd.h
header.
This implementation was basically a breeze to use after figuring out the avahi API,
and in my opinion was actually a lot simpler in terms of API design and my resulting implementation.
That said, both APIs really gave me too much work when all I wanted was to register a service with and ID string, and to get a list of names and IP addresses.
Windows
Just a quick note about windows. SSDP (and UPnP) seems to be more of a windows thing, and I believe that they already have a SSDP daemon (service) which we can query. So to solve the daemon problem, we can keep the existing SSDP server-side, and switch to using a new API for the client. Also, it should be possible to build the Bonjour code on windows given that Apple distributes a windows adaptation for iTunes reasons...
Now you can imagine the interop-ability matrix between SSDP/MDNS implementations, daemons, and operating systems. So its probably for the best to keep both styles of discovery for the server regardless.
SoapyMDNS in action
Run the server as usual (either manually or through systemd):
SoapySDRServer --bind ###################################################### ## Soapy Server -- Use any Soapy SDR remotely ###################################################### ce650e5a-6a4c-1517-8567-773a007f0101 Launching the server... tcp://[::]:55132 Server bound to [::]:55132 Launching discovery server... Connecting to DNS-SD daemon... [INFO] Avahi version: avahi 0.6.32 [INFO] Avahi hostname: blah_server [INFO] Avahi domain: local [INFO] Avahi FQDN: blah_server.local [INFO] avahi_entry_group_add_service(blah_server._soapy._tcp) Press Ctrl+C to stop the server
Notice the new avahi prints, that means the service is now registered with the avahi daemon.
Look at mdns-scan on the client
Run mdns-scan (sudo apt-get install mdns-scan) to see a myriad of services on your network:
mdns-scan + blah_server._udisks-ssh._tcp.local + BLAH_SERVER._smb._tcp.local + Samsung ML-2510 Series @ blah_server._printer._tcp.local + Canon MF4320-4350 (UFRII LT) @ blah_server._printer._tcp.local + HLL8350CDW @ blah_server._printer._tcp.local + Brother MFC-8860DN @ blah_server._printer._tcp.local + Samsung ML-2510 Series @ blah_server._ipps._tcp.local + Canon MF4320-4350 (UFRII LT) @ blah_server._ipps._tcp.local + blah_server._soapy._tcp.local + HLL8350CDW @ blah_server._ipps._tcp.local + Brother MFC-8860DN @ blah_server._ipps._tcp.local + Brother MFC-8860DN @ blah_server._ipp._tcp.local + Samsung ML-2510 Series @ blah_server._ipp._tcp.local + HLL8350CDW @ blah_server._ipp._tcp.local + Canon MF4320-4350 (UFRII LT) @ blah_server._ipp._tcp.local + raspberrypi [b8:27:eb:b6:d2:3a]._workstation._tcp.local
Cool! We have ssh, samba, printers I don't even know about, and oh, a raspberry pi! It looks like every printer that I have ever connected has a cups profile and advertises itself to the network. But in the sea of printers, you might have spotted blah_server._soapy._tcp.local
, and that's our server!
SoapySDRUtil with debug prints
SoapyRemote will discover devices as usual, and will transparently use the MDNS when possible. But if we enable DEBUG logging we can see the MDNS and SSDP discovery at work:
SOAPY_SDR_LOG_LEVEL=DEBUG SoapySDRUtil --find ###################################################### ## Soapy SDR -- the SDR abstraction library ###################################################### [DEBUG] Avahi client running... [DEBUG] SoapyMDNS resolving blah_server._soapy._tcp.local IPv6... [DEBUG] SoapyMDNS resolving blah_server._soapy._tcp.local IPv4... [DEBUG] SoapyMDNS discovered tcp://[fe80::118a:5059:fe39:9fd4%2]:55132 [ce650e5a-6a4c-1517-8567-773a007f0101] IPv6 [DEBUG] SoapyMDNS discovered tcp://192.168.1.225:55132 [ce650e5a-6a4c-1517-8567-773a007f0101] IPv4 [DEBUG] SoapySSDP discovered tcp://192.168.1.225:55132 [ce650e5a-6a4c-1517-8567-773a007f0101] IPv4 [DEBUG] SoapySSDP discovered tcp://192.168.1.225:55132 [ce650e5a-6a4c-1517-8567-773a007f0101] IPv4 [DEBUG] SoapyClient querying devices for tcp://192.168.1.225:55132
Lets analyse this line by line:
- SoapyMDNS sees
blah_server._soapy._tcp.local
just like the mdns-scan above, and resolves both the IPv6 and IPv4 addresses. - That crazy number "ce650e5a-6a4c-1517-8567-773a007f0101" is the same number from the --bind command above. That's our server UUID , its unique to this particular instance of the server, and it lets us know which addresses belong to the same server.
- SoapySSDP also discovers the same address and UUID. Interestingly, it does not see SSDP for IPv6 in this case, even though the server is bound to [::] and the particular client machine has seen other IPv6 SoapySSDP servers. Networking config issues? ...this is why we need redundancy.
- SoapySSDP prints twice because we receive both a unicast and a multi-cast reply to the client search. Sometimes one reply or the other may be blocked or lost, so the server sends both.
- Finally, the servers are queries for available devices. The IPv4 address is used because SoapyRemote prefers IPv4 when the service is discovered under both protocols. See the 'remote:ipver' device argument to augment this preference.
Wrapping it up
In short, SoapyRemote (master branch) will transparently use MDNS in addition to its SSDP discovery protocol to get some additional flexibility and reliability for discovering servers on a local network. There are no additional required dependencies. If you made it this far, I hope you found this explanation informative, and the MDNS support useful. Please have some fun with mdns-scan and wireshark on your own home or work network.