fix(ipsec): различать частичный и неподтверждённый результат операций
This commit is contained in:
1 parent
a83e4734df
commit
43b00e0740
15 files changed
+263
-37
No files matched your search
@@ -311,3 +311,8 @@ export function buildClientInstructions(args: {
|
||||
"Адрес выдаётся автоматически при подключении (mode-config).",
|
||||
].join("\n")
|
||||
}
|
||||
|
||||
export function normalizeIpsecRemoteId(value: string): string {
|
||||
const id = value.trim()
|
||||
return id && !id.includes(":") && !["auto", "ignore"].includes(id) ? `key-id:${id}` : id
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { trackIpsecMutation } from "./ipsec-operation.js"
|
||||
import { MikrotikError, type MikrotikClient } from "./mikrotik.js"
|
||||
const rejected = new MikrotikError(403, "/ip/ipsec/peer", "forbidden")
|
||||
for (const steps of [0, 1]) for (const failure of [rejected, new Error("socket closed"), new MikrotikError(500, "/ip/ipsec/peer", "server error")]) {
|
||||
let attempts = 0
|
||||
const client = { patch: async () => { attempts++; if (attempts > steps) throw failure }, put: async () => {}, delete: async () => {}, post: async () => {} } as unknown as MikrotikClient
|
||||
const tracked = trackIpsecMutation(client)
|
||||
if (steps) await tracked.client.patch("/own", {})
|
||||
await assert.rejects(tracked.client.patch("/own", {}))
|
||||
assert.equal(tracked.receipt()?.state, failure === rejected ? steps ? "partial" : undefined : "unknown")
|
||||
assert.equal(tracked.receipt()?.confirmedWrites ?? 0, steps)
|
||||
}
|
||||
const client = { patch: async () => {}, put: async () => {}, delete: async () => {}, post: async () => {}, removeCertificate: async function () { await this.delete("/certificate/own") } } as unknown as MikrotikClient
|
||||
const tracked = trackIpsecMutation(client)
|
||||
await tracked.client.removeCertificate("own")
|
||||
assert.equal(tracked.receipt()?.confirmedWrites, 1)
|
||||
tracked.complete(); assert.deepEqual(tracked.receipt(), { state: "applied", confirmedWrites: 1, needsReview: true })
|
||||
console.log("IPsec progress: native 4xx, partial writes, transport/5xx uncertainty, nested methods and completed operation")
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { MikrotikClient, MikrotikError } from "./mikrotik.js"
|
||||
|
||||
/** Request-local evidence only: never claims that an unacknowledged request failed to apply. */
|
||||
export function trackIpsecMutation(client: MikrotikClient) {
|
||||
let confirmedWrites = 0, uncertainWrite = false, completed = false
|
||||
const methods = client as unknown as Record<string, (...args: unknown[]) => Promise<unknown>>
|
||||
for (const method of ["put", "patch", "delete", "post"]) {
|
||||
const original = methods[method]!.bind(client)
|
||||
methods[method] = async (...args) => {
|
||||
try { const result = await original(...args); confirmedWrites++; return result }
|
||||
catch (error) {
|
||||
if (!(error instanceof MikrotikError && error.statusCode >= 400 && error.statusCode < 500)) uncertainWrite = true
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
client,
|
||||
complete: () => { completed = true },
|
||||
receipt: () => {
|
||||
const state = completed ? "applied" : uncertainWrite ? "unknown" : confirmedWrites ? "partial" : undefined
|
||||
return state ? { state, confirmedWrites, needsReview: true } : undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
export type IpsecMutationProgress = ReturnType<typeof trackIpsecMutation>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { MikrotikClient } from "./mikrotik.js"
|
||||
import {
|
||||
import { normalizeIpsecRemoteId,
|
||||
ipsecManagedComment,
|
||||
isManagedIpsecModeConfig,
|
||||
selectIke2Peers,
|
||||
@@ -197,7 +197,7 @@ export function identityRosBody(f: IdentityFields): Record<string, string> {
|
||||
"remote-certificate": f.remoteCertificate,
|
||||
"match-by": f.authMethod === "certificate" && f.remoteCertificate ? "certificate" : undefined,
|
||||
secret: f.secret,
|
||||
"remote-id": f.remoteId && !f.remoteId.includes(":") && !["auto", "ignore"].includes(f.remoteId) ? `key-id:${f.remoteId}` : f.remoteId,
|
||||
"remote-id": f.remoteId === undefined ? undefined : normalizeIpsecRemoteId(f.remoteId),
|
||||
"mode-config": f.modeConfig,
|
||||
"generate-policy": "port-strict",
|
||||
comment: f.comment,
|
||||
|
||||
Reference in new issue
Block a user