OpenVPN DCO optimization (mt7621, filogic)

The OpenVPN DCO kernel module handles asynchronous crypto requests inefficiently, waiting for each request to complete before starting the next one.

An official DCO patchset has been submitted upstream with improved async request handling. However, it is currently incompatible with the existing OpenVPN userspace version.
https://lore.kernel.org/netdev/20250211-b4-ovpn-v19-0-86d5daf2a47a@openvpn.net/

To bridge this gap, I created a fork that incorporates these changes, tested on OpenWrt 22.

Feedback, tests, and PRs are welcome! :rocket:

Config tweaks:
mssfix or fragment is not supported by DCO. In order to avoid fragmentation, you should calculate MTU/MSS:
Inner MTU = Outer MTU (WAN MTU) - 20 bytes IPv4 header (for IPv4 endpoint) or 40 bytes IPv6 header (for IPv6 endpoint) - 8 bytes UDP header - 4 bytes OP32 - 4 bytes SEQ# - 16 bytes GCM auth tag - 1 byte compress stub (if the server enables compression).
Say you have a PPPoE WAN with a typical MTU of 1492, and you connected to an OpenVPN server over IPv4, and you have the full access to the server, then the calculated inner MTU should be 1492-20-8-4-4-16=1440 bytes

On both the server and the client configs, add the two options below

allow-compression no
tun-mtu 1440

Once the correct MTU is set, you can rely on the clamp MSS option of OpenWrt firewall.

But in many cases when you cannot tweak the server config, those options should not be added, as MTU mismatch can result in large packets being dropped. Then you have to manually set MSS:
IPv4 MSS = Inner MTU - 20 bytes IPv4 header - 20 bytes TCP header
IPv6 MSS = Inner MTU - 40 bytes IPv6 header - 20 bytes TCP header

Use the same case above, without the full access to the server, we must assume it has enabled compression (1 byte overhead), so the inner MTU should be 1439 bytes, IPv4 MSS = 1399 bytes, and IPv6 MSS = 1379 bytes.
Say the virtual device is tun0:

iptables/fw3: add the rules below to /etc/firewall.user

iptables -t mangle -A POSTROUTING -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1399
iptables -t mangle -A PREROUTING -i tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1399
ip6tables -t mangle -A POSTROUTING -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1379
ip6tables -t mangle -A PREROUTING -i tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1379

nftables/fw4: create a new file in /etc/nftables.d, and add the rules below into the new file

chain mangle_postrouting {
	type filter hook postrouting priority mangle
	oifname tun0 meta nfproto ipv4 tcp flags syn / syn,rst tcp option maxseg size set 1399
	oifname tun0 meta nfproto ipv6 tcp flags syn / syn,rst tcp option maxseg size set 1379
}
chain mangle_prerouting {
	type filter hook prerouting priority mangle
	iifname tun0 meta nfproto ipv4 tcp flags syn / syn,rst tcp option maxseg size set 1399
	iifname tun0 meta nfproto ipv6 tcp flags syn / syn,rst tcp option maxseg size set 1379
}
3 Likes

mt7981, openwrt-23.05.5

crashes with crypto_safexcel kernel module

