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

Merge pull request #5 from 9seconds/tgproxies

Refactoring
tags/0.9
Sergey Arkhipov 7 лет назад
Родитель
Сommit
08ea80680c
Аккаунт пользователя с таким Email не найден

+ 4
- 3
Dockerfile Просмотреть файл

10
     curl \
10
     curl \
11
     git \
11
     git \
12
     make \
12
     make \
13
+    upx \
13
   && update-ca-certificates
14
   && update-ca-certificates
14
 
15
 
15
 ADD . /go/src/github.com/9seconds/mtg
16
 ADD . /go/src/github.com/9seconds/mtg
17
 RUN set -x \
18
 RUN set -x \
18
   && cd /go/src/github.com/9seconds/mtg \
19
   && cd /go/src/github.com/9seconds/mtg \
19
   && make clean \
20
   && make clean \
20
-  && make -j 4 static
21
+  && make -j 4 static \
22
+  && upx --ultra-brute -qq ./mtg
21
 
23
 
22
 
24
 
23
 ###############################################################################
25
 ###############################################################################
29
 ENV MTG_IP=0.0.0.0 \
31
 ENV MTG_IP=0.0.0.0 \
30
     MTG_PORT=3128 \
32
     MTG_PORT=3128 \
31
     MTG_STATS_IP=0.0.0.0 \
33
     MTG_STATS_IP=0.0.0.0 \
32
-    MTG_STATS_PORT=3129 \
33
-    MTG_USE_IPV6=true
34
+    MTG_STATS_PORT=3129
34
 EXPOSE 3128 3129
35
 EXPOSE 3128 3129
35
 
36
 
36
 COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
37
 COPY --from=0 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt

+ 2
- 2
Makefile Просмотреть файл

1
 ROOT_DIR     := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
1
 ROOT_DIR     := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
2
 IMAGE_NAME   := mtg
2
 IMAGE_NAME   := mtg
3
 APP_NAME     := $(IMAGE_NAME)
3
 APP_NAME     := $(IMAGE_NAME)
4
-GOMETALINTER := gometalinter.v2
4
+GOMETALINTER := gometalinter
5
 
5
 
6
 VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor")
6
 VENDOR_FILES := $(shell find "$(ROOT_DIR)/vendor" 2>/dev/null || echo -n "vendor")
7
 CC_BINARIES  := $(shell bash -c "echo -n $(APP_NAME)-{linux,windows,darwin,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
7
 CC_BINARIES  := $(shell bash -c "echo -n $(APP_NAME)-{linux,windows,darwin,freebsd,openbsd}-{386,amd64} $(APP_NAME)-linux-{arm,arm64}")
76
 
76
 
77
 .PHONY: install-lint
77
 .PHONY: install-lint
78
 install-lint:
78
 install-lint:
79
-	@go get gopkg.in/alecthomas/gometalinter.v2 && \
79
+	@go get github.com/alecthomas/gometalinter && \
80
 		$(GOMETALINTER) --install >/dev/null
80
 		$(GOMETALINTER) --install >/dev/null

+ 31
- 103
README.md Просмотреть файл

8
 
8
 
9
 # Rationale
9
 # Rationale
10
 
10
 
11
-Telegram supports proxies and proxies act as a shield for censorship
12
-and blocking actions of different goverments. At the moment of writing,
13
-Telegram supports 2 types of proxies:
14
-
15
-1. SOCKS5
16
-2. MTPROTO
17
-
18
-SOCKS5 proxy is general SOCKS proxy as defined in
19
-[RFC1928](https://www.ietf.org/rfc/rfc1928.txt). The problem is that
20
-by default SOCKS5 proxy has an access to the whole internet so a lot
21
-of people tend to hide them "just for a case". It is possible to setup
22
-SOCKS5 proxy so it is able to access just some IPs/CIDRs but, you know,
23
-yeah.
24
-
25
-MTPROTO proxy is a native Telegram proxy. It has several advantages:
26
-
27
-1. Traffic is obfuscated by AES-CTR;
28
-2. It allows connections only to Telegram services;
29
-3. It gives proxy maintainer an ability to promote its channel.
30
-
31
-But in reality, MTPROTO have 2 advantages (from my biased view):
32
-
33
-1. Obfuscation
34
-2. Simplify connection chain.
35
-
36
-Here is how it looks like to work with SOCKS5 proxy:
37
-
38
-```
39
-Client -> SOCKS -> MTPROTO -> Telegram
40
-```
41
-
42
-SOCKS5 connects to IPs of Telegram proxies. AFAIK this is because
43
-Telegram wants us to avoid censorship and regulations.
44
-
45
-What MTPROTO proxies do:
46
-
47
-```
48
-Client -> MTPROTO -> Telegram
49
-```
50
-
51
-And promoted channels. I do not tend to use them because mtg was created
52
-for slightly other way of using it but yeah. People want moneys.
53
-
54
-There are a number of unofficial proxies and one
55
-[OFFICIAL](https://github.com/TelegramMessenger/MTProxy), so why bother?
56
-
57
-<start-biased-rant>
58
-
59
-I'm a big fan of [ShadowSocks](http://www.shadowsocks.org/en/index.html)
60
-project and I like how people use it. The majority of SS proxies are
61
-disposable ones which are blocked/unblocked frequently. There are some
62
-public lists of them in Internet so if one proxy has stopped to work,
63
-you throw it out and use another one.
64
-
65
-Some SS proxies are long-living. This is because they are not public and
66
-intended to be used only by limited number of people. And single secret
67
-is fine there.
68
-
69
-What I do not get about official and some unofficial implementation is
70
-why they decided to support multiple secrets? I mean, WTF with all of
71
-you?
72
-
73
-1. MTPROTO obfuscation (called obfuscated2) does not allow to verify
74
-   client easily. You need to decrypt the frame for every secret. So, you
75
-   need a number of workers which will constantly try to crack initial
76
-   handshake frames with a list of secrets. That does not scale and will
77
-   never be.
78
-
79
-2. Why do you need a multiple secrets? Which task are you trying to
80
-   solve with them? Valid secret means only 1 thing: access to Telegram. A
81
-   binary thing. Absurd and rudimentarty access control.
82
-
83
-Okay, you want to revoke an access, thats fine. Will you ssh to the
84
-machine and restart the container? Do you want to have API for that? Web
85
-UI? Maybe store secrets in database and collect statisitcs per each?
86
-
87
-With all respect, this is idiotic thing. Guysngals, this is a proxy.
88
-Gateway to Telegram. This is not a webservice, or SASS or name that
89
-shit. This is disposable stuff. Blocked? Fine, go to the next one. Just
90
-look at ShadowSocks. There is multiple user implementation available,
91
-with control you want. Does anyone gives a flying fuck about it?
92
-
93
-> Those Who Do Not Learn History Are Doomed To Repeat It
94
-- George Santayana
95
-
96
-What I want to have?
97
-
98
-1. Minimal tool for me and my friends (which are not all my FB friends but
99
-   a limited number of close friends).
100
-2. Minimum viable configuration.
101
-3. Single artifact runnable on every platform (not always Docker, some
102
-   environments may have no Docker)
103
-4. Smallest Docker image
104
-5. Lightweight
105
-6. Have as less management as possible.
106
-
107
-</end-biased-rant>
108
-
109
-So, please do not ask for:
110
-
111
-1. Multiple users/secrets
112
-2. Web UI
113
-3. Detailed statistics/histograms etc.
11
+There are several available proxies for Telegram MTPROTO available. Here
12
+are the most notable:
13
+
14
+* [Official](https://github.com/TelegramMessenger/MTProxy)
15
+* [Python](https://github.com/alexbers/mtprotoproxy)
16
+* [Erlang](https://github.com/seriyps/mtproto_proxy)
17
+* [JS](https://github.com/FreedomPrevails/JSMTProxy)
18
+
19
+Almost all of them follow the way how official proxy was build. This
20
+includes support of multiple secrets, support of promoted channels etc.
21
+
22
+mtg is an implementation in golang which is intended to be:
23
+
24
+* **Lightweight**
25
+  It has to consume as less resources as possible but not by losing
26
+  maintainability.
27
+* **Easily deployable**
28
+  I strongly believe that Telegram proxies should follow the way of
29
+  ShadowSocks: promoted channels is a strange way of doing business
30
+  I suppose. I think the only viable way is to have a proxy with
31
+  minimum configuration which should work everywhere.
32
+* **Single secret**
33
+  I think that multiple secrets solves no problems and just complexify
34
+  software. I also believe that in case of throwout proxies, this feature
35
+  is useless luxury.
36
+* **Minimum docker image size**
37
+  Official image is less than 2 megabytes. Literally.
38
+* **No management WebUI**
39
+  This is an implementation of simple lightweight proxy. I won't do that.
114
 
40
 
115
 
41
 
116
 # How to build
42
 # How to build
170
 You will have this tool up and running on port 444. Now curl
96
 You will have this tool up and running on port 444. Now curl
171
 `localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
97
 `localhost:3129` to get `tg://` links or do `docker logs mtg`. Also,
172
 port 3129 will show you some statistics if you are interested in.
98
 port 3129 will show you some statistics if you are interested in.
99
+
100
+Also, you can use [run-mtg.sh](https://github.com/9seconds/mtg/blob/master/run-mtg.sh) script

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

1
+package client
2
+
3
+import (
4
+	"io"
5
+	"net"
6
+
7
+	"github.com/9seconds/mtg/config"
8
+)
9
+
10
+// Init has to initialize client connection based on given config.
11
+type Init func(net.Conn, *config.Config) (int16, io.ReadWriteCloser, error)

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

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

+ 142
- 0
config/config.go Просмотреть файл

1
+package config
2
+
3
+import (
4
+	"encoding/hex"
5
+	"fmt"
6
+	"net"
7
+	"strconv"
8
+	"time"
9
+
10
+	"github.com/juju/errors"
11
+)
12
+
13
+// Config represents common configuration of mtg.
14
+type Config struct {
15
+	Debug   bool
16
+	Verbose bool
17
+
18
+	BindPort       uint16
19
+	PublicIPv4Port uint16
20
+	PublicIPv6Port uint16
21
+	StatsPort      uint16
22
+
23
+	TimeoutRead  time.Duration
24
+	TimeoutWrite time.Duration
25
+
26
+	BindIP     net.IP
27
+	PublicIPv4 net.IP
28
+	PublicIPv6 net.IP
29
+	StatsIP    net.IP
30
+
31
+	Secret []byte
32
+}
33
+
34
+// URLs contains links to the proxy (tg://, t.me) and their QR codes.
35
+type URLs struct {
36
+	TG        string `json:"tg_url"`
37
+	TMe       string `json:"tme_url"`
38
+	TGQRCode  string `json:"tg_qrcode"`
39
+	TMeQRCode string `json:"tme_qrcode"`
40
+}
41
+
42
+// IPURLs contains links to both ipv4 and ipv6 of the proxy.
43
+type IPURLs struct {
44
+	IPv4 URLs `json:"ipv4"`
45
+	IPv6 URLs `json:"ipv6"`
46
+}
47
+
48
+// BindAddr returns connection for this server to bind to.
49
+func (c *Config) BindAddr() string {
50
+	return getAddr(c.BindIP, c.BindPort)
51
+}
52
+
53
+// IPv4Addr returns connection string to ipv6 for mtproto proxy.
54
+func (c *Config) IPv4Addr() string {
55
+	return getAddr(c.PublicIPv4, c.PublicIPv4Port)
56
+}
57
+
58
+// IPv6Addr returns connection string to ipv6 for mtproto proxy.
59
+func (c *Config) IPv6Addr() string {
60
+	return getAddr(c.PublicIPv6, c.PublicIPv6Port)
61
+}
62
+
63
+// StatAddr returns connection string to the stats API.
64
+func (c *Config) StatAddr() string {
65
+	return getAddr(c.StatsIP, c.StatsPort)
66
+}
67
+
68
+// GetURLs returns configured IPURLs instance with links to this server.
69
+func (c *Config) GetURLs() IPURLs {
70
+	return IPURLs{
71
+		IPv4: getURLs(c.PublicIPv4, c.PublicIPv4Port, c.Secret),
72
+		IPv6: getURLs(c.PublicIPv6, c.PublicIPv6Port, c.Secret),
73
+	}
74
+}
75
+
76
+func getAddr(host fmt.Stringer, port uint16) string {
77
+	return net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
78
+}
79
+
80
+// NewConfig returns new configuration. If required, it manages and
81
+// fetches data from external sources. Parameters passed to this
82
+// function, should come from command line arguments.
83
+func NewConfig(debug, verbose bool, // nolint: gocyclo
84
+	bindIP net.IP, bindPort uint16,
85
+	publicIPv4 net.IP, PublicIPv4Port uint16,
86
+	publicIPv6 net.IP, publicIPv6Port uint16,
87
+	statsIP net.IP, statsPort uint16,
88
+	timeoutRead, timeoutWrite time.Duration,
89
+	secret string) (*Config, error) {
90
+	secretBytes, err := hex.DecodeString(secret)
91
+	if err != nil {
92
+		return nil, errors.Annotate(err, "Cannot create config")
93
+	}
94
+
95
+	if publicIPv4 == nil {
96
+		publicIPv4, err = getGlobalIPv4()
97
+		if err != nil {
98
+			return nil, errors.Errorf("Cannot get public IP")
99
+		}
100
+	}
101
+	if publicIPv4.To4() == nil {
102
+		return nil, errors.Errorf("IP %s is not IPv4", publicIPv4.String())
103
+	}
104
+	if PublicIPv4Port == 0 {
105
+		PublicIPv4Port = bindPort
106
+	}
107
+
108
+	if publicIPv6 == nil {
109
+		publicIPv6, err = getGlobalIPv6()
110
+		if err != nil {
111
+			publicIPv6 = publicIPv4
112
+		}
113
+	}
114
+	if publicIPv6.To16() == nil {
115
+		return nil, errors.Errorf("IP %s is not IPv6", publicIPv6.String())
116
+	}
117
+	if publicIPv6Port == 0 {
118
+		publicIPv6Port = bindPort
119
+	}
120
+
121
+	if statsIP == nil {
122
+		statsIP = publicIPv4
123
+	}
124
+
125
+	conf := &Config{
126
+		Debug:          debug,
127
+		Verbose:        verbose,
128
+		BindIP:         bindIP,
129
+		BindPort:       bindPort,
130
+		PublicIPv4:     publicIPv4,
131
+		PublicIPv4Port: PublicIPv4Port,
132
+		PublicIPv6:     publicIPv6,
133
+		PublicIPv6Port: publicIPv6Port,
134
+		StatsIP:        statsIP,
135
+		StatsPort:      statsPort,
136
+		TimeoutRead:    timeoutRead,
137
+		TimeoutWrite:   timeoutWrite,
138
+		Secret:         secretBytes,
139
+	}
140
+
141
+	return conf, nil
142
+}

+ 39
- 0
config/global_ips.go Просмотреть файл

1
+package config
2
+
3
+import (
4
+	"io/ioutil"
5
+	"net"
6
+	"net/http"
7
+	"strings"
8
+
9
+	"github.com/juju/errors"
10
+)
11
+
12
+func getGlobalIPv4() (net.IP, error) {
13
+	return fetchIP("https://v4.ifconfig.co/ip")
14
+}
15
+
16
+func getGlobalIPv6() (net.IP, error) {
17
+	return fetchIP("https://v6.ifconfig.co/ip")
18
+}
19
+
20
+func fetchIP(url string) (net.IP, error) {
21
+	resp, err := http.Get(url)
22
+	if err != nil {
23
+		return nil, err
24
+	}
25
+	defer resp.Body.Close() // nolint: errcheck
26
+
27
+	respDataBytes, err := ioutil.ReadAll(resp.Body)
28
+	if err != nil {
29
+		return nil, err
30
+	}
31
+	respData := strings.TrimSpace(string(respDataBytes))
32
+
33
+	ip := net.ParseIP(respData)
34
+	if ip == nil {
35
+		return nil, errors.Errorf("ifconfig.co returns incorrect IP %s", respData)
36
+	}
37
+
38
+	return ip, nil
39
+}

+ 59
- 0
config/urls.go Просмотреть файл

1
+package config
2
+
3
+import (
4
+	"encoding/hex"
5
+	"net"
6
+	"net/url"
7
+	"strconv"
8
+)
9
+
10
+func getURLs(addr net.IP, port uint16, secret []byte) (urls URLs) {
11
+	values := url.Values{}
12
+	values.Set("server", addr.String())
13
+	values.Set("port", strconv.Itoa(int(port)))
14
+	values.Set("secret", hex.EncodeToString(secret))
15
+
16
+	urls.TG = makeTGURL(values)
17
+	urls.TMe = makeTMeURL(values)
18
+	urls.TGQRCode = makeQRCodeURL(urls.TG)
19
+	urls.TMeQRCode = makeQRCodeURL(urls.TG)
20
+
21
+	return
22
+}
23
+
24
+func makeTGURL(values url.Values) string {
25
+	tgURL := url.URL{
26
+		Scheme:   "tg",
27
+		Host:     "proxy",
28
+		RawQuery: values.Encode(),
29
+	}
30
+
31
+	return tgURL.String()
32
+}
33
+
34
+func makeTMeURL(values url.Values) string {
35
+	tMeURL := url.URL{
36
+		Scheme:   "https",
37
+		Host:     "t.me",
38
+		Path:     "proxy",
39
+		RawQuery: values.Encode(),
40
+	}
41
+
42
+	return tMeURL.String()
43
+}
44
+
45
+func makeQRCodeURL(data string) string {
46
+	QRURL := url.URL{
47
+		Scheme: "https",
48
+		Host:   "api.qrserver.com",
49
+		Path:   "v1/create-qr-code",
50
+	}
51
+
52
+	values := url.Values{}
53
+	values.Set("qzone", "4")
54
+	values.Set("format", "svg")
55
+	values.Set("data", data)
56
+	QRURL.RawQuery = values.Encode()
57
+
58
+	return QRURL.String()
59
+}

+ 39
- 47
main.go Просмотреть файл

3
 //go:generate scripts/generate_version.sh
3
 //go:generate scripts/generate_version.sh
4
 
4
 
5
 import (
5
 import (
6
-	"encoding/hex"
7
 	"encoding/json"
6
 	"encoding/json"
8
 	"io"
7
 	"io"
9
-	"io/ioutil"
10
-	"net/http"
11
 	"os"
8
 	"os"
12
-	"strings"
13
 
9
 
14
-	"github.com/9seconds/mtg/proxy"
15
 	"go.uber.org/zap"
10
 	"go.uber.org/zap"
16
 	"go.uber.org/zap/zapcore"
11
 	"go.uber.org/zap/zapcore"
17
 	kingpin "gopkg.in/alecthomas/kingpin.v2"
12
 	kingpin "gopkg.in/alecthomas/kingpin.v2"
13
+
14
+	"github.com/9seconds/mtg/config"
15
+	"github.com/9seconds/mtg/proxy"
18
 )
16
 )
19
 
17
 
20
 var (
18
 var (
28
 		Short('v').
26
 		Short('v').
29
 		Envar("MTG_VERBOSE").
27
 		Envar("MTG_VERBOSE").
30
 		Bool()
28
 		Bool()
29
+
31
 	bindIP = app.Flag("bind-ip", "Which IP to bind to.").
30
 	bindIP = app.Flag("bind-ip", "Which IP to bind to.").
32
-		Short('i').
31
+		Short('b').
33
 		Envar("MTG_IP").
32
 		Envar("MTG_IP").
34
 		Default("127.0.0.1").
33
 		Default("127.0.0.1").
35
 		IP()
34
 		IP()
38
 			Envar("MTG_PORT").
37
 			Envar("MTG_PORT").
39
 			Default("3128").
38
 			Default("3128").
40
 			Uint16()
39
 			Uint16()
41
-	portToShow = app.Flag("show-bind-port",
42
-		"Which port to show in URL. Default is the value of bind-port").
43
-		Short('a').
44
-		Envar("MTG_SHOW_PORT").
45
-		Uint16()
40
+
41
+	publicIPv4 = app.Flag("public-ipv4", "Which IPv4 address is public.").
42
+			Short('4').
43
+			Envar("MTG_IPV4").
44
+			IP()
45
+	publicIPv4Port = app.Flag("public-ipv4-port", "Which IPv4 port is public. Default is 'bind-port' value.").
46
+			Envar("MTG_IPV4_PORT").
47
+			Uint16()
48
+
49
+	publicIPv6 = app.Flag("public-ipv6", "Which IPv6 address is public.").
50
+			Short('6').
51
+			Envar("MTG_IPV6").
52
+			IP()
53
+	publicIPv6Port = app.Flag("public-ipv6-port", "Which IPv6 port is public. Default is 'bind-port' value.").
54
+			Envar("MTG_IPV6_PORT").
55
+			Uint16()
56
+
46
 	statsIP = app.Flag("stats-ip", "Which IP bind stats server to").
57
 	statsIP = app.Flag("stats-ip", "Which IP bind stats server to").
47
 		Short('t').
58
 		Short('t').
48
 		Envar("MTG_STATS_IP").
59
 		Envar("MTG_STATS_IP").
53
 			Envar("MTG_STATS_PORT").
64
 			Envar("MTG_STATS_PORT").
54
 			Default("3129").
65
 			Default("3129").
55
 			Uint16()
66
 			Uint16()
67
+
56
 	readTimeout = app.Flag("read-timeout", "Socket read timeout.").
68
 	readTimeout = app.Flag("read-timeout", "Socket read timeout.").
57
 			Short('r').
69
 			Short('r').
58
 			Envar("MTG_READ_TIMEOUT").
70
 			Envar("MTG_READ_TIMEOUT").
63
 			Envar("MTG_WRITE_TIMEOUT").
75
 			Envar("MTG_WRITE_TIMEOUT").
64
 			Default("30s").
76
 			Default("30s").
65
 			Duration()
77
 			Duration()
66
-	serverName = app.Flag("server-name",
67
-		"Which server name to use. Default is IP address resolved by ipify.").
68
-		Short('s').
69
-		Envar("MTG_SERVER").
70
-		String()
71
-	preferIPv6 = app.Flag("prefer-ipv6", "Use IPv6").
72
-			Short('6').
73
-			Envar("MTG_USE_IPV6").
74
-			Bool()
75
 
78
 
76
 	secret = app.Arg("secret", "Secret of this proxy.").Required().String()
79
 	secret = app.Arg("secret", "Secret of this proxy.").Required().String()
77
 )
80
 )
80
 	app.Version(version)
83
 	app.Version(version)
81
 	kingpin.MustParse(app.Parse(os.Args[1:]))
84
 	kingpin.MustParse(app.Parse(os.Args[1:]))
82
 
85
 
83
-	secretBytes, err := hex.DecodeString(*secret)
86
+	conf, err := config.NewConfig(*debug, *verbose,
87
+		*bindIP, *bindPort,
88
+		*publicIPv4, *publicIPv4Port,
89
+		*publicIPv6, *publicIPv6Port,
90
+		*statsIP, *statsPort,
91
+		*readTimeout, *writeTimeout,
92
+		*secret,
93
+	)
84
 	if err != nil {
94
 	if err != nil {
85
-		usage("Secret has to be hexadecimal string.")
86
-	}
87
-
88
-	if *portToShow == 0 {
89
-		*portToShow = *bindPort
90
-	}
91
-
92
-	if *serverName == "" {
93
-		resp, err := http.Get("https://api.ipify.org")
94
-		if err != nil || resp.StatusCode != http.StatusOK {
95
-			usage("Cannot get local IP address.")
96
-		}
97
-		myIPBytes, err := ioutil.ReadAll(resp.Body)
98
-		resp.Body.Close() // nolint: errcheck
99
-
100
-		if err != nil {
101
-			usage("Cannot get local IP address.")
102
-		}
103
-		*serverName = strings.TrimSpace(string(myIPBytes))
95
+		usage(err.Error())
104
 	}
96
 	}
105
 
97
 
106
 	atom := zap.NewAtomicLevel()
98
 	atom := zap.NewAtomicLevel()
107
-	if *debug {
99
+	if conf.Debug {
108
 		atom.SetLevel(zapcore.DebugLevel)
100
 		atom.SetLevel(zapcore.DebugLevel)
109
-	} else if *verbose {
101
+	} else if conf.Verbose {
110
 		atom.SetLevel(zapcore.InfoLevel)
102
 		atom.SetLevel(zapcore.InfoLevel)
111
 	} else {
103
 	} else {
112
 		atom.SetLevel(zapcore.ErrorLevel)
104
 		atom.SetLevel(zapcore.ErrorLevel)
118
 		atom,
110
 		atom,
119
 	)).Sugar()
111
 	)).Sugar()
120
 
112
 
121
-	stat := proxy.NewStats(*serverName, *portToShow, *secret)
122
-	go stat.Serve(*statsIP, *statsPort)
123
-	printURLs(stat.URLs)
113
+	stat := proxy.NewStats(conf)
114
+	go stat.Serve()
115
+
116
+	srv := proxy.NewServer(conf, logger, stat)
117
+	printURLs(conf.GetURLs())
124
 
118
 
125
-	srv := proxy.NewServer(*bindIP, int(*bindPort), secretBytes, logger,
126
-		*readTimeout, *writeTimeout, *preferIPv6, stat)
127
 	if err := srv.Serve(); err != nil {
119
 	if err := srv.Serve(); err != nil {
128
 		logger.Fatal(err.Error())
120
 		logger.Fatal(err.Error())
129
 	}
121
 	}

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

58
 		n = 1
58
 		n = 1
59
 	}
59
 	}
60
 
60
 
61
-	if n < 0 {
62
-		n = -n
63
-	} else if n == 0 {
64
-		n = 1
65
-	}
66
-
67
-	return n - 1
61
+	return
68
 }
62
 }
69
 
63
 
70
 // Valid checks that *decrypted* frame is valid. Only magic bytes are checked.
64
 // Valid checks that *decrypted* frame is valid. Only magic bytes are checked.

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

34
 }
34
 }
