Browse Source

reality: реклама reality-параметров сокетов через BGP и применение к исходящим TCP-линкам

v2
evgeny 3 weeks ago
parent
commit
898a0a4a66
  1. 299
      3.go
  2. 47
      AGENTS.md
  3. 4
      src/routing_layer/conn_mgr_core.c
  4. 106
      src/routing_layer/topo_node.c
  5. 30
      src/routing_layer/topo_node.h
  6. 4
      src/transport_layer/etcp_connect.c
  7. 1
      src/transport_layer/etcp_connections.c
  8. 4
      src/transport_layer/etcp_connections.h
  9. 4
      src/transport_layer/node_conn_direct.c
  10. 16
      src/transport_layer/stcp_link.c
  11. 5
      tests/Makefile.am
  12. 94
      tests/test_reality_bgp.c
  13. 23
      utun.conf.sample

299
3.go

@ -0,0 +1,299 @@
package reality
import (
"bytes"
"context"
"crypto/ecdh"
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
gotls "crypto/tls"
"crypto/x509"
"encoding/binary"
"fmt"
"io"
"net/http"
"reflect"
"regexp"
"strings"
"sync"
"time"
"unsafe"
"github.com/cloudflare/circl/sign/mldsa/mldsa65"
utls "github.com/refraction-networking/utls"
"github.com/xtls/reality"
"github.com/xtls/xray-core/common/crypto"
"github.com/xtls/xray-core/common/errors"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/common/utils"
"github.com/xtls/xray-core/core"
"github.com/xtls/xray-core/transport/internet/tls"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
type Conn struct {
*reality.Conn
}
func (c *Conn) HandshakeAddress() net.Address {
if err := c.Handshake(); err != nil {
return nil
}
state := c.ConnectionState()
if state.ServerName == "" {
return nil
}
return net.ParseAddress(state.ServerName)
}
func Server(c net.Conn, config *reality.Config) (net.Conn, error) {
realityConn, err := reality.Server(context.Background(), c, config)
return &Conn{Conn: realityConn}, err
}
type UConn struct {
*utls.UConn
Config *Config
ServerName string
AuthKey []byte
Verified bool
}
func (c *UConn) HandshakeAddress() net.Address {
if err := c.Handshake(); err != nil {
return nil
}
state := c.ConnectionState()
if state.ServerName == "" {
return nil
}
return net.ParseAddress(state.ServerName)
}
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if c.Config.Show {
localAddr := c.LocalAddr().String()
fmt.Printf("REALITY localAddr: %v\tis using X25519MLKEM768 for TLS' communication: %v\n", localAddr, c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768)
fmt.Printf("REALITY localAddr: %v\tis using ML-DSA-65 for cert's extra verification: %v\n", localAddr, len(c.Config.Mldsa65Verify) > 0)
}
p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
certs := *(*([]*x509.Certificate))(unsafe.Pointer(uintptr(unsafe.Pointer(c.Conn)) + p.Offset))
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.AuthKey)
h.Write(pub)
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
if len(c.Config.Mldsa65Verify) > 0 {
if len(certs[0].Extensions) > 0 {
h.Write(c.HandshakeState.Hello.Raw)
h.Write(c.HandshakeState.ServerHello.Raw)
verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify)
if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) {
c.Verified = true
return nil
}
}
} else {
c.Verified = true
return nil
}
}
}
opts := x509.VerifyOptions{
DNSName: c.ServerName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
return err
}
return nil
}
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {
localAddr := c.LocalAddr().String()
uConn := &UConn{
Config: config,
}
utlsConfig := &utls.Config{
VerifyPeerCertificate: uConn.VerifyPeerCertificate,
ServerName: config.ServerName,
InsecureSkipVerify: true,
SessionTicketsDisabled: true,
KeyLogWriter: KeyLogWriterFromConfig(config),
}
if utlsConfig.ServerName == "" {
utlsConfig.ServerName = dest.Address.String()
}
uConn.ServerName = utlsConfig.ServerName
fingerprint := tls.GetFingerprint(config.Fingerprint)
if fingerprint == nil {
return nil, errors.New("REALITY: failed to get fingerprint").AtError()
}
uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)
{
uConn.BuildHandshakeState()
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
copy(hello.Raw[39:], hello.SessionId) // the fixed location of `Session ID`
hello.SessionId[0] = core.Version_x
hello.SessionId[1] = core.Version_y
hello.SessionId[2] = core.Version_z
hello.SessionId[3] = 0 // reserved
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
copy(hello.SessionId[8:], config.ShortId)
if config.Show {
fmt.Printf("REALITY localAddr: %v\thello.SessionId[:16]: %v\n", localAddr, hello.SessionId[:16])
}
publicKey, err := ecdh.X25519().NewPublicKey(config.PublicKey)
if err != nil {
return nil, errors.New("REALITY: publicKey == nil")
}
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
if ecdhe == nil {
ecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe
}
if ecdhe == nil {
return nil, errors.New("Current fingerprint ", uConn.ClientHelloID.Client, uConn.ClientHelloID.Version, " does not support TLS 1.3, REALITY handshake cannot establish.")
}
uConn.AuthKey, _ = ecdhe.ECDH(publicKey)
if uConn.AuthKey == nil {
return nil, errors.New("REALITY: SharedKey == nil")
}
if _, err := hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey); err != nil {
return nil, err
}
aead := crypto.NewAesGcm(uConn.AuthKey)
if config.Show {
fmt.Printf("REALITY localAddr: %v\tuConn.AuthKey[:16]: %v\tAEAD: %T\n", localAddr, uConn.AuthKey[:16], aead)
}
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
copy(hello.Raw[39:], hello.SessionId)
}
if err := uConn.HandshakeContext(ctx); err != nil {
return nil, err
}
if config.Show {
fmt.Printf("REALITY localAddr: %v\tuConn.Verified: %v\n", localAddr, uConn.Verified)
}
if !uConn.Verified {
errors.LogError(ctx, "REALITY: received real certificate (potential MITM or redirection)")
go func() {
client := &http.Client{
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, cfg *gotls.Config) (net.Conn, error) {
if config.Show {
fmt.Printf("REALITY localAddr: %v\tDialTLSContext\n", localAddr)
}
return uConn, nil
},
},
}
prefix := []byte("https://" + uConn.ServerName)
maps.Lock()
if maps.maps == nil {
maps.maps = make(map[string]map[string]struct{})
}
paths := maps.maps[uConn.ServerName]
if paths == nil {
paths = make(map[string]struct{})
paths[config.SpiderX] = struct{}{}
maps.maps[uConn.ServerName] = paths
}
firstURL := string(prefix) + getPathLocked(paths)
maps.Unlock()
get := func(first bool) {
var (
req *http.Request
resp *http.Response
err error
body []byte
)
if first {
req, _ = http.NewRequest("GET", firstURL, nil)
} else {
maps.Lock()
req, _ = http.NewRequest("GET", string(prefix)+getPathLocked(paths), nil)
maps.Unlock()
}
if req == nil {
return
}
utils.TryDefaultHeadersWith(req.Header, "nav")
if first && config.Show {
fmt.Printf("REALITY localAddr: %v\treq.UserAgent(): %v\n", localAddr, req.UserAgent())
}
times := 1
if !first {
times = int(crypto.RandBetween(config.SpiderY[4], config.SpiderY[5]))
}
for j := 0; j < times; j++ {
if !first && j == 0 {
req.Header.Set("Referer", firstURL)
}
req.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", int(crypto.RandBetween(config.SpiderY[0], config.SpiderY[1])))})
if resp, err = client.Do(req); err != nil {
break
}
defer resp.Body.Close()
req.Header.Set("Referer", req.URL.String())
if body, err = io.ReadAll(resp.Body); err != nil {
break
}
maps.Lock()
for _, m := range href.FindAllSubmatch(body, -1) {
m[1] = bytes.TrimPrefix(m[1], prefix)
if !bytes.Contains(m[1], dot) {
paths[string(m[1])] = struct{}{}
}
}
req.URL.Path = getPathLocked(paths)
if config.Show {
fmt.Printf("REALITY localAddr: %v\treq.Referer(): %v\n", localAddr, req.Referer())
fmt.Printf("REALITY localAddr: %v\tlen(body): %v\n", localAddr, len(body))
fmt.Printf("REALITY localAddr: %v\tlen(paths): %v\n", localAddr, len(paths))
}
maps.Unlock()
if !first {
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[6], config.SpiderY[7])) * time.Millisecond) // interval
}
}
}
get(true)
concurrency := int(crypto.RandBetween(config.SpiderY[2], config.SpiderY[3]))
for i := 0; i < concurrency; i++ {
go get(false)
}
// Do not close the connection
}()
time.Sleep(time.Duration(crypto.RandBetween(config.SpiderY[8], config.SpiderY[9])) * time.Millisecond) // return
return nil, errors.New("REALITY: processed invalid connection").AtWarning()
}
return uConn, nil
}
var (
href = regexp.MustCompile(`href="([/h].*?)"`)
dot = []byte(".")
)
var maps struct {
sync.Mutex
maps map[string]map[string]struct{}
}
func getPathLocked(paths map[string]struct{}) string {
stopAt := int(crypto.RandBetween(0, int64(len(paths)-1)))
i := 0
for s := range paths {
if i == stopAt {
return s
}
i++
}
return "/"
}

