Warhammer 40,000: Space Marine 2 in Russia: Fixing CSF (EAC) error via WireGuard bridge on the router

Warhammer 40,000: Space Marine 2 in Russia: Fixing CSF (EAC) error via WireGuard bridge on the router

Stand

  • Router: Keenetic Giga
  • Firmware/ environment: Xkeen — running proxy core directly on Keenetic
  • Core: Mihomo (clash.meta) with rule-based routing
  • Rule lists: DigneZzZ/routing + MetaCubeX geo-data

All home network traffic is wrapped in Mihomo on the router (TPROXY), and then Mihomo distributes it according to rules: direct, or via VPN.

In short

SM2 emits CSF (or Error 4) and blocks access to network services. The reason is that game servers are unavailable from Russia by IP, and a regular VPN breaks anti-cheat. Solution: WireGuard bridge “entry into Russia → exit abroad”, configured directly in Mihomo as the default exit, so all traffic goes through a single clean foreign IP with open NAT.


Symptoms

  • On startup, cannot connect to online services; in the menu — Error code: CSF (or Error 4, N-14/N-15).
  • Network modes unavailable; co-op PvP won’t start.
  • Local toggles (file checks, EOS reinstall, firewall) do not help.

CSF basically = handoff failure of Easy Anti-Cheat: the game cannot authenticate you and kicks you at the login.


Why this happens in RF

Two independent problems stack up:

  1. Roskomnadzor blocks game servers by IP. SM2 backend and matchmaking run on foreign clouds (AWS, Azure). Some addresses are blocked, so you cannot directly access them from Russia.
  2. Regular VPN breaks anti-cheat:
    • data-center IPs in EAC flag;
    • strict/symmetric NAT for most proxies — the game needs open NAT (1/2);
    • if the exit IP “jumps” (load balancing/nodes), the EAC session breaks instantly.

Trade-off: without VPN — IP block; with regular VPN — anti-cheat stumbles. Hence the endless CSF.


What didn’t work

Initially, the game was routed through a VLESS subscription (Mihomo, selective rules for Epic/EOS domains, EAC and game ports). Routing was correct — but CSF remained:

  • exit IP of subscription nodes — data center, which EAC dislikes;
  • symmetric NAT → matchmaking won’t start;
  • different parts of the game traffic (login, servers, STUN) could go out via different IPs — the anti-cheat sees desynchronization.

Conclusion: it’s not about “where to direct,” but the quality of the exit — you need a clean IP and open NAT.


What worked: WireGuard bridge

WireGuard, configured as a bridge: entry point — a server inside Russia, exit — abroad.

Why exactly this:

  • Entry into RF — connection from you to the entry point uses a Russian address: fast, stable, not hitting foreign IP blocks. The WG handshake is reliable.
  • Exit abroad — externally you appear under a foreign IP, and game servers on AWS/Azure become accessible.
  • Clean IP + full-cone NAT — such a bridge exit usually isn’t in the EAC flag, and NAT is open. Exactly what was missing.
  • Single stable exit — one IP for the entire session, nothing jumps.

And the key — WG is brought up in Mihomo on the router, so all game traffic (login EOS, game servers, STUN, anti-cheat) goes through the same clean IP. This consistency finally defeats CSF.


Setup

1. Move wg.conf into Mihomo

Normal WireGuard config provided by the bridge provider:

[Interface]
PrivateKey = <YOUR_PRIVATE_KEY>
Address = 10.10.2.215/32
DNS = 8.8.8.8

[Peer]
PublicKey = <PUBLIC_KEY>"EY_SERVER>
Endpoint = <HOST_SERVER>:39547
PersistentKeepalive = 20
AllowedIPs = 0.0.0.0/0

Translates into a proxies entry for Mihomo as:

proxies:
  - name: 🪽 WG
    type: wireguard
    private-key: <YOUR_PRIVATE_KEY>     # = [Interface] PrivateKey
    server: <HOST_SERVER>               # = host from Endpoint (before ":")
    port: 39547                        # = port from Endpoint (after ":")
    ip: 10.10.2.215                    # = [Interface] Address without /mask
    public-key: <SERVER_PUBLIC_KEY>     # = [Peer] PublicKey
    allowed-ips: ['0.0.0.0/0']         # = [Peer] AllowedIPs
    persistent-keepalive: 20           # = [Peer] PersistentKeepalive
    udp: true
    mtu: 1408
    remote-dns-resolve: true
    dns: [8.8.8.8]                     # = [Interface] DNS
    # pre-shared-key: <PSK>            # = [Peer] PresharedKey (if any)