35
 
35
 
36
 func TestFrameDC(t *testing.T) {
36
 func TestFrameDC(t *testing.T) {
37
-	assert.Equal(t, int16(770), makeFrame().DC())
37
+	assert.Equal(t, int16(771), makeFrame().DC())
38
 }
38
 }
39
 
39
 
40
 func TestFrameValid(t *testing.T) {
40
 func TestFrameValid(t *testing.T) {

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

11
 // Obfuscated2 contains AES CTR encryption and decryption streams
11
 // Obfuscated2 contains AES CTR encryption and decryption streams
12
 // for telegram connection.
12
 // for telegram connection.
13
 type Obfuscated2 struct {
13
 type Obfuscated2 struct {
14
-	decryptor cipher.Stream
15
-	encryptor cipher.Stream
16
-}
17
-
18
-// Encrypt encrypts given data.
19
-func (o *Obfuscated2) Encrypt(data []byte) []byte {
20
-	buf := make([]byte, len(data))
21
-	o.encryptor.XORKeyStream(buf, data)
22
-	return buf
23
-}
24
-
25
-// Decrypt decrypts given data.
26
-func (o *Obfuscated2) Decrypt(data []byte) []byte {
27
-	buf := make([]byte, len(data))
28
-	o.decryptor.XORKeyStream(buf, data)
29
-	return buf
14
+	Decryptor cipher.Stream
15
+	Encryptor cipher.Stream
30
 }
16
 }
31
 
17
 
32
 // ParseObfuscated2ClientFrame parses client frame. Please check this link for
18
 // ParseObfuscated2ClientFrame parses client frame. Please check this link for
33
 // details: http://telegra.ph/telegram-blocks-wtf-05-26
19
 // details: http://telegra.ph/telegram-blocks-wtf-05-26
34
 //
20
 //
35
 // Beware, link above is in russian.
21
 // Beware, link above is in russian.
36
-func ParseObfuscated2ClientFrame(secret, data []byte) (*Obfuscated2, int16, error) {
37
-	frame := Frame(data)
38
-
22
+func ParseObfuscated2ClientFrame(secret []byte, frame Frame) (*Obfuscated2, int16, error) {
39
 	decHasher := sha256.New()
23
 	decHasher := sha256.New()
40
 	decHasher.Write(frame.Key()) // nolint: errcheck
24
 	decHasher.Write(frame.Key()) // nolint: errcheck
41
 	decHasher.Write(secret)      // nolint: errcheck
25
 	decHasher.Write(secret)      // nolint: errcheck
54
 	}
38
 	}
55
 
39
 
56
 	obfs := &Obfuscated2{
40
 	obfs := &Obfuscated2{
57
-		decryptor: decryptor,
58
-		encryptor: encryptor,
41
+		Decryptor: decryptor,
42
+		Encryptor: encryptor,
59
 	}
43
 	}
60
 
44
 
61
 	return obfs, decryptedFrame.DC(), nil
45
 	return obfs, decryptedFrame.DC(), nil
77
 	copy(frame, copyFrame)
61
 	copy(frame, copyFrame)
78
 
62
 
79
 	obfs := &Obfuscated2{
63
 	obfs := &Obfuscated2{
80
-		decryptor: decryptor,
81
-		encryptor: encryptor,
64
+		Decryptor: decryptor,
65
+		Encryptor: encryptor,
82
 	}
66
 	}
83
 
67
 
84
 	return obfs, frame
68
 	return obfs, frame

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

25
 	data := []byte{1, 2, 3}
25
 	data := []byte{1, 2, 3}
26
 	encrypted := make([]byte, 3)
26
 	encrypted := make([]byte, 3)
27
 	encryptor.XORKeyStream(encrypted, data)
27
 	encryptor.XORKeyStream(encrypted, data)
28
-	decrypted := obfs2.Decrypt(encrypted)
28
+	decrypted := make([]byte, 3)
29
+	obfs2.Decryptor.XORKeyStream(decrypted, encrypted)
29
 
30
 
30
 	assert.Equal(t, data, decrypted)
31
 	assert.Equal(t, data, decrypted)
31
 }
32
 }
