blog.

Adventures with DLNA

I recently acquired a Dell PowerEdge R430, and I’m running it as a (for now) single-node Kubernetes cluster that will host all of the various services currently running on a smattering of Raspberry Pis and my desktop machine. (That will be a blog post all on its own, once it’s done — there’s still a few services to migrate.) In the meantime, the setup for Plex’s DLNA server was a nightmare and a half, and I wanted to document the process here.


Digital Living Network Alliance (DLNA) is a corporation that publishes a (paid) specification that narrows UPnP to a well-defined request/response format specific to home network applications. It allows devices in their UPnP broadcast to the local network to specify, “Hey, I’m a printer!” or, in Plex’s case, “Hey, I’m a media server!” Importantly, either is able to say, “Here’s how to connect to my service,” and any client can configure itself automatically.

I had two major issues with this in my specific setup. The first, more minor issue was that, since Plex is running inside Kubernetes, it’s binding and broadcasting to Flannel’s 10.244.0.0/16 network. To fix this, I set the Pod to use the node’s hostNetwork, forcing the container to bind to my home 10.10.0.0/16 subnet.

apiVersion: apps/v1
kind: Deployment
spec:
  containers:
    - name: plex
      # ...
      hostNetwork: true

This isn’t an ideal solution (and I’m actually not 100% sure how it’ll function once I eventually set up more nodes in the Kubernetes cluster), but I don’t know of a better way to do it without ripping out Flannel and swapping to a CNI provider that provides broadcast forwarding functionality.

Speaking of broadcast forwarding… the second issue. I have all my IoT crap on an isolated VLAN, so (similar to the Kubernetes problem), so at this point, I could connect to Plex and play media from my desktop, but broadcasts weren’t reaching the receiver. The fix for this was multi-pronged. (For context, my router is a Ubiquiti UDM Pro.)

  • Inside the Network application, inside Settings > Networks, turn on Multicast DNS for my LAN and IoT networks. (NB: I’m not 100% sure if this is necessary, but a few Ubiquiti forum posts mentioned it, and I had to turn it on anyways to get AirPlay/Chromecast to work. YMMV.) I did not have to turn on IGMP Snooping, as some forum posts suggested; in fact, turning it on made no difference at all, so I left it off.

  • If either device (client or server) is on WiFi, you may need to turn on Multicast Enhancement in the Network app under WiFi > (network) > Advanced > Multicast Management. My devices are wired, do I didn’t do this, but some forum posts suggested this may be necessary.

  • Allow the desired service ports through your firewall. I have a “Lan In” rule explicitly dropping all packets from IoT -> LAN, so I created a higher-priority “Lan In” rule to explicitly allow from anywhere -> LAN, ports 1900 (UPnP/SSDP), 32400 (Plex), or 32469 (Plex DLNA service).

  • Run docker.io/scyto/multicast-relay on your UDM. This is where the magic happens, and I owe it all to a blog post about Sonos.

    • SSH into your UDM:

      ssh -o HostKeyAlgorithms=ssh-rsa root@unifi

      Remember that the SSH password here is not the one from the Network application (in Settings > System > Network Device SSH Authentication), but rather the one from the UnifiOS Dashboard (in Console Settings > Advanced > SSH). Additionally, you may need to manually specify ssh-rsa as an allowed HostKeyAlgorithms (reflected above) — that algorithm was disabled by default on my machine, which threw an error at first.

    • Test the relay:

      podman run \
          --env INTERFACES="br0 ${VLAN_INTERFACES}" \
          --env OPTS="--verbose --noMDNS" \
          --network host \
          --rm \
          docker.io/scyto/multicast-relay

      Replace the VLAN_INTERFACES variable with the bridge interfaces for your VLANs. On my UDM, they were named like br${VLAN_ID}, but YMMV. You can check with ip address show.

      At this point, try to use your service! It can take a while for the DLNA process to complete and the client to recognize the server, so let it sit for ~5-10 minutes if you don’t see the server show up immediately.

    • ^C and run a daemonized version:

      podman run \
          --detach \
          --env INTERFACES="br0 ${VLAN_INTERFACES}" \
          --env OPTS="--verbose --noMDNS" \
          --network host \
          --restart unless-stopped \
          docker.io/scyto/multicast-relay

      This container (despite the --restart unless-stopped flag) will not survive reboots of the UDM. I chose to install and utilize the very handy on-boot-script from unifios-utilities:

      # install `on-boot-script`
      
      unifi-os shell
      cd
      curl -fsSL https://udm-boot.boostchicken.dev \
          -o udm-boot_1.0.5_all.deb
      dpkg -i udm-boot_1.0.5_all.deb
      systemctl enable udm-boot
      exit
      
      # create the script to run on boot
      # with the command from before
      
      cat <<EOF > /mnt/data/on_boot.d/multicast-relay.sh
      #!/usr/bin/env sh
      
      podman run \
          --detach \
          --env INTERFACES="br0 ${VLAN_INTERFACES}" \
          --env OPTS="--verbose --noMDNS" \
          --network host \
          --restart unless-stopped \
          docker.io/scyto/multicast-relay
      EOF

Congratulations — you now have DLNA working between isolated VLANs! 🚀

I truly wish DLNA wasn’t built on UPnP/SSDP, because it’s (among other things) a security nightmare. A better solution would have been the ability to configure my Plex’s DLNA server IP address manually, instead of only accepting it through SSDP (like I can with a printer). Most home users are familiar with this process already, and virtually no one is going to want to take the time to get this shit working if it doesn’t out of the box; most will just continue to stream from Spotify or Apple Music.

With that said… I hope getting to listen to all your favorite tracks in full 16-bit 44.1KHz lossless glory makes this trek as worth it for you as it did for me.

@ezra