---
title: 'Tutorial: Forward Multicast Traffic Between VPN Clients Across Multiple Daemons in Access Server'
canonical: https://openvpn.net/as-docs/v3/tutorials/tutorial--multicast-routing-vpn-clients/
product: Access Server Docs
description: 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.
---

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

## 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](https://openvpn.net/as-docs/v3/tutorials/tutorial--udp-multicast-and-igmp-allowed.md).

### 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**
>
> **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)](https://openvpn.net/as-docs/v3/openvpn-data-channel-offload.md) 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](https://openvpn.net/as-docs/v3/tutorials/tutorial--managing-nftables.md) and [Tutorial: Managing iptables Settings in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--manage-iptables.md).

## Step 1: Allow client-to-client and multicast traffic

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.

## Step 2: Turn off OpenVPN DCO

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
   ```

## Step 3: Identify the VPN tun interfaces

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](https://openvpn.net/as-docs/v3/tutorials/tutorial--turn-off-multi-daemon.md).

## Step 4: Install SMCRoute

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.

## Step 5: Configure static multicast routes

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.

   > **Tip**
   >
   > **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`.

## Step 6: Restart SMCRoute after Access Server restarts

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](https://github.com/troglobit/smcroute).

## Step 7: Configure the multicast TTL

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](https://openvpn.net/as-docs/v3/tutorials/tutorial--managing-nftables.md) 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 -151; policy accept; }'
   ```

   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](https://openvpn.net/as-docs/v3/tutorials/tutorial--managing-nftables.md) 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](https://openvpn.net/as-docs/v3/tutorials/tutorial--manage-iptables.md) for additional guidance.

## Step 8: Verify the multicast routing state

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
   ```

## Step 9: Test multicast forwarding

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.

## Troubleshooting

| **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. |

## Related topics

- [Tutorial: Allow UDP Multicast and IGMP to Pass Through](https://openvpn.net/as-docs/v3/tutorials/tutorial--udp-multicast-and-igmp-allowed.md)
- [Tutorial: Managing nftables Settings in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--managing-nftables.md)
- [Tutorial: Managing iptables Settings in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--manage-iptables.md)
- [Tutorial: Turn Off Multi-Daemon Mode and Use Only TCP or UDP](https://openvpn.net/as-docs/v3/tutorials/tutorial--turn-off-multi-daemon.md)
- [OpenVPN Data Channel Offload (DCO)](https://openvpn.net/as-docs/v3/openvpn-data-channel-offload.md)

---

Prev: [Tutorial: Allow UDP Multicast and IGMP to Pass Through](https://openvpn.net/as-docs/v3/tutorials/tutorial--udp-multicast-and-igmp-allowed.md)
Next: [APIs and Automation](https://openvpn.net/as-docs/v3/tutorials/apis-and-automation.md)
Index: https://openvpn.net/as-docs/llms.txt