67
 	tgEncryptedMessage := make([]byte, len(message))
68
 	tgEncryptedMessage := make([]byte, len(message))
68
 	tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
69
 	tgEncryptor.XORKeyStream(tgEncryptedMessage, message)
69
 
70
 
70
-	tgEncDecryptedMessage := tgObfs.Decrypt(tgEncryptedMessage)
71
+	tgEncDecryptedMessage := make([]byte, len(tgEncryptedMessage))
72
+	tgObfs.Decryptor.XORKeyStream(tgEncDecryptedMessage, tgEncryptedMessage)
71
 	assert.Equal(t, message, tgEncDecryptedMessage)
73
 	assert.Equal(t, message, tgEncDecryptedMessage)
72
 
74
 
73
-	clientEncryptedMessage := clientObfs.Encrypt(tgEncDecryptedMessage)
75
+	clientEncryptedMessage := make([]byte, len(tgEncDecryptedMessage))
76
+	clientObfs.Encryptor.XORKeyStream(clientEncryptedMessage, tgEncDecryptedMessage)
74
 	finalMessage := make([]byte, len(clientEncryptedMessage))
77
 	finalMessage := make([]byte, len(clientEncryptedMessage))
75
 	clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
78
 	clientDecryptor.XORKeyStream(finalMessage, clientEncryptedMessage)
76
 
79
 

+ 0
- 56
proxy/cipherrwc.go Просмотреть файл

