mirror of
https://github.com/ScuroNeko/mtg.git
synced 2026-08-31 15:24:01 +03:00
Add validation of telegram connectivity
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
package essentials
|
||||||
|
|
||||||
|
// TelegramCoreAddresses are publicly known addresses of Telegram core network.
|
||||||
|
var TelegramCoreAddresses = map[int][]string{
|
||||||
|
1: {
|
||||||
|
"149.154.175.50:443",
|
||||||
|
"[2001:b28:f23d:f001::a]:443",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"149.154.167.51:443",
|
||||||
|
"95.161.76.100:443",
|
||||||
|
"[2001:67c:04e8:f002::a]:443",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
"149.154.175.100:443",
|
||||||
|
"[2001:b28:f23d:f003::a]:443",
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
"149.154.167.91:443",
|
||||||
|
"[2001:67c:04e8:f004::a]:443",
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
"149.154.171.5:443",
|
||||||
|
"[2001:b28:f23f:f005::a]:443",
|
||||||
|
},
|
||||||
|
203: {
|
||||||
|
"91.105.192.100:443",
|
||||||
|
"[2a0a:f280:0203:000a:5000:0000:0000:0100]:443",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/beevik/ntp v1.5.0
|
||||||
github.com/ncruces/go-dns v1.3.2
|
github.com/ncruces/go-dns v1.3.2
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4
|
github.com/pelletier/go-toml/v2 v2.2.4
|
||||||
github.com/pires/go-proxyproto v0.11.0
|
github.com/pires/go-proxyproto v0.11.0
|
||||||
@@ -36,7 +37,6 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/beevik/ntp v1.5.0 // indirect
|
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
|||||||
+188
-8
@@ -1,13 +1,21 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/essentials"
|
||||||
"github.com/9seconds/mtg/v2/internal/config"
|
"github.com/9seconds/mtg/v2/internal/config"
|
||||||
"github.com/9seconds/mtg/v2/internal/utils"
|
"github.com/9seconds/mtg/v2/internal/utils"
|
||||||
"github.com/9seconds/mtg/v2/mtglib"
|
"github.com/9seconds/mtg/v2/mtglib"
|
||||||
|
"github.com/9seconds/mtg/v2/network/v2"
|
||||||
"github.com/beevik/ntp"
|
"github.com/beevik/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +41,20 @@ var (
|
|||||||
template.New("").
|
template.New("").
|
||||||
Parse(" ❌ Time drift is {{ .drift }}, but tolerate-time-skewness is {{ .value }}. You will get many rejected connections!\n"),
|
Parse(" ❌ Time drift is {{ .drift }}, but tolerate-time-skewness is {{ .value }}. You will get many rejected connections!\n"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
tplODCConnect = template.Must(
|
||||||
|
template.New("").Parse(" ✅ DC {{ .dc }}\n"),
|
||||||
|
)
|
||||||
|
tplEDCConnect = template.Must(
|
||||||
|
template.New("").Parse(" ❌ DC {{ .dc }}: {{ .error }}\n"),
|
||||||
|
)
|
||||||
|
|
||||||
|
tplODNSSNIMatch = template.Must(
|
||||||
|
template.New("").Parse(" ✅ IP address {{ .ip }} matches secret hostname {{ .hostname }}\n"),
|
||||||
|
)
|
||||||
|
tplEDNSSNIMatch = template.Must(
|
||||||
|
template.New("").Parse(" ❌ Hostname {{ .hostname }} {{ if .resolved }}is resolved to {{ .resolved }} addresses, not {{ if .ip4 }}{{ .ip4 }}{{ else }}{{ .ip6 }}{{ end }}{{ else }}cannot be resolved to any host{{ end }}\n"),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
type Doctor struct {
|
type Doctor struct {
|
||||||
@@ -41,6 +63,28 @@ type Doctor struct {
|
|||||||
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` //nolint: lll
|
ConfigPath string `kong:"arg,required,type='existingfile',help='Path to the configuration file.',name='config-path'"` //nolint: lll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type wrappedNetwork struct {
|
||||||
|
mtglib.Network
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wrappedNetwork) Dial(network, address string) (net.Conn, error) {
|
||||||
|
rv, err := w.Network.Dial(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv.(net.Conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wrappedNetwork) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
|
rv, err := w.Network.DialContext(ctx, network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv.(net.Conn), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Doctor) Run(cli *CLI, version string) error {
|
func (d *Doctor) Run(cli *CLI, version string) error {
|
||||||
conf, err := utils.ReadConfig(d.ConfigPath)
|
conf, err := utils.ReadConfig(d.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,20 +92,42 @@ func (d *Doctor) Run(cli *CLI, version string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
d.conf = conf
|
d.conf = conf
|
||||||
everythingOK := true
|
|
||||||
|
|
||||||
fmt.Println("Deprecated options")
|
fmt.Println("Deprecated options")
|
||||||
if !d.checkDeprecatedConfig() {
|
everythingOK := d.checkDeprecatedConfig()
|
||||||
everythingOK = false
|
|
||||||
} else {
|
|
||||||
fmt.Println(" ✅ All good")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Time skewness")
|
fmt.Println("Time skewness")
|
||||||
if !d.checkTimeSkewness() {
|
everythingOK = d.checkTimeSkewness() && everythingOK
|
||||||
everythingOK = false
|
|
||||||
|
resolver, err := network.GetDNS(conf.GetDNS())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot create DNS resolver: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
base := network.New(
|
||||||
|
resolver,
|
||||||
|
"",
|
||||||
|
conf.Network.Timeout.TCP.Get(10*time.Second),
|
||||||
|
conf.Network.Timeout.HTTP.Get(0),
|
||||||
|
conf.Network.Timeout.Idle.Get(0),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println("Validate native network connectivity")
|
||||||
|
everythingOK = d.checkNetwork(base) && everythingOK
|
||||||
|
|
||||||
|
for _, url := range conf.Network.Proxies {
|
||||||
|
value, err := network.NewProxyNetwork(base, url.Get(nil))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Validate network connectivity with proxy %s\n", url.Get(nil))
|
||||||
|
everythingOK = d.checkNetwork(value) && everythingOK
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Validate SNI-DNS match")
|
||||||
|
everythingOK = d.checkSecretHost(resolver, base) && everythingOK
|
||||||
|
|
||||||
if !everythingOK {
|
if !everythingOK {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -116,6 +182,10 @@ func (d *Doctor) checkDeprecatedConfig() bool {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
fmt.Println(" ✅ All good")
|
||||||
|
}
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,3 +219,113 @@ func (d *Doctor) checkTimeSkewness() bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Doctor) checkNetwork(ntw mtglib.Network) bool {
|
||||||
|
dcs := slices.Collect(maps.Keys(essentials.TelegramCoreAddresses))
|
||||||
|
slices.Sort(dcs)
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
|
||||||
|
for _, dc := range dcs {
|
||||||
|
err := d.checkNetworkAddresses(ntw, essentials.TelegramCoreAddresses[dc])
|
||||||
|
if err == nil {
|
||||||
|
tplODCConnect.Execute(os.Stdout, map[string]any{
|
||||||
|
"dc": dc,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
tplEDCConnect.Execute(os.Stdout, map[string]any{
|
||||||
|
"dc": dc,
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
ok = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doctor) checkNetworkAddresses(ntw mtglib.Network, addresses []string) error {
|
||||||
|
checkAddresses := []string{}
|
||||||
|
|
||||||
|
switch d.conf.PreferIP.Get("prefer-ip4") {
|
||||||
|
case "only-ipv4":
|
||||||
|
for _, addr := range addresses {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil && ip.To4() != nil {
|
||||||
|
checkAddresses = append(checkAddresses, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "only-ipv6":
|
||||||
|
for _, addr := range addresses {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := net.ParseIP(host); ip != nil && ip.To4() == nil {
|
||||||
|
checkAddresses = append(checkAddresses, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
checkAddresses = addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var (
|
||||||
|
conn net.Conn
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, addr := range checkAddresses {
|
||||||
|
conn, err = ntw.DialContext(ctx, "tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Doctor) checkSecretHost(resolver *net.Resolver, ntw mtglib.Network) bool {
|
||||||
|
addresses, err := resolver.LookupIPAddr(context.Background(), d.conf.Secret.Host)
|
||||||
|
if err != nil {
|
||||||
|
// TODO
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
access := &Access{}
|
||||||
|
ourIP4 := access.getIP(ntw, "tcp4")
|
||||||
|
ourIP6 := access.getIP(ntw, "tcp6")
|
||||||
|
|
||||||
|
strAddresses := []string{}
|
||||||
|
for _, value := range addresses {
|
||||||
|
if value.IP.String() == ourIP4.String() || value.IP.String() == ourIP6.String() {
|
||||||
|
tplODNSSNIMatch.Execute(os.Stdout, map[string]any{
|
||||||
|
"ip": value.IP,
|
||||||
|
"hostname": d.conf.Secret.Host,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
strAddresses = append(strAddresses, `"`+value.IP.String()+`"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
tplEDNSSNIMatch.Execute(os.Stdout, map[string]any{
|
||||||
|
"hostname": d.conf.Secret.Host,
|
||||||
|
"resolved": strings.Join(strAddresses, ", "),
|
||||||
|
"ip4": ourIP4,
|
||||||
|
"ip6": ourIP6,
|
||||||
|
})
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
+35
-42
@@ -2,7 +2,10 @@ package dc
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/9seconds/mtg/v2/essentials"
|
||||||
)
|
)
|
||||||
|
|
||||||
type preferIP uint8
|
type preferIP uint8
|
||||||
@@ -39,46 +42,36 @@ type Updater interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
|
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp#L30
|
||||||
var defaultDCAddrSet = dcAddrSet{
|
var defaultDCAddrSet = (func() dcAddrSet {
|
||||||
v4: map[int][]Addr{
|
addrSet := dcAddrSet{
|
||||||
1: {
|
v4: make(map[int][]Addr),
|
||||||
{Network: "tcp4", Address: "149.154.175.50:443"},
|
v6: make(map[int][]Addr),
|
||||||
},
|
|
||||||
2: {
|
|
||||||
{Network: "tcp4", Address: "149.154.167.51:443"},
|
|
||||||
{Network: "tcp4", Address: "95.161.76.100:443"},
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
{Network: "tcp4", Address: "149.154.175.100:443"},
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
{Network: "tcp4", Address: "149.154.167.91:443"},
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
{Network: "tcp4", Address: "149.154.171.5:443"},
|
|
||||||
},
|
|
||||||
203: {
|
|
||||||
{Network: "tcp4", Address: "91.105.192.100:443"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
v6: map[int][]Addr{
|
|
||||||
1: {
|
|
||||||
{Network: "tcp6", Address: "[2001:b28:f23d:f001::a]:443"},
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
{Network: "tcp6", Address: "[2001:67c:04e8:f002::a]:443"},
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
{Network: "tcp6", Address: "[2001:b28:f23d:f003::a]:443"},
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
{Network: "tcp6", Address: "[2001:67c:04e8:f004::a]:443"},
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
{Network: "tcp6", Address: "[2001:b28:f23f:f005::a]:443"},
|
|
||||||
},
|
|
||||||
203: {
|
|
||||||
{Network: "tcp6", Address: "[2a0a:f280:0203:000a:5000:0000:0000:0100]:443"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for dcid, ips := range essentials.TelegramCoreAddresses {
|
||||||
|
for _, addr := range ips {
|
||||||
|
host, _, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := net.ParseIP(host)
|
||||||
|
if ip == nil {
|
||||||
|
panic(addr)
|
||||||
|
}
|
||||||
|
if ip.To4() == nil {
|
||||||
|
addrSet.v6[dcid] = append(addrSet.v6[dcid], Addr{
|
||||||
|
Network: "tcp6",
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
addrSet.v4[dcid] = append(addrSet.v4[dcid], Addr{
|
||||||
|
Network: "tcp4",
|
||||||
|
Address: addr,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return addrSet
|
||||||
|
})()
|
||||||
|
|||||||
Reference in New Issue
Block a user