Fetch DC ips from Telegram

This commit is contained in:
9seconds
2026-02-15 23:56:53 +01:00
parent 301bde88ac
commit 852ca713c8
9 changed files with 262 additions and 101 deletions
-41
View File
@@ -1,41 +0,0 @@
package telegram
import "math/rand"
type addressPool struct {
v4 [][]tgAddr
v6 [][]tgAddr
}
func (a addressPool) isValidDC(dc int) bool {
return dc > 0 && dc <= len(a.v4) && dc <= len(a.v6)
}
func (a addressPool) getRandomDC() int {
return 1 + rand.Intn(len(a.v4))
}
func (a addressPool) getV4(dc int) []tgAddr {
return a.get(a.v4, dc-1)
}
func (a addressPool) getV6(dc int) []tgAddr {
return a.get(a.v6, dc-1)
}
func (a addressPool) get(addresses [][]tgAddr, dc int) []tgAddr {
if dc < 0 || dc >= len(addresses) {
return nil
}
rv := make([]tgAddr, len(addresses[dc]))
copy(rv, addresses[dc])
if len(rv) > 1 {
rand.Shuffle(len(rv), func(i, j int) {
rv[i], rv[j] = rv[j], rv[i]
})
}
return rv
}
+19
View File
@@ -0,0 +1,19 @@
package telegram
type dcAddresses struct {
v4 map[int][]tgAddr
v6 map[int][]tgAddr
}
func (a dcAddresses) getV4(dc int) []tgAddr {
return a.v4[dc]
}
func (a dcAddresses) getV6(dc int) []tgAddr {
return a.v6[dc]
}
func (a dcAddresses) isValidDC(dc int) bool {
_, ok := a.v4[dc]
return ok
}
+25 -35
View File
@@ -3,6 +3,7 @@ package telegram
import (
"context"
"errors"
"time"
"github.com/9seconds/mtg/v2/essentials"
)
@@ -18,6 +19,18 @@ const (
preferIPPreferIPv6
)
const (
defaultDC = 2
defaultUpdateDCAddressesEach = time.Hour
defaultAppID = 123456
defaultAppHash = ""
)
type loggerInterface interface {
Info(msg string)
WarningError(msg string, err error)
}
type tgAddr struct {
network string
address string
@@ -25,64 +38,41 @@ type tgAddr struct {
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
var (
productionV4Addresses = [][]tgAddr{
{ // dc1
defaultV4Addresses = map[int][]tgAddr{
1: {
{network: "tcp4", address: "149.154.175.50:443"},
},
{ // dc2
2: {
{network: "tcp4", address: "149.154.167.51:443"},
{network: "tcp4", address: "95.161.76.100:443"},
},
{ // dc3
3: {
{network: "tcp4", address: "149.154.175.100:443"},
},
{ // dc4
4: {
{network: "tcp4", address: "149.154.167.91:443"},
},
{ // dc5
5: {
{network: "tcp4", address: "149.154.171.5:443"},
},
}
productionV6Addresses = [][]tgAddr{
{ // dc1
defaultV6Addresses = map[int][]tgAddr{
1: {
{network: "tcp6", address: "[2001:b28:f23d:f001::a]:443"},
},
{ // dc2
2: {
{network: "tcp6", address: "[2001:67c:04e8:f002::a]:443"},
},
{ // dc3
3: {
{network: "tcp6", address: "[2001:b28:f23d:f003::a]:443"},
},
{ // dc4
4: {
{network: "tcp6", address: "[2001:67c:04e8:f004::a]:443"},
},
{ // dc5
5: {
{network: "tcp6", address: "[2001:b28:f23f:f005::a]:443"},
},
}
testV4Addresses = [][]tgAddr{
{ // dc1
{network: "tcp4", address: "149.154.175.10:443"},
},
{ // dc2
{network: "tcp4", address: "149.154.167.40:443"},
},
{ // dc3
{network: "tcp4", address: "149.154.175.117:443"},
},
}
testV6Addresses = [][]tgAddr{
{ // dc1
{network: "tcp6", address: "[2001:b28:f23d:f001::e]:443"},
},
{ // dc2
{network: "tcp6", address: "[2001:67c:04e8:f002::e]:443"},
},
{ // dc3
{network: "tcp6", address: "[2001:b28:f23d:f003::e]:443"},
},
}
)
type Dialer interface {
+42
View File
@@ -0,0 +1,42 @@
package telegram
import (
"context"
"net"
"strconv"
"github.com/gotd/td/telegram"
)
type rpcClient struct {
*telegram.Client
}
func (r rpcClient) getDCAddresses(logger loggerInterface, ctx context.Context) (dcAddresses, error) {
addrs := dcAddresses{
v4: map[int][]tgAddr{},
v6: map[int][]tgAddr{},
}
err := r.Client.Run(ctx, func(_ context.Context) error {
for _, opt := range r.Client.Config().DCOptions {
addr := net.JoinHostPort(opt.IPAddress, strconv.Itoa(opt.Port))
if opt.Ipv6 {
addrs.v6[opt.ID] = append(addrs.v6[opt.ID], tgAddr{
network: "tcp6",
address: addr,
})
} else {
addrs.v4[opt.ID] = append(addrs.v4[opt.ID], tgAddr{
network: "tcp4",
address: addr,
})
}
}
return nil
})
return addrs, err
}
+80 -24
View File
@@ -4,29 +4,39 @@ import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/9seconds/mtg/v2/essentials"
"github.com/gotd/td/telegram"
)
type Telegram struct {
dialer Dialer
preferIP preferIP
pool addressPool
ctx context.Context
ctxCancel context.CancelFunc
lock sync.RWMutex
dialer Dialer
preferIP preferIP
addresses dcAddresses
rpc rpcClient
}
func (t Telegram) Dial(ctx context.Context, dc int) (essentials.Conn, error) {
func (t *Telegram) Dial(ctx context.Context, dc int) (essentials.Conn, error) {
var addresses []tgAddr
t.lock.RLock()
switch t.preferIP {
case preferIPOnlyIPv4:
addresses = t.pool.getV4(dc)
addresses = t.addresses.getV4(dc)
case preferIPOnlyIPv6:
addresses = t.pool.getV6(dc)
addresses = t.addresses.getV6(dc)
case preferIPPreferIPv4:
addresses = append(t.pool.getV4(dc), t.pool.getV6(dc)...)
addresses = append(t.addresses.getV4(dc), t.addresses.getV6(dc)...)
case preferIPPreferIPv6:
addresses = append(t.pool.getV6(dc), t.pool.getV4(dc)...)
addresses = append(t.addresses.getV6(dc), t.addresses.getV4(dc)...)
}
t.lock.RUnlock()
var conn essentials.Conn
@@ -42,15 +52,60 @@ func (t Telegram) Dial(ctx context.Context, dc int) (essentials.Conn, error) {
return nil, fmt.Errorf("cannot dial to %d dc: %w", dc, err)
}
func (t Telegram) IsKnownDC(dc int) bool {
return t.pool.isValidDC(dc)
func (t *Telegram) IsKnownDC(dc int) bool {
return t.addresses.isValidDC(dc)
}
func (t Telegram) GetFallbackDC() int {
return t.pool.getRandomDC()
func (t *Telegram) GetFallbackDC() int {
return defaultDC
}
func New(dialer Dialer, ipPreference string, useTestDCs bool) (*Telegram, error) {
func (t *Telegram) Shutdown() {
t.ctxCancel()
}
func (t *Telegram) Run(logger loggerInterface, updateEach time.Duration) {
if updateEach == 0 {
updateEach = defaultUpdateDCAddressesEach
}
t.update(logger)
ticker := time.NewTicker(updateEach)
defer func() {
ticker.Stop()
select {
case <-ticker.C:
default:
}
}()
for {
select {
case <-t.ctx.Done():
return
case <-ticker.C:
t.update(logger)
}
}
}
func (t *Telegram) update(logger loggerInterface) {
otherAddresses, err := t.rpc.getDCAddresses(logger, t.ctx)
if err != nil {
logger.WarningError("Cannot update DC list", err)
return
}
t.lock.Lock()
t.addresses = otherAddresses
t.lock.Unlock()
logger.Info(fmt.Sprintf("DC are updated: %v", t.addresses))
}
func New(dialer Dialer, ipPreference string) (*Telegram, error) {
var pref preferIP
switch strings.ToLower(ipPreference) {
@@ -66,18 +121,19 @@ func New(dialer Dialer, ipPreference string, useTestDCs bool) (*Telegram, error)
return nil, fmt.Errorf("unknown ip preference %s", ipPreference)
}
pool := addressPool{
v4: productionV4Addresses,
v6: productionV6Addresses,
}
if useTestDCs {
pool.v4 = testV4Addresses
pool.v6 = testV6Addresses
}
ctx, cancel := context.WithCancel(context.Background())
return &Telegram{
dialer: dialer,
preferIP: pref,
pool: pool,
ctx: ctx,
ctxCancel: cancel,
dialer: dialer,
preferIP: pref,
addresses: dcAddresses{
v4: defaultV4Addresses,
v6: defaultV6Addresses,
},
rpc: rpcClient{
Client: telegram.NewClient(defaultAppID, defaultAppHash, telegram.Options{}),
},
}, nil
}