Table of field mappings:

wg.conf Mihomo
[Interface] PrivateKey private-key
[Interface] Address (IPv4) ip (without /32)
[Interface] DNS dns (+ remote-dns-resolve: true)
[Peer] PublicKey public-key
[Peer] Endpoint host:port server + port
[Peer] PresharedKey pre-shared-key
[Peer] AllowedIPs allowed-ips
[Peer] PersistentKeepalive persistent-keepalive

2. Making WG the default exit

In the group selector put 🪽 WG first — it becomes the exit for all traffic going to the VPN. Subscriber nodes remain as a reserve:

proxy-groups:
  - name: 🛡️ VPN
    type: select
    proxies:
      - 🪽 WG               # ← default exit (Moscow РФ → abroad)
      - ⚡ Fastest          # reserve: subscription nodes
      - 📶 First available

3. Routing rules

Launch the game (Epic/EOS/EAC domains + SM2 backend + ports + STUN) at the top and route to 🛡️ VPN (= WG). Global model: everything in VPN, exceptions (RU/local/Direct) — bypass.

List of game domains (inline rule):

games-vpn:
  type: inline
  behavior: domain
  payload:
    - "+.epicgames.com"      # Epic authentication
    - "+.epicgames.dev"      # EOS: matchmaking/relay/cross-play (core of multiplayer)
    - "+.easyanticheat.net"  # Easy Anti-Cheat
    - "+.eac-cdn.com"
    - "+.easy.ac"
    - "+.prismray.io"        # backend of SM2 online services (Saber)
    - "+.hydrapi.net"        # STUN/NAT traversal SM2
    # ...epicgamescdn, unrealengine, fortnite, etc.

Order of rules (main):

rules:
  # private networks and local access directly
  - RULE-SET,direct-ip,DIRECT,no-resolve
  - GEOIP,PRIVATE,DIRECT,no-resolve
  - RULE-SET,geosite-private,DIRECT

  # GAME → VPN(=WG), at the top, so nothing intercepts
  - RULE-SET,games-vpn,🛡️ VPN
  - AND,((NETWORK,tcp),(DST-PORT,11700-11720)),🛡️ VPN   # SM2 ports (Saber)
  - AND,((NETWORK,udp),(DST-PORT,48800-55000)),🛡️ VPN   # SM2 ports (Saber)

  # services needing a real RU-IP (Gosuslugi, pushes) — direct
  - RULE-SET,vpndetect,DIRECT
  # torrent trackers — direct
  - RULE-SET,torrent-trackers,DIRECT

  # bypass Roskomnadzor → VPN
  - RULE-SET,refilter-d,🛡️ VPN
  - RULE-SET,proxy,🛡️ VPN

  # Russian sites/IP — direct
  - RULE-SET,geosite-ru,🏠 RU
  - RULE-SET,direct,DIRECT
  - RULE-SET,geoip-ru,🏠 RU,no-resolve

  # everything else → VPN(=WG)
  - MATCH,🛡️ VPN

 The lists refilter-*, proxy, direct, games, geoip-ru etc. are rule-providers from DigneZzZ/routing and MetaCubeX in .mrs format (compact for router).

The gist is that both game rules and the final MATCH lead to the same group 🛡️ VPN, which equals 🪽 WG. This means all game traffic exits through one WG-IP — exactly what EAC needs.


Important points, without which it won’t work

  1. One tunnel for all game traffic. If some connections go through a different IP, anti-cheat may fail again. MATCH → VPN(=WG) guarantees a single exit even for “raw” IPs and STUN.
  2. Open game ports: SM2 — TCP 11700–11720 and UDP 48800–55000; the tunnel must allow UDP.
  3. Open NAT (full-cone, type 1/2) on the bridge exit — otherwise co-op won’t form.
  4. Do not duplicate WG. One client per key. Do not run WireGuard on the router (as a separate interface) and Mihomo with the same keys at the same time — server dislikes two sessions.
  5. Client hygiene: verify file integrity in Steam, no mods (EAC triggers “Mods Detected” → also CSF), correct system time.

Result

CSF in Space Marine 2 from Russia is a sum of IP blocks and EAC’s hostility to data-center VPNs with strict NAT. The remedy is not “any VPN,” but a WireGuard bridge with Russian ingress and foreign egress, set up in Mihomo on the router and default-exit, so all game traffic goes through a single clean IP with open NAT. After that, EAC handshake passes and network modes finally work.

 The same logic treats other EAC/EOS games blocked by IP.