test(mtproto): cover single-backend DC aliases
Follow up gramsrv PR #17 with boundary coverage and public configuration docs.
Canonical-Telesrv: b4df3e72c24470e4ce09e5fc1d46e0c2944f6d3e
Reviewed-Head: e7eb07b9eb
Co-authored-by: onysd <beatvin128@gmail.com>
This commit is contained in:
parent
5dcdb57e4c
commit
9ef746d45e
8 changed files with 127 additions and 69 deletions
|
|
@ -7,6 +7,8 @@
|
||||||
TELESRV_LISTEN=0.0.0.0:2398
|
TELESRV_LISTEN=0.0.0.0:2398
|
||||||
TELESRV_ADVERTISE_IP=127.0.0.1
|
TELESRV_ADVERTISE_IP=127.0.0.1
|
||||||
TELESRV_DC=2
|
TELESRV_DC=2
|
||||||
|
# Single-backend default: accept any client wire DC label during key exchange.
|
||||||
|
TELESRV_STRICT_DC_CHECK=false
|
||||||
TELESRV_DEV_AUTH_CODE=12345
|
TELESRV_DEV_AUTH_CODE=12345
|
||||||
TELESRV_AUTH_CODE_TTL=5m
|
TELESRV_AUTH_CODE_TTL=5m
|
||||||
TELESRV_AUTH_CODE_MAX_ATTEMPTS=5
|
TELESRV_AUTH_CODE_MAX_ATTEMPTS=5
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@ This document describes every setting loaded by `internal/config`. Defaults and
|
||||||
| `TELESRV_LISTEN` | string / `0.0.0.0:2398` | MTProto TCP listen address. Must match the address/port reachable by patched clients. |
|
| `TELESRV_LISTEN` | string / `0.0.0.0:2398` | MTProto TCP listen address. Must match the address/port reachable by patched clients. |
|
||||||
| `TELESRV_ADVERTISE_IP` | string / `127.0.0.1` | Client-reachable server IP used by media/call fallbacks. The current static Desktop DC patch does not derive its MTProto endpoint from this value. |
|
| `TELESRV_ADVERTISE_IP` | string / `127.0.0.1` | Client-reachable server IP used by media/call fallbacks. The current static Desktop DC patch does not derive its MTProto endpoint from this value. |
|
||||||
| `TELESRV_RSA_KEY` | path / `data/server_rsa.pem` | MTProto RSA private key. Generated when missing. Treat the file as a secret and keep it stable across restarts. |
|
| `TELESRV_RSA_KEY` | path / `data/server_rsa.pem` | MTProto RSA private key. Generated when missing. Treat the file as a secret and keep it stable across restarts. |
|
||||||
| `TELESRV_DC` | int / `2` | Server DC ID. Must match patched client expectations and stored media/DC metadata. |
|
| `TELESRV_DC` | int / `2` | Canonical server DC ID used in server-originated configuration and media/DC metadata. It does not partition key-exchange state on the current single backend. |
|
||||||
|
| `TELESRV_STRICT_DC_CHECK` | bool / `false` | Default `false` accepts every wire int32 DC label for permanent and temporary key exchange. `true` requires permanent `dc_id == TELESRV_DC` and temporary `abs(dc_id) == TELESRV_DC`; it is only a diagnostic and does not provide multi-DC isolation. |
|
||||||
| `TELESRV_WEBSOCKET_ENABLE` | bool / `true` | Enables MTProto-over-WebSocket demultiplexing on the MTProto listener. |
|
| `TELESRV_WEBSOCKET_ENABLE` | bool / `true` | Enables MTProto-over-WebSocket demultiplexing on the MTProto listener. |
|
||||||
| `TELESRV_WEBSOCKET_ALLOWED_ORIGINS` | list / `http://localhost:1234,http://127.0.0.1:1234` | Browser WebSocket origin allow-list. `*` is for temporary debugging only. |
|
| `TELESRV_WEBSOCKET_ALLOWED_ORIGINS` | list / `http://localhost:1234,http://127.0.0.1:1234` | Browser WebSocket origin allow-list. `*` is for temporary debugging only. |
|
||||||
| `TELESRV_MTPROTO_MAX_CONNECTIONS` | int / `200000` | Global physical connection admission limit. Negative disables this gate. |
|
| `TELESRV_MTPROTO_MAX_CONNECTIONS` | int / `200000` | Global physical connection admission limit. Negative disables this gate. |
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,8 @@
|
||||||
| `TELESRV_LISTEN` | string / `0.0.0.0:2398` | MTProto TCP 监听地址,必须与 patched 客户端可达地址/端口一致。 |
|
| `TELESRV_LISTEN` | string / `0.0.0.0:2398` | MTProto TCP 监听地址,必须与 patched 客户端可达地址/端口一致。 |
|
||||||
| `TELESRV_ADVERTISE_IP` | string / `127.0.0.1` | 媒体、通话等回退路径使用的客户端可达 IP;当前 TDesktop 静态 DC patch 不从这里获取 MTProto 地址。 |
|
| `TELESRV_ADVERTISE_IP` | string / `127.0.0.1` | 媒体、通话等回退路径使用的客户端可达 IP;当前 TDesktop 静态 DC patch 不从这里获取 MTProto 地址。 |
|
||||||
| `TELESRV_RSA_KEY` | path / `data/server_rsa.pem` | MTProto RSA 私钥;缺失时自动生成。属于敏感文件,重启和升级间必须稳定保存。 |
|
| `TELESRV_RSA_KEY` | path / `data/server_rsa.pem` | MTProto RSA 私钥;缺失时自动生成。属于敏感文件,重启和升级间必须稳定保存。 |
|
||||||
| `TELESRV_DC` | int / `2` | 服务端 DC ID,必须与客户端 patch 及媒体/DC 元数据一致。 |
|
| `TELESRV_DC` | int / `2` | 服务端输出配置及媒体/DC 元数据使用的规范 DC ID;当前单后端不会按它分区密钥交换状态。 |
|
||||||
|
| `TELESRV_STRICT_DC_CHECK` | bool / `false` | 默认 `false`,永久与临时密钥交换接受任意 wire int32 DC 标签。设为 `true` 时永久标签必须等于 `TELESRV_DC`、临时标签绝对值必须等于 `TELESRV_DC`;它仅是诊断开关,不提供多 DC 隔离。 |
|
||||||
| `TELESRV_WEBSOCKET_ENABLE` | bool / `true` | 在 MTProto 监听端口启用 MTProto-over-WebSocket 分流。 |
|
| `TELESRV_WEBSOCKET_ENABLE` | bool / `true` | 在 MTProto 监听端口启用 MTProto-over-WebSocket 分流。 |
|
||||||
| `TELESRV_WEBSOCKET_ALLOWED_ORIGINS` | list / `http://localhost:1234,http://127.0.0.1:1234` | 浏览器 WebSocket origin 白名单;`*` 只用于临时调试。 |
|
| `TELESRV_WEBSOCKET_ALLOWED_ORIGINS` | list / `http://localhost:1234,http://127.0.0.1:1234` | 浏览器 WebSocket origin 白名单;`*` 只用于临时调试。 |
|
||||||
| `TELESRV_MTPROTO_MAX_CONNECTIONS` | int / `200000` | 全局物理连接 admission 上限;负数关闭该门禁。 |
|
| `TELESRV_MTPROTO_MAX_CONNECTIONS` | int / `200000` | 全局物理连接 admission 上限;负数关闭该门禁。 |
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,10 @@ type Config struct {
|
||||||
RSAKeyPath string
|
RSAKeyPath string
|
||||||
// DC 是本 server 的 DC ID。
|
// DC 是本 server 的 DC ID。
|
||||||
DC int
|
DC int
|
||||||
// StrictDCCheck turns on exact DC-ID validation for the permanent-key
|
// StrictDCCheck enables the default-off key-exchange DC-label diagnostic.
|
||||||
// exchange (default off = lenient). See mtprotoedge.Options.StrictDC doc
|
// The normal single-backend mode accepts every wire int32 label without
|
||||||
// for the full rationale: telesrv is always a single physical backend, but
|
// partitioning auth keys, sessions, or business state. See
|
||||||
// many client forks alias dc_id 1..5 to it, so a mismatched client-chosen
|
// mtprotoedge.Options.StrictDC for the optional strict behavior.
|
||||||
// dc_id is expected, not an attack — strict mode exists only for a
|
|
||||||
// hypothetical future real multi-DC deployment.
|
|
||||||
StrictDCCheck bool
|
StrictDCCheck bool
|
||||||
// MTProtoMaxConnections / PerIP 覆盖 raw Accept、codec sniff、握手到认证 session
|
// MTProtoMaxConnections / PerIP 覆盖 raw Accept、codec sniff、握手到认证 session
|
||||||
// 的完整物理连接生命周期;负数关闭对应 admission 上限。
|
// 的完整物理连接生命周期;负数关闭对应 admission 上限。
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,34 @@ func TestLoadUsesExplicitAdvertiseIP(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadStrictDCCheck(t *testing.T) {
|
||||||
|
t.Run("defaults off", func(t *testing.T) {
|
||||||
|
disableDefaultConfigFile(t)
|
||||||
|
t.Setenv("TELESRV_STRICT_DC_CHECK", "")
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if cfg.StrictDCCheck {
|
||||||
|
t.Fatal("StrictDCCheck = true, want default false")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("explicitly enabled", func(t *testing.T) {
|
||||||
|
disableDefaultConfigFile(t)
|
||||||
|
t.Setenv("TELESRV_STRICT_DC_CHECK", "true")
|
||||||
|
|
||||||
|
cfg, err := Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Load: %v", err)
|
||||||
|
}
|
||||||
|
if !cfg.StrictDCCheck {
|
||||||
|
t.Fatal("StrictDCCheck = false, want true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadMTProtoAdmissionAndRPCBudgets(t *testing.T) {
|
func TestLoadMTProtoAdmissionAndRPCBudgets(t *testing.T) {
|
||||||
disableDefaultConfigFile(t)
|
disableDefaultConfigFile(t)
|
||||||
t.Setenv("TELESRV_MTPROTO_MAX_CONNECTIONS", "12345")
|
t.Setenv("TELESRV_MTPROTO_MAX_CONNECTIONS", "12345")
|
||||||
|
|
|
||||||
|
|
@ -25,14 +25,12 @@ import (
|
||||||
|
|
||||||
// runServerExchange is a gotd server exchange compatibility shim.
|
// runServerExchange is a gotd server exchange compatibility shim.
|
||||||
//
|
//
|
||||||
// DrKLO Android marks media temporary auth-key exchange with a negative DC in
|
// In the default single-backend mode, p_q_inner_data_dc and
|
||||||
// p_q_inner_data_temp_dc (for example DC 2 -> -2). gotd v0.158.0 validates this
|
// p_q_inner_data_temp_dc carry client routing labels only: every int32 value is
|
||||||
// field by exact equality and rejects that legitimate media-temp path. The
|
// admitted and the label is not persisted or used for key/session identity.
|
||||||
// temp-key DC check accepts any value whose absolute value matches this
|
// This also covers DrKLO Android's negative media-temp labels. StrictDC retains
|
||||||
// server DC. The permanent-key check is lenient by default too (see
|
// exact permanent / absolute-value temporary validation as an explicit,
|
||||||
// Options.StrictDC doc) — self-hosted single-server deployments commonly
|
// default-off diagnostic for a future real multi-DC deployment.
|
||||||
// have clients that alias dc_id 1..5 to the one backend, so a client-chosen
|
|
||||||
// dc_id that isn't our configured DC is expected, not an error.
|
|
||||||
func (s *Server) runServerExchange(ctx context.Context, conn transport.Conn) (exchange.ServerExchangeResult, error) {
|
func (s *Server) runServerExchange(ctx context.Context, conn transport.Conn) (exchange.ServerExchangeResult, error) {
|
||||||
ex := serverExchangeCompat{
|
ex := serverExchangeCompat{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
|
|
@ -351,14 +349,7 @@ func (s serverExchangeCompat) validatePQInnerDataDC(d mt.PQInnerDataClass) error
|
||||||
case *mt.PQInnerDataDC:
|
case *mt.PQInnerDataDC:
|
||||||
if innerDataDC.DC != s.dc {
|
if innerDataDC.DC != s.dc {
|
||||||
if !s.strictDC {
|
if !s.strictDC {
|
||||||
// Lenient by default (Options.StrictDC doc has the full
|
s.log.Debug("Accepted permanent auth key DC alias",
|
||||||
// rationale): telesrv is a single physical backend, and
|
|
||||||
// self-hosted client forks commonly alias dc_id 1..5 to this
|
|
||||||
// one server, so a client-chosen dc_id that isn't our
|
|
||||||
// configured DC is expected, not an error. dc_id plays no
|
|
||||||
// role in key derivation, so accepting it doesn't weaken the
|
|
||||||
// exchange.
|
|
||||||
s.log.Debug("Accepted permanent auth key DC mismatch (lenient mode)",
|
|
||||||
zap.Int("server_dc", s.dc),
|
zap.Int("server_dc", s.dc),
|
||||||
zap.Int("client_dc", innerDataDC.DC))
|
zap.Int("client_dc", innerDataDC.DC))
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -369,8 +360,8 @@ func (s serverExchangeCompat) validatePQInnerDataDC(d mt.PQInnerDataClass) error
|
||||||
if !sameDCByAbs(innerDataDC.DC, s.dc) && s.strictDC {
|
if !sameDCByAbs(innerDataDC.DC, s.dc) && s.strictDC {
|
||||||
return wrongDCError(s.dc, innerDataDC.DC)
|
return wrongDCError(s.dc, innerDataDC.DC)
|
||||||
}
|
}
|
||||||
if innerDataDC.DC < 0 {
|
if !sameDCByAbs(innerDataDC.DC, s.dc) {
|
||||||
s.log.Warn("Accepted Android media temp auth key negative DC",
|
s.log.Debug("Accepted temporary auth key DC alias",
|
||||||
zap.Int("server_dc", s.dc),
|
zap.Int("server_dc", s.dc),
|
||||||
zap.Int("client_dc", innerDataDC.DC),
|
zap.Int("client_dc", innerDataDC.DC),
|
||||||
zap.Int("expires_in", innerDataDC.ExpiresIn))
|
zap.Int("expires_in", innerDataDC.ExpiresIn))
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -366,41 +367,85 @@ func TestKeyExchangeAcceptsAndroidMediaTempNegativeDC(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestKeyExchangeRejectsWrongNegativeTempDCWhenStrict(t *testing.T) {
|
func TestKeyExchangeAcceptsAnyDCLabelByDefault(t *testing.T) {
|
||||||
ex := serverExchangeCompat{dc: 2, strictDC: true, log: zaptest.NewLogger(t)}
|
|
||||||
err := ex.validatePQInnerDataDC(&mt.PQInnerDataTempDC{DC: -3})
|
|
||||||
var exErr *exchange.ServerExchangeError
|
|
||||||
if !errors.As(err, &exErr) {
|
|
||||||
t.Fatalf("err = %T %v, want ServerExchangeError", err, err)
|
|
||||||
}
|
|
||||||
if exErr.Code != codec.CodeWrongDC {
|
|
||||||
t.Fatalf("error code = %d, want %d", exErr.Code, codec.CodeWrongDC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestKeyExchangeAcceptsMismatchedDCByDefault asserts that, in the default
|
|
||||||
// lenient mode, neither permanent nor temp key exchange requires dc_id to
|
|
||||||
// equal the server's configured DC. telesrv is always a single physical
|
|
||||||
// backend; self-hosted client forks commonly alias dc_id 1..5 to it (see
|
|
||||||
// Options.StrictDC doc), so a mismatched client-chosen dc_id must not be
|
|
||||||
// rejected — doing so previously broke every account whose client picked a
|
|
||||||
// starting dc_id other than the server's.
|
|
||||||
func TestKeyExchangeAcceptsMismatchedDCByDefault(t *testing.T) {
|
|
||||||
ex := serverExchangeCompat{dc: 2, log: zaptest.NewLogger(t)}
|
ex := serverExchangeCompat{dc: 2, log: zaptest.NewLogger(t)}
|
||||||
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataDC{DC: 3}); err != nil {
|
labels := []int{
|
||||||
t.Fatalf("permanent DC mismatch: err = %v, want nil (lenient by default)", err)
|
2, // canonical
|
||||||
|
3, // another production DC
|
||||||
|
0, // no conventional DC mapping
|
||||||
|
-2, // Android media-temp convention
|
||||||
|
10002, // test-environment style label
|
||||||
|
-10002, // negative test-environment style label
|
||||||
|
-1 << 31,
|
||||||
|
1<<31 - 1,
|
||||||
}
|
}
|
||||||
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataTempDC{DC: -3}); err != nil {
|
|
||||||
t.Fatalf("temp DC mismatch: err = %v, want nil (lenient by default)", err)
|
for _, label := range labels {
|
||||||
|
t.Run(fmt.Sprintf("permanent_%d", label), func(t *testing.T) {
|
||||||
|
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataDC{DC: label}); err != nil {
|
||||||
|
t.Fatalf("validate permanent DC label %d: %v", label, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run(fmt.Sprintf("temporary_%d", label), func(t *testing.T) {
|
||||||
|
if err := ex.validatePQInnerDataDC(&mt.PQInnerDataTempDC{DC: label, ExpiresIn: 60}); err != nil {
|
||||||
|
t.Fatalf("validate temporary DC label %d: %v", label, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestKeyExchangeRejectsMismatchedPermanentDCWhenStrict asserts that
|
func TestKeyExchangePersistsArbitraryPermanentDCLabelByDefault(t *testing.T) {
|
||||||
// strictDC=true still enforces exact DC-ID equality for permanent-key
|
const clientDC = 10002
|
||||||
// exchange (kept for a hypothetical future real multi-DC deployment).
|
keys := memory.NewAuthKeyStore()
|
||||||
func TestKeyExchangeRejectsMismatchedPermanentDCWhenStrict(t *testing.T) {
|
addr, pub, _ := startTestServer(t, Options{DC: 2, AuthKeys: keys})
|
||||||
|
|
||||||
|
_, auth, _ := dialHandshake(t, addr, clientDC, pub)
|
||||||
|
saved, found, err := keys.Get(context.Background(), auth.AuthKey.ID)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("get persisted auth key: %v", err)
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Fatalf("auth key %x was not persisted", auth.AuthKey.ID)
|
||||||
|
}
|
||||||
|
if saved.Value != [256]byte(auth.AuthKey.Value) {
|
||||||
|
t.Fatal("persisted auth key value mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestKeyExchangeStrictDCValidation(t *testing.T) {
|
||||||
ex := serverExchangeCompat{dc: 2, strictDC: true, log: zaptest.NewLogger(t)}
|
ex := serverExchangeCompat{dc: 2, strictDC: true, log: zaptest.NewLogger(t)}
|
||||||
err := ex.validatePQInnerDataDC(&mt.PQInnerDataDC{DC: 3})
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data mt.PQInnerDataClass
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{name: "permanent exact", data: &mt.PQInnerDataDC{DC: 2}},
|
||||||
|
{name: "permanent other", data: &mt.PQInnerDataDC{DC: 3}, wantErr: true},
|
||||||
|
{name: "permanent zero", data: &mt.PQInnerDataDC{DC: 0}, wantErr: true},
|
||||||
|
{name: "temporary positive exact", data: &mt.PQInnerDataTempDC{DC: 2}},
|
||||||
|
{name: "temporary negative exact", data: &mt.PQInnerDataTempDC{DC: -2}},
|
||||||
|
{name: "temporary other", data: &mt.PQInnerDataTempDC{DC: 3}, wantErr: true},
|
||||||
|
{name: "temporary negative other", data: &mt.PQInnerDataTempDC{DC: -3}, wantErr: true},
|
||||||
|
{name: "temporary test label", data: &mt.PQInnerDataTempDC{DC: 10002}, wantErr: true},
|
||||||
|
{name: "temporary min int32", data: &mt.PQInnerDataTempDC{DC: -1 << 31}, wantErr: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
err := ex.validatePQInnerDataDC(test.data)
|
||||||
|
if !test.wantErr {
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("validate: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assertWrongDCError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertWrongDCError(t *testing.T, err error) {
|
||||||
|
t.Helper()
|
||||||
var exErr *exchange.ServerExchangeError
|
var exErr *exchange.ServerExchangeError
|
||||||
if !errors.As(err, &exErr) {
|
if !errors.As(err, &exErr) {
|
||||||
t.Fatalf("err = %T %v, want ServerExchangeError", err, err)
|
t.Fatalf("err = %T %v, want ServerExchangeError", err, err)
|
||||||
|
|
|
||||||
|
|
@ -303,20 +303,12 @@ type Options struct {
|
||||||
|
|
||||||
// DC 是本 server 的 DC ID。默认 2。
|
// DC 是本 server 的 DC ID。默认 2。
|
||||||
DC int
|
DC int
|
||||||
// StrictDC turns on exact DC-ID validation for the permanent-key exchange
|
// StrictDC enables DC-label validation during key exchange. It is false by
|
||||||
// (default off = lenient). telesrv is always a single physical backend —
|
// default: this single physical backend accepts every wire int32 label for
|
||||||
// there is no real multi-DC federation behind it — but self-hosted client
|
// permanent and temporary keys, and the label never changes auth-key
|
||||||
// forks commonly run in "single-server backend" mode, where dc_id 1..5 all
|
// persistence, session identity, or business state. When enabled,
|
||||||
// alias to this one server so that any old data referencing a specific
|
// permanent labels must equal DC and temporary labels may equal +/-DC.
|
||||||
// dc_id still resolves correctly. When a client adds a new local account it
|
// This diagnostic switch does not itself provide multi-DC isolation.
|
||||||
// picks its own starting dc_id (its usual multi-DC load-spreading
|
|
||||||
// behavior, unrelated to which physical server it's actually talking to)
|
|
||||||
// — that choice is not guaranteed to equal our configured DC. Strict
|
|
||||||
// validation would reject those accounts with "-444 wrong dc_id" even
|
|
||||||
// though they are connecting to the right (and only) server; dc_id is a
|
|
||||||
// client-side routing label here, not part of key derivation, so
|
|
||||||
// accepting the mismatch does not weaken the exchange. The switch exists
|
|
||||||
// for a hypothetical future real multi-DC deployment.
|
|
||||||
StrictDC bool
|
StrictDC bool
|
||||||
// RSAKey 是 server RSA 私钥,用于密钥交换。nil 时无法完成握手。
|
// RSAKey 是 server RSA 私钥,用于密钥交换。nil 时无法完成握手。
|
||||||
RSAKey *rsa.PrivateKey
|
RSAKey *rsa.PrivateKey
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue