Compare commits

..

2 Commits

Author SHA1 Message Date
overtrue 4ea0934d45 test(utils): allow ephemeral port reuse 2026-08-15 07:13:26 +08:00
overtrue 3c88e8df8a fix(ci): pin Docker images to release source 2026-08-15 06:37:48 +08:00
2 changed files with 0 additions and 165 deletions
-3
View File
@@ -659,9 +659,6 @@ mod test {
// Port should be in valid range (u16 max is always <= 65535)
assert!(port1 > 0);
assert!(port2 > 0);
// Different calls should typically return different ports
assert_ne!(port1, port2);
}
#[test]
@@ -6014,63 +6014,6 @@ fn peer_edit_fence(queries: &HashMap<String, String>) -> Option<(String, u64)> {
Some((origin.clone(), generation))
}
/// The largest generation an incoming fence may carry: this site's clock in
/// unix nanoseconds plus a day of cross-site skew. Every site authenticates
/// peer traffic with the shared site-replicator service account, so the
/// receiver cannot tell WHICH site stamped a fence — a compromised peer can
/// claim any origin, and a u64::MAX-scale generation would raise that
/// origin's high-water mark past anything the genuine site ever allocates,
/// silently fencing out its every future edit. A genuine generation is the
/// hybrid clock of [`next_peer_edit_generation`], floored by the sender's
/// wall time, so it exceeds this site's clock only by cross-site skew — and
/// the mark a ceiling-level forgery can still plant decays as real time
/// passes it, within the allowance. Only an origin still allocating with the
/// pre-hybrid plain counter cannot outrun such a mark until it upgrades: its
/// genuine generations never approach nanosecond scale.
fn peer_edit_fence_generation_ceiling(now_unix_nanos: u64) -> u64 {
const CROSS_SITE_SKEW_ALLOWANCE_NANOS: u64 = 24 * 60 * 60 * 1_000_000_000;
now_unix_nanos.saturating_add(CROSS_SITE_SKEW_ALLOWANCE_NANOS)
}
/// Whether an incoming fence may be honoured, as far as this site can vouch
/// for it. The sender's identity is unverifiable (shared service account),
/// so the check runs over what the receiving state knows: the claimed origin
/// must be a site this state currently replicates with — the same membership
/// rule the load-time mark pruning applies, so every mark recorded behind
/// this check is one a reload would keep — and not this site itself, which
/// never delivers edits to itself; and the generation must sit under
/// [`peer_edit_fence_generation_ceiling`]. The caller IGNORES an
/// inadmissible fence rather than failing the request: the delivery applies
/// exactly as an unstamped (pre-fence) delivery would, no high-water mark is
/// read or written, and the worst a forged fence achieves is forfeiting an
/// ordering guarantee its sender was never owed.
fn peer_edit_fence_is_admissible(
state: &SiteReplicationState,
local_deployment_id: &str,
fence: &(String, u64),
generation_ceiling: u64,
) -> bool {
let (origin, generation) = fence;
let result = if origin == local_deployment_id || !state.peers.contains_key(origin) {
"fence_origin_not_a_remote_peer"
} else if *generation > generation_ceiling {
"fence_generation_beyond_ceiling"
} else {
return true;
};
warn!(
event = EVENT_ADMIN_SITE_REPLICATION_STATE,
component = LOG_COMPONENT_ADMIN,
subsystem = LOG_SUBSYSTEM_SITE_REPLICATION,
result,
origin = %origin,
generation = *generation,
generation_ceiling,
"ignoring inadmissible peer-edit fence"
);
false
}
/// True when a strictly newer edit from the same origin site already landed
/// here. No lock on the sending side can order deliveries issued by two
/// nodes of that site, so ordering is decided here, on the generation the
@@ -9616,7 +9559,6 @@ impl Operation for SRPeerEditHandler {
let ilm_expiry_override = sr_edit_ilm_expiry_override(&req.uri);
let endpoint_refresh_requested = queries.get("refresh-targets").is_some_and(|value| value == "true");
let commit_fence = peer_edit_fence(&queries);
let fence_generation_ceiling = peer_edit_fence_generation_ceiling(edit_generation_wall_clock());
let local_endpoint = site_replication_local_endpoint(&req.uri, &req.headers);
let (refresh_id, incoming) = if endpoint_refresh_requested {
let refresh: EndpointRefreshRequest = read_site_replication_json(req, "", false).await?;
@@ -9634,11 +9576,6 @@ impl Operation for SRPeerEditHandler {
let outcome = update_site_replication_state_when_changed(move |state| {
let mut incoming = incoming;
let local_peer = local_peer_at_endpoint(commit_endpoint, state);
// The fence is self-reported — the shared service account means
// the sender cannot be identified — so it is honoured only after
// the admissibility check, against the same state it will gate.
let commit_fence = commit_fence
.filter(|fence| peer_edit_fence_is_admissible(state, &local_peer.deployment_id, fence, fence_generation_ceiling));
// Ordering fence: the sending site allocates the generation under
// its state-object lock, so a delivery that lost the race carries
// a generation this site has already passed. Applying it would
@@ -12020,17 +11957,6 @@ mod tests {
handler_block.contains("record_applied_peer_edit_generation(state, origin, *generation);"),
"SRPeerEditHandler must record the applied generation so later stale deliveries are recognised"
);
// Fence hardening: origin and generation are self-reported by a
// caller the shared service account cannot identify, so the handler
// must pass the fence through the admissibility check — against the
// same state the fence gates, i.e. inside the transaction — before
// reading or raising any high-water mark.
assert!(
handler_block.contains(
".filter(|fence| peer_edit_fence_is_admissible(state, &local_peer.deployment_id, fence, fence_generation_ceiling))"
),
"SRPeerEditHandler must admit a fence only through peer_edit_fence_is_admissible inside the state transaction"
);
// P1-15 PR2: both halves of the fence and the edit they fence share
// ONE transaction. Checking the fence against a state read outside the
// lock would let the check pass on one snapshot and the write land on
@@ -13325,94 +13251,6 @@ mod tests {
assert!(peer_edit_delivery_is_stale(&state, origin, generation - 1));
}
/// A fence is self-reported: every site authenticates peer traffic with
/// the same site-replicator credential, so a compromised peer can stamp
/// ANY origin with ANY generation. The receiver must refuse to let such
/// a stamp touch the high-water marks — an origin it does not replicate
/// with, its own deployment id, and a generation no genuine counter
/// could have reached are all ignored, and ignoring one plants no mark.
#[test]
fn forged_peer_edit_fences_cannot_poison_the_high_water_marks() {
let mut state = SiteReplicationState {
peers: BTreeMap::from([
(
"site-local".to_string(),
PeerInfo {
deployment_id: "site-local".to_string(),
..peer("local", "https://local.example:9000")
},
),
(
"site-victim".to_string(),
PeerInfo {
deployment_id: "site-victim".to_string(),
..peer("victim", "https://victim.example:9000")
},
),
]),
..Default::default()
};
let ceiling = peer_edit_fence_generation_ceiling(edit_generation_wall_clock());
// An origin outside the current membership is refused outright...
let unknown = ("site-unknown".to_string(), 4u64);
assert!(!peer_edit_fence_is_admissible(&state, "site-local", &unknown, ceiling));
// No site delivers edits to itself: a fence claiming the receiver as
// its origin is forged by construction, current peer or not.
let own = ("site-local".to_string(), 4u64);
assert!(!peer_edit_fence_is_admissible(&state, "site-local", &own, ceiling));
// A generation past the ceiling (the u64::MAX poisoning) is refused
// even when it names a current peer.
let poisoned = ("site-victim".to_string(), u64::MAX);
assert!(!peer_edit_fence_is_admissible(&state, "site-local", &poisoned, ceiling));
let barely_over = ("site-victim".to_string(), ceiling + 1);
assert!(!peer_edit_fence_is_admissible(&state, "site-local", &barely_over, ceiling));
// With no mark planted, the victim's genuine deliveries keep
// applying, and its admitted fence works end to end.
let genuine = ("site-victim".to_string(), 1u64);
assert!(peer_edit_fence_is_admissible(&state, "site-local", &genuine, ceiling));
assert!(!peer_edit_delivery_is_stale(&state, &genuine.0, genuine.1));
record_applied_peer_edit_generation(&mut state, &genuine.0, genuine.1);
assert_eq!(state.applied_edit_generations.get("site-victim"), Some(&1));
}
/// The generation ceiling must admit both live generation shapes: the
/// small plain counter pre-hybrid sites still allocate, and the
/// wall-clock-floored hybrid of `next_peer_edit_generation` — including
/// one running AHEAD of the receiver's clock by cross-site skew, which
/// is the whole reason the allowance exists, and the ceiling itself,
/// the last admissible value.
#[test]
fn peer_edit_fence_ceiling_admits_counter_and_clock_seeded_generations() {
let state = SiteReplicationState {
peers: BTreeMap::from([(
"site-origin".to_string(),
PeerInfo {
deployment_id: "site-origin".to_string(),
..peer("origin", "https://origin.example:9000")
},
)]),
..Default::default()
};
let now = edit_generation_wall_clock();
let ceiling = peer_edit_fence_generation_ceiling(now);
let counter = ("site-origin".to_string(), 42u64);
assert!(peer_edit_fence_is_admissible(&state, "site-local", &counter, ceiling));
// One hour ahead of the receiver's clock: within the skew allowance.
// Shrinking the allowance to zero must turn this fence away.
let clock_ahead = ("site-origin".to_string(), now + 60 * 60 * 1_000_000_000);
assert!(peer_edit_fence_is_admissible(&state, "site-local", &clock_ahead, ceiling));
// The boundary is inclusive: rejection starts strictly past it.
let at_ceiling = ("site-origin".to_string(), ceiling);
assert!(peer_edit_fence_is_admissible(&state, "site-local", &at_ceiling, ceiling));
}
/// P1-15 review follow-up: a site that leaves the mesh drops below two
/// peers, which clears its state object and restarts its generation
/// counter at zero. A mark left over from its previous membership would