1
-package proxy
2
-
3
-import (
4
-	"bytes"
5
-	"io"
6
-)
7
-
8
-// Cipher is an interface to anything which can encrypt and decrypt
9
-type Cipher interface {
10
-	Encrypt([]byte) []byte
11
-	Decrypt([]byte) []byte
12
-}
13
-
14
-// CipherReadWriteCloser wraps connection for transparent encryption
15
-type CipherReadWriteCloser struct {
16
-	crypt Cipher
17
-	conn  io.ReadWriteCloser
18
-	rest  *bytes.Buffer
19
-}
20
-
21
-// Read reads from connection
22
-func (c *CipherReadWriteCloser) Read(p []byte) (n int, err error) {
23
-	n, err = c.conn.Read(p)
24
-	copy(p, c.crypt.Decrypt(p[:n]))
25
-	return
26
-}
27
-
28
-// Write writes into connection.
29
-func (c *CipherReadWriteCloser) Write(p []byte) (int, error) {
30
-	encrypted := c.crypt.Encrypt(p)
31
-	allWritten := 0
32
-
33
-	for len(encrypted) > 0 {
34
-		n, err := c.conn.Write(encrypted)
35
-		allWritten += n
36
-		if err != nil {
37
-			return allWritten, err
38
-		}
39
-		encrypted = encrypted[n:]
40
-	}
41
-
42
-	return allWritten, nil
43
-}
44
-
45
-// Close closes underlying connection.
46
-func (c *CipherReadWriteCloser) Close() error {
47
-	return c.conn.Close()
48
-}
49
-
50
-func newCipherReadWriteCloser(conn io.ReadWriteCloser, crypt Cipher) *CipherReadWriteCloser {
51
-	return &CipherReadWriteCloser{
52
-		conn:  conn,
53
-		crypt: crypt,
54
-		rest:  &bytes.Buffer{},
55
-	}
56
-}

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

4
 	"context"
4
 	"context"
5
 	"io"
5
 	"io"
6
 	"net"
6
 	"net"
7
-	"strconv"
8
 	"sync"
7
 	"sync"
9
-	"time"
10
 
8
 
11
-	"github.com/9seconds/mtg/obfuscated2"
12
 	"github.com/juju/errors"
9
 	"github.com/juju/errors"
13
 	uuid "github.com/satori/go.uuid"
10
 	uuid "github.com/satori/go.uuid"
14
 	"go.uber.org/zap"
11
 	"go.uber.org/zap"
12
+
13
+	"github.com/9seconds/mtg/client"
14
+	"github.com/9seconds/mtg/config"
15
+	"github.com/9seconds/mtg/telegram"
16
+	"github.com/9seconds/mtg/wrappers"
15
 )
17
 )
16
 
18
 
17
 // Server is an insgtance of MTPROTO proxy.
19
 // Server is an insgtance of MTPROTO proxy.
18
 type Server struct {
20
 type Server struct {
19
-	ip           net.IP
20
-	port         int
21
-	secret       []byte
22
-	logger       *zap.SugaredLogger
23
-	ctx          context.Context
24
-	readTimeout  time.Duration
25
-	writeTimeout time.Duration
26
-	stats        *Stats
27
-	ipv6         bool
21
+	conf       *config.Config
22
+	logger     *zap.SugaredLogger
23
+	stats      *Stats
24
+	tg         telegram.Telegram
25
+	clientInit client.Init
28
 }
26
 }
29
 
27
 
30
 // Serve does MTPROTO proxying.
28
 // Serve does MTPROTO proxying.
