Skip to content

Commit 5c521f0

Browse files
committed
refactor(node): add advertise-address convergence and enforce observed-address truth across inbound and outbound paths
1 parent 872f6ef commit 5c521f0

15 files changed

Lines changed: 3539 additions & 98 deletions

docs/protocol/errors.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ For `incompatible-protocol-version` errors, additional fields are included:
2828
}
2929
```
3030

31+
A separate transport-control frame `connection_notice` carries advisory
32+
close reasons that the responder wants the initiator to learn before the
33+
socket goes away. It mirrors the error frame shape but adds `status` and
34+
`details`:
35+
36+
```json
37+
{
38+
"type": "connection_notice",
39+
"code": "observed-address-mismatch",
40+
"status": "closing",
41+
"error": "advertised address does not match observed remote address",
42+
"details": { "observed_address": "203.0.113.50" }
43+
}
44+
```
45+
46+
The initiator should treat `connection_notice` as informational: record
47+
the `details`, then expect the socket to close. Status codes surfaced
48+
through `connection_notice` are listed under "Connection Notice Codes"
49+
below.
50+
3151
## Error Code Reference
3252

3353
| Code | When Returned | HTTP Analogy | Details |
@@ -124,6 +144,17 @@ These errors indicate internal server issues:
124144
- `encode-failed` - Response serialization failed
125145
- `read` - Network I/O error
126146

147+
### Connection Notice Codes
148+
149+
Delivered through the `connection_notice` frame type rather than
150+
`type="error"`. They are advisory — the responder is going to close the
151+
connection and wants the initiator to learn a machine-readable reason
152+
first.
153+
154+
| Code | When Sent | Details |
155+
|------|-----------|---------|
156+
| `observed-address-mismatch` | Inbound `hello` advertised a world-reachable IP that does not match the observed TCP source | `details.observed_address` holds the IP the responder sees for the initiator. Initiator records it as the trusted self-advertise IP so the next outbound hello self-corrects |
157+
127158
## Error Handling Guide
128159

129160
### Client Responsibilities
@@ -266,6 +297,26 @@ graph TB
266297
}
267298
```
268299

300+
Отдельный transport-control фрейм `connection_notice` передаёт
301+
рекомендательные причины закрытия соединения, о которых ответчик хочет
302+
сообщить инициатору до разрыва сокета. По форме совпадает с error, но
303+
добавляет поля `status` и `details`:
304+
305+
```json
306+
{
307+
"type": "connection_notice",
308+
"code": "observed-address-mismatch",
309+
"status": "closing",
310+
"error": "advertised address does not match observed remote address",
311+
"details": { "observed_address": "203.0.113.50" }
312+
}
313+
```
314+
315+
Инициатор должен рассматривать `connection_notice` как информационный:
316+
записать `details`, затем ожидать закрытия сокета. Коды статусов,
317+
передаваемые через `connection_notice`, перечислены ниже в секции
318+
«Коды connection_notice».
319+
269320
## Справочник кодов ошибок
270321

271322
| Код | Когда возвращается | Аналогия HTTP | Детали |
@@ -362,6 +413,17 @@ graph TB
362413
- `encode-failed` - Сериализация ответа не удалась
363414
- `read` - Ошибка ввода-вывода сети
364415

416+
### Коды connection_notice
417+
418+
Передаются через фрейм `connection_notice`, а не через `type="error"`.
419+
Носят рекомендательный характер — ответчик собирается закрыть
420+
соединение и хочет, чтобы инициатор узнал машинно-читаемую причину
421+
заранее.
422+
423+
| Код | Когда отправляется | Детали |
424+
|-----|---------------------|--------|
425+
| `observed-address-mismatch` | Входящий `hello` объявил world-reachable IP, который не совпадает с наблюдаемым TCP-источником | `details.observed_address` содержит IP, который ответчик видит у инициатора. Инициатор сохраняет его как trusted-self-advertise-IP, чтобы следующий исходящий hello сам скорректировался |
426+
365427
## Руководство по обработке ошибок
366428

367429
### Ответственность клиента

docs/protocol/handshake.md

Lines changed: 291 additions & 0 deletions
Large diffs are not rendered by default.

internal/core/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const (
7373
CorsaVersion = "0.36 alpha"
7474
CorsaWireVersion = "0.36-alpha"
7575
ClientBuild = 36
76-
ProtocolVersion = 9
76+
ProtocolVersion = 10
7777
MinimumProtocolVersion = 7
7878
DefaultOutgoingPeers = 8
7979
DefaultPeerPort = "64646"

internal/core/domain/peer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ type PeerAddress string
2626
// with remote transport addresses at compile time.
2727
type ListenAddress string
2828

29+
// PeerIP is the canonical bare-IP form of a peer endpoint (IPv4 or
30+
// IPv6 textual representation after IPv4-mapped IPv6 has been collapsed
31+
// to the bare IPv4 form). Distinct from PeerAddress, which carries the
32+
// host:port tuple used for dialling. Used by the advertise-address
33+
// convergence layer for trusted-advertise triples, observed-IP history,
34+
// consensus computation, and self-advertise overrides. An empty value
35+
// is a legal "no value" sentinel — absence is encoded by the zero value
36+
// rather than a separate optional wrapper because the canonical form of
37+
// a missing IP is still an empty string on the wire.
38+
type PeerIP string
39+
2940
// PeerSource describes how a peer address was discovered.
3041
type PeerSource string
3142

0 commit comments

Comments
 (0)