Просмотр исходного кода

Support different client connection types

tags/0.9
9seconds 7 лет назад
Родитель
Сommit
588475268a

+ 2
- 1
client/client.go Просмотреть файл

@@ -5,7 +5,8 @@ import (
5 5
 	"net"
6 6
 
7 7
 	"github.com/9seconds/mtg/config"
8
+	"github.com/9seconds/mtg/mtproto"
8 9
 )
9 10
 
10 11
 // Init has to initialize client connection based on given config.
11
-type Init func(net.Conn, *config.Config) (int16, io.ReadWriteCloser, error)
12
+type Init func(net.Conn, *config.Config) (*mtproto.ConnectionOpts, io.ReadWriteCloser, error)

+ 6
- 5
client/direct.go Просмотреть файл

@@ -7,25 +7,26 @@ import (
7 7
 	"github.com/juju/errors"
8 8
 
9 9
 	"github.com/9seconds/mtg/config"
10
+	"github.com/9seconds/mtg/mtproto"
10 11
 	"github.com/9seconds/mtg/obfuscated2"
11 12
 	"github.com/9seconds/mtg/wrappers"
12 13
 )
13 14
 
14 15
 // DirectInit initializes client to access Telegram bypassing middleproxies.
15
-func DirectInit(conn net.Conn, conf *config.Config) (int16, io.ReadWriteCloser, error) {
16
+func DirectInit(conn net.Conn, conf *config.Config) (*mtproto.ConnectionOpts, io.ReadWriteCloser, error) {
16 17
 	socket := wrappers.NewTimeoutRWC(conn, conf.TimeoutRead, conf.TimeoutWrite)
17 18
 	frame, err := obfuscated2.ExtractFrame(socket)
18 19
 	if err != nil {
19
-		return 0, nil, errors.Annotate(err, "Cannot extract frame")
20
+		return nil, nil, errors.Annotate(err, "Cannot extract frame")
20 21
 	}
21 22
 	defer obfuscated2.ReturnFrame(frame)
22 23
 
23
-	obfs2, dc, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
24
+	obfs2, connOpts, err := obfuscated2.ParseObfuscated2ClientFrame(conf.Secret, frame)
24 25
 	if err != nil {
25
-		return 0, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
26
+		return nil, nil, errors.Annotate(err, "Cannot parse obfuscated frame")
26 27
 	}
27 28
 
28 29
 	socket = wrappers.NewStreamCipherRWC(socket, obfs2.Encryptor, obfs2.Decryptor)
29 30
 
30
-	return dc, socket, nil
31
+	return connOpts, socket, nil
31 32
 }

+ 55
- 0
mtproto/connection_options.go Просмотреть файл

@@ -0,0 +1,55 @@
1
+package mtproto
2
+
3
+import (
4
+	"bytes"
5
+
6
+	"github.com/juju/errors"
7
+)
8
+
9
+// ConnectionType is a type of obfuscated2/mtproto connection requested
10
+// by the user.
11
+type ConnectionType uint8
12
+
13
+// ConnectionOpts presents an options, metadata on connection requested
14
+// by the user on handshake.
15
+type ConnectionOpts struct {
16
+	DC             int16
17
+	ConnectionType ConnectionType
18
+}
19
+
20
+// Different connection types which user requests from Telegram.
21
+const (
22
+	ConnectionTypeUnknown ConnectionType = iota
23
+	ConnectionTypeAbridged
24
+	ConnectionTypeIntermediate
25
+)
26
+
27
+// Connection tags for mtproto handshakes.
28
+var (
29
+	ConnectionTagAbridged     = []byte{0xef, 0xef, 0xef, 0xef}
30
+	ConnectionTagIntermediate = []byte{0xee, 0xee, 0xee, 0xee}
31
+)
32
+
33
+// Tag maps connection type to the corresponding handshake tag.
34
+func (t ConnectionType) Tag() ([]byte, error) {
35
+	switch t {
36
+	case ConnectionTypeAbridged:
37
+		return ConnectionTagAbridged, nil
38
+	case ConnectionTypeIntermediate:
39
+		return ConnectionTagIntermediate, nil
40
+	default:
41
+		return nil, errors.Errorf("Unknown connection type %d", t)
42
+	}
43
+}
44
+
45
+// ConnectionTagFromHandshake maps magic bytes to the connection type.
46
+func ConnectionTagFromHandshake(magic []byte) (ConnectionType, error) {
47
+	if bytes.Equal(magic, ConnectionTagIntermediate) {
48
+		return ConnectionTypeIntermediate, nil
49
+	}
50
+	if bytes.Equal(magic, ConnectionTagAbridged) {
51
+		return ConnectionTypeAbridged, nil
52
+	}
53
+
54
+	return ConnectionTypeUnknown, errors.New("Unknown handshake protocol")
55
+}

+ 9
- 9
obfuscated2/frame.go Просмотреть файл

@@ -7,6 +7,8 @@ import (
7 7
 	"io"
8 8
 
9 9
 	"github.com/juju/errors"
10
+
11
+	"github.com/9seconds/mtg/mtproto"
10 12
 )
11 13
 
12 14
 // [frameOffsetFirst:frameOffsetKey:frameOffsetIV:frameOffsetMagic:frameOffsetDC:frameOffsetEnd]
@@ -22,13 +24,9 @@ const (
22 24
 	frameOffsetMagic = frameOffsetIV + frameLenMagic
23 25
 	frameOffsetDC    = frameOffsetMagic + frameLenDC
24 26
 
25
-	tgMagicByte = byte(239)
26
-
27 27
 	FrameLen = 64
28 28
 )
29 29
 
30
-var tgMagicBytes = []byte{tgMagicByte, tgMagicByte, tgMagicByte, tgMagicByte}
31
-
32 30
 // Frame represents handshake frame. Telegram sends 64 bytes of obfuscated2
33 31
 // initialization data first.
34 32
 // https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
@@ -61,9 +59,9 @@ func (f Frame) DC() (n int16) {
61 59
 	return
62 60
 }
63 61
 
64
-// Valid checks that *decrypted* frame is valid. Only magic bytes are checked.
65
-func (f Frame) Valid() bool {
66
-	return bytes.Equal(f.Magic(), tgMagicBytes)
62
+// ConnectionType identifies connection type of the handshake frame.
63
+func (f Frame) ConnectionType() (mtproto.ConnectionType, error) {
64
+	return mtproto.ConnectionTagFromHandshake(f.Magic())
67 65
 }
68 66
 
69 67
 // Invert inverts frame for extracting encryption keys. Pkease check that link:
@@ -94,7 +92,7 @@ func ExtractFrame(conn io.Reader) (*Frame, error) {
94 92
 	return frame, nil
95 93
 }
96 94
 
97
-func generateFrame() *Frame {
95
+func generateFrame(connectionType mtproto.ConnectionType) *Frame {
98 96
 	frame := MakeFrame()
99 97
 	data := *frame
100 98
 
@@ -116,7 +114,9 @@ func generateFrame() *Frame {
116 114
 			continue
117 115
 		}
118 116
 
119
-		copy(data.Magic(), tgMagicBytes)
117
+		// error has to be checked before calling this function
118
+		tag, _ := connectionType.Tag() // nolint: errcheck
119
+		copy(data.Magic(), tag)
120 120
 
121 121
 		return frame
122 122
 	}

+ 22
- 5
obfuscated2/frame_test.go Просмотреть файл

@@ -2,9 +2,12 @@ package obfuscated2
2 2
 
3 3
 import (
4 4
 	"bytes"
5
+	"strconv"
5 6
 	"testing"
6 7
 
7 8
 	"github.com/stretchr/testify/assert"
9
+
10
+	"github.com/9seconds/mtg/mtproto"
8 11
 )
9 12
 
10 13
 func TestFrameKey(t *testing.T) {
@@ -28,7 +31,7 @@ func TestFrameIV(t *testing.T) {
28 31
 func TestFrameMagic(t *testing.T) {
29 32
 	toCompare := make([]byte, 4)
30 33
 	for i := 0; i < 4; i++ {
31
-		toCompare[i] = tgMagicByte
34
+		toCompare[i] = 0xee
32 35
 	}
33 36
 
34 37
 	assert.Equal(t, toCompare, makeFrame().Magic())
@@ -40,10 +43,13 @@ func TestFrameDC(t *testing.T) {
40 43
 
41 44
 func TestFrameValid(t *testing.T) {
42 45
 	frame := makeFrame()
43
-	assert.True(t, frame.Valid())
46
+	connType, err := frame.ConnectionType()
47
+	assert.Nil(t, err)
48
+	assert.Equal(t, connType, mtproto.ConnectionTypeIntermediate)
44 49
 
45 50
 	frame[8+32+16+2] = byte(3)
46
-	assert.False(t, frame.Valid())
51
+	_, err = frame.ConnectionType()
52
+	assert.NotNil(t, err)
47 53
 }
48 54
 
49 55
 func TestFrameDoubleInvert(t *testing.T) {
@@ -66,7 +72,18 @@ func TestFrameInvert(t *testing.T) {
66 72
 }
67 73
 
68 74
 func TestFrameGenerateValid(t *testing.T) {
69
-	assert.True(t, generateFrame().Valid())
75
+	validTests := []mtproto.ConnectionType{
76
+		mtproto.ConnectionTypeIntermediate,
77
+		mtproto.ConnectionTypeAbridged,
78
+	}
79
+	for _, test := range validTests {
80
+		t.Run(strconv.Itoa(int(test)), func(tt *testing.T) {
81
+			frame := generateFrame(test)
82
+			conType, err := frame.ConnectionType()
83
+			assert.Nil(t, err)
84
+			assert.Equal(t, conType, test)
85
+		})
86
+	}
70 87
 }
71 88
 
72 89
 func makeFrame() Frame {
@@ -79,7 +96,7 @@ func makeFrame() Frame {
79 96
 		f[i] = byte(2)
80 97
 	}
81 98
 	for i := (8 + 32 + 16); i < (8 + 32 + 16 + 4); i++ {
82
-		f[i] = tgMagicByte
99
+		f[i] = 0xee
83 100
 	}
84 101
 	for i := (8 + 32 + 16 + 4); i < (8 + 32 + 16 + 4 + 2); i++ {
85 102
 		f[i] = byte(3)

+ 13
- 6
obfuscated2/obfuscated2.go Просмотреть файл

@@ -6,6 +6,8 @@ import (
6 6
 	"crypto/sha256"
7 7
 
8 8
 	"github.com/juju/errors"
9
+
10
+	"github.com/9seconds/mtg/mtproto"
9 11
 )
10 12
 
11 13
 // Obfuscated2 contains AES CTR encryption and decryption streams
@@ -19,7 +21,7 @@ type Obfuscated2 struct {
19 21
 // details: http://telegra.ph/telegram-blocks-wtf-05-26
20 22
 //
21 23
 // Beware, link above is in russian.
22
-func ParseObfuscated2ClientFrame(secret []byte, frame *Frame) (*Obfuscated2, int16, error) {
24
+func ParseObfuscated2ClientFrame(secret []byte, frame *Frame) (*Obfuscated2, *mtproto.ConnectionOpts, error) {
23 25
 	decHasher := sha256.New()
24 26
 	decHasher.Write(frame.Key()) // nolint: errcheck
25 27
 	decHasher.Write(secret)      // nolint: errcheck
@@ -34,23 +36,28 @@ func ParseObfuscated2ClientFrame(secret []byte, frame *Frame) (*Obfuscated2, int
34 36
 	decryptedFrame := MakeFrame()
35 37
 	defer ReturnFrame(decryptedFrame)
36 38
 	decryptor.XORKeyStream(*decryptedFrame, *frame)
37
-	if !decryptedFrame.Valid() {
38
-		return nil, 0, errors.New("Unknown protocol")
39
+	connType, err := decryptedFrame.ConnectionType()
40
+	if err != nil {
41
+		return nil, nil, errors.Annotate(err, "Unknown protocol")
39 42
 	}
40 43
 
41 44
 	obfs := &Obfuscated2{
42 45
 		Decryptor: decryptor,
43 46
 		Encryptor: encryptor,
44 47
 	}
48
+	connOpts := &mtproto.ConnectionOpts{
49
+		DC:             decryptedFrame.DC(),
50
+		ConnectionType: connType,
51
+	}
45 52
 
46
-	return obfs, decryptedFrame.DC(), nil
53
+	return obfs, connOpts, nil
47 54
 }
48 55
 
49 56
 // MakeTelegramObfuscated2Frame creates new handshake frame to send to
50 57
 // Telegram.
51 58
 // https://blog.susanka.eu/how-telegram-obfuscates-its-mtproto-traffic/
52
-func MakeTelegramObfuscated2Frame() (*Obfuscated2, *Frame) {
53
-	frame := generateFrame()
59
+func MakeTelegramObfuscated2Frame(opts *mtproto.ConnectionOpts) (*Obfuscated2, *Frame) {
60
+	frame := generateFrame(opts.ConnectionType)
54 61
 
55 62
 	encryptor := makeStreamCipher(frame.Key(), frame.IV())
56 63
 	decryptorFrame := frame.Invert()

+ 22
- 6
obfuscated2/obfuscated2_test.go Просмотреть файл

@@ -5,20 +5,31 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	"github.com/stretchr/testify/assert"
8
+
9
+	"github.com/9seconds/mtg/mtproto"
8 10
 )
9 11
 
10 12
 func TestObfs2TelegramFrameDecrypt(t *testing.T) {
11
-	_, frame := MakeTelegramObfuscated2Frame()
13
+	connOpts := &mtproto.ConnectionOpts{
14
+		DC:             1,
15
+		ConnectionType: mtproto.ConnectionTypeIntermediate,
16
+	}
17
+	_, frame := MakeTelegramObfuscated2Frame(connOpts)
12 18
 	decryptor := makeStreamCipher(frame.Key(), frame.IV())
13 19
 
14 20
 	decrypted := make(Frame, FrameLen)
15 21
 	decryptor.XORKeyStream(decrypted, *frame)
16 22
 
17
-	assert.True(t, decrypted.Valid())
23
+	_, err := decrypted.ConnectionType()
24
+	assert.Nil(t, err)
18 25
 }
19 26
 
20 27
 func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
21
-	obfs2, frame := MakeTelegramObfuscated2Frame()
28
+	connOpts := &mtproto.ConnectionOpts{
29
+		DC:             1,
30
+		ConnectionType: mtproto.ConnectionTypeIntermediate,
31
+	}
32
+	obfs2, frame := MakeTelegramObfuscated2Frame(connOpts)
22 33
 	inverted := frame.Invert()
23 34
 	encryptor := makeStreamCipher(inverted.Key(), inverted.IV())
24 35
 
@@ -34,7 +45,7 @@ func TestObfs2TelegramDecryptEncryptDecrypt(t *testing.T) {
34 45
 func TestObfs2Full(t *testing.T) {
35 46
 	secret := []byte{1, 2, 3, 4, 5}
36 47
 
37
-	clientFrame := generateFrame()
48
+	clientFrame := generateFrame(mtproto.ConnectionTypeIntermediate)
38 49
 	clientHasher := sha256.New()
39 50
 	clientHasher.Write(clientFrame.Key())
40 51
 	clientHasher.Write(secret)
@@ -55,11 +66,16 @@ func TestObfs2Full(t *testing.T) {
55 66
 	clientObfs, _, err := ParseObfuscated2ClientFrame(secret, &encrypted)
56 67
 	assert.Nil(t, err)
57 68
 
58
-	tgObfs, tgFrame := MakeTelegramObfuscated2Frame()
69
+	connOpts := &mtproto.ConnectionOpts{
70
+		DC:             1,
71
+		ConnectionType: mtproto.ConnectionTypeIntermediate,
72
+	}
73
+	tgObfs, tgFrame := MakeTelegramObfuscated2Frame(connOpts)
59 74
 	tgDecryptor := makeStreamCipher(tgFrame.Key(), tgFrame.IV())
60 75
 	decrypted := make(Frame, FrameLen)
61 76
 	tgDecryptor.XORKeyStream(decrypted, *tgFrame)
62
-	assert.True(t, decrypted.Valid())
77
+	_, err = decrypted.ConnectionType()
78
+	assert.Nil(t, err)
63 79
 
64 80
 	tgInvertedFrame := tgFrame.Invert()
65 81
 	tgEncryptor := makeStreamCipher(tgInvertedFrame.Key(), tgInvertedFrame.IV())

+ 10
- 9
proxy/server.go Просмотреть файл

@@ -12,6 +12,7 @@ import (
12 12
 
13 13
 	"github.com/9seconds/mtg/client"
14 14
 	"github.com/9seconds/mtg/config"
15
+	"github.com/9seconds/mtg/mtproto"
15 16
 	"github.com/9seconds/mtg/telegram"
16 17
 	"github.com/9seconds/mtg/wrappers"
17 18
 )
@@ -60,7 +61,7 @@ func (s *Server) accept(conn net.Conn) {
60 61
 		"socketid", socketID,
61 62
 	)
62 63
 
63
-	dc, clientConn, err := s.getClientStream(ctx, cancel, conn, socketID)
64
+	connOpts, clientConn, err := s.getClientStream(ctx, cancel, conn, socketID)
64 65
 	if err != nil {
65 66
 		s.logger.Warnw("Cannot initialize client connection",
66 67
 			"addr", conn.RemoteAddr().String(),
@@ -71,7 +72,7 @@ func (s *Server) accept(conn net.Conn) {
71 72
 	}
72 73
 	defer clientConn.Close() // nolint: errcheck
73 74
 
74
-	tgConn, err := s.getTelegramStream(ctx, cancel, dc, socketID)
75
+	tgConn, err := s.getTelegramStream(ctx, cancel, connOpts, socketID)
75 76
 	if err != nil {
76 77
 		s.logger.Warnw("Cannot initialize Telegram connection",
77 78
 			"socketid", socketID,
@@ -96,27 +97,27 @@ func (s *Server) accept(conn net.Conn) {
96 97
 	)
97 98
 }
98 99
 
99
-func (s *Server) getClientStream(ctx context.Context, cancel context.CancelFunc, conn net.Conn, socketID string) (int16, io.ReadWriteCloser, error) {
100
-	dc, socket, err := s.clientInit(conn, s.conf)
100
+func (s *Server) getClientStream(ctx context.Context, cancel context.CancelFunc, conn net.Conn, socketID string) (*mtproto.ConnectionOpts, io.ReadWriteCloser, error) {
101
+	connOpts, socket, err := s.clientInit(conn, s.conf)
101 102
 	if err != nil {
102
-		return 0, nil, errors.Annotate(err, "Cannot init client connection")
103
+		return nil, nil, errors.Annotate(err, "Cannot init client connection")
103 104
 	}
104 105
 
105 106
 	socket = wrappers.NewTrafficRWC(socket, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
106 107
 	socket = wrappers.NewLogRWC(socket, s.logger, socketID, "client")
107 108
 	socket = wrappers.NewCtxRWC(ctx, cancel, socket)
108 109
 
109
-	return dc, socket, nil
110
+	return connOpts, socket, nil
110 111
 }
111 112
 
112
-func (s *Server) getTelegramStream(ctx context.Context, cancel context.CancelFunc, dc int16, socketID string) (io.ReadWriteCloser, error) {
113
-	conn, err := s.tg.Dial(dc)
113
+func (s *Server) getTelegramStream(ctx context.Context, cancel context.CancelFunc, connOpts *mtproto.ConnectionOpts, socketID string) (io.ReadWriteCloser, error) {
114
+	conn, err := s.tg.Dial(connOpts)
114 115
 	if err != nil {
115 116
 		return nil, errors.Annotate(err, "Cannot connect to Telegram")
116 117
 	}
117 118
 
118 119
 	conn = wrappers.NewTrafficRWC(conn, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
119
-	conn, err = s.tg.Init(conn)
120
+	conn, err = s.tg.Init(connOpts, conn)
120 121
 	if err != nil {
121 122
 		return nil, errors.Annotate(err, "Cannot handshake Telegram")
122 123
 	}

+ 2
- 2
telegram/dialer.go Просмотреть файл

@@ -45,8 +45,8 @@ func (t *tgDialer) dialRWC(addr string) (io.ReadWriteCloser, error) {
45 45
 	return wrappers.NewTimeoutRWC(conn, t.conf.TimeoutRead, t.conf.TimeoutWrite), nil
46 46
 }
47 47
 
48
-func newDialer(conf *config.Config) *tgDialer {
49
-	return &tgDialer{
48
+func newDialer(conf *config.Config) tgDialer {
49
+	return tgDialer{
50 50
 		Dialer: net.Dialer{Timeout: conf.TimeoutRead},
51 51
 		conf:   conf,
52 52
 	}

+ 10
- 8
telegram/direct.go Просмотреть файл

@@ -6,6 +6,7 @@ import (
6 6
 	"github.com/juju/errors"
7 7
 
8 8
 	"github.com/9seconds/mtg/config"
9
+	"github.com/9seconds/mtg/mtproto"
9 10
 	"github.com/9seconds/mtg/obfuscated2"
10 11
 	"github.com/9seconds/mtg/wrappers"
11 12
 )
@@ -31,18 +32,19 @@ type directTelegram struct {
31 32
 	baseTelegram
32 33
 }
33 34
 
34
-func (t *directTelegram) Dial(dcIdx int16) (io.ReadWriteCloser, error) {
35
-	if dcIdx < 0 {
36
-		dcIdx = -dcIdx
37
-	} else if dcIdx == 0 {
38
-		dcIdx = 1
35
+func (t *directTelegram) Dial(connOpts *mtproto.ConnectionOpts) (io.ReadWriteCloser, error) {
36
+	dc := connOpts.DC
37
+	if dc < 0 {
38
+		dc = -dc
39
+	} else if dc == 0 {
40
+		dc = 1
39 41
 	}
40 42
 
41
-	return t.baseTelegram.Dial(dcIdx - 1)
43
+	return t.baseTelegram.dial(dc - 1)
42 44
 }
43 45
 
44
-func (t *directTelegram) Init(conn io.ReadWriteCloser) (io.ReadWriteCloser, error) {
45
-	obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame()
46
+func (t *directTelegram) Init(connOpts *mtproto.ConnectionOpts, conn io.ReadWriteCloser) (io.ReadWriteCloser, error) {
47
+	obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame(connOpts)
46 48
 	defer obfuscated2.ReturnFrame(frame)
47 49
 
48 50
 	if n, err := conn.Write(*frame); err != nil || n != len(*frame) {

+ 6
- 4
telegram/telegram.go Просмотреть файл

@@ -5,24 +5,26 @@ import (
5 5
 	"math/rand"
6 6
 
7 7
 	"github.com/juju/errors"
8
+
9
+	"github.com/9seconds/mtg/mtproto"
8 10
 )
9 11
 
10 12
 // Telegram defines an interface to connect to Telegram. This
11 13
 // encapsulates logic of working with middleproxies or direct
12 14
 // connections.
13 15
 type Telegram interface {
14
-	Dial(int16) (io.ReadWriteCloser, error)
15
-	Init(io.ReadWriteCloser) (io.ReadWriteCloser, error)
16
+	Dial(*mtproto.ConnectionOpts) (io.ReadWriteCloser, error)
17
+	Init(*mtproto.ConnectionOpts, io.ReadWriteCloser) (io.ReadWriteCloser, error)
16 18
 }
17 19
 
18 20
 type baseTelegram struct {
19
-	dialer *tgDialer
21
+	dialer tgDialer
20 22
 
21 23
 	v4Addresses map[int16][]string
22 24
 	v6Addresses map[int16][]string
23 25
 }
24 26
 
25
-func (b *baseTelegram) Dial(dcIdx int16) (io.ReadWriteCloser, error) {
27
+func (b *baseTelegram) dial(dcIdx int16) (io.ReadWriteCloser, error) {
26 28
 	addrs := make([]string, 2)
27 29
 	if addr, ok := b.v6Addresses[dcIdx]; ok && len(addr) > 0 {
28 30
 		addrs = append(addrs, addr[rand.Intn(len(addr))])

Загрузка…
Отмена
Сохранить