28.773019] Unable to handle kernel read from unreadable memory at virtual address 000000004452c880
[   28.782071] Mem abort info:
[   28.784852]   ESR = 0x0000000096000005
[   28.788587]   EC = 0x25: DABT (current EL), IL = 32 bits
[   28.793895]   SET = 0, FnV = 0
[   28.796937]   EA = 0, S1PTW = 0
[   28.800070]   FSC = 0x05: level 1 translation fault
[   28.804932] Data abort info:
[   28.807799]   ISV = 0, ISS = 0x00000005
[   28.811624]   CM = 0, WnR = 0
[   28.814579] user pgtable: 4k pages, 39-bit VAs, pgdp=000000004443e000
[   28.821008] [000000004452c880] pgd=0000000000000000, p4d=0000000000000000, pud=0000000000000000
[   28.829699] Internal error: Oops: 0000000096000005 [#1] SMP
[   29.056591] CPU: 1 PID: 1068 Comm: irq/20-10320000 Not tainted 5.15.167 #0
[   29.063453] Hardware name: Routerich AX3000 (DT)
[   29.068056] pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[   29.075002] pc : dst_release+0x20/0xb4
[   29.078746] lr : skb_scrub_packet+0xdc/0xf0
[   29.082918] sp : ffffffc00914baa0
[   29.086219] x29: ffffffc00914baa0 x28: 0000000000000000 x27: 0000000000000040
[   29.093342] x26: 0000000000000011 x25: ffffffc008c34440 x24: 0000000000000000
[   29.100464] x23: 00000000010fa8c0 x22: 00000000020fa8c0 x21: ffffff8004740000
[   29.107587] x20: 0000000000000000 x19: 000000004452c840 x18: 0000000000000070
[   29.114709] x17: ffffffc007441000 x16: ffffffc008008000 x15: 0000000000000080
[   29.121830] x14: 0000000000000001 x13: 000000000000aa04 x12: 00000000000001e7
[   29.128953] x11: ffffff80018b712c x10: 000000000000005c x9 : ffffff800452c800
[   29.136075] x8 : 0000000000000020 x7 : 0000000000000040 x6 : 0000000000000000
[   29.143196] x5 : 0000000000000011 x4 : 00000000020fa8c0 x3 : 00000000010fa8c0
[   29.150318] x2 : 000000004452c880 x1 : 0000000000000060 x0 : 0000000000000001
[   29.157441] Call trace:
[   29.159876]  dst_release+0x20/0xb4
[   29.163268]  skb_scrub_packet+0xdc/0xf0
[   29.167091]  iptunnel_xmit+0x78/0x240
[   29.170742]  udp_tunnel_xmit_skb+0xd4/0xf4 [udp_tunnel]
[   29.176008]  ovpn_udp_send_skb+0x270/0x45c [ovpn_dco_v2]
[   29.181319]  ovpn_crypto_key_slots_swap+0xe0/0x900 [ovpn_dco_v2]
[   29.187324]  ovpn_encrypt_async_cb+0x30/0xa0 [ovpn_dco_v2]
[   29.192807]  0xffffffc000bfa86c
[   29.195945]  irq_thread_fn+0x28/0x90
[   29.199509]  irq_thread+0x10c/0x210
[   29.202985]  kthread+0x11c/0x130
[   29.206201]  ret_from_fork+0x10/0x20
[   29.209768] Code: aa0003f3 91010262 52800020 f9800051 (885f7c54)
[   29.215846] ---[ end trace f69865f342a98f98 ]---
[   29.225354] Kernel panic - not syncing: Oops: Fatal exception in interrupt

In 24.10.0 failed to build? Is it normal, or maybe is my fault?

I have no serial access to my device, in a case like yours, you could boot normally and flash an old firmware?

There are kernel API changes which are incompatible with my code. I'll update it later.

1 Like

No worries @LGA1150 take your time :grinning:

Code updated to build on OpenWrt 24

openwrt-24

With mcpu patch from keenetic
ovpn_decrypt_async_cb: decrypt failed: -74

And there is nothing without mcpu patch. No output at all when running iperf3/ping.

My fork has set the workqueue to WQ_UNBOUND so it may be incompatible with the mcpu patch. I don't think the mcpu patch is tested with async handling either.

I wrote that there is no traffic and no errors without mcpu patch. Removing crypto_safexcel.ko solving this issue.

What OpenWrt version are you using, 23, 24, or snapshot?

24.10.0 stable + https://github.com/LGA1150/ovpn-dco/tree/async

The same on openwrt 23.05.5

It ran fine on SF21 w/ OpenWrt 22. I'll test it on a MediaTek router later