Skip to main content

Tutorial: Forward Multicast Traffic Between VPN Clients Across Multiple Daemons in Access Server

Abstract

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 as0t interfaces.

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.

  1. Connect to the console and get root privileges.

  2. Allow VPN clients to communicate with each other:

    sacli --key "vpn.client.routing.inter_client" --value "true" ConfigPut
  3. Enable UDP multicast and IGMP traffic:

    sacli --key "vpn.routing.allow_mcast" --value "true" ConfigPut
  4. Restart Access Server services:

    sacli start
  5. Connect both VPN clients.

  6. 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 2003ms
    

    Note

    In this example, the clients received addresses from different subnets, 172.27.236.0/22 and 172.27.232.0/22, because they're connected to different OpenVPN daemons. Both subnets are part of the default 172.27.224.0/20 dynamic 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.

  1. Turn off DCO:

    sacli --key "vpn.server.daemon.ovpndco" --value "false" ConfigPut
    sacli start
  2. Verify that the as0t interfaces no longer report an ovpn link 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.

  1. 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
  2. Record each as0t interface 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 as0t interfaces. 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.

  1. Install SMCRoute on the Access Server host:

    apt update
    apt install smcroute
  2. 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.

  1. Open /etc/smcroute.conf in a text editor:

    nano /etc/smcroute.conf
  2. Enable multicast routing on each VPN interface:

    phyint as0t0 enable
    phyint as0t1 enable
    phyint as0t2 enable
    phyint as0t3 enable
  3. Configure multicast forwarding for 239.1.1.1 between 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
  4. Save the file and exit the editor.

    Note

    Define all phyint entries before any mroute entry that references them.

    Generate the configuration automatically

    For a server with a different number of as0t interfaces, 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.conf
  5. Restart SMCRoute:

    systemctl restart smcroute
  6. 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.

  1. Review the existing SMCRoute systemd unit:

    systemctl cat smcroute
  2. 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
  3. 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.

  1. Create a custom nftables table and PREROUTING chain:

    nft add table ip openvpn_mcast_ttl
    nft add chain ip openvpn_mcast_ttl PREROUTING '{ type filter hook prerouting priority -1511; policy accept; }'

    1

    Priority -151 places the custom chain immediately before Access Server's as_mangle PREROUTING chain at priority -150.

  2. Set the TTL to 2 for 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
  3. Adjust the destination address if you configured a different multicast group or range in Step 5.

  4. Verify the custom table:

    nft list table ip openvpn_mcast_ttl

    Important

    Rules added with nft add don'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:

  1. For Access Server 3.1.0 and newer, verify that as.conf contains:

    xtables=iptables
  2. If you change this setting, perform a full Access Server service restart:

    service openvpnas restart
  3. 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
  4. Increment the TTL by 1 for 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
  5. Adjust the destination if you configured a different multicast group or range in Step 5.

  6. 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-persistent or a systemd unit ordered after openvpnas.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.

  1. 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_forwarding is read-only. Don't attempt to set it manually.

    A value of 0 indicates that the multicast routing daemon isn't active for that interface. Verify that smcrouted is running and that each required interface has a phyint entry in /etc/smcroute.conf.

  2. Review the configured multicast routes:

    smcroutectl show routes
  3. 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

  1. 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
  2. Install socat:

    apt install socat
    
  3. Listen for multicast datagrams:

    socat -u UDP4-RECV:5000,ip-add-membership=239.1.1.1:tun0,reuseaddr -

On the caster

  1. Install socat:

    apt install socat
  2. 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 vpn.routing.allow_mcast is true and that the sender is bound to its VPN interface.

Traffic reaches one as0t interface but isn't forwarded.

Verify that every required as0t interface has a phyint entry and appears in the appropriate mroute entries (Steps 3 and 5). Run smcroutectl show routes and verify that mc_forwarding is 1.

Multicast forwarding worked and then stopped.

Access Server may have restarted and recreated its as0t interfaces. Restart SMCRoute as described in Step 6.

Traffic is forwarded but doesn't reach clients.

Check the multicast TTL. Verify that the sender uses a TTL greater than 1 or that the nftables/iptables rules from Step 7 are present and match the multicast group.

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 as0t interface missing from the SMCRoute configuration. Also verify that the system hasn't reached its multicast VIF limit. Recheck Steps 3 and 5.