Making DLNA through site-to-site VPN work
I have read tons of articles about how does L3 multicast routing across subnets work, but none of them was complete for my scenario. Here is my solution.
The Scenario
I have two sites connected by (L3-routed) WireGuard VPN. If we want to browse a DLNA server (e.g. a NAS serving media files) where the server is at one site and the client device (e.g. a TV) is at the other site, it will not work out-of-the-box because DLNA uses SSDP to discover services which is a multicast protocol reaching only the same subnet by default.
We have to route the SSDP packets through the WireGuard VPN tunnel somehow. The solution is multicast routing which is implemented in the Linux kernel, but it is not so easy to use.
Because the multicast routing mechanism is operated by cache tables (unlike unicast routing where the routing table is permanent), a user-level daemon is needed to keep the cache entries up-to-date. One such user-level daemon is smcroute what does static multicast routing. Static routing is more than enough for such a 2-site configuration.
Used Raspbian and Armbian at the two endpoints (but any other Linux systems should work).
The Initial Network Setup
The two sites connected by the WireGuard VPN are:
- Site #1 network: 192.168.1.0/24
- Site #2 network: 192.168.2.0/24
The WireGuard VPN tunnel endpoints are operating at 192.168.1.10 and 192.168.2.10 respectively. The LAN network interfaces are called eth0 and the VPN point-to-point interfaces are called wg0 on each site with the following IP addresses:
- Site #1 eth0: 192.168.1.10/24, wg0: 192.168.11.1/32
- Site #2 eth0: 192.168.2.10/24, wg1: 192.168.11.2/32
WireGuard configuration (/etc/wireguard/wg0.conf) at Site #1 is:
[Interface]
Address = 192.168.11.1/32
ListenPort = 51820
PrivateKey = [Site #1 privkey]
MTU = 1380[Peer]
PublicKey = [Site #2 pubkey]
AllowedIPs = 192.168.11.2/32,192.168.2.0/24
PersistentkeepAlive = 60
WireGuard configuration (/etc/wireguard/wg0.conf) at Site #2 is:
[Interface]
Address = 192.168.11.2/32
ListenPort = 51820
PrivateKey = [Site #2 privkey]
MTU = 1380[Peer]
PublicKey = [Site #1 pubkey]
AllowedIPs = 192.168.11.1/32,192.168.1.0/24
PersistentkeepAlive = 60
Endpoint = [Site #1 Public IP]:51820
According to the Endpoint Peer parameter at Site #2, Site #2 is initiating the VPN tunnel connection to Site #1.
Also note the MTU value at the Interface setup, the default MTU value 1420 caused weird hangs in the connection, with the slightly lower value 1380 it was working for me.
The IPv4 (unicast) routing on the connected networks should be set up properly also:
- Site #1: route packets to 192.168.2.0/24 via 192.168.1.10 and SNAT on 192.168.1.10 to the WireGuard interface 192.168.11.1
- Site #2: route packets to 192.168.1.0/24 via 192.168.2.10 and SNAT on 192.168.2.10 to the WireGuard interface 192.168.11.2
Setting up Multicast Routing
The L3 routing solution should do:
- Pass SSDP multicast packets (identified by target IP 239.255.255.250) on Site #1 VPN endpoint from the Site #1 LAN interface eth0 to the tunnel WireGuard tunnel interface wg0.
- Pass SSDP multicast packets on Site #2 VPN endpoint from the WireGuard tunnel interface wg0 to the Site #2 LAN interface eth0.
And vice versa: on Site #2 VPN endpoint route SSDP packets from eth0 to wg0 and on Site #1 VPN endpoint route SSDP packets from wg0 to eth0.
This makes SSDP multicast packets initiated from Site #1 LAN appear on Site #2 LAN and vice versa: SSDP multicast packets initiated from Site #2 LAN appear on Site #1 LAN.
Step 1: SMCRoute config
For the user-level daemon I used SMCRoute: https://github.com/troglobit/smcroute. It is also available packaged in common distros like Debian, Raspbian, etc.
Configuration on Site #1 VPN endpoint (mgroup joins the multicast group and mroute updates the kernel mulitcast routing cache table):
phyint wg0 enable
phyint eth0 enablemgroup from eth0 group 239.255.255.250
mroute from eth0 group 239.255.255.250 to wg0mgroup from wg0 group 239.255.255.250
mroute from wg0 group 239.255.255.250 to eth0
The configuration on Site #2 VPN endpoint is the same.
Step 2: Enable MULTICAST for the tunnel interfaces
If you try to start (systemctl start smcroute) SMCRoute at this point, it shall fail because MULTICAST is not enabled for the WireGuard wg0 interfaces.
The command ip link set wg0 multicast on
should enable it.
It can be added in the Interface section to the WireGuard setup configuration for convenience:
PostUp = ip link set wg0 multicast on
Now starting the WireGuard tunnel (by systemctl start wg-quick@wg0) automatically enables MULTICAST also.
Step 3: Adjusting TTL
Note, that multicast packets usually have a TTL (time to live) value of 1 limiting the packets lifetime to stay inside the subnet (and blocking routing loops).
For hopping through interfaces, it should be adjusted. Adjusting should be done primarily at the sender, but it can be also done at the multicast router by iptables.
So adding such rules (at Site #1 and at Site #2) is mandatory:
iptables -t mangle -A PREROUTING -i eth0 -d 239.255.255.250 -j TTL --ttl-inc 2
We increment TTL value by 2 because there are 2 hops (Site #1 eth0 → wg0 and Site #2 wg0 → eth0). This results that our multicast packets survive until reaching the other LAN.
Making iptables persitant across reboots is recommended (look for iptables-persistent package).
Testing and debugging
Now everything should work fine. You should be able to browse your NAS at Site #2 from a TV device (e.g. with Kodi) at Site #1 through UPnP/DLNA (and playing videos should also work if unicast routing is set up properly).
For testing gupnp-av-cp from GUPnP project is recommended.
For debugging, ping 239.255.255.250
and tcpdump
are your friends. Examining TTL values closely should help, also note that ping can set TTL values using the option -t
.
Happy playing with UPnP/DLNA and SSDP multicast routing. ;)