Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

sni_check_test.go 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package cli
  2. import (
  3. "context"
  4. "io"
  5. "net"
  6. "net/http"
  7. "strings"
  8. "testing"
  9. "github.com/9seconds/mtg/v2/essentials"
  10. "github.com/9seconds/mtg/v2/internal/config"
  11. "github.com/9seconds/mtg/v2/mtglib"
  12. "github.com/stretchr/testify/require"
  13. "golang.org/x/net/dns/dnsmessage"
  14. )
  15. // startSNITestDNS spins up a loopback UDP resolver that answers every query
  16. // with the given A and AAAA records, so runSNICheck sees a dual-stack secret
  17. // host without touching the real network. It returns a *net.Resolver wired to
  18. // it.
  19. func startSNITestDNS(t *testing.T, a, aaaa net.IP) *net.Resolver {
  20. t.Helper()
  21. pc, err := net.ListenPacket("udp", "127.0.0.1:0")
  22. require.NoError(t, err)
  23. t.Cleanup(func() { pc.Close() }) //nolint: errcheck
  24. go func() {
  25. buf := make([]byte, 512)
  26. for {
  27. n, addr, err := pc.ReadFrom(buf)
  28. if err != nil {
  29. return
  30. }
  31. var parser dnsmessage.Parser
  32. hdr, err := parser.Start(buf[:n])
  33. if err != nil {
  34. continue
  35. }
  36. question, err := parser.Question()
  37. if err != nil {
  38. continue
  39. }
  40. builder := dnsmessage.NewBuilder(nil, dnsmessage.Header{
  41. ID: hdr.ID,
  42. Response: true,
  43. RecursionAvailable: true,
  44. })
  45. builder.EnableCompression()
  46. _ = builder.StartQuestions()
  47. _ = builder.Question(question)
  48. _ = builder.StartAnswers()
  49. rh := dnsmessage.ResourceHeader{
  50. Name: question.Name,
  51. Class: dnsmessage.ClassINET,
  52. TTL: 60,
  53. }
  54. switch question.Type {
  55. case dnsmessage.TypeA:
  56. rh.Type = dnsmessage.TypeA
  57. var v4 [4]byte
  58. copy(v4[:], a.To4())
  59. _ = builder.AResource(rh, dnsmessage.AResource{A: v4})
  60. case dnsmessage.TypeAAAA:
  61. rh.Type = dnsmessage.TypeAAAA
  62. var v6 [16]byte
  63. copy(v6[:], aaaa.To16())
  64. _ = builder.AAAAResource(rh, dnsmessage.AAAAResource{AAAA: v6})
  65. }
  66. msg, err := builder.Finish()
  67. if err != nil {
  68. continue
  69. }
  70. pc.WriteTo(msg, addr) //nolint: errcheck
  71. }
  72. }()
  73. dnsAddr := pc.LocalAddr().String()
  74. return &net.Resolver{
  75. PreferGo: true,
  76. Dial: func(ctx context.Context, _, _ string) (net.Conn, error) {
  77. var d net.Dialer
  78. return d.DialContext(ctx, "udp", dnsAddr)
  79. },
  80. }
  81. }
  82. // ipv4OnlyEgressNetwork fakes mtglib.Network so that public-IP detection
  83. // succeeds over tcp4 and fails over tcp6 — the classic IPv4-only-egress
  84. // server. getIP's per-protocol dial is routed at a loopback listener: a tcp4
  85. // dial to 127.0.0.1 connects, a tcp6 dial to the same address fails ("no
  86. // suitable address"), so we exercise the real getIP code path without the
  87. // internet.
  88. type ipv4OnlyEgressNetwork struct {
  89. listenerAddr string
  90. detectedV4 string
  91. }
  92. func (n *ipv4OnlyEgressNetwork) Dial(_, _ string) (essentials.Conn, error) {
  93. panic("unused")
  94. }
  95. func (n *ipv4OnlyEgressNetwork) DialContext(_ context.Context, _, _ string) (essentials.Conn, error) {
  96. panic("unused")
  97. }
  98. func (n *ipv4OnlyEgressNetwork) NativeDialer() *net.Dialer {
  99. return &net.Dialer{}
  100. }
  101. func (n *ipv4OnlyEgressNetwork) MakeHTTPClient(
  102. dialFunc func(ctx context.Context, network, address string) (essentials.Conn, error),
  103. ) *http.Client {
  104. return &http.Client{
  105. Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
  106. conn, err := dialFunc(req.Context(), "tcp", n.listenerAddr)
  107. if err != nil {
  108. return nil, err
  109. }
  110. conn.Close() //nolint: errcheck
  111. return &http.Response{
  112. StatusCode: http.StatusOK,
  113. Body: io.NopCloser(strings.NewReader(n.detectedV4)),
  114. Header: make(http.Header),
  115. }, nil
  116. }),
  117. }
  118. }
  119. type roundTripFunc func(*http.Request) (*http.Response, error)
  120. func (f roundTripFunc) RoundTrip(r *http.Request) (*http.Response, error) {
  121. return f(r)
  122. }
  123. // TestRunSNICheckIPv4OnlyEgressGraceful reproduces the #529/#542 regression:
  124. // a dual-stack secret host on a server whose IPv6 egress is down. The tcp6
  125. // public-IP detection fails, but the tcp4 detection succeeds and matches the
  126. // host's A record, so the SNI check must NOT report a hard error — one
  127. // family being undetectable is graceful degradation, not failure.
  128. func TestRunSNICheckIPv4OnlyEgressGraceful(t *testing.T) {
  129. const ourV4 = "192.0.2.4" // RFC 5737 TEST-NET-1
  130. resolver := startSNITestDNS(t, net.ParseIP(ourV4), net.ParseIP("2001:db8::1")) // RFC 3849 doc range
  131. // Loopback target for getIP's dial. Keep it the IPv4 literal 127.0.0.1: a
  132. // "tcp6" dial to it fails deterministically ("no suitable address") on any
  133. // host regardless of IPv6 connectivity, which is what makes tcp6 detection
  134. // fail here. Do not replace with a real ::1 setup — that reintroduces flake.
  135. listener, err := net.Listen("tcp", "127.0.0.1:0")
  136. require.NoError(t, err)
  137. t.Cleanup(func() { listener.Close() }) //nolint: errcheck
  138. go func() {
  139. for {
  140. conn, err := listener.Accept()
  141. if err != nil {
  142. return
  143. }
  144. conn.Close() //nolint: errcheck
  145. }
  146. }()
  147. ntw := &ipv4OnlyEgressNetwork{
  148. listenerAddr: listener.Addr().String(),
  149. detectedV4: ourV4,
  150. }
  151. conf := &config.Config{}
  152. conf.Secret.Host = "secret-host.test"
  153. res, err := runSNICheck(context.Background(), conf, resolver, ntw)
  154. // The load-bearing assertion: a single family's detection failure must not
  155. // poison the whole result. Before the fix this returns a non-nil error.
  156. require.NoError(t, err)
  157. require.Equal(t, ourV4, res.OurIP4, "IPv4 public IP should be detected and match the A record")
  158. require.Empty(t, res.OurIP6, "IPv6 is undetectable on IPv4-only egress; must degrade, not error")
  159. }
  160. var _ mtglib.Network = (*ipv4OnlyEgressNetwork)(nil)