Internet Radios, UDP, Multicasts, SSDP and Windows Phone
December 30, 2014We recently got a new kitchen radio (a Christmas present my wife and I bought for ourselves) and it is a lovely little device. It features:
- plain old FM radio stations
- also Web radio
- Spotify Connect
- network media playback
- podcast discovery and playback
The only thing it lacks is a remote control, which is apparently considered unnecessary since there is an app for this device.
As so often, this app is not available for Windows or Windows Phone.
So, once again, I decided to do some network recording using my favorite tool, some protocol reverse engineering and crafting my very own Windows Phone app for this device.
Locating the Device on the Network
The first interesting challenge was discovering this new device on the network. Actually this turned out to be quite the challenge, and it’ the main reason I decided to write this blog post about it.
The radio uses SSDP (Simple Service Discovery Protocol) to announce its presence and services on the network, which (fortunately) is a common and standardized behavior for devices like that.
SSDP uses HTTP over UDP (HTTPU) to request or announce service data.
A typical SSDP M-SEARCH request asking for “all kinds of services” on a network looks like this (frame data copied from Microsoft Network Monitor):
- http: Request, M-SEARCH *
Command: M-SEARCH
- URI: *
Location: *
ProtocolVersion: HTTP/1.1
ST: ssdp:all
MX: 3
MAN: "ssdp:discover"
Host: 239.255.255.250:1900
HeaderEnd: CRLF
A device responds in the same format with an additional LOCATION header, which usually points to a URL that contains further metadata about the device:
- http: Response, HTTP/1.1, Status: Ok, URL:
ProtocolVersion: HTTP/1.1
StatusCode: 200, Ok
Reason: OK
ST: urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
USN: uuid:bb418659-8b41-4247-9cb4-f7a4719d4cb2::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
Location: http://192.168.1.149:2869/upnphost/udhisapi.dll?content=uuid:bb418659-8b41-4247-9cb4-f7a4719d4cb2
OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS: 01d397012d31c69c18df286a97210895
Cache-Control: max-age=900
Server: Microsoft-Windows/6.3 UPnP/1.0 UPnP-Device-Host/1.0
Ext:
HeaderEnd: CRLF
It’s interesting how many devices on a typical local network will advertise their services that way. I “discovered” my Xbox One, two routers, a couple of PCs and laptops in addition to the actual radio answering my request.
Implementing the Protocol
Let’s have a look at the actual SSDP protocol implementation for a Windows Phone Universal app.
First, we create an instance of a DatagramSocket (UDP) and register an event handler for receiving data.
_socket = new DatagramSocket(); _socket.MessageReceived += HandleMessageReceived;
Then, we need to bind the socket to a local port and prepare it for multicast usage. Hard-coding the local port is currently a weak point of this implementation and I will have to find a better solution to do that.
await _socket.BindServiceNameAsync(6000); _socket.JoinMulticastGroup(new HostName("239.255.255.250"));
The next step is opening an output stream to a multicast endpoint and sending the request data. It’s not a lot of code, but the UDP multicast API is poorly documented and it’s critical to use the right set of API calls in the correct order here.
Because of its simplicity, the SSDP message is hard-coded here. Note: the ST part of the headers is the specific service (urn:schemas-frontier-silicon-com:fs_reference:fsapi:1) we are requesting to found the Internet Radio device.
var s = await _socket.GetOutputStreamAsync(new HostName(239.255.255.250), "1900"); using (var dataWriter = new DataWriter(s)) { var msg = "M-SEARCH * HTTP/1.1\r\n" + "HOST:239.255.255.250:1900\r\n" + "MAN:\"ssdp:discover\"\r\n" + "ST:urn:schemas-frontier-silicon-com:fs_reference:fsapi:1\r\n" + "MX:3\r\n\r\n"; var data = Encoding.UTF8.GetBytes(msg); dataWriter.WriteBytes(data); await dataWriter.StoreAsync(); }
After that, we expect the HandleMessageReceived event handler to get called, in which we can process the response.
private void HandleMessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args) { var reader = args.GetDataReader(); var data = new byte[reader.UnconsumedBufferLength]; reader.ReadBytes(data); var s = Encoding.UTF8.GetString(data, 0, data.Length); // TODO ParseResponse(s); }
That’s everything we need to make first contact with our device on the network. The LOCATION header contains a URL which again returns a bit of XML describing the device further. We get:
- a “friendly” name
- it’s software version
- and the base URL for its Web API.
<netRemote> <friendlyName>DUAL IR6 002261c53c78</friendlyName> <version>ir-mmi-FS2026-0500-0095_V2.6.17.EX53300-1RC5</version> <webfsapi>http://192.168.1.144:80/fsapi</webfsapi> </netRemote>I have already reverse engineered and implemented a little of this Web API functionality, but it is still a long way to go before we may see a usable app. I will probably dedicate a couple of more blog posts to this endeavor.
Dude, where’s the Code?
The code for the SSDP implementation will probably end up in my development framework Newport, since I can see its general usefulness. Meanwhile you can use the code in the snippets since it’s completely functional.
The code for the Web API implementation will be on GitHub as soon as it is in a half-way usable and decent state.
The app itself will probably be available on GitHub too some day. But as I said, there’s still a long way to go.