This commit is contained in:
9seconds
2019-10-07 12:13:07 +03:00
parent 072bce2922
commit c9743b5675
28 changed files with 630 additions and 172 deletions
+3
View File
@@ -0,0 +1,3 @@
package conntypes
type Packet []byte
@@ -1,4 +1,4 @@
package wrappers package conntypes
import ( import (
"io" "io"
@@ -8,8 +8,6 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
) )
type Packet []byte
// Wrap is a base interface for all wrappers in this package. // Wrap is a base interface for all wrappers in this package.
type Wrap interface { type Wrap interface {
Conn() net.Conn Conn() net.Conn
+2
View File
@@ -9,6 +9,8 @@ require (
github.com/allegro/bigcache v1.2.1 github.com/allegro/bigcache v1.2.1
github.com/beevik/ntp v0.2.0 github.com/beevik/ntp v0.2.0
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash v1.1.0
github.com/dustin/go-humanize v1.0.0
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d
github.com/kr/pretty v0.1.0 // indirect github.com/kr/pretty v0.1.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
+4
View File
@@ -24,6 +24,10 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622 h1:lxbhOGZ9pU3Kf8P6lFluUcE82yVZn2EqEf4+mWRNPV0=
github.com/gammazero/deque v0.0.0-20190521012701-46e4ffb7a622/go.mod h1:D90+MBHVc9Sk1lJAbEVgws0eYEurY4mv2TDso3Nxh3w=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+59
View File
@@ -0,0 +1,59 @@
package hub
import (
"context"
"errors"
"time"
"github.com/9seconds/mtg/conntypes"
)
const closeableChannelReadTimeout = 2 * time.Minute
type ChannelReadCloser interface {
Read() (conntypes.Packet, error)
Close()
}
type closeableChannel struct {
channel chan conntypes.Packet
ctx context.Context
cancel context.CancelFunc
}
func (c *closeableChannel) Read() (conntypes.Packet, error) {
timer := time.NewTimer(closeableChannelReadTimeout)
defer timer.Stop()
select {
case <-timer.C:
return nil, errors.New("timeout")
case <-c.ctx.Done():
return nil, errors.New("channel was closed")
case packet := <-c.channel:
return packet, nil
}
}
func (c *closeableChannel) write(packet conntypes.Packet) error {
select {
case <-c.ctx.Done():
return errors.New("channel was closed")
case c.channel <- packet:
return nil
}
}
func (c *closeableChannel) Close() {
c.cancel()
c.channel = nil
}
func newCloseableChannel(ctx context.Context) *closeableChannel {
ctx, cancel := context.WithCancel(ctx)
return &closeableChannel{
channel: make(chan conntypes.Packet),
ctx: ctx,
cancel: cancel,
}
}
+100
View File
@@ -0,0 +1,100 @@
package hub
import (
"fmt"
"math/rand"
"sync"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto"
"github.com/9seconds/mtg/protocol"
)
type connectionID int
type connection struct {
conn conntypes.PacketReadWriteCloser
mutex sync.RWMutex
id connectionID
hub *connectionHub
pending uint
closing bool
}
func (c *connection) Write(packet conntypes.Packet) error {
c.mutex.Lock()
defer c.mutex.Unlock()
err := c.conn.Write(packet)
if err != nil {
// if we tried to write into a socket and it was broken, it is
// a time to reconsider the prescence of this socket at all.
//
// probably we need to remove it completely because it seems
// that connection is broken.
c.pending = 0
}
return err
}
func (c *connection) Read() (conntypes.Packet, error) {
packet, err := c.conn.Read()
c.mutex.Lock()
if err != nil {
c.pending--
} else {
c.pending = 0
}
c.mutex.Unlock()
return packet, err
}
func (c *connection) Stats() (bool, uint) {
c.mutex.RLock()
defer c.mutex.RUnlock()
return c.closing, c.pending
}
func (c *connection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()
c.closing = true
return c.conn.Close()
}
func (c *connection) run() {
for {
packet, err := c.conn.Read()
if err != nil {
c.Close()
c.hub.brokenSocketsChan <- c.id
c.hub = nil
return
}
// TODO
if channel, ok := Registry.getChannel(conntypes.ConnID{}); ok {
go channel.write(packet) // nolint: errcheck
}
}
}
func newConnection(hub *connectionHub, req *protocol.TelegramRequest) (*connection, error) {
conn, err := mtproto.TelegramProtocol(req)
if err != nil {
return nil, fmt.Errorf("cannot create a new connection: %w", err)
}
rv := &connection{
conn: conn,
hub: hub,
id: connectionID(rand.Int()),
}
go rv.run()
return rv, nil
}
+84
View File
@@ -0,0 +1,84 @@
package hub
import "time"
const hubGCEvery = time.Minute
type connectionHub struct {
sockets map[connectionID]*connection
brokenSocketsChan chan connectionID
connectionRequestsChan chan *connectionHubRequest
returnConnectionsChan chan *connection
}
func (h *connectionHub) run() {
gcTicker := time.NewTicker(hubGCEvery)
defer gcTicker.Stop()
for {
select {
case <-gcTicker.C:
h.runGC()
case id := <-h.brokenSocketsChan:
h.runBrokenConnection(id)
case request := <-h.connectionRequestsChan:
h.runConnectionRequest(request)
case conn := <-h.returnConnectionsChan:
h.runReturnConnection(conn)
}
}
}
func (h *connectionHub) runBrokenConnection(id connectionID) {
delete(h.sockets, id)
}
func (h *connectionHub) runGC() {
for key, conn := range h.sockets {
closing, pending := conn.Stats()
switch {
case closing:
delete(h.sockets, key)
case pending == 0:
conn.Close()
delete(h.sockets, key)
return
}
}
}
func (h *connectionHub) runConnectionRequest(req *connectionHubRequest) {
for key, conn := range h.sockets {
closing, _ := conn.Stats()
delete(h.sockets, key)
if !closing {
req.responseChan <- conn
return
}
}
newConn, err := newConnection(h, req.req)
if err != nil {
close(req.responseChan)
return
}
req.responseChan <- newConn
}
func (h *connectionHub) runReturnConnection(conn *connection) {
h.sockets[conn.id] = conn
}
func newConnectionHub() *connectionHub {
return &connectionHub{
sockets: map[connectionID]*connection{},
brokenSocketsChan: make(chan connectionID, 1),
connectionRequestsChan: make(chan *connectionHubRequest),
returnConnectionsChan: make(chan *connection, 1),
}
}
+8
View File
@@ -0,0 +1,8 @@
package hub
import "github.com/9seconds/mtg/protocol"
type connectionHubRequest struct {
req *protocol.TelegramRequest
responseChan chan<- *connection
}
+48
View File
@@ -0,0 +1,48 @@
package hub
import (
"errors"
"sync"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol"
)
type Concentrator struct {
hubs sync.Map
}
func (c *Concentrator) Write(packet conntypes.Packet, req *protocol.TelegramRequest) error {
hub := c.getHub(req)
connectionChan := make(chan *connection)
hub.connectionRequestsChan <- &connectionHubRequest{
req: req,
responseChan: connectionChan,
}
conn, ok := <-connectionChan
if !ok {
return errors.New("cannot establish connection to telegram")
}
}
func (c *Concentrator) getHub(req *protocol.TelegramRequest) *connectionHub {
dcMapRaw, ok := c.hubs.Load(req.ClientProtocol.DC())
if !ok {
dcMapRaw, _ = c.hubs.LoadOrStore(req.ClientProtocol.DC(), &sync.Map{})
}
dcMap := dcMapRaw.(*sync.Map)
loaded := true
hubRaw, ok := dcMap.Load(req.ClientProtocol.ConnectionProtocol())
if !ok {
hubRaw, loaded = dcMap.LoadOrStore(req.ClientProtocol.ConnectionProtocol(),
newConnectionHub())
}
hub := hubRaw.(*connectionHub)
if !loaded {
go hub.run()
}
return hub
}
+53
View File
@@ -0,0 +1,53 @@
package hub
import (
"context"
"sync"
"github.com/9seconds/mtg/conntypes"
)
var Registry *RegistryStruct
type RegistryStruct struct {
conns map[string]*closeableChannel
ctx context.Context
mutex sync.RWMutex
}
func (r *RegistryStruct) Register(id conntypes.ConnID) ChannelReadCloser {
channel := newCloseableChannel(r.ctx)
r.mutex.Lock()
r.conns[string(id[:])] = channel
r.mutex.Unlock()
return channel
}
func (r *RegistryStruct) Unregister(id conntypes.ConnID) {
r.mutex.Lock()
defer r.mutex.Unlock()
if channel, ok := r.conns[string(id[:])]; ok {
channel.Close()
delete(r.conns, string(id[:]))
}
}
func (r *RegistryStruct) getChannel(id conntypes.ConnID) (*closeableChannel, bool) {
r.mutex.RLock()
defer r.mutex.RUnlock()
if value, ok := r.conns[string(id[:])]; ok {
return value, true
}
return nil, false
}
func InitRegistry(ctx context.Context) {
Registry = &RegistryStruct{
ctx: ctx,
conns: map[string]*closeableChannel{},
}
}
+7 -8
View File
@@ -3,16 +3,15 @@ package mtproto
import ( import (
"fmt" "fmt"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/wrappers" "github.com/9seconds/mtg/wrappers"
) )
func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.PacketReadWriteCloser, error) {
conn, err := telegram.Middle.Dial(req.Ctx, conn, err := telegram.Middle.Dial(req.ClientProtocol.DC(),
req.Cancel,
req.ClientProtocol.DC(),
req.ClientProtocol.ConnectionProtocol()) req.ClientProtocol.ConnectionProtocol())
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot connect to telegram: %w", err) return nil, fmt.Errorf("cannot connect to telegram: %w", err)
@@ -42,7 +41,7 @@ func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) {
return frameConn, nil return frameConn, nil
} }
func doRPCNonceRequest(conn wrappers.PacketWriter) (*rpc.NonceRequest, error) { func doRPCNonceRequest(conn conntypes.PacketWriter) (*rpc.NonceRequest, error) {
rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret()) rpcNonceReq, err := rpc.NewNonceRequest(telegram.Middle.Secret())
if err != nil { if err != nil {
panic(err) panic(err)
@@ -54,7 +53,7 @@ func doRPCNonceRequest(conn wrappers.PacketWriter) (*rpc.NonceRequest, error) {
return rpcNonceReq, nil return rpcNonceReq, nil
} }
func getRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) { func getRPCNonceResponse(conn conntypes.PacketReader, req *rpc.NonceRequest) (*rpc.NonceResponse, error) {
packet, err := conn.Read() packet, err := conn.Read()
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot read from connection: %w", err) return nil, fmt.Errorf("cannot read from connection: %w", err)
@@ -71,14 +70,14 @@ func getRPCNonceResponse(conn wrappers.PacketReader, req *rpc.NonceRequest) (*rp
return resp, nil return resp, nil
} }
func doRPCHandshakeRequest(conn wrappers.PacketWriter) error { func doRPCHandshakeRequest(conn conntypes.PacketWriter) error {
if err := conn.Write(rpc.HandshakeRequest); err != nil { if err := conn.Write(rpc.HandshakeRequest); err != nil {
return fmt.Errorf("cannot make a request: %w", err) return fmt.Errorf("cannot make a request: %w", err)
} }
return nil return nil
} }
func getRPCHandshakeResponse(conn wrappers.PacketReader) error { func getRPCHandshakeResponse(conn conntypes.PacketReader) error {
packet, err := conn.Read() packet, err := conn.Read()
if err != nil { if err != nil {
return fmt.Errorf("cannot read a response: %w", err) return fmt.Errorf("cannot read a response: %w", err)
+3 -3
View File
@@ -37,7 +37,7 @@ func (c *ClientProtocol) DC() conntypes.DC {
return c.dc return c.dc
} }
func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) { func (c *ClientProtocol) Handshake(socket conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error) {
fm, err := c.ReadFrame(socket) fm, err := c.ReadFrame(socket)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot make a client handshake: %w", err) return nil, fmt.Errorf("cannot make a client handshake: %w", err)
@@ -88,7 +88,7 @@ func (c *ClientProtocol) Handshake(socket wrappers.StreamReadWriteCloser) (wrapp
return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil
} }
func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err error) { func (c *ClientProtocol) ReadFrame(socket conntypes.StreamReader) (fm Frame, err error) {
if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil { if _, err = io.ReadFull(handshakeReader{socket}, fm.Bytes()); err != nil {
err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err) err = fmt.Errorf("cannot extract obfuscated2 frame: %w", err)
} }
@@ -96,7 +96,7 @@ func (c *ClientProtocol) ReadFrame(socket wrappers.StreamReader) (fm Frame, err
} }
type handshakeReader struct { type handshakeReader struct {
parent wrappers.StreamReader parent conntypes.StreamReader
} }
func (h handshakeReader) Read(p []byte) (int, error) { func (h handshakeReader) Read(p []byte) (int, error) {
+7 -6
View File
@@ -4,20 +4,21 @@ import (
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/protocol" "github.com/9seconds/mtg/protocol"
"github.com/9seconds/mtg/telegram" "github.com/9seconds/mtg/telegram"
"github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/utils"
"github.com/9seconds/mtg/wrappers" "github.com/9seconds/mtg/wrappers"
) )
func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) { func TelegramProtocol(req *protocol.TelegramRequest) (conntypes.StreamReadWriteCloser, error) {
socket, err := telegram.Direct.Dial(req.Ctx, conn, err := telegram.Direct.Dial(req.ClientProtocol.DC(),
req.Cancel,
req.ClientProtocol.DC(),
req.ClientProtocol.ConnectionProtocol()) req.ClientProtocol.ConnectionProtocol())
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot dial to telegram: %w", err) return nil, fmt.Errorf("cannot dial to telegram: %w", err)
} }
conn = wrappers.NewTimeout(conn)
conn = wrappers.NewCtx(req.Ctx, req.Cancel, conn)
fm := generateFrame(req.ClientProtocol) fm := generateFrame(req.ClientProtocol)
data := fm.Bytes() data := fm.Bytes()
@@ -30,11 +31,11 @@ func TelegramProtocol(req *protocol.TelegramRequest) (wrappers.Wrap, error) {
encryptor.XORKeyStream(data, data) encryptor.XORKeyStream(data, data)
copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV]) copy(data[:frameOffsetIV], copyFrame[:frameOffsetIV])
if _, err := socket.Write(data); err != nil { if _, err := conn.Write(data); err != nil {
return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err) return nil, fmt.Errorf("cannot write handshake frame to telegram: %w", err)
} }
return wrappers.NewObfuscated2(socket, encryptor, decryptor), nil return wrappers.NewObfuscated2(conn, encryptor, decryptor), nil
} }
func generateFrame(cp protocol.ClientProtocol) (fm Frame) { func generateFrame(cp protocol.ClientProtocol) (fm Frame) {
+2 -6
View File
@@ -1,16 +1,12 @@
package protocol package protocol
import ( import "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/wrappers"
)
type ClientProtocol interface { type ClientProtocol interface {
Handshake(wrappers.StreamReadWriteCloser) (wrappers.StreamReadWriteCloser, error) Handshake(conntypes.StreamReadWriteCloser) (conntypes.StreamReadWriteCloser, error)
ConnectionType() conntypes.ConnectionType ConnectionType() conntypes.ConnectionType
ConnectionProtocol() conntypes.ConnectionProtocol ConnectionProtocol() conntypes.ConnectionProtocol
DC() conntypes.DC DC() conntypes.DC
} }
type TelegramProtocol func(*TelegramRequest) (wrappers.Wrap, error)
type ClientProtocolMaker func() ClientProtocol type ClientProtocolMaker func() ClientProtocol
+1 -2
View File
@@ -6,12 +6,11 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/wrappers"
) )
type TelegramRequest struct { type TelegramRequest struct {
Logger *zap.SugaredLogger Logger *zap.SugaredLogger
ClientConn wrappers.StreamReadWriteCloser ClientConn conntypes.StreamReadWriteCloser
ConnID conntypes.ConnID ConnID conntypes.ConnID
Ctx context.Context Ctx context.Context
Cancel context.CancelFunc Cancel context.CancelFunc
+10 -9
View File
@@ -63,25 +63,26 @@ func (p *Proxy) accept(conn net.Conn) {
ctx, cancel := context.WithCancel(p.Context) ctx, cancel := context.WithCancel(p.Context)
defer cancel() defer cancel()
wrappedConn := wrappers.NewClientConn(ctx, cancel, conn, connID) clientConn := wrappers.NewClientConn(conn, connID)
wrappedConn = wrappers.NewTraffic(wrappedConn) clientConn = wrappers.NewCtx(ctx, cancel, clientConn)
defer wrappedConn.Close() clientConn = wrappers.NewTimeout(clientConn)
clientConn = wrappers.NewTraffic(clientConn)
defer clientConn.Close()
clientProtocol := p.ClientProtocolMaker() clientProtocol := p.ClientProtocolMaker()
wrappedConn, err := clientProtocol.Handshake(wrappedConn) clientConn, err := clientProtocol.Handshake(clientConn)
if err != nil { if err != nil {
logger.Warnw("Cannot perform client handshake", "error", err) logger.Warnw("Cannot perform client handshake", "error", err)
return return
} }
defer wrappedConn.Close()
stats.S.ClientConnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) stats.S.ClientConnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), wrappedConn.RemoteAddr()) defer stats.S.ClientDisconnected(clientProtocol.ConnectionType(), clientConn.RemoteAddr())
logger.Infow("Client connected", "addr", conn.RemoteAddr()) logger.Infow("Client connected", "addr", conn.RemoteAddr())
req := &protocol.TelegramRequest{ req := &protocol.TelegramRequest{
Logger: logger, Logger: logger,
ClientConn: wrappedConn, ClientConn: clientConn,
ConnID: connID, ConnID: connID,
Ctx: ctx, Ctx: ctx,
Cancel: cancel, Cancel: cancel,
@@ -102,7 +103,7 @@ func (p *Proxy) acceptDirectConnection(request *protocol.TelegramRequest) error
if err != nil { if err != nil {
return err return err
} }
telegramConn := telegramConnRaw.(wrappers.StreamReadWriteCloser) telegramConn := telegramConnRaw.(conntypes.StreamReadWriteCloser)
defer telegramConn.Close() defer telegramConn.Close()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
+16 -22
View File
@@ -1,7 +1,6 @@
package telegram package telegram
import ( import (
"context"
"fmt" "fmt"
"math/rand" "math/rand"
"net" "net"
@@ -28,25 +27,8 @@ func (b *baseTelegram) Secret() []byte {
return b.secret return b.secret
} }
func (b *baseTelegram) dialToAddress(ctx context.Context, func (b *baseTelegram) dial(dc conntypes.DC,
cancel context.CancelFunc, protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
addr string) (wrappers.StreamReadWriteCloser, error) {
conn, err := b.dialer.Dial("tcp", addr)
if err != nil {
return nil, fmt.Errorf("dial has failed: %w", err)
}
if err := utils.InitTCP(conn); err != nil {
return nil, fmt.Errorf("cannot initialize tcp socket: %w", err)
}
return wrappers.NewTelegramConn(ctx, cancel, conn), nil
}
func (b *baseTelegram) dial(ctx context.Context,
cancel context.CancelFunc,
dc conntypes.DC,
protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
addr := "" addr := ""
switch protocol { switch protocol {
@@ -56,7 +38,16 @@ func (b *baseTelegram) dial(ctx context.Context,
addr = b.chooseAddress(b.v6Addresses, dc, b.V6DefaultDC) addr = b.chooseAddress(b.v6Addresses, dc, b.V6DefaultDC)
} }
return b.dialToAddress(ctx, cancel, addr) conn, err := b.dialer.Dial("tcp", addr)
if err != nil {
return nil, fmt.Errorf("dial has failed: %w", err)
}
if err := utils.InitTCP(conn); err != nil {
return nil, fmt.Errorf("cannot initialize tcp socket: %w", err)
}
return wrappers.NewTelegramConn(conn), nil
} }
func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string, func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string,
@@ -66,7 +57,10 @@ func (b *baseTelegram) chooseAddress(addresses map[conntypes.DC][]string,
addrs, _ = addresses[defaultDC] addrs, _ = addresses[defaultDC]
} }
if len(addrs) > 0 { switch {
case len(addrs) == 1:
return addrs[0]
case len(addrs) > 1:
return addrs[rand.Intn(len(addrs))] return addrs[rand.Intn(len(addrs))]
} }
+4 -12
View File
@@ -1,15 +1,11 @@
package telegram package telegram
import ( import (
"context"
"net" "net"
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/wrappers"
) )
var Direct = newDirectTelegram()
const ( const (
directV4DefaultIdx conntypes.DC = 1 directV4DefaultIdx conntypes.DC = 1
directV6DefaultIdx conntypes.DC = 1 directV6DefaultIdx conntypes.DC = 1
@@ -36,10 +32,8 @@ type directTelegram struct {
baseTelegram baseTelegram
} }
func (d *directTelegram) Dial(ctx context.Context, func (d *directTelegram) Dial(dc conntypes.DC,
cancel context.CancelFunc, protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
dc conntypes.DC,
protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
switch { switch {
case dc < 0: case dc < 0:
dc = -dc dc = -dc
@@ -47,11 +41,10 @@ func (d *directTelegram) Dial(ctx context.Context,
dc = conntypes.DCDefaultIdx dc = conntypes.DCDefaultIdx
} }
return d.baseTelegram.dial(ctx, cancel, dc-1, protocol) return d.baseTelegram.dial(dc-1, protocol)
} }
func newDirectTelegram() Telegram { var Direct = &directTelegram{
return &directTelegram{
baseTelegram: baseTelegram{ baseTelegram: baseTelegram{
dialer: net.Dialer{Timeout: telegramDialTimeout}, dialer: net.Dialer{Timeout: telegramDialTimeout},
v4DefaultDC: directV4DefaultIdx, v4DefaultDC: directV4DefaultIdx,
@@ -59,5 +52,4 @@ func newDirectTelegram() Telegram {
v4Addresses: directV4Addresses, v4Addresses: directV4Addresses,
v6Addresses: directV6Addresses, v6Addresses: directV6Addresses,
}, },
}
} }
+2 -10
View File
@@ -1,16 +1,8 @@
package telegram package telegram
import ( import "github.com/9seconds/mtg/conntypes"
"context"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/wrappers"
)
type Telegram interface { type Telegram interface {
Dial(context.Context, Dial(conntypes.DC, conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error)
context.CancelFunc,
conntypes.DC,
conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error)
Secret() []byte Secret() []byte
} }
+3 -7
View File
@@ -1,7 +1,6 @@
package telegram package telegram
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"sync" "sync"
@@ -11,7 +10,6 @@ import (
"github.com/9seconds/mtg/conntypes" "github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/telegram/api" "github.com/9seconds/mtg/telegram/api"
"github.com/9seconds/mtg/wrappers"
) )
const middleTelegramBackgroundUpdateEvery = time.Hour const middleTelegramBackgroundUpdateEvery = time.Hour
@@ -67,10 +65,8 @@ func (m *middleTelegram) backgroundUpdate() {
} }
} }
func (m *middleTelegram) Dial(ctx context.Context, func (m *middleTelegram) Dial(dc conntypes.DC,
cancel context.CancelFunc, protocol conntypes.ConnectionProtocol) (conntypes.StreamReadWriteCloser, error) {
dc conntypes.DC,
protocol conntypes.ConnectionProtocol) (wrappers.StreamReadWriteCloser, error) {
if dc == 0 { if dc == 0 {
dc = conntypes.DCDefaultIdx dc = conntypes.DCDefaultIdx
} }
@@ -78,7 +74,7 @@ func (m *middleTelegram) Dial(ctx context.Context,
m.mutex.RLock() m.mutex.RLock()
defer m.mutex.RUnlock() defer m.mutex.RUnlock()
return m.baseTelegram.dial(ctx, cancel, dc, protocol) return m.baseTelegram.dial(dc, protocol)
} }
func MiddleInit() { func MiddleInit() {
+10 -6
View File
@@ -10,6 +10,8 @@ import (
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
) )
const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram operates with blocks mod 4 const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram operates with blocks mod 4
@@ -17,7 +19,7 @@ const blockCipherReadCurrentDataBufferSize = 1024 + 1 // +1 because telegram ope
type wrapperBlockCipher struct { type wrapperBlockCipher struct {
buf bytes.Buffer buf bytes.Buffer
parent StreamReadWriteCloser parent conntypes.StreamReadWriteCloser
encryptor cipher.BlockMode encryptor cipher.BlockMode
decryptor cipher.BlockMode decryptor cipher.BlockMode
} }
@@ -47,7 +49,8 @@ func (w *wrapperBlockCipher) ReadTimeout(p []byte, timeout time.Duration) (int,
return w.read(p, readAllTimeout(timeout)) return w.read(p, readAllTimeout(timeout))
} }
func (w *wrapperBlockCipher) read(p []byte, reader func(StreamReadWriteCloser) ([]byte, error)) (int, error) { func (w *wrapperBlockCipher) read(p []byte,
reader func(conntypes.StreamReadWriteCloser) ([]byte, error)) (int, error) {
if w.buf.Len() > 0 { if w.buf.Len() > 0 {
return w.flush(p) return w.flush(p)
} }
@@ -90,7 +93,7 @@ func (w *wrapperBlockCipher) encrypt(p []byte) ([]byte, error) {
return encrypted, nil return encrypted, nil
} }
func readAll(src StreamReadWriteCloser) (rv []byte, err error) { func readAll(src conntypes.StreamReadWriteCloser) (rv []byte, err error) {
buf := make([]byte, blockCipherReadCurrentDataBufferSize) buf := make([]byte, blockCipherReadCurrentDataBufferSize)
n := blockCipherReadCurrentDataBufferSize n := blockCipherReadCurrentDataBufferSize
@@ -105,8 +108,8 @@ func readAll(src StreamReadWriteCloser) (rv []byte, err error) {
return rv, nil return rv, nil
} }
func readAllTimeout(timeout time.Duration) func(StreamReadWriteCloser) ([]byte, error) { func readAllTimeout(timeout time.Duration) func(conntypes.StreamReadWriteCloser) ([]byte, error) {
return func(src StreamReadWriteCloser) (rv []byte, err error) { return func(src conntypes.StreamReadWriteCloser) (rv []byte, err error) {
tmo := timeout tmo := timeout
buf := make([]byte, blockCipherReadCurrentDataBufferSize) buf := make([]byte, blockCipherReadCurrentDataBufferSize)
n := blockCipherReadCurrentDataBufferSize n := blockCipherReadCurrentDataBufferSize
@@ -148,7 +151,8 @@ func (w *wrapperBlockCipher) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr() return w.parent.RemoteAddr()
} }
func newBlockCipher(parent StreamReadWriteCloser, encryptor, decryptor cipher.BlockMode) StreamReadWriteCloser { func newBlockCipher(parent conntypes.StreamReadWriteCloser,
encryptor, decryptor cipher.BlockMode) conntypes.StreamReadWriteCloser {
return &wrapperBlockCipher{ return &wrapperBlockCipher{
parent: parent, parent: parent,
encryptor: encryptor, encryptor: encryptor,
+17 -49
View File
@@ -1,7 +1,6 @@
package wrappers package wrappers
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"time" "time"
@@ -19,15 +18,8 @@ const (
connPurposeTelegram connPurposeTelegram
) )
const (
connTimeoutRead = 2 * time.Minute
connTimeoutWrite = 2 * time.Minute
)
type wrapperConn struct { type wrapperConn struct {
parent net.Conn parent net.Conn
ctx context.Context
cancel context.CancelFunc
connID conntypes.ConnID connID conntypes.ConnID
logger *zap.SugaredLogger logger *zap.SugaredLogger
localAddr *net.TCPAddr localAddr *net.TCPAddr
@@ -35,43 +27,34 @@ type wrapperConn struct {
} }
func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) { func (w *wrapperConn) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err())
default:
if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil { if err := w.parent.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
w.Close() // nolint: gosec w.Close()
return 0, fmt.Errorf("cannot set write deadline to the socket: %w", err) return 0, fmt.Errorf("cannot set write deadline to the socket: %w", err)
} }
return w.Write(p)
}
func (w *wrapperConn) Write(p []byte) (int, error) {
n, err := w.parent.Write(p) n, err := w.parent.Write(p)
w.logger.Debugw("Write to stream", "bytes", n, "error", err) w.logger.Debugw("write to stream", "bytes", n, "error", err)
if err != nil { if err != nil {
w.Close() // nolint: gosec w.Close() // nolint: gosec
} }
return n, err return n, err
}
}
func (w *wrapperConn) Write(p []byte) (int, error) {
return w.WriteTimeout(p, connTimeoutWrite)
} }
func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) { func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot read because context was closed: %w", w.ctx.Err())
default:
if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil { if err := w.parent.SetReadDeadline(time.Now().Add(timeout)); err != nil {
w.Close() w.Close()
return 0, fmt.Errorf("cannot set read deadline to the socket: %w", err) return 0, fmt.Errorf("cannot set read deadline to the socket: %w", err)
} }
return w.Read(p)
}
func (w *wrapperConn) Read(p []byte) (int, error) {
n, err := w.parent.Read(p) n, err := w.parent.Read(p)
w.logger.Debugw("Read from stream", "bytes", n, "error", err) w.logger.Debugw("Read from stream", "bytes", n, "error", err)
if err != nil { if err != nil {
@@ -79,17 +62,10 @@ func (w *wrapperConn) ReadTimeout(p []byte, timeout time.Duration) (int, error)
} }
return n, err return n, err
}
}
func (w *wrapperConn) Read(p []byte) (int, error) {
return w.ReadTimeout(p, connTimeoutRead)
} }
func (w *wrapperConn) Close() error { func (w *wrapperConn) Close() error {
w.logger.Debugw("Close connection") w.logger.Debugw("Close connection")
w.cancel()
return w.parent.Close() return w.parent.Close()
} }
@@ -109,11 +85,9 @@ func (w *wrapperConn) RemoteAddr() *net.TCPAddr {
return w.remoteAddr return w.remoteAddr
} }
func newConn(ctx context.Context, func newConn(parent net.Conn,
cancel context.CancelFunc,
parent net.Conn,
connID conntypes.ConnID, connID conntypes.ConnID,
purpose connPurpose) StreamReadWriteCloser { purpose connPurpose) conntypes.StreamReadWriteCloser {
localAddr := *parent.LocalAddr().(*net.TCPAddr) localAddr := *parent.LocalAddr().(*net.TCPAddr)
if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil { if parent.RemoteAddr().(*net.TCPAddr).IP.To4() != nil {
@@ -135,8 +109,6 @@ func newConn(ctx context.Context,
return &wrapperConn{ return &wrapperConn{
parent: parent, parent: parent,
ctx: ctx,
cancel: cancel,
connID: connID, connID: connID,
logger: logger, logger: logger,
remoteAddr: parent.RemoteAddr().(*net.TCPAddr), remoteAddr: parent.RemoteAddr().(*net.TCPAddr),
@@ -144,15 +116,11 @@ func newConn(ctx context.Context,
} }
} }
func NewClientConn(ctx context.Context, func NewClientConn(parent net.Conn,
cancel context.CancelFunc, connID conntypes.ConnID) conntypes.StreamReadWriteCloser {
parent net.Conn, return newConn(parent, connID, connPurposeClient)
connID conntypes.ConnID) StreamReadWriteCloser {
return newConn(ctx, cancel, parent, connID, connPurposeClient)
} }
func NewTelegramConn(ctx context.Context, func NewTelegramConn(parent net.Conn) conntypes.StreamReadWriteCloser {
cancel context.CancelFunc, return newConn(parent, conntypes.ConnID{}, connPurposeTelegram)
parent net.Conn) StreamReadWriteCloser {
return newConn(ctx, cancel, parent, conntypes.ConnID{}, connPurposeTelegram)
} }
+89
View File
@@ -0,0 +1,89 @@
package wrappers
import (
"context"
"fmt"
"net"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
)
type wrapperCtx struct {
parent conntypes.StreamReadWriteCloser
ctx context.Context
cancel context.CancelFunc
}
func (w *wrapperCtx) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err())
default:
return w.parent.WriteTimeout(p, timeout)
}
}
func (w *wrapperCtx) Write(p []byte) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err())
default:
return w.parent.Write(p)
}
}
func (w *wrapperCtx) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err())
default:
return w.parent.ReadTimeout(p, timeout)
}
}
func (w *wrapperCtx) Read(p []byte) (int, error) {
select {
case <-w.ctx.Done():
w.Close()
return 0, fmt.Errorf("cannot write because context was closed: %w", w.ctx.Err())
default:
return w.parent.Read(p)
}
}
func (w *wrapperCtx) Close() error {
w.cancel()
return w.parent.Close()
}
func (w *wrapperCtx) Conn() net.Conn {
return w.parent.Conn()
}
func (w *wrapperCtx) Logger() *zap.SugaredLogger {
return w.parent.Logger().Named("ctx")
}
func (w *wrapperCtx) LocalAddr() *net.TCPAddr {
return w.parent.LocalAddr()
}
func (w *wrapperCtx) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr()
}
func NewCtx(ctx context.Context,
cancel context.CancelFunc,
parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser {
return &wrapperCtx{
parent: parent,
ctx: ctx,
cancel: cancel,
}
}
+3 -2
View File
@@ -9,6 +9,7 @@ import (
"encoding/binary" "encoding/binary"
"net" "net"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/mtproto/rpc" "github.com/9seconds/mtg/mtproto/rpc"
"github.com/9seconds/mtg/utils" "github.com/9seconds/mtg/utils"
) )
@@ -22,10 +23,10 @@ const (
var mtprotoEmptyIP = [4]byte{0x00, 0x00, 0x00, 0x00} var mtprotoEmptyIP = [4]byte{0x00, 0x00, 0x00, 0x00}
func NewMiddleProxyCipher(parent StreamReadWriteCloser, func NewMiddleProxyCipher(parent conntypes.StreamReadWriteCloser,
req *rpc.NonceRequest, req *rpc.NonceRequest,
resp *rpc.NonceResponse, resp *rpc.NonceResponse,
secret []byte) StreamReadWriteCloser { secret []byte) conntypes.StreamReadWriteCloser {
localAddr := parent.LocalAddr() localAddr := parent.LocalAddr()
remoteAddr := parent.RemoteAddr() remoteAddr := parent.RemoteAddr()
+6 -4
View File
@@ -11,6 +11,8 @@ import (
"net" "net"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
) )
const ( const (
@@ -33,13 +35,13 @@ var mtprotoFramePadding = []byte{0x04, 0x00, 0x00, 0x00}
// PADDING is custom padding schema to complete frame length to such that // PADDING is custom padding schema to complete frame length to such that
// len(frame) % 16 == 0 // len(frame) % 16 == 0
type wrapperMtprotoFrame struct { type wrapperMtprotoFrame struct {
parent StreamReadWriteCloser parent conntypes.StreamReadWriteCloser
logger *zap.SugaredLogger logger *zap.SugaredLogger
readSeqNo int32 readSeqNo int32
writeSeqNo int32 writeSeqNo int32
} }
func (w *wrapperMtprotoFrame) Read() (Packet, error) { func (w *wrapperMtprotoFrame) Read() (conntypes.Packet, error) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
sum := crc32.NewIEEE() sum := crc32.NewIEEE()
writer := io.MultiWriter(buf, sum) writer := io.MultiWriter(buf, sum)
@@ -101,7 +103,7 @@ func (w *wrapperMtprotoFrame) Read() (Packet, error) {
return data, nil return data, nil
} }
func (w *wrapperMtprotoFrame) Write(p Packet) error { func (w *wrapperMtprotoFrame) Write(p conntypes.Packet) error {
messageLength := 4 + 4 + len(p) + 4 messageLength := 4 + 4 + len(p) + 4
paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize paddingLength := (aes.BlockSize - messageLength%aes.BlockSize) % aes.BlockSize
@@ -149,7 +151,7 @@ func (w *wrapperMtprotoFrame) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr() return w.parent.RemoteAddr()
} }
func NewMtprotoFrame(parent StreamReadWriteCloser, seqNo int32) PacketReadWriteCloser { func NewMtprotoFrame(parent conntypes.StreamReadWriteCloser, seqNo int32) conntypes.PacketReadWriteCloser {
return &wrapperMtprotoFrame{ return &wrapperMtprotoFrame{
parent: parent, parent: parent,
logger: parent.Logger().Named("mtproto-frame"), logger: parent.Logger().Named("mtproto-frame"),
+5 -2
View File
@@ -7,12 +7,14 @@ import (
"time" "time"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
) )
type wrapperObfuscated2 struct { type wrapperObfuscated2 struct {
encryptor cipher.Stream encryptor cipher.Stream
decryptor cipher.Stream decryptor cipher.Stream
parent StreamReadWriteCloser parent conntypes.StreamReadWriteCloser
} }
func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) { func (w *wrapperObfuscated2) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
@@ -71,7 +73,8 @@ func (w *wrapperObfuscated2) Close() error {
return w.parent.Close() return w.parent.Close()
} }
func NewObfuscated2(socket StreamReadWriteCloser, encryptor, decryptor cipher.Stream) StreamReadWriteCloser { func NewObfuscated2(socket conntypes.StreamReadWriteCloser,
encryptor, decryptor cipher.Stream) conntypes.StreamReadWriteCloser {
return &wrapperObfuscated2{ return &wrapperObfuscated2{
parent: socket, parent: socket,
encryptor: encryptor, encryptor: encryptor,
+3 -2
View File
@@ -6,11 +6,12 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
"github.com/9seconds/mtg/stats" "github.com/9seconds/mtg/stats"
) )
type wrapperStats struct { type wrapperStats struct {
parent StreamReadWriteCloser parent conntypes.StreamReadWriteCloser
} }
func (w *wrapperStats) Write(p []byte) (int, error) { func (w *wrapperStats) Write(p []byte) (int, error) {
@@ -61,6 +62,6 @@ func (w *wrapperStats) Close() error {
return w.parent.Close() return w.parent.Close()
} }
func NewTraffic(parent StreamReadWriteCloser) StreamReadWriteCloser { func NewTraffic(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser {
return &wrapperStats{parent} return &wrapperStats{parent}
} }
+61
View File
@@ -0,0 +1,61 @@
package wrappers
import (
"net"
"time"
"go.uber.org/zap"
"github.com/9seconds/mtg/conntypes"
)
const (
timeoutRead = 2 * time.Minute
timeoutWrite = 2 * time.Minute
)
type wrapperTimeout struct {
parent conntypes.StreamReadWriteCloser
}
func (w *wrapperTimeout) WriteTimeout(p []byte, timeout time.Duration) (int, error) {
return w.parent.WriteTimeout(p, timeout)
}
func (w *wrapperTimeout) Write(p []byte) (int, error) {
return w.parent.WriteTimeout(p, timeoutWrite)
}
func (w *wrapperTimeout) ReadTimeout(p []byte, timeout time.Duration) (int, error) {
return w.parent.ReadTimeout(p, timeout)
}
func (w *wrapperTimeout) Read(p []byte) (int, error) {
return w.parent.ReadTimeout(p, timeoutRead)
}
func (w *wrapperTimeout) Close() error {
return w.parent.Close()
}
func (w *wrapperTimeout) Conn() net.Conn {
return w.parent.Conn()
}
func (w *wrapperTimeout) Logger() *zap.SugaredLogger {
return w.parent.Logger().Named("timeout")
}
func (w *wrapperTimeout) LocalAddr() *net.TCPAddr {
return w.parent.LocalAddr()
}
func (w *wrapperTimeout) RemoteAddr() *net.TCPAddr {
return w.parent.RemoteAddr()
}
func NewTimeout(parent conntypes.StreamReadWriteCloser) conntypes.StreamReadWriteCloser {
return &wrapperTimeout{
parent: parent,
}
}