31
 func (s *Server) Serve() error {
29
 func (s *Server) Serve() error {
32
-	addr := net.JoinHostPort(s.ip.String(), strconv.Itoa(s.port))
33
-	lsock, err := net.Listen("tcp", addr)
30
+	lsock, err := net.Listen("tcp", s.conf.BindAddr())
34
 	if err != nil {
31
 	if err != nil {
35
 		return errors.Annotate(err, "Cannot create listen socket")
32
 		return errors.Annotate(err, "Cannot create listen socket")
36
 	}
33
 	}
56
 
53
 
57
 	s.stats.newConnection()
54
 	s.stats.newConnection()
58
 	ctx, cancel := context.WithCancel(context.Background())
55
 	ctx, cancel := context.WithCancel(context.Background())
59
-	socketID := s.makeSocketID()
56
+	socketID := uuid.NewV4().String()
60
 
57
 
61
 	s.logger.Debugw("Client connected",
58
 	s.logger.Debugw("Client connected",
62
-		"secret", s.secret,
63
 		"addr", conn.RemoteAddr().String(),
59
 		"addr", conn.RemoteAddr().String(),
64
 		"socketid", socketID,
60
 		"socketid", socketID,
65
 	)
61
 	)
66
 
62
 
67
-	clientConn, dc, err := s.getClientStream(ctx, cancel, conn, socketID)
63
+	dc, clientConn, err := s.getClientStream(ctx, cancel, conn, socketID)
68
 	if err != nil {
64
 	if err != nil {
69
 		s.logger.Warnw("Cannot initialize client connection",
65
 		s.logger.Warnw("Cannot initialize client connection",
70
-			"secret", s.secret,
71
 			"addr", conn.RemoteAddr().String(),
66
 			"addr", conn.RemoteAddr().String(),
72
 			"socketid", socketID,
67
 			"socketid", socketID,
73
 			"error", err,
68
 			"error", err,
100
 	wait.Wait()
95
 	wait.Wait()
101
 
96
 
102
 	s.logger.Debugw("Client disconnected",
97
 	s.logger.Debugw("Client disconnected",
103
-		"secret", s.secret,
104
 		"addr", conn.RemoteAddr().String(),
98
 		"addr", conn.RemoteAddr().String(),
105
 		"socketid", socketID,
99
 		"socketid", socketID,
106
 	)
100
 	)
107
 }
101
 }
108
 
102
 
109
-func (s *Server) makeSocketID() string {
110
-	return uuid.NewV4().String()
111
-}
112
-
113
-func (s *Server) getClientStream(ctx context.Context, cancel context.CancelFunc, conn net.Conn, socketID string) (io.ReadWriteCloser, int16, error) {
114
-	wConn := newTimeoutReadWriteCloser(conn, s.readTimeout, s.writeTimeout)
115
-	wConn = newTrafficReadWriteCloser(wConn, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
116
-	frame, err := obfuscated2.ExtractFrame(wConn)
103
+func (s *Server) getClientStream(ctx context.Context, cancel context.CancelFunc, conn net.Conn, socketID string) (int16, io.ReadWriteCloser, error) {
104
+	dc, socket, err := s.clientInit(conn, s.conf)
117
 	if err != nil {
105
 	if err != nil {
118
-		return nil, 0, errors.Annotate(err, "Cannot create client stream")
106
+		return 0, nil, errors.Annotate(err, "Cannot init client connection")
119
 	}
107
 	}
120
 
108
 
121
-	obfs2, dc, err := obfuscated2.ParseObfuscated2ClientFrame(s.secret, frame)
122
-	if err != nil {
123
-		return nil, 0, errors.Annotate(err, "Cannot create client stream")
124
-	}
125
-
126
-	wConn = newLogReadWriteCloser(wConn, s.logger, socketID, "client")
127
-	wConn = newCipherReadWriteCloser(wConn, obfs2)
128
-	wConn = newCtxReadWriteCloser(ctx, cancel, wConn)
109
+	socket = wrappers.NewTrafficRWC(socket, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
110
+	socket = wrappers.NewLogRWC(socket, s.logger, socketID, "client")
111
+	socket = wrappers.NewCtxRWC(ctx, cancel, socket)
129
 
112
 
130
-	return wConn, dc, nil
113
+	return dc, socket, nil
131
 }
114
 }
132
 
115
 
133
 func (s *Server) getTelegramStream(ctx context.Context, cancel context.CancelFunc, dc int16, socketID string) (io.ReadWriteCloser, error) {
116
 func (s *Server) getTelegramStream(ctx context.Context, cancel context.CancelFunc, dc int16, socketID string) (io.ReadWriteCloser, error) {
134
-	socket, err := dialToTelegram(s.ipv6, dc, s.readTimeout)
117
+	conn, err := s.tg.Dial(dc)
135
 	if err != nil {
118
 	if err != nil {
136
-		return nil, errors.Annotate(err, "Cannot dial")
119
+		return nil, errors.Annotate(err, "Cannot connect to Telegram")
137
 	}
120
 	}
138
-	wConn := newTimeoutReadWriteCloser(socket, s.readTimeout, s.writeTimeout)
139
-	wConn = newTrafficReadWriteCloser(wConn, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
140
 
121
 
141
-	obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame()
142
-	if n, err := socket.Write(frame); err != nil || n != len(frame) {
143
-		return nil, errors.Annotate(err, "Cannot write hadnshake frame")
122
+	conn = wrappers.NewTrafficRWC(conn, s.stats.addIncomingTraffic, s.stats.addOutgoingTraffic)
123
+	conn, err = s.tg.Init(conn)
124
+	if err != nil {
125
+		return nil, errors.Annotate(err, "Cannot handshake Telegram")
144
 	}
126
 	}
145
 
127
 
146
-	wConn = newLogReadWriteCloser(wConn, s.logger, socketID, "telegram")
147
-	wConn = newCipherReadWriteCloser(wConn, obfs2)
148
-	wConn = newCtxReadWriteCloser(ctx, cancel, wConn)
128
+	conn = wrappers.NewLogRWC(conn, s.logger, socketID, "telegram")
129
+	conn = wrappers.NewCtxRWC(ctx, cancel, conn)
149
 
130
 
150
-	return wConn, nil
131
+	return conn, nil
151
 }
132
 }
152
 
133
 
153
 // NewServer creates new instance of MTPROTO proxy.
134
 // NewServer creates new instance of MTPROTO proxy.
154
-func NewServer(ip net.IP, port int, secret []byte, logger *zap.SugaredLogger,
155
-	readTimeout, writeTimeout time.Duration, ipv6 bool, stat *Stats) *Server {
135
+func NewServer(conf *config.Config, logger *zap.SugaredLogger, stat *Stats) *Server {
156
 	return &Server{
136
 	return &Server{
157
-		ip:           ip,
158
-		port:         port,
159
-		secret:       secret,
160
-		ctx:          context.Background(),
161
-		logger:       logger,
162
-		readTimeout:  readTimeout,
163
-		writeTimeout: writeTimeout,
164
-		stats:        stat,
165
-		ipv6:         ipv6,
137
+		conf:       conf,
138
+		logger:     logger,
139
+		stats:      stat,
140
+		tg:         telegram.NewDirectTelegram(conf),
141
+		clientInit: client.DirectInit,
166
 	}
142
 	}
167
 }
143
 }

+ 14
- 67
proxy/stats.go Просмотреть файл

2
 
2
 
3
 import (
3
 import (
4
 	"encoding/json"
4
 	"encoding/json"
5
-	"fmt"
6
-	"net"
7
 	"net/http"
5
 	"net/http"
8
-	"net/url"
9
 	"strconv"
6
 	"strconv"
10
 	"sync/atomic"
7
 	"sync/atomic"
11
 	"time"
8
 	"time"
9
+
10
+	"github.com/9seconds/mtg/config"
12
 )
11
 )
13
 
12
 
14
 type statsUptime time.Time
13
 type statsUptime time.Time
26
 		Incoming uint64 `json:"incoming"`
25
 		Incoming uint64 `json:"incoming"`
27
 		Outgoing uint64 `json:"outgoing"`
26
 		Outgoing uint64 `json:"outgoing"`
28
 	} `json:"traffic"`
27
 	} `json:"traffic"`
29
-	URLs struct {
30
-		TG        string `json:"tg_url"`
31
-		TMe       string `json:"tme_url"`
32
-		TGQRCode  string `json:"tg_qrcode"`
33
-		TMeQRCode string `json:"tme_qrcode"`
34
-	} `json:"urls"`
35
-	Uptime statsUptime `json:"uptime"`
28
+	URLs   config.IPURLs `json:"urls"`
29
+	Uptime statsUptime   `json:"uptime"`
30
+
31
+	conf *config.Config
36
 }
32
 }
37
 
33
 
38
 func (s *Stats) newConnection() {
34
 func (s *Stats) newConnection() {
53
 }
49
 }
54
 
50
 
55
 // Serve runs statistics HTTP server.
51
 // Serve runs statistics HTTP server.
56
-func (s *Stats) Serve(host fmt.Stringer, port uint16) {
52
+func (s *Stats) Serve() {
57
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
53
 	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
58
 		w.Header().Set("Content-Type", "application/json")
54
 		w.Header().Set("Content-Type", "application/json")
59
 
55
 
63
 		encoder.Encode(s) // nolint: errcheck, gas
59
 		encoder.Encode(s) // nolint: errcheck, gas
64
 	})
60
 	})
65
 
61
 
66
-	addr := net.JoinHostPort(host.String(), strconv.Itoa(int(port)))
67
-	http.ListenAndServe(addr, nil) // nolint: errcheck, gas
62
+	http.ListenAndServe(s.conf.StatAddr(), nil) // nolint: errcheck, gas
68
 }
63
 }
69
 
64
 
70
 // NewStats returns new instance of statistics datastructure.
65
 // NewStats returns new instance of statistics datastructure.
71
-func NewStats(serverName string, port uint16, secret string) *Stats {
72
-	urlQuery := makeURLQuery(serverName, port, secret)
73
-
74
-	stat := &Stats{Uptime: statsUptime(time.Now())}
75
-	stat.URLs.TG = makeTGURL(urlQuery)
76
-	stat.URLs.TMe = makeTMeURL(urlQuery)
77
-	stat.URLs.TGQRCode = makeQRCodeURL(stat.URLs.TG)
78
-	stat.URLs.TMeQRCode = makeQRCodeURL(stat.URLs.TMe)
79
-
80
-	return stat
81
-}
82
-
83
-func makeURLQuery(serverName string, port uint16, secret string) url.Values {
84
-	values := url.Values{}
85
-	values.Set("server", serverName)
86
-	values.Set("port", strconv.Itoa(int(port)))
87
-	values.Set("secret", secret)
88
-
89
-	return values
90
-}
91
-
92
-func makeTGURL(values url.Values) string {
93
-	tgURL := url.URL{
94
-		Scheme:   "tg",
95
-		Host:     "proxy",
96
-		RawQuery: values.Encode(),
66
+func NewStats(conf *config.Config) *Stats {
67
+	stat := &Stats{
68
+		Uptime: statsUptime(time.Now()),
69
+		conf:   conf,
97
 	}
70
 	}
71
+	stat.URLs = conf.GetURLs()
98
 
72
 
99
-	return tgURL.String()
100
-}
101
-
102
-func makeTMeURL(values url.Values) string {
103
-	tMeURL := url.URL{
104
-		Scheme:   "https",
105
-		Host:     "t.me",
106
-		Path:     "proxy",
107
-		RawQuery: values.Encode(),
108
-	}
109
-
110
-	return tMeURL.String()
111
-}
112
-
113
-func makeQRCodeURL(data string) string {
114
-	QRURL := url.URL{
115
-		Scheme: "https",
116
-		Host:   "api.qrserver.com",
117
-		Path:   "v1/create-qr-code",
118
-	}
119
-
120
-	values := url.Values{}
121
-	values.Set("qzone", "4")
122
-	values.Set("format", "svg")
123
-	values.Set("data", data)
124
-	QRURL.RawQuery = values.Encode()
125
-
126
-	return QRURL.String()
73
+	return stat
127
 }
74
 }

+ 0
- 75
proxy/telegram.go Просмотреть файл

