Tutorial: Forward Multicast Traffic Between VPN Clients Across Multiple Daemons in Access Server
Configure multicast traffic forwarding between VPN clients in Access Server — covers enabling client-to-client multicast, installing SMCRoute for cross-daemon forwarding, configuring static multicast routes, raising multicast TTL, and verifying routing state.
Overview
OpenVPN Access Server can allow UDP multicast and IGMP traffic between VPN clients. In a single-daemon configuration, the vpn.routing.allow_mcast configuration key allows multicast and IGMP traffic to pass through Access Server.
Multi-daemon mode requires additional configuration. By default, Access Server runs one UDP and one TCP OpenVPN daemon per CPU core. Each daemon has its own Linux tun interface, named as0t0, as0t1, and so on. VPN clients connected through different daemons can therefore be on different as0t interfaces.
For multicast forwarding between these clients, the Access Server host must route multicast traffic between VPN interfaces.
This tutorial configures SMCRoute, a third-party static multicast routing daemon for Linux, to forward multicast traffic between the as0t interfaces. SMCRoute isn't included with Access Server and isn't installed, managed, or upgraded by OpenVPN.
If you only need to enable multicast and IGMP without routing between multiple VPN daemons, see Tutorial: Allow UDP Multicast and IGMP to Pass Through.
Example setup
In this example, two VPN clients exchange traffic using multicast group 239.1.1.1.
The caster sends traffic to
239.1.1.1.The subscriber joins
239.1.1.1.The clients are connected through different OpenVPN daemons and therefore different
as0tinterfaces.
Note
Static multicast routes send traffic to every interface configured for the multicast group, even if no client on that interface has joined the group.
This approach works well for a small number of low-bandwidth multicast groups because it doesn't require a multicast routing protocol, rendezvous point, or PIM state. For environments with many groups or high-bandwidth streams, consider a dynamic multicast routing solution that can prune traffic based on IGMP membership.
Important DCO isn't currently supported with this configuration
Multicast forwarding between as0t interfaces isn't currently supported with OpenVPN Data Channel Offload (DCO). You must turn off DCO for this configuration, which can reduce VPN throughput.
See OpenVPN Data Channel Offload (DCO) for information about DCO behavior and performance.
Important
SMCRoute is third-party software. Access Server doesn't install, manage, or upgrade it. This tutorial creates a custom Linux networking configuration outside Access Server's default behavior. Test the configuration before deploying it in production.
Prerequisites
Before you begin, ensure you have:
Access Server installed on Ubuntu 22.04, 24.04, or 26.04.
Console access and the ability to get root privileges.
At least two CPU cores on the Access Server.
Firewall backend
This tutorial includes configuration for both nftables and iptables mode.
Access Server 3.1.0 and newer uses nftables by default. Follow the nftables instructions in Step 7 unless your deployment is explicitly configured to use iptables.
If Access Server 3.1.0 or newer has been switched to iptables mode with: xtables=iptables, follow the iptables instructions instead.
For additional background, see Tutorial: Managing nftables Settings in Access Server and Tutorial: Managing iptables Settings in Access Server.
Connect to the console and get root privileges.
Allow VPN clients to communicate with each other:
sacli --key "vpn.client.routing.inter_client" --value "true" ConfigPut
Enable UDP multicast and IGMP traffic:
sacli --key "vpn.routing.allow_mcast" --value "true" ConfigPut
Restart Access Server services:
sacli start
Connect both VPN clients.
Before configuring multicast forwarding, verify that unicast traffic works between them. For example, on the caster:
root@caster:~# ip -4 addr show tun0 tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1420 inet 172.27.236.2/22 scope global tun0 root@caster:~# ping -c 3 172.27.232.2 PING 172.27.232.2 (172.27.232.2) 56(84) bytes of data. 64 bytes from 172.27.232.2: icmp_seq=1 ttl=63 time=14.2 ms 64 bytes from 172.27.232.2: icmp_seq=2 ttl=63 time=13.4 ms 64 bytes from 172.27.232.2: icmp_seq=3 ttl=63 time=13.3 ms --- 172.27.232.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003msNote
In this example, the clients received addresses from different subnets,
172.27.236.0/22and172.27.232.0/22, because they're connected to different OpenVPN daemons. Both subnets are part of the default172.27.224.0/20dynamic address pool.This multi-daemon scenario requires IP multicast routing between VPN interfaces.
Multicast forwarding requires packets to traverse the as0t tun interfaces so the Linux kernel's multicast routing can process them. DCO bypasses the networking path required by this configuration.
Turn off DCO:
sacli --key "vpn.server.daemon.ovpndco" --value "false" ConfigPut sacli start
Verify that the
as0tinterfaces no longer report anovpnlink type:ip -details link show | grep -A2 as0t
Each OpenVPN daemon has its own as0t tun interface. The number of interfaces depends on the CPU count and OpenVPN daemon configuration. For example, an Access Server with two CPU cores can run two UDP and two TCP daemons, resulting in four as0t interfaces.
List the VPN interfaces and their IPv4 addresses:
root@as:~# ip -br -4 addr show | grep as0t as0t0 UNKNOWN 172.27.224.1/22 as0t1 UNKNOWN 172.27.228.1/22 as0t2 UNKNOWN 172.27.232.1/22 as0t3 UNKNOWN 172.27.236.1/22
Record each
as0tinterface name. You'll configure all of them in Step 5.Important
Changing the Access Server CPU count or daemon configuration can change the number of
as0tinterfaces. If that happens, repeat this step and update the SMCRoute configuration in Step 5. Otherwise, multicast traffic won't reach clients connected through interfaces that aren't configured in SMCRoute.
For information about changing daemon configuration, see Tutorial: Turn Off Multi-Daemon Mode and Use Only TCP or UDP.
Install SMCRoute on the Access Server host:
apt update apt install smcroute
Verify the package installs:
smcrouted— the multicast routing daemon.smcroutectl— the command-line tool for managing and inspecting SMCRoute.
Configure SMCRoute to forward multicast group 239.1.1.1 between the VPN interfaces identified in Step 3. Each mroute entry defines an inbound interface and the interfaces where SMCRoute should forward matching multicast packets. The packet isn't sent back through the interface where it arrived, so you need one route for each possible inbound interface.
Open
/etc/smcroute.confin a text editor:nano /etc/smcroute.conf
Enable multicast routing on each VPN interface:
phyint as0t0 enable phyint as0t1 enable phyint as0t2 enable phyint as0t3 enable
Configure multicast forwarding for
239.1.1.1between the interfaces:mroute from as0t0 group 239.1.1.1 to as0t1 as0t2 as0t3 mroute from as0t1 group 239.1.1.1 to as0t0 as0t2 as0t3 mroute from as0t2 group 239.1.1.1 to as0t0 as0t1 as0t3 mroute from as0t3 group 239.1.1.1 to as0t0 as0t1 as0t2
Save the file and exit the editor.
Note
Define all
phyintentries before anymrouteentry that references them.Generate the configuration automatically
For a server with a different number of
as0tinterfaces, you can generate the equivalent configuration:GROUP=239.1.1.1 IFACES=$(ip -br link show | grep -o '^as0t[0-9]*') { for i in $IFACES; do echo "phyint $i enable" done echo for i in $IFACES; do out=$(echo "$IFACES" | tr ' ' '\n' | grep -v "^${i}$" | tr '\n' ' ') echo "mroute from $i group $GROUP to $out" done } > /etc/smcroute.confRestart SMCRoute:
systemctl restart smcroute
Confirm that SMCRoute accepted the routes:
smcroutectl show routes
Note
To forward a multicast range instead of a single group, specify a prefix such as:
group 239.0.0.0/8
Omitting source from an mroute means traffic can originate from any source. This is useful for VPN clients because their addresses may be assigned dynamically. The kernel installs the active multicast route for a sender when it receives the first packet, so the first packet of a new stream may be dropped.
Note
Linux multicast routing uses virtual interfaces (VIFs), and most systems support up to 32. A server with many CPU cores can approach this limit; for example, 16 cores can produce 32 as0t interfaces. For large servers, consider starting smcrouted with -N so interfaces aren't enabled automatically, then explicitly enable only the required interfaces with phyint.
Running sacli start recreates the as0t interfaces. This invalidates the virtual interfaces SMCRoute uses, so SMCRoute must start after Access Server and restart whenever Access Server recreates those interfaces.
Review the existing SMCRoute systemd unit:
systemctl cat smcroute
Create a systemd drop-in so SMCRoute starts after Access Server at boot:
mkdir -p /etc/systemd/system/smcroute.service.d cat > /etc/systemd/system/smcroute.service.d/10-after-openvpnas.conf <<'EOF' [Unit] After=openvpnas.service Wants=openvpnas.service EOF systemctl daemon-reload
After each time you run
sacli start, restart SMCRoute:systemctl restart smcroute
Important
This is a common cause of multicast forwarding working immediately after setup and then stopping after a later configuration change. Any change that runs
sacli start, including changes made in the Admin Web UI, recreates the VPN interfaces and requires an SMCRoute restart.
For additional SMCRoute configuration options, refer to the official SMCRoute documentation.
Multicast TTL determines how many routing hops a multicast packet can traverse. Multicast senders commonly use a TTL of 1, which confines traffic to the local network. For the traffic in this tutorial to pass through Access Server's multicast router, it needs a TTL of at least 2.
Whenever possible, configure the multicast TTL in the sending application. This avoids modifying firewall rules on Access Server. For example, with socat:
socat - UDP4-DATAGRAM:239.1.1.1:5000,ip-multicast-ttl=2,ip-multicast-if=172.27.236.3
If you can't change the sender, modify the TTL as the traffic enters Access Server. Follow the section for your firewall backend.
Using nftables (Access Server 3.1.0 and newer)
Access Server 3.1.0 and newer uses nftables by default. Create your multicast TTL rule in a custom table rather than modifying the Access Server-generated as_filter, as_nat, or as_mangle tables. Those generated rules can change when Access Server restarts or is upgraded.
Create a custom nftables table and
PREROUTINGchain:nft add table ip openvpn_mcast_ttl nft add chain ip openvpn_mcast_ttl PREROUTING '{ type filter hook prerouting priority -1511; policy accept; }'Priority
-151places the custom chain immediately before Access Server'sas_mangle PREROUTINGchain at priority-150.Set the TTL to
2for multicast packets entering each VPN interface:for i in $(ip -br link show | grep -o '^as0t[0-9]'); do nft add rule ip openvpn_mcast_ttl PREROUTING iifname "$i" ip daddr 239.1.1.1/32 ip ttl set 2 done
Adjust the destination address if you configured a different multicast group or range in Step 5.
Verify the custom table:
nft list table ip openvpn_mcast_ttl
Important
Rules added with
nft adddon't persist across a reboot. Save the table and configure nftables to restore it at boot. For example:nft list table ip openvpn_mcast_ttl > /etc/nftables.d/90-multicast-ttl.nft
Include the file from
/etc/nftables.conf, and verify the custom table after Access Server upgrades.
See Tutorial: Managing nftables Settings in Access Server for more information about Access Server's nftables tables, chains, and priorities.
Using iptables (Access Server before 3.1.0, or after switching from nftables)
Access Server versions before 3.1.0 use iptables by default. Access Server 3.1.0 and newer can also be explicitly configured in iptables mode.
Note
If you're running Access Server 3.1.0 or newer with nftables, you don't need to switch to iptables for this tutorial; use the nftables procedure above.
If your deployment is configured to use iptables:
For Access Server 3.1.0 and newer, verify that
as.confcontains:xtables=iptables
If you change this setting, perform a full Access Server service restart:
service openvpnas restart
Configure Access Server to append its generated iptables rules after existing rules so the custom TTL rules retain precedence:
sacli --key "iptables.append" --value "true" ConfigPut sacli start
Increment the TTL by
1for traffic entering each VPN interface:for i in $(ip -br link show | grep -o '^as0t[0-9]'); do iptables -t mangle -I PREROUTING 1 -i "$i" -d 239.1.1.1/32 -j TTL --ttl-inc 1 done
Adjust the destination if you configured a different multicast group or range in Step 5.
Verify the rules:
iptables -t mangle -L PREROUTING -n -v --line-numbers
Important
These custom iptables rules don't persist across a reboot. Make them persistent using the method appropriate for your Linux distribution, such as
iptables-persistentor a systemd unit ordered afteropenvpnas.service.Recheck the rules after running
sacli start.
See Tutorial: Managing iptables Settings in Access Server for additional guidance.
The Linux kernel's mc_forwarding flag indicates whether IP multicast routing is active on an interface. SMCRoute causes the kernel to enable this state for the interfaces it manages.
Verify the kernel is forwarding multicast on the VPN interfaces:
root@as:~# sysctl -a 2>/dev/null | grep mc_forwarding net.ipv4.conf.all.mc_forwarding = 1 net.ipv4.conf.as0t0.mc_forwarding = 1 net.ipv4.conf.as0t1.mc_forwarding = 1 net.ipv4.conf.as0t2.mc_forwarding = 1 net.ipv4.conf.as0t3.mc_forwarding = 1
Note
mc_forwardingis read-only. Don't attempt to set it manually.A value of
0indicates that the multicast routing daemon isn't active for that interface. Verify thatsmcroutedis running and that each required interface has aphyintentry in/etc/smcroute.conf.Review the configured multicast routes:
smcroutectl show routes
Review multicast group state:
smcroutectl show groups
Test multicast forwarding between a subscriber and caster. Skip this if your application joins the group itself.
On the subscriber
If the application doesn't join the multicast group itself, install SMCRoute and join the group on
tun0:root@subscriber:~# apt install smcroute root@subscriber:~# smcroutectl join tun0 239.1.1.1
Install
socat:apt install socat
Listen for multicast datagrams:
socat -u UDP4-RECV:5000,ip-add-membership=239.1.1.1:tun0,reuseaddr -
On the caster
Install
socat:apt install socat
Send multicast traffic once per second:
while :; do echo "mcast $(date +%T)"; sleep 1; done | socat -u - UDP4-DATAGRAM:239.1.1.1:5000,ip-multicast-ttl=8,ip-multicast-if=172.27.236.3
Expected result
The subscriber receives multicast packets similar to:
root@subscriber:~# socat -u UDP4-RECV:5000,ip-add-membership=239.1.1.1:tun0,reuseaddr - mcast 13:57:52 mcast 13:57:53 mcast 13:57:55 mcast 13:57:56 mcast 13:57:57 mcast 13:57:58 mcast 13:57:59 mcast 13:58:00 mcast 13:58:01 root@subscriber:~#
On Access Server, verify that SMCRoute shows the active route and the expected inbound and outbound interfaces:
smcroutectl show routes
Note
The first packet from a new sender may be missing. When an mroute accepts traffic from any source, the kernel creates the sender-specific forwarding entry when the first packet arrives. Send several packets before determining whether multicast forwarding is working.
Symptom | What to check |
|---|---|
No multicast traffic reaches Access Server. | Verify that |
Traffic reaches one | Verify that every required |
Multicast forwarding worked and then stopped. | Access Server may have restarted and recreated its |
Traffic is forwarded but doesn't reach clients. | Check the multicast TTL. Verify that the sender uses a TTL greater than |
Only the first packet from each stream is lost. | This is expected when the multicast route accepts traffic from any source. The kernel installs the forwarding entry when the first packet from that sender arrives. |
Multicast works for some clients but fails as more connect. | Check whether new clients connected through an |