Synopsis
========
iked's ikev2_update_sa_addresses() unconditionally calls pfkey_flow_add()
on the SA's flows when handling a MOBIKE UPDATE_SA_ADDRESSES informational.
This installs "type require" SPD flows even for policies declared with
"iface secX" (sec(4) interface-mode), which sec(4) does not use and
which actively break outbound encryption: once such flows exist, the
IP layer's policy-mode IPsec preempts sec(4) interface-mode encryption
and outbound packets are silently dropped.
ikev2_childsa_enable() guards its pfkey_flow_add() call with a
IKED_POLICY_ROUTING check (ikev2.c:6597); ikev2_update_sa_addresses()
is missing that same check.
iOS IKEv2 clients send a MOBIKE UPDATE_SA_ADDRESSES ~60s after IKE_AUTH
even when their peer address has not changed (the iked log entry shows
"old <addr> new <addr>" with identical addresses), so the breakage is
deterministic: works ~60s, then permanent one-way silence. Inbound
continues because the IN SA's kernel binding is unaffected.
Confirmed unfixed in -current as of ikev2.c 1.398 (2025-05-05).
Environment
===========
OpenBSD 7.8 (GENERIC.MP) #7 amd64 (syspatch-78-amd64), running iked
built from ikev2.c 1.394. The bug is structurally identical in -current.
Topology required for the dropped-packet symptom:
- sec1 (iface for the road-warrior policy) in rdomain 0
- pppoe0 (outer ESP transport, holds the local IP for the SA)
in rdomain 1
- paired pair0/pair1 bridging the two rdomains
This split is a legitimate setup to chain pf actions that pf cannot
combine in a single session (NAT64 in rdomain 0, smart routing in
rdomain 1).
Without the rdomain split, the spurious flows are still installed but
the outer packet happens to find a usable path, so the symptom is
hidden. The bug -- iked installing flows for sec(4) interface-mode
policies on MOBIKE update -- is independent of rdomain layout.
iked.conf snippet (road-warrior policy):
ikev2 "home" passive esp \
rdomain 0 \
from any to dynamic \
local <local_ipv4> peer any \
...
iface sec1 \
...
How to repeat
=============
1. Configure as above.
2. Connect any IKEv2 client that supports MOBIKE. iOS native VPN
reproduces this consistently.
3. Within ~60s of IKE_AUTH the client sends UPDATE_SA_ADDRESSES.
Before the update (rdomain 0):
# ipsecctl -sa
FLOWS:
No flows
SAD:
esp tunnel from <local> to <peer> spi 0x... enc aes-256-gcm
After (rdomain 0, immediately after iked logs
"ikev2_update_sa_addresses: old <X> new <X>"):
# ipsecctl -sa
FLOWS:
flow esp in from <client_inner_v4> to 0.0.0.0/0 peer <peer> \
srcid ... dstid ... type require
flow esp out from 0.0.0.0/0 to <client_inner_v4> peer <peer> \
srcid ... dstid ... type require
flow esp in from <client_inner_v6> to ::/0 peer <peer> ... type require
flow esp out from ::/0 to <client_inner_v6> peer <peer> ... type require
SAD: unchanged
sec1 Opkts (netstat -n -I sec1) stop incrementing the moment the
flows appear, and never resume. iPhone observes one-way connectivity
loss from that point on.
Setting "set nomobike" in iked.conf masks the symptom by preventing
the client from sending UPDATE_SA_ADDRESSES in the first place.
Fix
===
Mirror the IKED_POLICY_ROUTING check from ikev2_childsa_enable() in
ikev2_update_sa_addresses(): skip the flow delete/add loop for policies
using sec(4) interface mode.
Patch applied to /usr/src/sbin/iked/ikev2.c (1.394) on amd64 7.8;
iked rebuilt, installed, and "set nomobike" removed. iPhone reconnected
and observed for 6+ minutes:
- iked log shows ikev2_update_sa_addresses old==new events as before
(iOS still sends MOBIKE)
- rdomain 0 FLOWS stays "No flows"
- sec1 Opkts keeps incrementing
- iPhone has bidirectional connectivity throughout
Site-to-site policy (no MOBIKE, no sec(4) rdomain split) unaffected.
Index: sbin/iked/ikev2.c
===================================================================
--- sbin/iked/ikev2.c
+++ sbin/iked/ikev2.c
@@ -7485,25 +7485,32 @@ ikev2_update_sa_addresses(struct iked *env,
struct iked_sa *sa)
log_debug("%s: failed to update sa", __func__);
}
- /* delete and re-add flows */
- TAILQ_FOREACH(flow, &sa->sa_flows, flow_entry) {
- if (flow->flow_loaded) {
- RB_REMOVE(iked_flows, &env->sc_activeflows, flow);
- (void)pfkey_flow_delete(env, flow);
- flow->flow_loaded = 0;
+ /*
+ * Delete and re-add flows. sec(4) interface-mode policies do not
+ * use SPD flows (see ikev2_childsa_enable()); installing them here
+ * would hijack sec(4) interface-mode encryption and break outbound
+ * traffic. Skip the whole block for those policies.
+ */
+ if (!(sa->sa_policy->pol_flags & IKED_POLICY_ROUTING)) {
+ TAILQ_FOREACH(flow, &sa->sa_flows, flow_entry) {
+ if (flow->flow_loaded) {
+ RB_REMOVE(iked_flows, &env->sc_activeflows, flow);
+ (void)pfkey_flow_delete(env, flow);
+ flow->flow_loaded = 0;
+ }
+ if (pfkey_flow_add(env, flow) != 0)
+ log_debug("%s: failed to add flow %p", __func__, flow);
+ if (!flow->flow_loaded)
+ continue;
+ if ((oflow = RB_FIND(iked_flows, &env->sc_activeflows, flow))
+ != NULL) {
+ log_debug("%s: replaced old flow %p with %p",
+ __func__, oflow, flow);
+ oflow->flow_loaded = 0;
+ RB_REMOVE(iked_flows, &env->sc_activeflows, oflow);
+ }
+ RB_INSERT(iked_flows, &env->sc_activeflows, flow);
}
- if (pfkey_flow_add(env, flow) != 0)
- log_debug("%s: failed to add flow %p", __func__, flow);
- if (!flow->flow_loaded)
- continue;
- if ((oflow = RB_FIND(iked_flows, &env->sc_activeflows, flow))
- != NULL) {
- log_debug("%s: replaced old flow %p with %p",
- __func__, oflow, flow);
- oflow->flow_loaded = 0;
- RB_REMOVE(iked_flows, &env->sc_activeflows, oflow);
- }
- RB_INSERT(iked_flows, &env->sc_activeflows, flow);
}
/* update pending requests and responses */
ok?
--
Li Leding
[email protected]