47
AGENTS.md

@ -288,6 +288,9 @@ MEMBER_SYNC=27
- `nat_transport.c/h` - NAT transport layer (packet relay)
- `ntp_time.c/h` - NTP time synchronization
- `ntp_node_time.c/h` - Inter-node time synchronization
- `broadcast.c/h` - Широковещательная рассылка данных по topo-группе (UUID-дедупликация, TTL)
- `video/video.c/h` - Пробинг и транскодирование видео (FFmpeg, опционально; H.264/MP4 ≤720p)
- `dnsmasq/` - Архив dnsmasq-2.93.tar.xz для встроенной сборки
**Transport Layer (src/transport_layer/)**
- `etcp.c/h` - ETCP protocol implementation (inflight queues, retrans, ACK, RTT/jitter)
@ -298,6 +301,8 @@ MEMBER_SYNC=27
- `etcp_dump.c/h` - ETCP дамп/декодирование пакетов
- `etcp_bbr.c/h` - BBR congestion control for ETCP
- `etcp_connect.c/h` - ETCP outbound connections
- `etcp_keepalive.c/h` - Keepalive-пакеты, адаптивный период, смена режима standby/normal (мобильные узлы)
- `auto_socket.c/h` - Автосоздание UDP+TCP сокетов на всех интерфейсах и линков к ETCP-соединениям (`auto_sockets=yes`)
- `pkt_normalizer.c/h` - Packet fragmentation/reassembly (packer/unpacker)
- `packet_dump.c/h` - Packet hex dump utility
- `dummynet.c/h` - Network emulator integrated into utun (for testing)
@ -309,6 +314,9 @@ MEMBER_SYNC=27
- `stcp_link.c/h` - STCP линк-уровень
- `node_conn_direct.c/h` - Direct config-based connections
- `socket_monitor.c/h` - Socket state monitoring
- `reality.c/h` - REALITY-style TLS ClientHello/ServerHello камуфляж для STCP
- `reality_fingerprint.c/h` - Статические отпечатки (cipher suites, groups, ALPN) для REALITY-камуфляжа
- `reality_relay.c/h` - Релей неавторизованных клиентов на реальный HTTPS-сайт
**BBR (src/transport_layer/BBR/)**
- `bbr_v3.c` - BBR congestion control algorithm v3
@ -328,6 +336,7 @@ MEMBER_SYNC=27
- `conn_mgr.h` + `conn_mgr_core.c`/`conn_mgr_indirect.c`/`conn_mgr_monitor.c` + `conn_mgr_priv.h` — Connection Manager (handle-based API, 3-phase DIR/REV/IND)
- `etcp_router.c/h` - ETCP router/multiplexer (мультиплексирование каналов, transit forwarding)
- `nat_detection.c/h` - NAT type detection
- `route_crypto.c/h` - Пред/пост-обработка SVC_ROUTE пакетов (encrypt-then-sign)
**Chat (src/chat/)** — децентрализованный P2P чат: сообщения и участники автоматически синхронизируются между всеми узлами канала без центрального сервера.
- `chat_core.c/h` - Главный модуль чата. Единственная точка входа из GUI: принять сообщение, создать канал, изменить настройку. Управляет БД и жизненным циклом всех chat-модулей
@ -342,6 +351,12 @@ MEMBER_SYNC=27
- `chat_channel.c` - Создание и настройка каналов: готовит таблицы в БД, генерирует криптоключи, запускает подключение ко всем участникам (часть chat_core)
- `chat_msg.c` - Отправка и приём сообщений: запись в локальную БД, автоматическая рассылка всем участникам канала (часть chat_core)
- `chat_status.c` - Сбор диагностики: текущее время, активные соединения, типы NAT. Отправляется в GUI (часть chat_core)
- `chat_member.c/h` - Единая структура мембера для отображения в GUI (десктоп/headless/Android), фиксированная сериализация на проводе
- `chat_admin.c/h` - Передача канального приватного ключа (прав админа) другому узлу
- `chat_whisper.c/h` - Whisper speech-to-text транскрипция голосовых сообщений (опционально, асинхронно через media_async)
- `chat_headless_control.c/h` - TCP control-socket для headless chat CLI (JSON line protocol)
- `invite_build.c/h` - Сборка invite-ссылок utun:// (выбор лучшего узла + адреса)
- `invite_link.c/h` - Формат invite-ссылок utun:// для каналов (base64: version|password|channel_id|pubkey|addrs)
**Прокси (src/proxy/)**
- `socks_proxy.c/h` - SOCKS5 прокси
@ -383,6 +398,13 @@ MEMBER_SYNC=27
- `opus_codec.c/h` - Opus audio codec wrapper
- `audio_compressor.c/h` - Audio dynamic range compressor
- `json_flat.c/h` - Flat JSON parser
- `strbuf.c/h` - Безопасный растущий printf-буфер (замена snprintf-цепочек)
- `async_dns.c/h` - Асинхронный (неблокирующий) DNS-резолвер A-записей поверх uasync, адаптер над libdns
- `dns.c/h` - Рекурсивный reentrant DNS-резолвер (libdns, MIT)
- `miniaudio.h` - Воспроизведение/захват аудио (single-header)
- `dr_mp3.h` - MP3-декодер (single-header, на базе minimp3)
- `liblmdb/` - Встраиваемый key-value store LMDB
- `libopus/` - Встраиваемый кодек Opus
- `wintun.h` - Wintun API header for Windows TUN driver
**Memory Pools in UTUN_INSTANCE:**
@ -563,19 +585,40 @@ cmake .. && make -j4
| `lottieicon.h/cpp` | Загрузка TGS (gzip→zlib), рендер кадров через `lottie_animation_render` |
| `animtimer.h/cpp` | Синглтон-таймер 30fps, refcount activate/deactivate, сигнал ticked |
| `channellist.h/cpp` `channeldelegate.h/cpp` | Список каналов с аватарами и превью |
| `accountlist.h/cpp` | Список аккаунтов/пользователей |
| `accountlist.h/cpp` `accountdelegate.h/cpp` | Список аккаунтов/пользователей и его делегат отрисовки |
| `memberlistmodel.h/cpp` | Модель списка участников канала |
| `memberpropsdialog.h/cpp` | Диалог свойств участника |
| `flagpainter.h/cpp` | Отрисовка иконок мембера (admin/mod/supernode/storage) |
| `joindialog.h/cpp` | Диалог присоединения к группе/каналу |
| `invitedialog.h/cpp` | Диалог приглашения пользователя |
| `invite_link.h/cpp` | Логика invite-ссылок |
| `inviteby.h/cpp` | Диалог приглашения по invite-ссылке (отправка через узел) |
| `settingsdialog.h/cpp` | Окно настроек |
| `creategroupdialog.h/cpp` | Диалог создания группы |
| `channelsettingsdialog.h/cpp` | Диалог настроек канала |
| `renamedialog.h/cpp` | Диалог переименования |
| `networksettingspage.h/cpp` | Страница сетевых настроек |
| `nodespage.h/cpp` | Таблица узлов + дамп в настройках |
| `statuspage.h/cpp` | Страница статуса (NTP + соединения + живые часы) |
| `databasesettingspage.h/cpp` | Страница обслуживания БД в настройках |
| `storagesettingspage.h/cpp` | Страница настроек хранилища |
| `soundsettingspage.h/cpp` | Страница настроек звука |
| `audiodevicesettingspage.h/cpp` | Страница настроек аудиоустройств |
| `sound_manager.h/cpp` | Управление звуками/уведомлениями |
| `audiorecorder.h/cpp` | Запись голосовых сообщений (захват аудио) |
| `voicemessageencoder.h/cpp` | Кодирование голосовых сообщений (Opus) |
| `voiceplayback.h/cpp` | Воспроизведение голосовых сообщений |
| `media_blocks.h/cpp` | Чтение/сборка фрагментированных медиафайлов |
| `imageviewer_window.h/cpp` | Окно просмотра изображений |
| `videoplayer_engine.h/cpp` | Движок декодирования видео |
| `videoplayer_window.h/cpp` | Окно просмотра видео |
| `connmonitorwindow.h/cpp` | Окно мониторинга ETCP-соединений (standalone) |
| `qrcode_utils.h/cpp` | Утилиты для QR-кодов |
| `debug_ui.h` | Отладочный UI |
### Другие поддиректории
- `db/` — SQLite3 amalgamation (`sqlite3.c/h`), `db_manager.h/cpp` (схема: nodes, node_addresses, channels, msg_<channel>, peers_<channel>, local_identity, accounts, ui_state)
- `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `chat_core.h/c` (управление), `chat_sync.h/c` (синхронизация), `db_sync_stub.c` (заглушка БД), `node_config.h/cpp`, `config_updater.h/cpp`, `utun_node.h/cpp`, `topo_node_sqlite.h/c`
- `transport/` — интеграция с uTun: `gui_bridge.h/gui_bridge_impl.cpp` (Qt signal-based обмен), `node_config.h/cpp` (конфиг узла), `config_updater.h/cpp` (правка конфига), `utun_node.h/cpp` (обёртка узла), `miniaudio_impl.c` (single-header miniaudio в отдельной единице трансляции). Chat-логика (`chat_core`, `chat_sync` и т.д.) компилируется напрямую из `src/chat/`, роутинг — из `src/routing_layer/` (см. `libutun/CMakeLists.txt`)
- `resources/` — `bg.jpg`, `chatgui.qrc` (встраивает 5 TGS в бинарник), `animations/*.tgs`
### Система анимированных эмодзи

4
src/routing_layer/conn_mgr_core.c

@ -547,7 +547,7 @@ static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* e
struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) { if (s->is_tcp && s->local_addr.ss_family == AF_INET && s->type != CFG_SERVER_TYPE_PRIVATE) {
struct ETCP_LINK *tlink = etcp_link_new(conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); any = 1; v4_tcp++; v4_cnt++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); any = 1; v4_tcp++; v4_cnt++; }
} s = s->next; }
}
if (a->protocol & TOPO_PROTO_UDP) {
@ -575,7 +575,7 @@ static void cm_direct_add_links(struct ETCP_CONN* conn, struct CONN_MGR_ENTRY* e
{ struct ETCP_SOCKET* s = entry->mgr->instance->etcp_sockets;
while (s) { if (s->is_tcp && s->local_addr.ss_family == AF_INET6 && s->type != CFG_SERVER_TYPE_PRIVATE) {
struct ETCP_LINK *tlink = etcp_link_new(conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a6->port); any = 1; v6_tcp++; v6_cnt++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a6->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a6->port); any = 1; v6_tcp++; v6_cnt++; }
} s = s->next; }
}
}

106
src/routing_layer/topo_node.c

@ -54,6 +54,26 @@ static void free_v4_sub_list(struct memory_pool* pool, struct TOPO_SUBNET4* head
static void free_v6_sub_list(struct memory_pool* pool, struct TOPO_SUBNET6* head) {
while (head) { struct TOPO_SUBNET6* next = head->next; memory_pool_free(pool, head); head = next; }
}
static void free_reality_sock_list(struct TOPO_REALITY_SOCK* head) {
while (head) { struct TOPO_REALITY_SOCK* next = head->next; u_free(head); head = next; }
}
const struct TOPO_REALITY_SOCK* topo_node_find_reality_sock(const struct TOPO_NODE* ni, uint8_t socket_id) {
if (!ni) return NULL;
for (const struct TOPO_REALITY_SOCK* r = ni->reality_socks; r; r = r->next)
if (r->socket_id == socket_id) return r;
return NULL;
}
void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_SOCK* rs) {
if (!link || !rs) return;
memcpy(link->reality.server_static_pubkey, rs->server_pubkey, REALITY_AUTH_KEY_SIZE);
memcpy(link->reality.short_id, rs->short_id, REALITY_SHORT_ID_SIZE);
memcpy(link->reality.version, rs->version, REALITY_VERSION_SIZE);
snprintf(link->reality.server_name, sizeof(link->reality.server_name), "%s", rs->server_name);
link->reality.fingerprint = REALITY_FP_CHROME;
link->reality_set = 1;
}
/* Упорядоченное сравнение списков адресов/мета (порядок обхода сокетов детерминирован,
prepend даёт одинаковый порядок при неизменном наборе сокетов). */
@ -87,6 +107,17 @@ static int addr6_list_equal(struct TOPO_ADDR6* a, struct TOPO_ADDR6* b) {
}
return a == NULL && b == NULL;
}
static int reality_sock_list_equal(struct TOPO_REALITY_SOCK* a, struct TOPO_REALITY_SOCK* b) {
while (a && b) {
if (a->socket_id != b->socket_id ||
memcmp(a->short_id, b->short_id, 8) != 0 ||
memcmp(a->server_pubkey, b->server_pubkey, 32) != 0 ||
memcmp(a->version, b->version, 3) != 0 ||
strncmp(a->server_name, b->server_name, TOPO_REALITY_SNAME_MAX) != 0) return 0;
a = a->next; b = b->next;
}
return a == NULL && b == NULL;
}
/* извлекает hop_list из лучшего живого path в nq->paths (или любого, если живых нет).
память принадлежит TOPO_NODEPATH в paths, не освобождать */
@ -109,6 +140,7 @@ void topo_node_free_raw(struct TOPO_GROUPS* groups, struct TOPO_NODE* ni) {
free_v6_sock_list(groups->v6_sock_meta_pool, ni->v6_sock_meta);
free_v6_addr_list(groups->v6_addr_pool, ni->v6_addrs);
}
free_reality_sock_list(ni->reality_socks);
u_free(ni->node_name);
u_free(ni);
}
@ -125,8 +157,10 @@ void topo_nodeq_free_group_fields(struct TOPO_GROUPS* groups, struct TOPO_GROUP_
free_v4_addr_list(groups->v4_addr_pool, ni->v4_addrs);
free_v6_sock_list(groups->v6_sock_meta_pool, ni->v6_sock_meta);
free_v6_addr_list(groups->v6_addr_pool, ni->v6_addrs);
free_reality_sock_list(ni->reality_socks);
ni->v4_sock_meta = NULL; ni->v4_addrs = NULL;
ni->v6_sock_meta = NULL; ni->v6_addrs = NULL;
ni->reality_socks = NULL;
topo_node_registry_unref(groups, nq->node_id);
}
if (nq->subnets) {
@ -164,6 +198,7 @@ struct TOPO_NODE* topo_node_registry_store(struct TOPO_GROUPS* groups, struct TO
topo_node_ref(existing);
if (!existing->v4_addrs && ni->v4_addrs) { existing->v4_addrs = ni->v4_addrs; ni->v4_addrs = NULL; }
if (!existing->v6_addrs && ni->v6_addrs) { existing->v6_addrs = ni->v6_addrs; ni->v6_addrs = NULL; }
if (!existing->reality_socks && ni->reality_socks) { existing->reality_socks = ni->reality_socks; ni->reality_socks = NULL; }
topo_node_free_raw(groups, ni);
return existing;
}
@ -235,6 +270,17 @@ int topo_node_build_sig_msg(struct TOPO_NODE* ni, uint8_t* buf, size_t buf_size)
buf[off++] = (uint8_t)(e->port & 0xFF); buf[off++] = (uint8_t)((e->port >> 8) & 0xFF);
buf[off++] = e->type; buf[off++] = e->socket_id; buf[off++] = e->protocol; e = e->next;
}}
{ struct TOPO_REALITY_SOCK* e = ni->reality_socks; while (e) {
size_t nlen = strnlen(e->server_name, TOPO_REALITY_SNAME_MAX);
if (off + 1 + 8 + 32 + 3 + nlen + 1 > buf_size) return -1;
buf[off++] = e->socket_id;
memcpy(buf + off, e->short_id, 8); off += 8;
memcpy(buf + off, e->server_pubkey, 32); off += 32;
memcpy(buf + off, e->version, 3); off += 3;
if (nlen) { memcpy(buf + off, e->server_name, nlen); off += nlen; }
buf[off++] = '\0';
e = e->next;
}}
return (int)off;
}
@ -253,6 +299,7 @@ int topo_node_dyn_size(const struct TOPOMSG_NODE* msg) {
+ msg->local_v4_addrs * sizeof(struct TOPOMSG_ADDR4)
+ msg->local_v6_sockets * sizeof(struct TOPOMSG_SOCKMETA6)
+ msg->local_v6_addrs * sizeof(struct TOPOMSG_ADDR6)
+ msg->local_reality_socks * sizeof(struct TOPOMSG_REALITY_SOCK)
+ ((msg->flags & TOPO_FLAG_SEND_SUBNETS)
? (msg->local_v4_subnets * sizeof(struct TOPOMSG_SUBNET4) + msg->local_v6_subnets * sizeof(struct TOPOMSG_SUBNET6))
: 0)
@ -278,6 +325,7 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
msg.local_v4_addrs = topo_list_count((struct _topo_head*)ni->v4_addrs);
msg.local_v6_sockets = topo_list_count((struct _topo_head*)ni->v6_sock_meta);
msg.local_v6_addrs = topo_list_count((struct _topo_head*)ni->v6_addrs);
msg.local_reality_socks = topo_list_count((struct _topo_head*)ni->reality_socks);
msg.local_v4_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v4_subnets) : 0;
msg.local_v6_subnets = nq->subnets ? topo_list_count((struct _topo_head*)nq->subnets->v6_subnets) : 0;
{ uint8_t hc; uint64_t* hl = topo_node_best_hop_list(nq, &hc, NULL);
@ -310,6 +358,15 @@ int topo_node_serialize(struct TOPO_NODE* ni, struct TOPO_GROUP_NODE* nq,
struct TOPOMSG_ADDR6 w = { .port=e->port, .type=e->type, .socket_id=e->socket_id, .protocol=e->protocol };
memcpy(w.addr, e->addr, 16); memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next;
}}
{ struct TOPO_REALITY_SOCK* e = ni->reality_socks; while (e) {
struct TOPOMSG_REALITY_SOCK w; memset(&w, 0, sizeof(w));
w.socket_id = e->socket_id;
memcpy(w.short_id, e->short_id, 8);
memcpy(w.server_pubkey, e->server_pubkey, 32);
memcpy(w.version, e->version, 3);
memcpy(w.server_name, e->server_name, TOPO_REALITY_SNAME_MAX);
memcpy(dp, &w, sizeof(w)); dp += sizeof(w); e = e->next;
}}
if (msg.flags & TOPO_FLAG_SEND_SUBNETS) {
if (nq->subnets) {
{ struct TOPO_SUBNET4* e = nq->subnets->v4_subnets; while (e) {
@ -393,6 +450,21 @@ int topo_node_deserialize(struct TOPO_GROUP* group, const uint8_t* data, size_t
if (!head) head = tail = e; else { tail->next = e; tail = e; }
}
ni->v6_addrs = head; }
{ struct TOPO_REALITY_SOCK* head = NULL; struct TOPO_REALITY_SOCK* tail = NULL;
for (int i = 0; i < msg->local_reality_socks; i++) {
const struct TOPOMSG_REALITY_SOCK* w = (const struct TOPOMSG_REALITY_SOCK*)dp; dp += sizeof(*w);
struct TOPO_REALITY_SOCK* e = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK));
if (!e) { topo_node_free_raw(group->instance->topo_groups, ni); return -1; }
e->socket_id = w->socket_id;
memcpy(e->short_id, w->short_id, 8);
memcpy(e->server_pubkey, w->server_pubkey, 32);
memcpy(e->version, w->version, 3);
memcpy(e->server_name, w->server_name, TOPO_REALITY_SNAME_MAX);
e->server_name[TOPO_REALITY_SNAME_MAX - 1] = 0;
e->next = NULL;
if (!head) head = tail = e; else { tail->next = e; tail = e; }
}
ni->reality_socks = head; }
if (out_subnets && (msg->flags & TOPO_FLAG_SEND_SUBNETS) && (msg->local_v4_subnets || msg->local_v6_subnets)) {
struct TOPO_NODESUBNETS* r = u_calloc(1, sizeof(struct TOPO_NODESUBNETS));
@ -818,6 +890,23 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
struct TOPO_ADDR4* v4_head = NULL;
struct TOPO_SOCKMETA6* m6_head = NULL;
struct TOPO_ADDR6* v6_head = NULL;
struct TOPO_REALITY_SOCK* r_head = NULL;
/* node-global reality-параметры (одинаковы для всех reality-сокетов): рекламируем первый short_id */
uint8_t r_short_id[8], r_pubkey[32], r_version[3]; char r_sname[TOPO_REALITY_SNAME_MAX];
uint8_t r_valid = 0;
{ const struct reality_config* rc = &instance->config->global.reality;
if (rc->enabled && rc->has_private_key) {
if (reality_pubkey_from_priv(rc->private_key, r_pubkey) == REALITY_OK) {
if (rc->short_id_count > 0) memcpy(r_short_id, rc->short_ids[0], 8);
else memcpy(r_short_id, rc->short_id, 8);
memcpy(r_version, rc->version, 3);
memset(r_sname, 0, sizeof(r_sname));
strncpy(r_sname, rc->server_name, sizeof(r_sname) - 1);
r_valid = 1;
}
}
}
struct ETCP_SOCKET* e_sock = instance->etcp_sockets;
while (e_sock) {
@ -879,18 +968,32 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
a6->type = TOPO_ADDR_INTERFACE; a6->socket_id = ts->sock_id; a6->protocol = TOPO_PROTO_TCP;
a6->next = v6_head; v6_head = a6; }
}
if (r_valid && ts->reality_enabled) {
struct TOPO_REALITY_SOCK* r = u_calloc(1, sizeof(struct TOPO_REALITY_SOCK));
if (!r) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "reality_sock alloc failed"); }
else {
r->socket_id = ts->sock_id;
memcpy(r->short_id, r_short_id, 8);
memcpy(r->server_pubkey, r_pubkey, 32);
memcpy(r->version, r_version, 3);
memcpy(r->server_name, r_sname, TOPO_REALITY_SNAME_MAX);
r->next = r_head; r_head = r;
}
}
ts = ts->next; }
}
/* Без изменений — подпись/рассылку гасим, но БД всё равно синхронизируем:
она могла быть не готова (или пуста) при предыдущем вызове. */
int changed = !(sockmeta4_list_equal(ni->v4_sock_meta, m4_head) && addr4_list_equal(ni->v4_addrs, v4_head) &&
sockmeta6_list_equal(ni->v6_sock_meta, m6_head) && addr6_list_equal(ni->v6_addrs, v6_head));
sockmeta6_list_equal(ni->v6_sock_meta, m6_head) && addr6_list_equal(ni->v6_addrs, v6_head) &&
reality_sock_list_equal(ni->reality_socks, r_head));
if (changed) {
free_v4_sock_list(instance->topo_groups->v4_sock_meta_pool, ni->v4_sock_meta); ni->v4_sock_meta = m4_head;
free_v4_addr_list(instance->topo_groups->v4_addr_pool, ni->v4_addrs); ni->v4_addrs = v4_head;
free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, ni->v6_sock_meta); ni->v6_sock_meta = m6_head;
free_v6_addr_list(instance->topo_groups->v6_addr_pool, ni->v6_addrs); ni->v6_addrs = v6_head;
free_reality_sock_list(ni->reality_socks); ni->reality_socks = r_head;
ni->ver = (ni->ver % 255) + 1;
topo_node_sign_self(instance, ni);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "my addresses updated, new ver=%d", ni->ver);
@ -899,6 +1002,7 @@ int topo_node_update_my_addresses(struct UTUN_INSTANCE* instance) {
free_v4_addr_list(instance->topo_groups->v4_addr_pool, v4_head);
free_v6_sock_list(instance->topo_groups->v6_sock_meta_pool, m6_head);
free_v6_addr_list(instance->topo_groups->v6_addr_pool, v6_head);
free_reality_sock_list(r_head);
DEBUG_DEBUG(DEBUG_CATEGORY_BGP, "my addresses unchanged (ver=%d), persist DB", ni->ver);
}

