Problem
BackoffDialPolicy.Dial (the only DialPolicy implementation shipped) builds its connect headers with TypeHeader, ConnectionIdHeader, GroupSecretHeader, and IsGroupedHeader, but never sets IsFirstGroupConnection.
A MultiListener only creates a new channel for a grouped underlay when IsFirstGroupConnection is set; otherwise, if no channel already exists for the connection id, it closes the underlay (multi_listener.go, the !isFirst branch). So BackoffDialPolicy can only ever add an underlay to an already-established group. It cannot:
- establish the first underlay of a group, or
- recover a channel that is configured to survive full underlay loss (
Min: 0 / MinTotalUnderlays: 0) and re-dial.
The DialPolicy interface gives the policy no way to know a dial is a first/re-establishment dial, even though the channel core knows it (in dialUnderlay, total underlay count == 0).
This blocks migrating ziti's control channel and link (xlink) dialers, which in v4 set IsFirstGroupConnection (and rotate the group id) on first connection and after full loss.
Proposed fix
- Add a first-connection signal to the
DialPolicy contract:
Dial(underlayType, connectionId string, groupSecret []byte, isFirst bool, connectTimeout, cancel) (Underlay, error).
The channel core computes isFirst in dialUnderlay (total underlay count == 0) and passes it.
BackoffDialPolicy honors it: when isFirst, set IsFirstGroupConnection=true and derive a fresh iteration-suffixed connection id (matching v4's groupId-N scheme, which avoids attaching to a still-closing listener-side channel during the loss/reconnect race); otherwise reuse the current iteration id with no header.
AcceptUnderlay validates by group secret (not connection id), so an iterated wire id is safe for the local channel; the divergence between the channel's fixed local id and the iterated wire id is cosmetic (logging only).
This is a breaking change to the DialPolicy interface, but BackoffDialPolicy is the only implementer today, so the cost is minimal and best paid before downstream consumers write policies against the current (incomplete) signature.
Problem
BackoffDialPolicy.Dial(the onlyDialPolicyimplementation shipped) builds its connect headers withTypeHeader,ConnectionIdHeader,GroupSecretHeader, andIsGroupedHeader, but never setsIsFirstGroupConnection.A
MultiListeneronly creates a new channel for a grouped underlay whenIsFirstGroupConnectionis set; otherwise, if no channel already exists for the connection id, it closes the underlay (multi_listener.go, the!isFirstbranch). SoBackoffDialPolicycan only ever add an underlay to an already-established group. It cannot:Min: 0/MinTotalUnderlays: 0) and re-dial.The
DialPolicyinterface gives the policy no way to know a dial is a first/re-establishment dial, even though the channel core knows it (indialUnderlay, total underlay count == 0).This blocks migrating ziti's control channel and link (xlink) dialers, which in v4 set
IsFirstGroupConnection(and rotate the group id) on first connection and after full loss.Proposed fix
DialPolicycontract:Dial(underlayType, connectionId string, groupSecret []byte, isFirst bool, connectTimeout, cancel) (Underlay, error).The channel core computes
isFirstindialUnderlay(total underlay count == 0) and passes it.BackoffDialPolicyhonors it: whenisFirst, setIsFirstGroupConnection=trueand derive a fresh iteration-suffixed connection id (matching v4'sgroupId-Nscheme, which avoids attaching to a still-closing listener-side channel during the loss/reconnect race); otherwise reuse the current iteration id with no header.AcceptUnderlayvalidates by group secret (not connection id), so an iterated wire id is safe for the local channel; the divergence between the channel's fixed local id and the iterated wire id is cosmetic (logging only).This is a breaking change to the
DialPolicyinterface, butBackoffDialPolicyis the only implementer today, so the cost is minimal and best paid before downstream consumers write policies against the current (incomplete) signature.