diff --git a/Gopkg.lock b/Gopkg.lock index 6088f9f..bfdaca5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -16,6 +16,61 @@ packages = ["."] revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + branch = "master" + name = "github.com/juju/errors" + packages = ["."] + revision = "c7d06af17c68cd34c835053720b21f6549d9b0ee" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/satori/go.uuid" + packages = ["."] + revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3" + version = "v1.2.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = ["assert"] + revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" + version = "v1.2.1" + +[[projects]] + name = "go.uber.org/atomic" + packages = ["."] + revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289" + version = "v1.3.2" + +[[projects]] + name = "go.uber.org/multierr" + packages = ["."] + revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a" + version = "v1.1.0" + +[[projects]] + name = "go.uber.org/zap" + packages = [ + ".", + "buffer", + "internal/bufferpool", + "internal/color", + "internal/exit", + "zapcore" + ] + revision = "eeedf312bc6c57391d84767a4cd413f02a917974" + version = "v1.8.0" + [[projects]] name = "gopkg.in/alecthomas/kingpin.v2" packages = ["."] @@ -25,6 +80,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9b3ace12d2b928915476e7d30a29e7e37e0a9d569b37ad070fb0b2d0e06fc088" + inputs-digest = "36bcaf7701d70b8c4648e01a7fc94e0cda9c2f9b48c42d453c87567f6f058036" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 20a0428..ed9feb4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -32,3 +32,15 @@ [[constraint]] name = "gopkg.in/alecthomas/kingpin.v2" version = "2.2.6" + +[[constraint]] + branch = "master" + name = "github.com/juju/errors" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.1" + +[[constraint]] + name = "github.com/satori/go.uuid" + version = "1.2.0" diff --git a/main.go b/main.go index 6ddbaed..8df75e7 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,21 @@ package main import ( + "encoding/hex" "fmt" "io" "io/ioutil" "net/http" "net/url" "os" - "regexp" "strconv" "strings" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" kingpin "gopkg.in/alecthomas/kingpin.v2" + + "github.com/9seconds/mtg/server" ) var ( @@ -43,7 +47,8 @@ var ( func main() { kingpin.MustParse(app.Parse(os.Args[1:])) - if matched, err := regexp.MatchString("[a-fA-F0-9]+", *secret); !matched || err != nil { + secretBytes, err := hex.DecodeString(*secret) + if err != nil { usage("Secret has to be hexadecimal string.") } @@ -61,8 +66,23 @@ func main() { *serverName = strings.TrimSpace(string(myIPBytes)) } + atom := zap.NewAtomicLevel() + if *debug { + atom.SetLevel(zapcore.DebugLevel) + } else { + atom.SetLevel(zapcore.ErrorLevel) + } + encoderCfg := zap.NewProductionEncoderConfig() + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stderr), + atom, + )).Sugar() + printURLs() - serve() + if err := server.NewServer(*bindIP, int(*bindPort), secretBytes, logger).Serve(); err != nil { + logger.Fatal(err.Error()) + } } func usage(msg string) { @@ -88,7 +108,3 @@ func printURLs() { tgURL.Path = "proxy" fmt.Println(tgURL.String()) } - -func serve() { - -} diff --git a/obfuscated2/frame.go b/obfuscated2/frame.go new file mode 100644 index 0000000..8a3fec2 --- /dev/null +++ b/obfuscated2/frame.go @@ -0,0 +1,109 @@ +package obfuscated2 + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "io" + + "github.com/juju/errors" +) + +// https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/ +// [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd] +const ( + frameLenKey = 32 + frameLenIV = 16 + frameLenMagic = 4 + frameLenDC = 2 + + frameOffsetFirst = 8 + frameOffsetKey = frameOffsetFirst + frameLenKey + frameOffsetIV = frameOffsetKey + frameLenIV + frameOffsetMagic = frameOffsetIV + frameLenMagic + frameOffsetDC = frameOffsetMagic + frameLenDC + + tgMagicByte = byte(239) + + FrameLen = 64 +) + +var tgMagicBytes = []byte{tgMagicByte, tgMagicByte, tgMagicByte, tgMagicByte} + +type Frame []byte + +func (f Frame) Key() []byte { + return f[frameOffsetFirst:frameOffsetKey] +} + +func (f Frame) IV() []byte { + return f[frameOffsetKey:frameOffsetIV] +} + +func (f Frame) Magic() []byte { + return f[frameOffsetIV:frameOffsetMagic] +} + +func (f Frame) DC() (n int16) { + buf := bytes.NewReader(f[frameOffsetMagic:frameOffsetDC]) + binary.Read(buf, binary.LittleEndian, &n) + + if n < 0 { + n = -n + } else if n == 0 { + n = 1 + } + + return n - 1 +} + +func (f Frame) Valid() bool { + return bytes.Equal(f.Magic(), tgMagicBytes) +} + +func (f Frame) Invert() Frame { + reversed := make(Frame, FrameLen) + copy(reversed, f) + + for i := 0; i < frameLenKey+frameLenIV; i++ { + reversed[frameOffsetFirst+i] = f[frameOffsetIV-1-i] + } + + return reversed +} + +func ExtractFrame(conn io.Reader) (Frame, error) { + buf := &bytes.Buffer{} + if _, err := io.CopyN(buf, conn, FrameLen); err != nil { + return nil, errors.Annotate(err, "Cannot extract obfuscated header") + } + + return Frame(buf.Bytes()), nil +} + +func generateFrame() Frame { + data := make(Frame, FrameLen) + + for { + if _, err := rand.Read(data); err != nil { + continue + } + if data[0] == 0xef { + continue + } + + val := (uint32(data[3]) << 24) | (uint32(data[2]) << 16) | (uint32(data[1]) << 8) | uint32(data[0]) + if val == 0x44414548 || val == 0x54534f50 || val == 0x20544547 || val == 0x4954504f || val == 0xeeeeeeee { + continue + } + + val = (uint32(data[7]) << 24) | (uint32(data[6]) << 16) | (uint32(data[5]) << 8) | uint32(data[4]) + if val == 0x00000000 { + continue + } + + copy(data.Magic(), tgMagicBytes) + + return data + } +} diff --git a/obfuscated2/frame_test.go b/obfuscated2/frame_test.go new file mode 100644 index 0000000..6b3d71c --- /dev/null +++ b/obfuscated2/frame_test.go @@ -0,0 +1,83 @@ +package obfuscated2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFrameKey(t *testing.T) { + toCompare := make([]byte, 32) + for i := 0; i < 32; i++ { + toCompare[i] = byte(1) + } + + assert.Equal(t, toCompare, makeFrame().Key()) +} + +func TestFrameIV(t *testing.T) { + toCompare := make([]byte, 16) + for i := 0; i < 16; i++ { + toCompare[i] = byte(2) + } + + assert.Equal(t, toCompare, makeFrame().IV()) +} + +func TestFrameMagic(t *testing.T) { + toCompare := make([]byte, 4) + for i := 0; i < 4; i++ { + toCompare[i] = tgMagicByte + } + + assert.Equal(t, toCompare, makeFrame().Magic()) +} + +func TestFrameDC(t *testing.T) { + assert.Equal(t, int16(771), makeFrame().DC()) +} + +func TestFrameValid(t *testing.T) { + frame := makeFrame() + assert.True(t, frame.Valid()) + + frame[8+32+16+2] = byte(3) + assert.False(t, frame.Valid()) +} + +func TestFrameInvert(t *testing.T) { + frame := makeFrame() + reversed := frame.Invert() + + assert.Exactly(t, frame[:8], reversed[:8]) + assert.Exactly(t, frame[56:], reversed[56:]) + + toCompare := make([]byte, 48) + for i := 0; i < 48; i++ { + toCompare[i] = frame[55-i] + } + assert.Equal(t, []byte(reversed[8:56]), toCompare) +} + +func TestFrameGenerateValid(t *testing.T) { + assert.True(t, generateFrame().Valid()) +} + +func makeFrame() Frame { + f := make(Frame, FrameLen) + + for i := 8; i < (8 + 32); i++ { + f[i] = byte(1) + } + for i := (8 + 32); i < (8 + 32 + 16); i++ { + f[i] = byte(2) + } + for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ { + f[i] = tgMagicByte + } + for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ { + f[i] = byte(3) + } + + return f +} diff --git a/obfuscated2/obfuscated2.go b/obfuscated2/obfuscated2.go new file mode 100644 index 0000000..e521d18 --- /dev/null +++ b/obfuscated2/obfuscated2.go @@ -0,0 +1,79 @@ +package obfuscated2 + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + + "github.com/juju/errors" +) + +type Obfuscated2 struct { + decryptor cipher.Stream + encryptor cipher.Stream +} + +func (o *Obfuscated2) Encrypt(data []byte) []byte { + buf := make([]byte, len(data)) + o.encryptor.XORKeyStream(buf, data) + return buf +} + +func (o *Obfuscated2) Decrypt(data []byte) []byte { + buf := make([]byte, len(data)) + o.decryptor.XORKeyStream(buf, data) + return buf +} + +func ParseObfuscated2ClientFrame(secret, data []byte) (*Obfuscated2, int16, error) { + frame := Frame(data) + + decHasher := sha256.New() + decHasher.Write(frame.Key()) + decHasher.Write(secret) + decryptor := makeStreamCipher(decHasher.Sum(nil), frame.IV()) + + invertedFrame := frame.Invert() + encHasher := sha256.New() + encHasher.Write(invertedFrame.Key()) + encHasher.Write(secret) + encryptor := makeStreamCipher(encHasher.Sum(nil), invertedFrame.IV()) + + decryptedFrame := make(Frame, FrameLen) + decryptor.XORKeyStream(decryptedFrame, frame) + if !decryptedFrame.Valid() { + return nil, 0, errors.New("Unknown protocol") + } + + obfs := &Obfuscated2{ + decryptor: decryptor, + encryptor: encryptor, + } + + return obfs, decryptedFrame.DC(), nil +} + +func MakeTelegramObfuscated2Frame() (*Obfuscated2, Frame) { + frame := generateFrame() + + encryptor := makeStreamCipher(frame.Key(), frame.IV()) + decryptorFrame := frame.Invert() + decryptor := makeStreamCipher(decryptorFrame.Key(), decryptorFrame.IV()) + + copyFrame := make(Frame, frameOffsetIV) + copy(copyFrame, frame) + encryptor.XORKeyStream(frame, frame) + copy(frame, copyFrame) + + obfs := &Obfuscated2{ + decryptor: decryptor, + encryptor: encryptor, + } + + return obfs, frame +} + +func makeStreamCipher(key, iv []byte) cipher.Stream { + block, _ := aes.NewCipher(key) + return cipher.NewCTR(block, iv) +} diff --git a/obfuscated2/obfuscated2_test.go b/obfuscated2/obfuscated2_test.go new file mode 100644 index 0000000..7a99b2c --- /dev/null +++ b/obfuscated2/obfuscated2_test.go @@ -0,0 +1,79 @@ +package obfuscated2 + +import ( + "crypto/sha256" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestObfs2TelegramFrameDecrypt(t *testing.T) { + _, frame := MakeTelegramObfuscated2Frame() + decryptor := makeStreamCipher(frame.Key(), frame.IV()) + + decrypted := make(Frame, FrameLen) + decryptor.XORKeyStream(decrypted, frame) + + assert.True(t, decrypted.Valid()) +} + +func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) { + obfs2, frame := MakeTelegramObfuscated2Frame() + inverted := frame.Invert() + encryptor := makeStreamCipher(inverted.Key(), inverted.IV()) + + data := []byte{1, 2, 3} + encrypted := make([]byte, 3) + encryptor.XORKeyStream(encrypted, data) + decrypted := obfs2.Decrypt(encrypted) + + assert.Equal(t, data, decrypted) +} + +func TestObfs2Full(t *testing.T) { + secret := []byte{1, 2, 3, 4, 5} + + clientFrame := generateFrame() + clientHasher := sha256.New() + clientHasher.Write(clientFrame.Key()) + clientHasher.Write(secret) + clientKey := clientHasher.Sum(nil) + + encryptor := makeStreamCipher(clientKey, clientFrame.IV()) + encrypted := make(Frame, FrameLen) + encryptor.XORKeyStream(encrypted, clientFrame) + copy(encrypted[:56], clientFrame[:56]) + + invertedClientFrame := clientFrame.Invert() + clientHasher = sha256.New() + clientHasher.Write(invertedClientFrame.Key()) + clientHasher.Write(secret) + invertedClientKey := clientHasher.Sum(nil) + clientDecryptor := makeStreamCipher(invertedClientKey, invertedClientFrame.IV()) + + clientObfs, _, err := ParseObfuscated2ClientFrame(secret, encrypted) + assert.Nil(t, err) + + tgObfs, tgFrame := MakeTelegramObfuscated2Frame() + tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV()) + decrypted := make(Frame, FrameLen) + tgDecryptor.XORKeyStream(decrypted, tgFrame) + assert.True(t, decrypted.Valid()) + + tgInvertedFrame := tgFrame.Invert() + tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV()) + + message := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} + tgEncryptedMessage := make([]byte, len(message)) + tgEncryptor.XORKeyStream(tgEncryptedMessage, message) + + tgEncDecryptedMessage := tgObfs.Decrypt(tgEncryptedMessage) + assert.Equal(t, message, tgEncDecryptedMessage) + + clientEncryptedMessage := clientObfs.Encrypt(tgEncDecryptedMessage) + finalMessage := make([]byte, len(clientEncryptedMessage)) + clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage) + + assert.Equal(t, finalMessage, message) +} diff --git a/server/cipherrwc.go b/server/cipherrwc.go new file mode 100644 index 0000000..a39be8c --- /dev/null +++ b/server/cipherrwc.go @@ -0,0 +1,52 @@ +package server + +import ( + "bytes" + "io" +) + +type Cipher interface { + Encrypt([]byte) []byte + Decrypt([]byte) []byte +} + +type CipherReadWriteCloser struct { + crypt Cipher + conn io.ReadWriteCloser + rest *bytes.Buffer +} + +func (c *CipherReadWriteCloser) Read(p []byte) (n int, err error) { + n, err = c.conn.Read(p) + copy(p, c.crypt.Decrypt(p[:n])) + return +} + +func (c *CipherReadWriteCloser) Write(p []byte) (n int, err error) { + c.rest.Write(c.crypt.Encrypt(p)) + newP := c.rest.Bytes()[:len(p)] + n, err = c.conn.Write(newP) + c.rest = bytes.NewBuffer(c.rest.Bytes()[n:]) + return +} + +func (c *CipherReadWriteCloser) Close() error { + var err1 error + if c.rest.Len() > 0 { + _, err1 = c.conn.Write(c.rest.Bytes()) + } + err2 := c.conn.Close() + + if err2 != nil { + return err2 + } + return err1 +} + +func newCipherReadWriteCloser(conn io.ReadWriteCloser, crypt Cipher) io.ReadWriteCloser { + return &CipherReadWriteCloser{ + conn: conn, + crypt: crypt, + rest: &bytes.Buffer{}, + } +} diff --git a/server/ctxrwc.go b/server/ctxrwc.go new file mode 100644 index 0000000..064074e --- /dev/null +++ b/server/ctxrwc.go @@ -0,0 +1,52 @@ +package server + +import ( + "context" + "io" + + "github.com/juju/errors" +) + +type CtxReadWriteCloser struct { + ctx context.Context + conn io.ReadWriteCloser + cancel context.CancelFunc +} + +func (c *CtxReadWriteCloser) Read(p []byte) (int, error) { + select { + case <-c.ctx.Done(): + return 0, errors.Annotate(c.ctx.Err(), "Read is failed because of closed context") + default: + n, err := c.conn.Read(p) + if err != nil { + c.cancel() + } + return n, err + } +} + +func (c *CtxReadWriteCloser) Write(p []byte) (int, error) { + select { + case <-c.ctx.Done(): + return 0, errors.Annotate(c.ctx.Err(), "Write is failed because of closed context") + default: + n, err := c.conn.Write(p) + if err != nil { + c.cancel() + } + return n, err + } +} + +func (c *CtxReadWriteCloser) Close() error { + return c.conn.Close() +} + +func newCtxReadWriteCloser(conn io.ReadWriteCloser, ctx context.Context, cancel context.CancelFunc) io.ReadWriteCloser { + return &CtxReadWriteCloser{ + conn: conn, + ctx: ctx, + cancel: cancel, + } +} diff --git a/server/logrwc.go b/server/logrwc.go new file mode 100644 index 0000000..4eb27fc --- /dev/null +++ b/server/logrwc.go @@ -0,0 +1,41 @@ +package server + +import ( + "io" + + "go.uber.org/zap" +) + +type LogReadWriteCloser struct { + conn io.ReadWriteCloser + logger *zap.SugaredLogger + sockid string + name string +} + +func (l *LogReadWriteCloser) Read(p []byte) (n int, err error) { + n, err = l.conn.Read(p) + l.logger.Debugw("Finish reading", "name", l.name, "socketid", l.sockid, "nbytes", n, "error", err) + return +} + +func (l *LogReadWriteCloser) Write(p []byte) (n int, err error) { + n, err = l.conn.Write(p) + l.logger.Debugw("Finish writing", "name", l.name, "socketid", l.sockid, "nbytes", n, "error", err) + return +} + +func (l *LogReadWriteCloser) Close() error { + err := l.conn.Close() + l.logger.Debugw("Finish closing socket", "name", l.name, "socketid", l.sockid, "error", err) + return err +} + +func newLogReadWriteCloser(conn io.ReadWriteCloser, logger *zap.SugaredLogger, sockid string, name string) io.ReadWriteCloser { + return &LogReadWriteCloser{ + conn: conn, + logger: logger, + sockid: sockid, + name: name, + } +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..705d82d --- /dev/null +++ b/server/server.go @@ -0,0 +1,184 @@ +package server + +import ( + "context" + "fmt" + "io" + "net" + "strconv" + "sync" + + "github.com/9seconds/mtg/obfuscated2" + "github.com/juju/errors" + uuid "github.com/satori/go.uuid" + "go.uber.org/zap" +) + +type Server struct { + ip net.IP + port int + secret []byte + logger *zap.SugaredLogger + lsock net.Listener + ctx context.Context +} + +func (s *Server) Serve() error { + lsock, err := net.Listen("tcp", s.Addr()) + if err != nil { + return errors.Annotate(err, "Cannot create listen socket") + } + + for { + if conn, err := lsock.Accept(); err != nil { + s.logger.Warn("Cannot allocate incoming connection", "error", err) + } else { + go s.accept(conn) + } + } + + return nil +} + +func (s *Server) Addr() string { + return net.JoinHostPort(s.ip.String(), strconv.Itoa(s.port)) +} + +func (s *Server) accept(conn net.Conn) { + defer conn.Close() + + ctx, cancel := context.WithCancel(context.Background()) + socketID := s.makeSocketID() + + s.logger.Debugw("Client connected", + "secret", s.secret, + "addr", conn.RemoteAddr().String(), + "socketid", socketID, + ) + + clientCipher, dc := s.getClientStream(conn, ctx, cancel, socketID) + // if err != nil { + // s.logger.Warnw("Cannot initialize client connection", + // "secret", s.secret, + // "addr", conn.RemoteAddr().String(), + // "socketid", socketID, + // "error", err, + // ) + // return + // } + + tgConn, tgCipher := s.getTelegramStream(dc, ctx, cancel, socketID) + // if err != nil { + // s.logger.Warnw("Cannot initialize Telegram connection", + // "socketid", socketID, + // "error", err, + // ) + // return + // } + defer tgConn.Close() + + wait := &sync.WaitGroup{} + wait.Add(2) + go func() { + buf := make([]byte, 128) + for { + n, err := conn.Read(buf) + if err != nil { + return + } + decrypted := clientCipher.Decrypt(buf[:n]) + encrypted := tgCipher.Encrypt(decrypted) + tgConn.Write(encrypted) + } + }() + go func() { + buf := make([]byte, 128) + for { + n, err := tgConn.Read(buf) + if err != nil { + return + } + decrypted := tgCipher.Decrypt(buf[:n]) + encrypted := clientCipher.Encrypt(decrypted) + conn.Write(encrypted) + } + }() + // go func() { + // buf := make([]byte, 128) + // for { + // n, err := conn. + // } + // defer wait.Done() + // io.Copy(tgConn, clientConn) + // }() + // go func() { + // defer wait.Done() + // io.Copy(clientConn, tgConn) + // }() + <-ctx.Done() + wait.Wait() + + s.logger.Debugw("Client disconnected", + "secret", s.secret, + "addr", conn.RemoteAddr().String(), + "socketid", socketID, + ) +} + +func (s *Server) makeSocketID() string { + return uuid.NewV4().String() +} + +func (s *Server) getClientStream(conn net.Conn, ctx context.Context, cancel context.CancelFunc, socketID string) (Cipher, int16) { + frame, err := obfuscated2.ExtractFrame(conn) + if err != nil { + fmt.Println(err) + // return nil, 0, errors.Annotate(err, "Cannot create client stream") + } + + obfs2, dc, err := obfuscated2.ParseObfuscated2ClientFrame(s.secret, frame) + if err != nil { + fmt.Println(err) + // return nil, 0, errors.Annotate(err, "Cannot create client stream") + } + + return obfs2, dc + + // cipherConn := newCipherReadWriteCloser(conn, obfs2) + // ctxConn := newCtxReadWriteCloser(cipherConn, ctx, cancel) + // // logConn := newLogReadWriteCloser(ctxConn, s.logger, socketID, "client") + + // return ctxConn, dc, nil +} + +func (s *Server) getTelegramStream(dc int16, ctx context.Context, cancel context.CancelFunc, socketID string) (io.ReadWriteCloser, Cipher) { + socket, err := dialToTelegram(dc) + if err != nil { + fmt.Println(err) + // return nil, errors.Annotate(err, "Cannot dial") + } + + obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame() + if n, err := socket.Write(frame); err != nil || n != len(frame) { + fmt.Println(err) + // return nil, errors.Annotate(err, "Cannot write hadnshake frame") + } + + return socket, obfs2 + + // cipherConn := newCipherReadWriteCloser(socket, obfs2) + // ctxConn := newCtxReadWriteCloser(cipherConn, ctx, cancel) + // logConn := newLogReadWriteCloser(ctxConn, s.logger, socketID, "telegram") + + // return logConn, nil +} + +func NewServer(ip net.IP, port int, secret []byte, logger *zap.SugaredLogger) *Server { + return &Server{ + ip: ip, + port: port, + secret: secret, + ctx: context.Background(), + logger: logger, + } +} diff --git a/server/telegram.go b/server/telegram.go new file mode 100644 index 0000000..da7e854 --- /dev/null +++ b/server/telegram.go @@ -0,0 +1,38 @@ +package server + +import ( + "net" + "time" + + "github.com/juju/errors" +) + +var telegramDCIPs = [5]string{ + "149.154.175.50:443", + "149.154.167.51:443", + "149.154.175.100:443", + "149.154.167.91:443", + "149.154.171.5:443", +} + +const telegramKeepAlive = 30 * time.Second + +func dialToTelegram(dcIdx int16) (net.Conn, error) { + if dcIdx < 0 || dcIdx >= 5 { + return nil, errors.New("Incorrect DC IDX") + } + + conn, err := net.Dial("tcp", telegramDCIPs[dcIdx]) + if err != nil { + return nil, errors.Annotate(err, "Cannot dial") + } + + // if err := conn.SetKeepAlive(true); err != nil { + // return nil, errors.Annotate(err, "Cannot establish keepalive connection") + // } + // if err := conn.SetKeepAlivePeriod(telegramKeepAlive); err != nil { + // return nil, errors.Annotate(err, "Cannot set keepalive timeout") + // } + + return conn, nil +}