You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
140 lines
3.4 KiB
140 lines
3.4 KiB
package dcs |
|
|
|
import ( |
|
"context" |
|
"io" |
|
"net" |
|
|
|
"github.com/go-faster/errors" |
|
"go.uber.org/multierr" |
|
|
|
"github.com/gotd/td/internal/crypto" |
|
"github.com/gotd/td/internal/mtproxy" |
|
"github.com/gotd/td/internal/mtproxy/obfuscator" |
|
"github.com/gotd/td/internal/proto/codec" |
|
"github.com/gotd/td/transport" |
|
) |
|
|
|
var _ Resolver = mtProxy{} |
|
|
|
type mtProxy struct { |
|
dial DialFunc |
|
protocol protocol |
|
addr, network string |
|
|
|
secret mtproxy.Secret |
|
tag [4]byte |
|
rand io.Reader |
|
} |
|
|
|
func (m mtProxy) Primary(ctx context.Context, dc int, _ List) (transport.Conn, error) { |
|
return m.resolve(ctx, dc) |
|
} |
|
|
|
func (m mtProxy) MediaOnly(ctx context.Context, dc int, _ List) (transport.Conn, error) { |
|
if dc > 0 { |
|
dc *= -1 |
|
} |
|
return m.resolve(ctx, dc) |
|
} |
|
|
|
func (m mtProxy) CDN(ctx context.Context, dc int, _ List) (transport.Conn, error) { |
|
return m.resolve(ctx, dc) |
|
} |
|
|
|
func (m mtProxy) resolve(ctx context.Context, dc int) (transport.Conn, error) { |
|
c, err := m.dial(ctx, m.network, m.addr) |
|
if err != nil { |
|
return nil, errors.Wrapf(err, "connect to the MTProxy %q", m.addr) |
|
} |
|
|
|
conn, err := m.handshakeConn(c, dc) |
|
if err != nil { |
|
err = errors.Wrap(err, "handshake") |
|
return nil, multierr.Combine(err, c.Close()) |
|
} |
|
|
|
return conn, nil |
|
} |
|
|
|
// handshakeConn inits given net.Conn as MTProto connection. |
|
func (m mtProxy) handshakeConn(c net.Conn, dc int) (transport.Conn, error) { |
|
var obsConn *obfuscator.Conn |
|
switch m.secret.Type { |
|
case mtproxy.Simple, mtproxy.Secured: |
|
obsConn = obfuscator.Obfuscated2(m.rand, c) |
|
case mtproxy.TLS: |
|
obsConn = obfuscator.FakeTLS(m.rand, c) |
|
default: |
|
return nil, errors.Errorf("unknown MTProxy secret type: %d", m.secret.Type) |
|
} |
|
|
|
secret := m.secret |
|
if err := obsConn.Handshake(m.tag, dc, secret); err != nil { |
|
return nil, errors.Wrap(err, "MTProxy handshake") |
|
} |
|
|
|
transportConn, err := m.protocol.Handshake(obsConn) |
|
if err != nil { |
|
return nil, errors.Wrap(err, "transport handshake") |
|
} |
|
|
|
return transportConn, nil |
|
} |
|
|
|
// MTProxyOptions is MTProxy resolver creation options. |
|
type MTProxyOptions struct { |
|
// Dial specifies the dial function for creating unencrypted TCP connections. |
|
// If Dial is nil, then the resolver dials using package net. |
|
Dial DialFunc |
|
// Network to use. Defaults to "tcp" |
|
Network string |
|
// Random source for MTProxy obfuscator. |
|
Rand io.Reader |
|
} |
|
|
|
func (m *MTProxyOptions) setDefaults() { |
|
if m.Dial == nil { |
|
var d net.Dialer |
|
m.Dial = d.DialContext |
|
} |
|
if m.Network == "" { |
|
m.Network = "tcp" |
|
} |
|
if m.Rand == nil { |
|
m.Rand = crypto.DefaultRand() |
|
} |
|
} |
|
|
|
// MTProxy creates MTProxy obfuscated DC resolver. |
|
// |
|
// See https://core.telegram.org/mtproto/mtproto-transports#transport-obfuscation. |
|
func MTProxy(addr string, secret []byte, opts MTProxyOptions) (Resolver, error) { |
|
s, err := mtproxy.ParseSecret(secret) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var cdc codec.Codec = codec.PaddedIntermediate{} |
|
tag := codec.PaddedIntermediateClientStart |
|
|
|
// FIXME(tdakkota): some proxies forces to use Padded (Secure) Intermediate |
|
// even if secret denotes to use another transport type. |
|
if s.Type != mtproxy.TLS { |
|
if c, ok := s.ExpectedCodec(); ok { |
|
cdc = c |
|
tag = [4]byte{s.Tag, s.Tag, s.Tag, s.Tag} |
|
} |
|
} |
|
|
|
opts.setDefaults() |
|
return mtProxy{ |
|
dial: opts.Dial, |
|
addr: addr, |
|
network: opts.Network, |
|
protocol: transport.NewProtocol(func() transport.Codec { return codec.NoHeader{Codec: cdc} }), |
|
secret: s, |
|
tag: tag, |
|
rand: opts.Rand, |
|
}, nil |
|
}
|
|
|