1
-package proxy
2
-
3
-import (
4
-	"net"
5
-	"time"
6
-
7
-	"github.com/juju/errors"
8
-)
9
-
10
-// TelegramAddress presents a pair of v4 and v6 addresses. This pairization
11
-// is required because we want to use DC indexes.
12
-type TelegramAddress struct {
13
-	v4 string
14
-	v6 string
15
-}
16
-
17
-// IPv4 returns v4 address.
18
-func (t *TelegramAddress) IPv4() string {
19
-	return net.JoinHostPort(t.v4, telegramPort)
20
-}
21
-
22
-// IPv6 returns v4 address.
23
-func (t *TelegramAddress) IPv6() string {
24
-	return net.JoinHostPort(t.v6, telegramPort)
25
-}
26
-
27
-// TelegramAddresses is a list of all known Telegram addresses for DC indexes.
28
-var TelegramAddresses = []TelegramAddress{
29
-	TelegramAddress{v4: "149.154.175.50", v6: "2001:b28:f23d:f001::a"},
30
-	TelegramAddress{v4: "149.154.167.51", v6: "2001:67c:04e8:f002::a"},
31
-	TelegramAddress{v4: "149.154.175.100", v6: "2001:b28:f23d:f003::a"},
32
-	TelegramAddress{v4: "149.154.167.91", v6: "2001:67c:04e8:f004::a"},
33
-	TelegramAddress{v4: "149.154.171.5", v6: "2001:b28:f23f:f005::a"},
34
-}
35
-
36
-const telegramPort = "443"
37
-
38
-const telegramKeepAlive = 30 * time.Second
39
-
40
-func dialToTelegram(ipv6 bool, dcIdx int16, timeout time.Duration) (net.Conn, error) {
41
-	if dcIdx < 0 || dcIdx >= 5 {
42
-		return nil, errors.New("Incorrect DC IDX")
43
-	}
44
-
45
-	conn, err := doDial(ipv6, dcIdx, timeout)
46
-	if err != nil {
47
-		return nil, errors.Annotate(err, "Cannot dial")
48
-	}
49
-
50
-	if err := conn.SetKeepAlive(true); err != nil {
51
-		return nil, errors.Annotate(err, "Cannot establish keepalive connection")
52
-	}
53
-	if err := conn.SetKeepAlivePeriod(telegramKeepAlive); err != nil {
54
-		return nil, errors.Annotate(err, "Cannot set keepalive timeout")
55
-	}
56
-
57
-	return conn, nil
58
-}
59
-
60
-func doDial(ipv6 bool, dcIdx int16, timeout time.Duration) (*net.TCPConn, error) {
61
-	dialer := net.Dialer{Timeout: timeout}
62
-	addr := TelegramAddresses[dcIdx]
63
-
64
-	if ipv6 {
65
-		if conn, err := dialer.Dial("tcp", addr.IPv6()); err == nil {
66
-			return conn.(*net.TCPConn), nil
67
-		}
68
-	}
69
-
70
-	conn, err := dialer.Dial("tcp", addr.IPv4())
71
-	if err == nil {
72
-		return conn.(*net.TCPConn), nil
73
-	}
74
-	return nil, err
75
-}

+ 36
- 0
run-mtg.sh Просмотреть файл

1
+#!/bin/bash
2
+set -eu -o pipefail
3
+
4
+IMAGE_NAME="nineseconds/mtg"
5
+CONTAINER_NAME="mtg"
6
+SECRET_PATH="$HOME/.mtg.secret"
7
+PROXY_PORT=444
8
+STAT_PORT=3129
9
+
10
+[[ -e "$SECRET_PATH" ]] || (
11
+  openssl rand -hex 16 > "$SECRET_PATH"
12
+  chmod 0400 "$SECRET_PATH"
13
+)
14
+
15
+# docker pull "$IMAGE_NAME"
16
+docker ps --filter "Name=$CONTAINER_NAME" -aq | xargs -r docker rm -fv
17
+docker run \
18
+    -d \
19
+    --name "$CONTAINER_NAME" \
20
+    --sysctl 'net.ipv4.ip_local_port_range=10000 65000' \
21
+    --sysctl net.ipv4.tcp_congestion_control=bbr \
22
+    --sysctl net.ipv4.tcp_fastopen=3 \
23
+    --sysctl net.ipv4.tcp_fin_timeout=30 \
24
+    --sysctl net.ipv4.tcp_keepalive_time=1200 \
25
+    --sysctl net.ipv4.tcp_max_syn_backlog=4096 \
26
+    --sysctl net.ipv4.tcp_max_tw_buckets=5000 \
27
+    --sysctl net.ipv4.tcp_mtu_probing=1 \
28
+    --sysctl 'net.ipv4.tcp_rmem=4096 87380 67108864' \
29
+    --sysctl net.ipv4.tcp_syncookies=1 \
30
+    --sysctl net.ipv4.tcp_tw_reuse=1 \
31
+    --sysctl 'net.ipv4.tcp_wmem=4096 65536 67108864' \
32
+    --ulimit nofile=51200:51200 \
33
+    --restart=unless-stopped \
34
+    -p $PROXY_PORT:3128 \
35
+    -p $STAT_PORT:3129 \
36
+  "$IMAGE_NAME" "$(cat "$SECRET_PATH")"

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

1
+package telegram
2
+
3
+import (
4
+	"io"
5
+	"net"
6
+	"time"
7
+
8
+	"github.com/juju/errors"
9
+
10
+	"github.com/9seconds/mtg/config"
11
+	"github.com/9seconds/mtg/wrappers"
12
+)
13
+
14
+const telegramKeepAlive = 30 * time.Second
15
+
16
+type tgDialer struct {
17
+	net.Dialer
18
+
19
+	conf *config.Config
20
+}
21
+
22
+func (t *tgDialer) dial(addr string) (net.Conn, error) {
23
+	connRaw, err := t.Dialer.Dial("tcp", addr)
24
+	if err != nil {
25
+		return nil, errors.Annotate(err, "Cannot connect to Telegram")
26
+	}
27
+	conn := connRaw.(*net.TCPConn)
28
+
29
+	if err = conn.SetKeepAlive(true); err != nil {
30
+		return nil, errors.Annotate(err, "Cannot establish keepalive connection")
31
+	}
32
+	if err = conn.SetKeepAlivePeriod(telegramKeepAlive); err != nil {
33
+		return nil, errors.Annotate(err, "Cannot set keepalive timeout")
34
+	}
35
+
36
+	return conn, nil
37
+}
38
+
39
+func (t *tgDialer) dialRWC(addr string) (io.ReadWriteCloser, error) {
40
+	conn, err := t.dial(addr)
41
+	if err != nil {
42
+		return nil, err
43
+	}
44
+
45
+	return wrappers.NewTimeoutRWC(conn, t.conf.TimeoutRead, t.conf.TimeoutWrite), nil
46
+}
47
+
48
+func newDialer(conf *config.Config) *tgDialer {
49
+	return &tgDialer{
50
+		Dialer: net.Dialer{Timeout: conf.TimeoutRead},
51
+		conf:   conf,
52
+	}
53
+}

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

1
+package telegram
2
+
3
+import (
4
+	"io"
5
+
6
+	"github.com/juju/errors"
7
+
8
+	"github.com/9seconds/mtg/config"
9
+	"github.com/9seconds/mtg/obfuscated2"
10
+	"github.com/9seconds/mtg/wrappers"
11
+)
12
+
13
+var (
14
+	directV4Addresses = map[int16][]string{
15
+		0: []string{"149.154.175.50:443"},
16
+		1: []string{"149.154.167.51:443"},
17
+		2: []string{"149.154.175.100:443"},
18
+		3: []string{"149.154.167.91:443"},
19
+		4: []string{"149.154.171.5:443"},
20
+	}
21
+	directV6Addresses = map[int16][]string{
22
+		0: []string{"[2001:b28:f23d:f001::a]:443"},
23
+		1: []string{"[2001:67c:04e8:f002::a]:443"},
24
+		2: []string{"[2001:b28:f23d:f003::a]:443"},
25
+		3: []string{"[2001:67c:04e8:f004::a]:443"},
26
+		4: []string{"[2001:b28:f23f:f005::a]:443"},
27
+	}
28
+)
29
+
30
+type directTelegram struct {
31
+	baseTelegram
32
+}
33
+
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
39
+	}
40
+
41
+	return t.baseTelegram.Dial(dcIdx - 1)
42
+}
43
+
44
+func (t *directTelegram) Init(conn io.ReadWriteCloser) (io.ReadWriteCloser, error) {
45
+	obfs2, frame := obfuscated2.MakeTelegramObfuscated2Frame()
46
+	if n, err := conn.Write(frame); err != nil || n != len(frame) {
47
+		return nil, errors.Annotate(err, "Cannot write hadnshake frame")
48
+	}
49
+
50
+	return wrappers.NewStreamCipherRWC(conn, obfs2.Encryptor, obfs2.Decryptor), nil
51
+}
52
+
53
+// NewDirectTelegram returns Telegram instance which connects directly
54
+// to Telegram bypassing middleproxies.
55
+func NewDirectTelegram(conf *config.Config) Telegram {
56
+	return &directTelegram{baseTelegram{
57
+		dialer:      newDialer(conf),
58
+		v4Addresses: directV4Addresses,
59
+		v6Addresses: directV6Addresses,
60
+	}}
61
+}

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