30
src/routing_layer/topo_node.h

@ -33,6 +33,7 @@ extern "C" {
struct TOPO_GROUP;
struct TOPO_GROUPS;
struct ETCP_SOCKET;
struct ETCP_LINK;
struct NODE_CONN_DIRECT;
struct ETCP_CONN;
struct UTUN_INSTANCE;
@ -112,12 +113,24 @@ struct TOPOMSG_ADDR6 { uint8_t addr[16]; uint16_t port; uint8_t type, socket
struct TOPOMSG_SUBNET4 { uint8_t addr[4]; uint8_t prefix_length; } __attribute__((packed));
struct TOPOMSG_SUBNET6 { uint8_t addr[16]; uint8_t prefix_length; } __attribute__((packed));
/* Reality-камуфляж сокета: что нужно клиенту для подключения к reality TCP-сокету.
* short_id(8) + server_pubkey(32) + version(3) + server_name(фиксированный 64). */
#define TOPO_REALITY_SNAME_MAX 64
struct TOPOMSG_REALITY_SOCK {
uint8_t socket_id;
uint8_t short_id[8];
uint8_t server_pubkey[32];
uint8_t version[3];
uint8_t server_name[TOPO_REALITY_SNAME_MAX];
} __attribute__((packed));
struct TOPOMSG_NODE {
uint8_t flags; uint64_t group_id, node_id; uint8_t ver;
uint8_t public_key[SC_PUBKEY_SIZE], ed25519_public_key[SC_PUBKEY_SIZE];
uint8_t x25519_self_sig[64];
uint8_t node_name_len, local_v4_sockets, local_v4_addrs, local_v6_sockets, local_v6_addrs;
uint8_t local_v4_subnets, local_v6_subnets, hop_count;
uint8_t local_reality_socks; // кол-во reality-сокетов
uint8_t client_type; // CLIENT_TYPE_SERVER/DESKTOP/MOBILE
uint8_t client_activity; // CLIENT_ACTIVITY_STANDBY/ACTIVE
uint16_t cumulative_rtt;
@ -132,6 +145,16 @@ struct TOPO_ADDR6 { struct TOPO_ADDR6* next; uint8_t addr[16]; uint16_t port
struct TOPO_SUBNET4 { struct TOPO_SUBNET4* next; uint8_t addr[4]; uint8_t prefix_length; };
struct TOPO_SUBNET6 { struct TOPO_SUBNET6* next; uint8_t addr[16]; uint8_t prefix_length; };
/* Reality-камуфляж сокета (in-memory): per-socket список, значения на узел одинаковы. */
struct TOPO_REALITY_SOCK {
struct TOPO_REALITY_SOCK* next;
uint8_t socket_id;
uint8_t short_id[8];
uint8_t server_pubkey[32];
uint8_t version[3];
char server_name[TOPO_REALITY_SNAME_MAX];
};
struct _topo_head { struct _topo_head* next; };
static inline int topo_list_count(struct _topo_head* head) { int n = 0; while (head) { n++; head = head->next; } return n; }
@ -149,6 +172,7 @@ struct TOPO_NODE {
struct TOPO_ADDR4* v4_addrs;
struct TOPO_SOCKMETA6* v6_sock_meta;
struct TOPO_ADDR6* v6_addrs;
struct TOPO_REALITY_SOCK* reality_socks; // reality-камуфляж сокетов (per-socket, значения на узел)
};
struct TOPO_NODESUBNETS {
@ -222,6 +246,12 @@ static inline const struct TOPO_ADDR6* topo_v6_addrs(const struct TOPO_NODE*
static inline const struct TOPO_SUBNET4* topo_v4_subnets(const struct TOPO_NODESUBNETS* r) { return r ? r->v4_subnets : NULL; }
static inline const struct TOPO_SUBNET6* topo_v6_subnets(const struct TOPO_NODESUBNETS* r) { return r ? r->v6_subnets : NULL; }
/* Найти reality-инфо сокета по socket_id (NULL, если сокет без reality). */
const struct TOPO_REALITY_SOCK* topo_node_find_reality_sock(const struct TOPO_NODE* ni, uint8_t socket_id);
/* Заполнить link->reality из reality-инфо пира (rs из BGP). link->reality_set = 1. */
void topo_node_apply_reality(struct ETCP_LINK* link, const struct TOPO_REALITY_SOCK* rs);
#ifdef __cplusplus
}
#endif

4
src/transport_layer/etcp_connect.c

@ -87,7 +87,7 @@ static void connect_create_links_v4(struct ETCP_CONNECT* ctx, struct TOPO_GROUP_
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->is_tcp && s->local_addr.ss_family == AF_INET) {
struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; addr_links++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; addr_links++; }
} s = s->next; }
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[etcp_connect] link_v4: %d.%d.%d.%d:%d proto=%d links=%d conn=[%s]",
@ -128,7 +128,7 @@ static void connect_create_links_v6(struct ETCP_CONNECT* ctx, struct TOPO_GROUP_
struct ETCP_SOCKET* s = ctx->instance->etcp_sockets;
while (s) { if (s->is_tcp && s->local_addr.ss_family == AF_INET6) {
struct ETCP_LINK *tlink = etcp_link_new(ctx->conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; addr_links++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; addr_links++; }
} s = s->next; }
}
DEBUG_INFO(DEBUG_CATEGORY_ETCP, "[etcp_connect] link_v6: %s:%d proto=%d links=%d conn=[%s]",

1
src/transport_layer/etcp_connections.c

@ -729,6 +729,7 @@ struct ETCP_SOCKET* tcp_socket_add(struct UTUN_INSTANCE* instance, struct CFG_SE
if (!ts) { DEBUG_ERROR(DEBUG_CATEGORY_SOCKET, "tcp_socket_add: alloc failed"); return NULL; }
ts->instance = instance;
ts->is_tcp = 1;
ts->reality_enabled = server->reality_enabled;
ts->fd = SOCKET_INVALID;
ts->type = server->type;
if (server->name && server->name[0]) {

4
src/transport_layer/etcp_connections.h

@ -9,6 +9,7 @@ extern "C" {
// подмодуль ETCP который обслуживает сокеты ETCP для приёма-передачи пакетов и одно ETCP подключение через несколько каналов связи (failover)
#include "secure_channel.h"
#include "reality.h"
#include "utun_instance.h"
#include "etcp_bbr.h"
#include "../lib/socket_compat.h"
@ -146,6 +147,7 @@ struct ETCP_SOCKET {
uint8_t type; // CFG_SERVER_TYPE_*
uint8_t sock_id; // unique id (0-255) for NAT matching
uint8_t is_tcp; // 1=TCP (STCP), 0=UDP
uint8_t reality_enabled; // 1=TCP-порт с REALITY-камуфляжем
uint8_t only_local; // 1 = only local, no forwarding (UDP)
uint32_t netif_index; // для sin6_scope_id
struct sockaddr_storage local_addr;
@ -322,6 +324,8 @@ struct ETCP_LINK {
struct sockaddr_storage local_bound_addr; // локальный адрес к которому привязан линк (interface_addr на момент создания)
uint8_t is_tcp; // 1 = TCP транспорт (STCP)
uint8_t reality_set; // 1 = link->reality валиден (реклама пира из BGP)
struct reality_client_config reality; // reality-параметры пира (для исходящего TCP)
struct stcp_link *tcp_link; // STCP линк (для TCP)
void *tcp_reconnect_timer; // таймер реконнекта (TCP клиент)
uint32_t tcp_reconnect_delay_ms; // задержка реконнекта (экспоненциальный backoff)

4
src/transport_layer/node_conn_direct.c

@ -169,7 +169,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
if (!s->is_tcp || s->local_addr.ss_family != AF_INET || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
struct ETCP_LINK* tlink = etcp_link_new(conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
}
}
if (a->protocol & TOPO_PROTO_UDP) {
@ -201,7 +201,7 @@ static int ncd_create_links(struct ncd_entry* entry, struct TOPO_NODE* ni,
for (struct ETCP_SOCKET* s = inst->etcp_sockets; s; s = s->next) {
if (!s->is_tcp || s->local_addr.ss_family != AF_INET6 || s->type == CFG_SERVER_TYPE_PRIVATE) continue;
struct ETCP_LINK* tlink = etcp_link_new(conn, s, &sa, 0);
if (tlink) { tlink->is_tcp = 1; etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
if (tlink) { tlink->is_tcp = 1; topo_node_apply_reality(tlink, topo_node_find_reality_sock(ni, a->socket_id)); etcp_tcp_link_start_connect(tlink, &sa, a->port); link_count++; }
}
}
if (a->protocol & TOPO_PROTO_UDP) {

16
src/transport_layer/stcp_link.c

@ -236,19 +236,9 @@ struct stcp_link *stcp_link_connect(struct ETCP_LINK *etcp_link,
const uint8_t *pubkey = etcp->crypto_ctx.peer_public_key;
// reality-камуфляж на клиенте: включается глобально + есть pubkey сервера + SNI
const struct reality_client_config *reality = NULL;
struct reality_client_config rcc;
if (inst->config && inst->config->global.reality.enabled &&
inst->config->global.reality.has_public_key &&
inst->config->global.reality.server_name[0]) {
memcpy(rcc.server_static_pubkey, inst->config->global.reality.public_key, REALITY_AUTH_KEY_SIZE);
memcpy(rcc.short_id, inst->config->global.reality.short_id, REALITY_SHORT_ID_SIZE);
memcpy(rcc.version, inst->config->global.reality.version, REALITY_VERSION_SIZE);
snprintf(rcc.server_name, sizeof(rcc.server_name), "%s", inst->config->global.reality.server_name);
rcc.fingerprint = inst->config->global.reality.fingerprint;
reality = &rcc;
}
// reality-камуфляж на клиенте: только если у пира есть реклама reality-сокета в BGP
// (etcp_link->reality_set). Глобальный [reality] для исходящих не используется.
const struct reality_client_config *reality = etcp_link->reality_set ? &etcp_link->reality : NULL;
link->cli = stcp_client_connect(inst->ua, addr_str, rport, &inst->my_keys, pubkey,
inst->my_ed25519_pubkey,

5
tests/Makefile.am

@ -76,6 +76,7 @@ check_PROGRAMS = \
test_media_delivery_full \
test_etcp_link_stress \
test_reality_hello \
test_reality_bgp \
bench_timeout_heap \
bench_uasync_timeouts
@ -127,6 +128,10 @@ test_reality_hello_SOURCES = test_reality_hello.c
test_reality_hello_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/lib
test_reality_hello_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_reality_bgp_SOURCES = test_reality_bgp.c
test_reality_bgp_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/src/transport_layer -I$(top_srcdir)/src/routing_layer -I$(top_srcdir)/lib
test_reality_bgp_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)
test_transport_SOURCES = test_stcp_link.c
test_transport_CFLAGS = -I$(top_srcdir)/src -I$(top_srcdir)/lib
test_transport_LDADD = $(LIBUTUN) $(CRYPTO_LIBS) $(COMMON_LIBS)

94
tests/test_reality_bgp.c

@ -0,0 +1,94 @@
// test_reality_bgp.c — тест reality-сокетов в BGP (topo_node)
//
// Проверяет:
// 1. topo_node_find_reality_sock — поиск по socket_id.
// 2. topo_node_apply_reality — заполнение reality_client_config линка.
// 3. Serialize → deserialize round-trip reality-сокетов.
#include "reality.h"
#include "topo_node.h"
#include "topo_group.h"
#include "etcp_connections.h"
#include "../lib/debug_config.h"
#include <stdio.h>
#include <string.h>
static int test_failed = 0;
#define CHECK(expr, msg) do { \
if (!(expr)) { \
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "FAIL: %s", msg); \
test_failed = 1; \
} else { \
DEBUG_INFO(DEBUG_CATEGORY_BGP, "PASS: %s", msg); \
} \
} while (0)
int main(void) {
debug_config_init();
debug_set_level(DEBUG_LEVEL_INFO);
debug_set_categories(DEBUG_CATEGORY_BGP);
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== Reality BGP Test ===");
// 1. узел с двумя reality-сокетами
struct TOPO_NODE ni; memset(&ni, 0, sizeof(ni));
struct TOPO_REALITY_SOCK r1, r2;
memset(&r1, 0, sizeof(r1)); memset(&r2, 0, sizeof(r2));
r1.socket_id = 3; r2.socket_id = 7;
r1.short_id[0] = 0xAA; r2.short_id[0] = 0xBB;
r1.version[0] = 1; r1.version[1] = 2; r1.version[2] = 3;
for (int i = 0; i < 32; i++) r1.server_pubkey[i] = (uint8_t)(i + 1);
strcpy(r1.server_name, "www.example.com");
strcpy(r2.server_name, "api.example.com");
r1.next = &r2; r2.next = NULL;
ni.reality_socks = &r1;
// 2. find
CHECK(topo_node_find_reality_sock(&ni, 3) == &r1, "find sock 3");
CHECK(topo_node_find_reality_sock(&ni, 7) == &r2, "find sock 7");
CHECK(topo_node_find_reality_sock(&ni, 99) == NULL, "find missing socket");
CHECK(topo_node_find_reality_sock(NULL, 3) == NULL, "find on NULL node");
// 3. apply
struct ETCP_LINK link; memset(&link, 0, sizeof(link));
topo_node_apply_reality(&link, &r1);
CHECK(link.reality_set == 1, "apply sets reality_set");
CHECK(memcmp(link.reality.short_id, r1.short_id, 8) == 0, "apply short_id");
CHECK(memcmp(link.reality.server_static_pubkey, r1.server_pubkey, 32) == 0, "apply pubkey");
CHECK(memcmp(link.reality.version, r1.version, 3) == 0, "apply version");
CHECK(strcmp(link.reality.server_name, "www.example.com") == 0, "apply server_name");
topo_node_apply_reality(&link, NULL);
CHECK(link.reality_set == 1, "apply NULL rs is no-op");
// 4. serialize → deserialize round-trip
struct TOPO_GROUP_NODE nq; memset(&nq, 0, sizeof(nq));
uint8_t buf[2048];
int len = topo_node_serialize(&ni, &nq, 0x1234, 0, buf, sizeof(buf), 0);
CHECK(len > 0, "serialize ok");
struct TOPO_GROUP g; memset(&g, 0, sizeof(g));
struct TOPO_NODE* out_ni = NULL;
struct TOPO_NODESUBNETS* out_sub = NULL;
uint64_t* hop = NULL; uint8_t hopc = 0; uint16_t rtt = 0;
int rc = topo_node_deserialize(&g, buf, len, &out_ni, &out_sub, &hop, &hopc, &rtt);
CHECK(rc == 0 && out_ni != NULL, "deserialize ok");
if (out_ni) {
int cnt = 0; for (const struct TOPO_REALITY_SOCK* r = out_ni->reality_socks; r; r = r->next) cnt++;
CHECK(cnt == 2, "round-trip: 2 reality socks");
const struct TOPO_REALITY_SOCK* rr = topo_node_find_reality_sock(out_ni, 7);
CHECK(rr != NULL, "round-trip: find sock 7");
if (rr) {
CHECK(rr->short_id[0] == 0xBB, "round-trip: short_id");
CHECK(strcmp(rr->server_name, "api.example.com") == 0, "round-trip: server_name");
}
const struct TOPO_REALITY_SOCK* rr1 = topo_node_find_reality_sock(out_ni, 3);
CHECK(rr1 != NULL && memcmp(rr1->server_pubkey, r1.server_pubkey, 32) == 0, "round-trip: pubkey");
CHECK(rr1 != NULL && rr1->version[0] == 1 && rr1->version[1] == 2 && rr1->version[2] == 3, "round-trip: version");
}
if (test_failed) {
DEBUG_ERROR(DEBUG_CATEGORY_BGP, "=== Reality BGP Test: FAILED ===");
return 1;
}
DEBUG_INFO(DEBUG_CATEGORY_BGP, "=== Reality BGP Test: PASSED ===");
return 0;
}

23
utun.conf.sample

@ -113,6 +113,29 @@ allow=all
#netif=eth1 ; Linux: SO_BINDTODEVICE (interface name)
#bind_ip=10.0.0.1 ; bind() local IP before connect()
# --- REALITY-камуфляж (reality socket) ---
# REALITY маскирует TCP-порт STCP под обычный TLS-трафик к реальному сайту.
# Сервер принимает TLS ClientHello, проверяет авторизацию (short_id + X25519)
# и отвечает ServerHello; неавторизованных клиентов релеит на реальный сайт (dest).
# Для включения нужен TCP-сокет: в [server: ...] задать transport=tcp и reality=1.
#[reality]
#enabled=1
#server_name=www.microsoft.com # SNI-таргет (каким сайтом прикидываемся)
#dest=www.microsoft.com:443 # host:port реального сайта для релея
#private_key=<64 hex X25519> # static приватный ключ сервера
#short_ids=0102030405060708,aabbccddeeff0011 # список short_id (по 16 hex, через запятую)
##short_id=0102030405060708 # одиночный short_id (альтернатива short_ids)
#version=1.0.0 # версия протокола utun (по умолчанию 1.0.0)
#time_window=30 # допуск timestamp, сек (антиреплей)
#fingerprint=chrome # TLS-отпечаток (только chrome)
## Пример TCP-сокета с reality-камуфляжем:
#[server: reality_srv]
#addr=0.0.0.0:1443
#type=public
#transport=tcp
#reality=1
# --- Chatserver: headless supernode config ---
#[chatserver]
#db_path=/var/lib/utun # путь к SQLite базе данных (обязательно)

Loading…
Cancel
Save