1
+package telegram
2
+
3
+import (
4
+	"io"
5
+	"math/rand"
6
+
7
+	"github.com/juju/errors"
8
+)
9
+
10
+// Telegram defines an interface to connect to Telegram. This
11
+// encapsulates logic of working with middleproxies or direct
12
+// connections.
13
+type Telegram interface {
14
+	Dial(int16) (io.ReadWriteCloser, error)
15
+	Init(io.ReadWriteCloser) (io.ReadWriteCloser, error)
16
+}
17
+
18
+type baseTelegram struct {
19
+	dialer *tgDialer
20
+
21
+	v4Addresses map[int16][]string
22
+	v6Addresses map[int16][]string
23
+}
24
+
25
+func (b *baseTelegram) Dial(dcIdx int16) (io.ReadWriteCloser, error) {
26
+	addrs := make([]string, 2)
27
+	if addr, ok := b.v6Addresses[dcIdx]; ok && len(addr) > 0 {
28
+		addrs = append(addrs, addr[rand.Intn(len(addr))])
29
+	}
30
+	if addr, ok := b.v4Addresses[dcIdx]; ok && len(addr) > 0 {
31
+		addrs = append(addrs, addr[rand.Intn(len(addr))])
32
+	}
33
+
34
+	for _, addr := range addrs {
35
+		if conn, err := b.dialer.dialRWC(addr); err == nil {
36
+			return conn, err
37
+		}
38
+	}
39
+
40
+	return nil, errors.New("Cannot connect to Telegram")
41
+}

proxy/ctxrwc.go → wrappers/ctxrwc.go Просмотреть файл

1
-package proxy
1
+package wrappers
2
 
2
 
3
 import (
3
 import (
4
 	"context"
4
 	"context"
48
 	return c.conn.Close()
48
 	return c.conn.Close()
49
 }
49
 }
50
 
50
 
51
-func newCtxReadWriteCloser(ctx context.Context, cancel context.CancelFunc, conn io.ReadWriteCloser) io.ReadWriteCloser {
51
+// NewCtxRWC returns ReadWriteCloser which respects given context,
52
+// cancellation etc.
53
+func NewCtxRWC(ctx context.Context, cancel context.CancelFunc, conn io.ReadWriteCloser) io.ReadWriteCloser {
52
 	return &CtxReadWriteCloser{
54
 	return &CtxReadWriteCloser{
53
 		conn:   conn,
55
 		conn:   conn,
54
 		ctx:    ctx,
56
 		ctx:    ctx,

proxy/logrwc.go → wrappers/logrwc.go Просмотреть файл

1
-package proxy
1
+package wrappers
2
 
2
 
3
 import (
3
 import (
4
 	"io"
4
 	"io"
36
 	return err
36
 	return err
37
 }
37
 }
38
 
38
 
39
-func newLogReadWriteCloser(conn io.ReadWriteCloser, logger *zap.SugaredLogger, sockid string, name string) io.ReadWriteCloser {
39
+// NewLogRWC wraps ReadWriteCloser with logger calls.
40
+func NewLogRWC(conn io.ReadWriteCloser, logger *zap.SugaredLogger, sockid string, name string) io.ReadWriteCloser {
40
 	return &LogReadWriteCloser{
41
 	return &LogReadWriteCloser{
41
 		conn:   conn,
42
 		conn:   conn,
42
 		logger: logger,
43
 		logger: logger,

+ 54
- 0
wrappers/streamcipherrwc.go Просмотреть файл

1
+package wrappers
2
+
3
+import (
4
+	"crypto/cipher"
5
+	"io"
6
+)
7
+
8
+// StreamCipherReadWriteCloser is a ReadWriteCloser which ciphers
9
+// incoming and outgoing data with givem cipher.Stream instances.
10
+type StreamCipherReadWriteCloser struct {
11
+	encryptor cipher.Stream
12
+	decryptor cipher.Stream
13
+	conn      io.ReadWriteCloser
14
+}
15
+
16
+// Read reads from connection
17
+func (c *StreamCipherReadWriteCloser) Read(p []byte) (n int, err error) {
18
+	n, err = c.conn.Read(p)
19
+	c.decryptor.XORKeyStream(p, p[:n])
20
+	return
21
+}
22
+
23
+// Write writes into connection.
24
+func (c *StreamCipherReadWriteCloser) Write(p []byte) (int, error) {
25
+	encrypted := make([]byte, len(p))
26
+	c.encryptor.XORKeyStream(encrypted, p)
27
+	allWritten := 0
28
+
29
+	for len(encrypted) > 0 {
30
+		n, err := c.conn.Write(encrypted)
31
+		allWritten += n
32
+		if err != nil {
33
+			return allWritten, err
34
+		}
35
+		encrypted = encrypted[n:]
36
+	}
37
+
38
+	return allWritten, nil
39
+}
40
+
41
+// Close closes underlying connection.
42
+func (c *StreamCipherReadWriteCloser) Close() error {
43
+	return c.conn.Close()
44
+}
45
+
46
+// NewStreamCipherRWC returns wrapper which transparently
47
+// encrypts/decrypts traffic with obfuscated2 protocol.
48
+func NewStreamCipherRWC(conn io.ReadWriteCloser, encryptor, decryptor cipher.Stream) io.ReadWriteCloser {
49
+	return &StreamCipherReadWriteCloser{
50
+		conn:      conn,
51
+		encryptor: encryptor,
52
+		decryptor: decryptor,
53
+	}
54
+}

proxy/timeoutrwc.go → wrappers/timeoutrwc.go Просмотреть файл

1
-package proxy
1
+package wrappers
2
 
2
 
3
 import (
3
 import (
4
 	"io"
4
 	"io"
31
 	return t.conn.Close()
31
 	return t.conn.Close()
32
 }
32
 }
33
 
33
 
34
-func newTimeoutReadWriteCloser(conn net.Conn, readTimeout, writeTimeout time.Duration) io.ReadWriteCloser {
34
+// NewTimeoutRWC returns wrapper over net.Conn which sets deadlines for
35
+// every wrapped Read/Write.
36
+func NewTimeoutRWC(conn net.Conn, readTimeout, writeTimeout time.Duration) io.ReadWriteCloser {
35
 	return &TimeoutReadWriteCloser{
37
 	return &TimeoutReadWriteCloser{
36
 		conn:         conn,
38
 		conn:         conn,
37
 		readTimeout:  readTimeout,
39
 		readTimeout:  readTimeout,

proxy/trafficrwc.go → wrappers/trafficrwc.go Просмотреть файл

1
-package proxy
1
+package wrappers
2
 
2
 
3
 import "io"
3
 import "io"
4
 
4
 
29
 	return t.conn.Close()
29
 	return t.conn.Close()
30
 }
30
 }
31
 
31
 
32
-func newTrafficReadWriteCloser(conn io.ReadWriteCloser, readCallback, writeCallback func(int)) io.ReadWriteCloser {
32
+// NewTrafficRWC wraps ReadWriteCloser to have read/write callbacks.
33
+func NewTrafficRWC(conn io.ReadWriteCloser, readCallback, writeCallback func(int)) io.ReadWriteCloser {
33
 	return &TrafficReadWriteCloser{
34
 	return &TrafficReadWriteCloser{
34
 		conn:          conn,
35
 		conn:          conn,
35
 		readCallback:  readCallback,
36
 		readCallback:  readCallback,

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